Merge branch 'master' into develop
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index cc864b4..49397f5 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '11.1.9'
+__version__ = '11.1.10'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 696736d..2d57da0 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -6,7 +6,7 @@
import frappe, erpnext, json
from frappe import _, scrub, ValidationError
from frappe.utils import flt, comma_or, nowdate, getdate
-from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
+from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on, get_allow_cost_center_in_entry_of_bs_account
from erpnext.accounts.party import get_party_account
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.setup.utils import get_exchange_rate
@@ -564,8 +564,8 @@
.format(frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"]))
# Add cost center condition
- if args.get("cost_center"):
- condition += " and cost_center='%s'" % args.get("cost_center")
+ if args.get("cost_center") and get_allow_cost_center_in_entry_of_bs_account():
+ condition += " and cost_center='%s'" % args.get("cost_center")
outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
args.get("party_account"), condition=condition)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index dfbfe7f..773d1cf 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -201,7 +201,8 @@
get_query: function() {
var filters = {
docstatus: 1,
- company: me.frm.doc.company
+ company: me.frm.doc.company,
+ is_return: 0
};
if(me.frm.doc.customer) filters["customer"] = me.frm.doc.customer;
return {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 895ca07..4cf3a1a 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1012,9 +1012,10 @@
for serial_no in item.serial_no.split("\n"):
sales_invoice = frappe.db.get_value("Serial No", serial_no, "sales_invoice")
if sales_invoice and self.name != sales_invoice:
- frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}".format(
- serial_no, sales_invoice
- )))
+ sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company")
+ if sales_invoice_company == self.company:
+ frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}"
+ .format(serial_no, sales_invoice)))
def update_project(self):
if self.project:
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index abfd08a..c251a02 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -270,7 +270,7 @@
for period in period_list:
total_row.setdefault(period.key, 0.0)
total_row[period.key] += row.get(period.key, 0.0)
- row[period.key] = 0.0
+ row[period.key] = row.get(period.key, 0.0)
total_row.setdefault("total", 0.0)
total_row["total"] += flt(row["total"])
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 3a51ceb..20af2c2 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -135,9 +135,9 @@
row.buying_rate, row.base_rate = 0.0, 0.0
# calculate gross profit
- row.gross_profit = row.base_amount - row.buying_amount
+ row.gross_profit = flt(row.base_amount - row.buying_amount, 3)
if row.base_amount:
- row.gross_profit_percent = (row.gross_profit / row.base_amount) * 100.0
+ row.gross_profit_percent = flt((row.gross_profit / row.base_amount) * 100.0, 3)
else:
row.gross_profit_percent = 0.0
@@ -174,8 +174,8 @@
self.grouped_data.append(row)
def set_average_rate(self, new_row):
- new_row.gross_profit = new_row.base_amount - new_row.buying_amount
- new_row.gross_profit_percent = ((new_row.gross_profit / new_row.base_amount) * 100.0) \
+ new_row.gross_profit = flt(new_row.base_amount - new_row.buying_amount,3)
+ new_row.gross_profit_percent = flt(((new_row.gross_profit / new_row.base_amount) * 100.0),3) \
if new_row.base_amount else 0
new_row.buying_rate = (new_row.buying_amount / new_row.qty) if new_row.qty else 0
new_row.base_rate = (new_row.base_amount / new_row.qty) if new_row.qty else 0
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 6b7c0f7..b85f38b 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -487,7 +487,6 @@
add_block_dates(events, start, end, employee, company)
add_holidays(events, start, end, employee, company)
-
return events
def add_department_leaves(events, start, end, employee, company):
@@ -500,10 +499,22 @@
department_employees = frappe.db.sql_list("""select name from tabEmployee where department=%s
and company=%s""", (department, company))
- match_conditions = "and employee in (\"%s\")" % '", "'.join(department_employees)
- add_leaves(events, start, end, match_conditions=match_conditions)
+ filter_conditions = "employee in (\"%s\")" % '", "'.join(department_employees)
+ add_leaves(events, start, end, filter_conditions=filter_conditions)
-def add_leaves(events, start, end, match_conditions=None):
+def add_leaves(events, start, end, filter_conditions=None):
+ conditions = []
+
+ if filter_conditions:
+ conditions.append(filter_conditions)
+
+ if not cint(frappe.db.get_value("HR Settings", None, "show_leaves_of_all_department_members_in_calendar")):
+ from frappe.desk.reportview import build_match_conditions
+ match_conditions = build_match_conditions("Leave Application")
+
+ if match_conditions:
+ conditions.append(match_conditions)
+
query = """select name, from_date, to_date, employee_name, half_day,
status, employee, docstatus
from `tabLeave Application` where
@@ -511,8 +522,8 @@
and docstatus < 2
and status!="Rejected" """
- if match_conditions:
- query += match_conditions
+ if conditions:
+ query += ' and ' + ' and '.join(conditions)
for d in frappe.db.sql(query, {"start":start, "end": end}, as_dict=True):
e = {
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
index 6d082bd..2f2ad00 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
@@ -41,12 +41,19 @@
work_done = nm and nm[0][3] or ''
else:
status = 'Open'
- mntc_date = ''
- service_person = ''
- work_done = ''
+ mntc_date = None
+ service_person = None
+ work_done = None
- frappe.db.sql("update `tabWarranty Claim` set resolution_date=%s, resolved_by=%s, resolution_details=%s, status=%s where name =%s",(mntc_date,service_person,work_done,status,d.prevdoc_docname))
+ wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname)
+ wc_doc.update({
+ 'resolution_date': mntc_date,
+ 'resolved_by': service_person,
+ 'resolution_details': work_done,
+ 'status': status
+ })
+ wc_doc.db_update()
def check_if_last_visit(self):
"""check if last maintenance visit against same sales order/ Warranty Claim"""
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index e53eac4..fb4f350 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -255,11 +255,16 @@
// get valid options for tree based on user permission & locals dict
let unscrub_option = frappe.model.unscrub(option);
let user_permission = frappe.defaults.get_user_permissions();
+ let options;
+
if(user_permission && user_permission[unscrub_option]) {
- return user_permission[unscrub_option].map(perm => perm.doc);
+ options = user_permission[unscrub_option].map(perm => perm.doc);
} else {
- return $.map(locals[`:${unscrub_option}`], function(c) { return c.name; }).sort();
+ options = $.map(locals[`:${unscrub_option}`], function(c) { return c.name; }).sort();
}
+
+ // filter unique values, as there may be multiple user permissions for any value
+ return options.filter((value, index, self) => self.indexOf(value) === index);
},
get_tree_default: function(option) {
// set default for a field based on user permission
diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py
index c86ad78..421994c 100644
--- a/erpnext/regional/italy/utils.py
+++ b/erpnext/regional/italy/utils.py
@@ -300,6 +300,9 @@
return progressive_name, progressive_number
def set_state_code(doc, method):
+ if not doc.get('state'):
+ return
+
if not (hasattr(doc, "state_code") and doc.country in ["Italy", "Italia", "Italian Republic", "Repubblica Italiana"]):
return
diff --git a/erpnext/setup/doctype/setup_progress/setup_progress.py b/erpnext/setup/doctype/setup_progress/setup_progress.py
index 9187eb7..e1402f5 100644
--- a/erpnext/setup/doctype/setup_progress/setup_progress.py
+++ b/erpnext/setup/doctype/setup_progress/setup_progress.py
@@ -16,8 +16,9 @@
return frappe.local.setup_progress
def get_action_completed_state(action_name):
- return [d.is_completed for d in get_setup_progress().actions
- if d.action_name == action_name][0]
+ for d in get_setup_progress().actions:
+ if d.action_name == action_name:
+ return d.is_completed
def update_action_completed_state(action_name):
action_table_doc = [d for d in get_setup_progress().actions
diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index 6242fa7..487c765 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -28,7 +28,7 @@
# user does not have access on warehouse
return []
- return frappe.db.get_all('Bin', fields=['item_code', 'warehouse', 'projected_qty',
+ items = frappe.db.get_all('Bin', fields=['item_code', 'warehouse', 'projected_qty',
'reserved_qty', 'reserved_qty_for_production', 'reserved_qty_for_sub_contract', 'actual_qty', 'valuation_rate'],
or_filters={
'projected_qty': ['!=', 0],
@@ -41,3 +41,10 @@
order_by=sort_by + ' ' + sort_order,
limit_start=start,
limit_page_length='21')
+
+ for item in items:
+ item.update({
+ 'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name')
+ })
+
+ return items
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index de6ebb3..869b614 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -394,7 +394,7 @@
return invoiced_qty_map
-def get_returned_qty_map(sales_orders):
+def get_returned_qty_map_against_so(sales_orders):
"""returns a map: {so_detail: returned_qty}"""
returned_qty_map = {}
@@ -406,12 +406,25 @@
return returned_qty_map
+def get_returned_qty_map_against_dn(delivery_note):
+ """returns a map: {so_detail: returned_qty}"""
+ returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.item_code, sum(abs(dn_item.qty)) as qty
+ from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
+ where dn.name = dn_item.parent
+ and dn.docstatus = 1
+ and dn.is_return = 1
+ and dn.return_against = %s
+ group by dn_item.item_code
+ """, delivery_note))
+
+ return returned_qty_map
+
@frappe.whitelist()
def make_sales_invoice(source_name, target_doc=None):
doc = frappe.get_doc('Delivery Note', source_name)
sales_orders = [d.against_sales_order for d in doc.items]
- returned_qty_map = get_returned_qty_map(sales_orders)
-
+ returned_qty_map_against_so = get_returned_qty_map_against_so(sales_orders)
+ returned_qty_map_against_dn = get_returned_qty_map_against_dn(source_name)
invoiced_qty_map = get_invoiced_qty_map(source_name)
def set_missing_values(source, target):
@@ -431,13 +444,26 @@
target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address))
def update_item(source_doc, target_doc, source_parent):
- target_doc.qty = (source_doc.qty -
- invoiced_qty_map.get(source_doc.name, 0) - returned_qty_map.get(source_doc.so_detail, 0))
+ target_doc.qty, returned_qty = get_pending_qty(source_doc)
+ if not source_doc.so_detail:
+ returned_qty_map_against_dn[source_doc.item_code] = returned_qty
if source_doc.serial_no and source_parent.per_billed > 0:
target_doc.serial_no = get_delivery_note_serial_no(source_doc.item_code,
target_doc.qty, source_parent.name)
+ def get_pending_qty(item_row):
+ pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) - returned_qty_map_against_so.get(item_row.so_detail, 0)
+ returned_qty = flt(returned_qty_map_against_dn.get(item_row.item_code, 0))
+ if not item_row.so_detail:
+ if returned_qty >= pending_qty:
+ pending_qty = 0
+ returned_qty -= pending_qty
+ else:
+ pending_qty -= returned_qty
+ returned_qty = 0
+ return pending_qty, returned_qty
+
doc = get_mapped_doc("Delivery Note", source_name, {
"Delivery Note": {
"doctype": "Sales Invoice",
@@ -456,7 +482,7 @@
"cost_center": "cost_center"
},
"postprocess": update_item,
- "filter": lambda d: abs(d.qty) - abs(invoiced_qty_map.get(d.name, 0))<=0
+ "filter": lambda d: get_pending_qty(d)[0]<=0
},
"Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges",
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 0c5a71c..5c03121 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -636,7 +636,7 @@
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
set_perpetual_inventory(0, company)
-
+
def test_make_sales_invoice_from_dn_for_returned_qty(self):
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
@@ -655,6 +655,33 @@
si = make_sales_invoice(dn.name)
self.assertEquals(si.items[0].qty, 1)
+ def test_make_sales_invoice_from_dn_with_returned_qty_against_dn(self):
+ from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
+
+ dn = create_delivery_note(qty=8, do_not_submit=True)
+ dn.append("items", {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 1,
+ "rate": 100,
+ "conversion_factor": 1.0,
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC"
+ })
+ dn.submit()
+
+ si1 = make_sales_invoice(dn.name)
+ si1.items[0].qty = 4
+ si1.items.pop(1)
+ si1.save()
+ si1.submit()
+
+ create_delivery_note(is_return=1, return_against=dn.name, qty=-2)
+
+ si2 = make_sales_invoice(dn.name)
+ self.assertEquals(si2.items[0].qty, 2)
+ self.assertEquals(si2.items[1].qty, 1)
+
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 8563d46..a3c1e27 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -405,6 +405,11 @@
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
+ doc = frappe.get_doc('Purchase Receipt', source_name)
+ purchase_orders = [d.purchase_order for d in doc.items]
+ returned_qty_map_against_po = get_returned_qty_map_against_po(purchase_orders)
+ returned_qty_map_against_pr = get_returned_qty_map_against_pr(source_name)
+
invoiced_qty_map = get_invoiced_qty_map(source_name)
def set_missing_values(source, target):
@@ -417,7 +422,23 @@
doc.run_method("calculate_taxes_and_totals")
def update_item(source_doc, target_doc, source_parent):
- target_doc.qty = source_doc.qty - invoiced_qty_map.get(source_doc.name, 0)
+ target_doc.qty, returned_qty = get_pending_qty(source_doc)
+ if not source_doc.purchase_order_item:
+ returned_qty_map_against_pr[source_doc.item_code] = returned_qty
+
+ def get_pending_qty(item_row):
+ pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) \
+ - returned_qty_map_against_po.get(item_row.purchase_order_item, 0)
+ returned_qty = flt(returned_qty_map_against_pr.get(item_row.item_code, 0))
+ if not item_row.purchase_order_item:
+ if returned_qty >= pending_qty:
+ pending_qty = 0
+ returned_qty -= pending_qty
+ else:
+ pending_qty -= returned_qty
+ returned_qty = 0
+ return pending_qty, returned_qty
+
doclist = get_mapped_doc("Purchase Receipt", source_name, {
"Purchase Receipt": {
@@ -440,7 +461,7 @@
"asset": "asset",
},
"postprocess": update_item,
- "filter": lambda d: abs(d.qty) - abs(invoiced_qty_map.get(d.name, 0))<=0
+ "filter": lambda d: get_pending_qty(d)[0]<=0
},
"Purchase Taxes and Charges": {
"doctype": "Purchase Taxes and Charges",
@@ -462,6 +483,31 @@
return invoiced_qty_map
+def get_returned_qty_map_against_po(purchase_orders):
+ """returns a map: {so_detail: returned_qty}"""
+ returned_qty_map = {}
+
+ for name, returned_qty in frappe.get_all('Purchase Order Item', fields = ["name", "returned_qty"],
+ filters = {'parent': ('in', purchase_orders), 'docstatus': 1}, as_list=1):
+ if not returned_qty_map.get(name):
+ returned_qty_map[name] = 0
+ returned_qty_map[name] += returned_qty
+
+ return returned_qty_map
+
+def get_returned_qty_map_against_pr(purchase_receipt):
+ """returns a map: {so_detail: returned_qty}"""
+ returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.item_code, sum(abs(pr_item.qty)) as qty
+ from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
+ where pr.name = pr_item.parent
+ and pr.docstatus = 1
+ and pr.is_return = 1
+ and pr.return_against = %s
+ group by pr_item.item_code
+ """, purchase_receipt))
+
+ return returned_qty_map
+
@frappe.whitelist()
def make_purchase_return(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 472083b..c5ae838 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -404,6 +404,44 @@
set_perpetual_inventory(0, pr.company)
+ def test_make_purchase_invoice_from_pr_for_returned_qty(self):
+ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, create_pr_against_po
+
+ po = create_purchase_order()
+ pr = create_pr_against_po(po.name)
+
+ pr1 = make_purchase_receipt(is_return=1, return_against=pr.name, qty=-1, do_not_submit=True)
+ pr1.items[0].purchase_order = po.name
+ pr1.items[0].purchase_order_item = po.items[0].name
+ pr1.submit()
+
+ pi = make_purchase_invoice(pr.name)
+ self.assertEquals(pi.items[0].qty, 3)
+
+ def test_make_purchase_invoice_from_dn_with_returned_qty_against_dn(self):
+ pr1 = make_purchase_receipt(qty=8, do_not_submit=True)
+ pr1.append("items", {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 1,
+ "received_qty": 1,
+ "rate": 100,
+ "conversion_factor": 1.0,
+ })
+ pr1.submit()
+
+ pi1 = make_purchase_invoice(pr1.name)
+ pi1.items[0].qty = 4
+ pi1.items.pop(1)
+ pi1.save()
+ pi1.submit()
+
+ make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2)
+
+ pi2 = make_purchase_invoice(pr1.name)
+ self.assertEquals(pi2.items[0].qty, 2)
+ self.assertEquals(pi2.items[1].qty, 1)
+
def get_gl_entries(voucher_type, voucher_no):
return frappe.db.sql("""select account, debit, credit, cost_center
from `tabGL Entry` where voucher_type=%s and voucher_no=%s