From 2c3c8aaa2dc9599522beb2e3d4d53ede68ca70d8 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 29 May 2020 21:55:38 +0530 Subject: [PATCH 01/12] fix: submitted sales order can be updated with no permission --- erpnext/controllers/accounts_controller.py | 46 ++++++++++++++----- erpnext/public/js/utils.js | 7 +++ .../doctype/sales_order/test_sales_order.py | 16 +++++++ .../sales_order_item/sales_order_item.json | 28 +++++------ 4 files changed, 71 insertions(+), 26 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index eecb143d556..ca78070bf4e 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1137,8 +1137,8 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, child_item.item_name = item.item_name child_item.description = item.description child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date + child_item.conversion_factor = flt(d.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.uom = item.stock_uom - child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) if not child_item.warehouse: frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") @@ -1157,8 +1157,8 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna child_item.item_name = item.item_name child_item.description = item.description child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date + child_item.conversion_factor = flt(d.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.uom = item.stock_uom - child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.base_rate = 1 # Initiallize value will update in parent validation child_item.base_amount = 1 # Initiallize value will update in parent validation return child_item @@ -1190,6 +1190,26 @@ def check_and_delete_children(parent, data): @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): + def check_permissions(doc, perm_type='create'): + try: + doc.check_permission(perm_type) + except: + action = "add" if perm_type == 'create' else "update" + frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions")) + + def get_new_child_item(): + if parent_doctype == "Sales Order": + return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) + if parent_doctype == "Purchase Order": + return set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) + + def validate_quantity(child_item, d): + if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): + frappe.throw(_("Cannot set quantity less than delivered quantity")) + + if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty): + frappe.throw(_("Cannot set quantity less than received quantity")) + data = json.loads(trans_items) sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation'] @@ -1201,20 +1221,19 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil new_child_flag = False if not d.get("docname"): new_child_flag = True - if parent_doctype == "Sales Order": - child_item = set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) - if parent_doctype == "Purchase Order": - child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) + check_permissions(parent, 'create') + child_item = get_new_child_item() else: + check_permissions(parent, 'write') child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) - if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")): + + rate_unchanged = flt(child_item.get("rate")) == flt(d.get("rate")) + qty_unchanged = flt(child_item.get("qty")) == flt(d.get("qty")) + conversion_factor_unchanged = flt(child_item.get("conversion_factor")) == flt(d.get("conversion_factor")) + if rate_unchanged and qty_unchanged and conversion_factor_changed: continue - if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): - frappe.throw(_("Cannot set quantity less than delivered quantity")) - - if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty): - frappe.throw(_("Cannot set quantity less than received quantity")) + validate_quantity(child_item, d) child_item.qty = flt(d.get("qty")) precision = child_item.precision("rate") or 2 @@ -1224,6 +1243,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil .format(child_item.idx, child_item.item_code)) else: child_item.rate = flt(d.get("rate")) + + if d.get("conversion_factor"): + child_item.conversion_factor = flt(d.get('conversion_factor')) if flt(child_item.price_list_rate): if flt(child_item.rate) > flt(child_item.price_list_rate): diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 58969f2a9fd..a487f83fe64 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -472,6 +472,12 @@ erpnext.utils.update_child_items = function(opts) { in_list_view: 1, label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date") }) + fields.splice(3, 0, { + fieldtype: 'Float', + fieldname: "conversion_factor", + in_list_view: 1, + label: __("UOM Conversion Factor") + }) } const dialog = new frappe.ui.Dialog({ @@ -519,6 +525,7 @@ erpnext.utils.update_child_items = function(opts) { "item_code": d.item_code, "delivery_date": d.delivery_date, "schedule_date": d.schedule_date, + "conversion_factor": d.conversion_factor, "qty": d.qty, "rate": d.rate, }); diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index b8b0d404e50..90f9b094d1e 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -400,6 +400,22 @@ class TestSalesOrder(unittest.TestCase): trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}]) self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) + def test_update_child_qty_rate_perm(self): + so = make_sales_order(item_code= "_Test Item", qty=4) + + user = 'test@example.com' + test_user = frappe.get_doc('User', user) + test_user.add_roles("Accounts User") + frappe.set_user(user) + + # update qty + trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': so.items[0].name}]) + self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) + + # add new item + trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}]) + self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) + def test_warehouse_user(self): frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com") frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com") diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index e59349926e6..18691591cd5 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -214,7 +214,6 @@ "fieldtype": "Link", "label": "UOM", "options": "UOM", - "print_hide": 0, "reqd": 1 }, { @@ -769,22 +768,23 @@ "label": "Against Blanket Order" }, { - "fieldname": "bom_no", - "fieldtype": "Link", - "label": "BOM No", - "no_copy": 1, - "options": "BOM", - "print_hide": 1 - }, - { - "fieldname": "manufacturing_section_section", - "fieldtype": "Section Break", - "label": "Manufacturing Section" - } + "fieldname": "bom_no", + "fieldtype": "Link", + "label": "BOM No", + "no_copy": 1, + "options": "BOM", + "print_hide": 1 + }, + { + "fieldname": "manufacturing_section_section", + "fieldtype": "Section Break", + "label": "Manufacturing Section" + } ], "idx": 1, "istable": 1, - "modified": "2020-05-15 18:13:43.006493", + "links": [], + "modified": "2020-05-29 20:54:32.309460", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", From 5da8b98a048bf16cd4bd396f5a5eaf4f2d07d150 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 29 May 2020 21:59:01 +0530 Subject: [PATCH 02/12] fix: label --- erpnext/public/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index a487f83fe64..1803fcdfd78 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -476,7 +476,7 @@ erpnext.utils.update_child_items = function(opts) { fieldtype: 'Float', fieldname: "conversion_factor", in_list_view: 1, - label: __("UOM Conversion Factor") + label: __("Conversion Factor") }) } From c579b0879ec170ea1a24cbaed8b7cc8d6c655da3 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 29 May 2020 22:21:50 +0530 Subject: [PATCH 03/12] fix: cannot update delivery date --- erpnext/controllers/accounts_controller.py | 24 ++++++++++++++++++---- erpnext/public/js/utils.js | 3 ++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ca78070bf4e..43a27333236 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1227,10 +1227,20 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil check_permissions(parent, 'write') child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) - rate_unchanged = flt(child_item.get("rate")) == flt(d.get("rate")) - qty_unchanged = flt(child_item.get("qty")) == flt(d.get("qty")) - conversion_factor_unchanged = flt(child_item.get("conversion_factor")) == flt(d.get("conversion_factor")) - if rate_unchanged and qty_unchanged and conversion_factor_changed: + prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate")) + prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty")) + prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor")) + + if parent_doctype == 'Sales Order': + prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date") + elif parent_doctype == 'Purchase Order': + prev_date, new_date = child_item.get("schedule_date") == d.get("schedule_date") + + rate_unchanged = prev_rate == new_rate + qty_unchanged = prev_qty == prev_qty + conversion_factor_unchanged = prev_con_fac == new_con_fac + date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc + if rate_unchanged and qty_unchanged and conversion_factor_unchanged and date_unchanged: continue validate_quantity(child_item, d) @@ -1247,6 +1257,12 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil if d.get("conversion_factor"): child_item.conversion_factor = flt(d.get('conversion_factor')) + if d.get("delivery_date") and parent_doctype == 'Sales Order': + child_item.delivery_date = d.get('delivery_date') + + if d.get("schedule_date") and parent_doctype == 'Purchase Order': + child_item.schedule_date = d.get('schedule_date') + if flt(child_item.price_list_rate): if flt(child_item.rate) > flt(child_item.price_list_rate): # if rate is greater than price_list_rate, set margin diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 1803fcdfd78..b10ec98d4cc 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -470,7 +470,8 @@ erpnext.utils.update_child_items = function(opts) { fieldtype: 'Date', fieldname: frm.doc.doctype == 'Sales Order' ? "delivery_date" : "schedule_date", in_list_view: 1, - label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date") + label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date"), + reqd: 1 }) fields.splice(3, 0, { fieldtype: 'Float', From a655db6a79e58f152229d84f0bb08d45ee69b392 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 29 May 2020 22:44:59 +0530 Subject: [PATCH 04/12] fix: unexpected removal of print hide field --- erpnext/selling/doctype/sales_order_item/sales_order_item.json | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 18691591cd5..eff17f8bc78 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -214,6 +214,7 @@ "fieldtype": "Link", "label": "UOM", "options": "UOM", + "print_hide": 0, "reqd": 1 }, { From 24f9a80e7d6cfc2eddfa6e0831ee573e06051308 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 3 Jun 2020 10:59:37 +0530 Subject: [PATCH 05/12] fix(India): Reverse charge mechanism for GST --- erpnext/hooks.py | 3 ++ .../gstr_3b_report/gstr_3b_report.html | 2 +- .../doctype/gstr_3b_report/gstr_3b_report.py | 12 ++--- erpnext/regional/india/utils.py | 52 ++++++++++++++++++- 4 files changed, 61 insertions(+), 8 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9d7cdc2a3b9..742cc8efbd1 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -238,6 +238,9 @@ doc_events = { "on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel", "on_trash": "erpnext.regional.check_deletion_permission" }, + "Purchase Invoice": { + "on_submit": "erpnext.regional.india.utils.make_reverse_charge_entries" + }, "Payment Entry": { "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status"], "on_trash": "erpnext.regional.check_deletion_permission" diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html index 35f9cf674ce..888b2da48eb 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html @@ -52,7 +52,7 @@ - (d) {{__("Inward Supplies(liable to reverse charge")}} + (d) {{__("Inward Supplies(liable to reverse charge)")}} {{ flt(data.sup_details.isup_rev.txval, 2) }} {{ flt(data.sup_details.isup_rev.iamt, 2) }} {{ flt(data.sup_details.isup_rev.camt, 2) }} diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 9e7a023926d..2691552d802 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -158,7 +158,7 @@ class GSTR3BReport(Document): self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"]) self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"]) - self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Registered Regular"], reverse_charge="Y") + self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered"], reverse_charge="Y") self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2) self.set_itc_details(itc_details) @@ -196,11 +196,12 @@ class GSTR3BReport(Document): if d["ty"] == 'ISRC': reverse_charge = "Y" + itc_type = 'All Other ITC' + gst_category = 'Unregistered' else: reverse_charge = "N" for account_head in self.account_heads: - d["iamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('igst_account')), {}).get("amount"), 2) d["camt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cgst_account')), {}).get("amount"), 2) d["samt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('sgst_account')), {}).get("amount"), 2) @@ -274,17 +275,16 @@ class GSTR3BReport(Document): """ #nosec .format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin")))) - def get_itc_details(self, reverse_charge='N'): - + def get_itc_details(self): itc_amount = frappe.db.sql(""" select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t - where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s + where s.docstatus = 1 and t.parent = s.name and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s and s.company_gstin = %s group by t.account_head, s.gst_category, s.eligibility_for_itc """, - (reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) itc_details = {} diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 3085a310c41..9fe29eba1b8 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -9,6 +9,8 @@ from erpnext.hr.utils import get_salary_assignment from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.regional.india import number_state_mapping from six import string_types +from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.accounts.utils import get_account_currency def validate_gstin_for_india(doc, method): if hasattr(doc, 'gst_state') and doc.gst_state: @@ -658,5 +660,53 @@ def get_gst_accounts(company, account_wise=False): elif val: gst_accounts[val] = acc - return gst_accounts + +def make_reverse_charge_entries(doc, method): + country = frappe.get_cached_value('Company', doc.company, 'country') + + if country != 'India': + return + + if doc.reverse_charge == 'Y': + gl_entries = [] + gst_accounts = get_gst_accounts(doc.company) + gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + + gst_accounts.get('igst_account') + + for tax in doc.get('taxes'): + if tax.category not in ("Total", "Valuation and Total"): + continue + + if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list: + account_currency = get_account_currency(tax.account_head) + + gl_entries.append(doc.get_gl_dict( + { + "account": tax.account_head, + "cost_center": tax.cost_center, + "posting_date": doc.posting_date, + "against": doc.supplier, + "credit": tax.base_tax_amount_after_discount_amount, + "credits_in_account_currency": tax.base_tax_amount_after_discount_amount \ + if account_currency==doc.company_currency \ + else tax.tax_amount_after_discount_amount + }, account_currency, item=tax) + ) + + gl_entries.append(doc.get_gl_dict( + { + "account": doc.credit_to if doc.doctype == 'Purchase Invoice' else doc.debit_to, + "cost_center": doc.cost_center, + "posting_date": doc.posting_date, + "party_type": 'Supplier', + "party": doc.supplier, + "against": tax.account_head, + "debit": tax.base_tax_amount_after_discount_amount, + "debit_in_account_currency": tax.base_tax_amount_after_discount_amount \ + if account_currency==doc.company_currency \ + else tax.tax_amount_after_discount_amount + }, account_currency, item=doc) + ) + + make_gl_entries(gl_entries) \ No newline at end of file From f9c4b209853569df89db57d1456b3d8128f7aa3e Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 12 Jun 2020 15:49:53 +0530 Subject: [PATCH 06/12] fix: Added PO test and conversion factor fix - Don't change conversion factor if stock uom and uom is the same - Added PO test - Added Accounts User basic role in PO - Minor fixes, wrong variables --- .../purchase_order/purchase_order.json | 8 ++++++- .../purchase_order/test_purchase_order.py | 22 ++++++++++++++++--- erpnext/controllers/accounts_controller.py | 15 ++++++++----- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index a4f60fbba5c..64dc3ed324b 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1068,7 +1068,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2020-04-24 12:13:14.186280", + "modified": "2020-06-12 14:08:11.777120", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", @@ -1112,6 +1112,12 @@ "read": 1, "role": "Purchase Manager", "write": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts User" } ], "search_fields": "status, transaction_date, supplier,grand_total", diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 1712369e60b..3d6cba891a6 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -118,7 +118,7 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEqual(po.get("items")[0].amount, 1400) self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3) - + def test_add_new_item_in_update_child_qty_rate(self): po = create_purchase_order(do_not_save=1) po.items[0].qty = 4 @@ -144,7 +144,7 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEquals(len(po.get('items')), 2) self.assertEqual(po.status, 'To Receive and Bill') - + def test_remove_item_in_update_child_qty_rate(self): po = create_purchase_order(do_not_save=1) po.items[0].qty = 4 @@ -185,6 +185,22 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEquals(len(po.get('items')), 1) self.assertEqual(po.status, 'To Receive and Bill') + def test_update_child_qty_rate_perm(self): + po = create_purchase_order(item_code= "_Test Item", qty=4) + + user = 'test@example.com' + test_user = frappe.get_doc('User', user) + test_user.add_roles("Accounts User") + frappe.set_user(user) + + # update qty + trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.items[0].name}]) + self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name) + + # add new item + trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}]) + self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name) + def test_update_qty(self): po = create_purchase_order() @@ -689,7 +705,7 @@ class TestPurchaseOrder(unittest.TestCase): po.save() self.assertEqual(po.schedule_date, add_days(nowdate(), 2)) - + def test_po_optional_blanket_order(self): """ Expected result: Blanket order Ordered Quantity should only be affected on Purchase Order with against_blanket_order = 1. diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 43a27333236..c75aff9264e 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1137,7 +1137,7 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, child_item.item_name = item.item_name child_item.description = item.description child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date - child_item.conversion_factor = flt(d.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 + child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.uom = item.stock_uom child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) if not child_item.warehouse: @@ -1157,7 +1157,7 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna child_item.item_name = item.item_name child_item.description = item.description child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date - child_item.conversion_factor = flt(d.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 + child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.uom = item.stock_uom child_item.base_rate = 1 # Initiallize value will update in parent validation child_item.base_amount = 1 # Initiallize value will update in parent validation @@ -1237,7 +1237,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil prev_date, new_date = child_item.get("schedule_date") == d.get("schedule_date") rate_unchanged = prev_rate == new_rate - qty_unchanged = prev_qty == prev_qty + qty_unchanged = prev_qty == new_qty conversion_factor_unchanged = prev_con_fac == new_con_fac date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc if rate_unchanged and qty_unchanged and conversion_factor_unchanged and date_unchanged: @@ -1253,13 +1253,16 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil .format(child_item.idx, child_item.item_code)) else: child_item.rate = flt(d.get("rate")) - + if d.get("conversion_factor"): - child_item.conversion_factor = flt(d.get('conversion_factor')) + if child_item.stock_uom == child_item.uom: + child_item.conversion_factor = 1 + else: + child_item.conversion_factor = flt(d.get('conversion_factor')) if d.get("delivery_date") and parent_doctype == 'Sales Order': child_item.delivery_date = d.get('delivery_date') - + if d.get("schedule_date") and parent_doctype == 'Purchase Order': child_item.schedule_date = d.get('schedule_date') From 5bf6bec6567fd6f3d6e12af233c0d4c5af872255 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 14 Jun 2020 13:18:08 +0530 Subject: [PATCH 07/12] fix: Consider Overseas category in RCM --- .../doctype/gstr_3b_report/gstr_3b_report.py | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 2691552d802..619734ff263 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -158,7 +158,7 @@ class GSTR3BReport(Document): self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"]) self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"]) - self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered"], reverse_charge="Y") + self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y") self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2) self.set_itc_details(itc_details) @@ -192,32 +192,27 @@ class GSTR3BReport(Document): for d in self.report_dict["itc_elg"]["itc_avl"]: itc_type = itc_type_map.get(d["ty"]) - gst_category = "Registered Regular" + gst_category = ["Registered Regular"] if d["ty"] == 'ISRC': reverse_charge = "Y" itc_type = 'All Other ITC' - gst_category = 'Unregistered' + gst_category = ['Unregistered', 'Overseas'] else: reverse_charge = "N" for account_head in self.account_heads: - d["iamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('igst_account')), {}).get("amount"), 2) - d["camt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cgst_account')), {}).get("amount"), 2) - d["samt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('sgst_account')), {}).get("amount"), 2) - d["csamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cess_account')), {}).get("amount"), 2) + for category in gst_category: + for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]: + d[key[0]] += flt(itc_details.get((category, itc_type, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2) - net_itc["iamt"] += flt(d["iamt"], 2) - net_itc["camt"] += flt(d["camt"], 2) - net_itc["samt"] += flt(d["samt"], 2) - net_itc["csamt"] += flt(d["csamt"], 2) + for key in ['iamt', 'camt', 'samt', 'csamt']: + net_itc[key] += flt(d[key], 2) for account_head in self.account_heads: itc_inelg = self.report_dict["itc_elg"]["itc_inelg"][1] - itc_inelg["iamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("igst_account")), {}).get("amount"), 2) - itc_inelg["camt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cgst_account")), {}).get("amount"), 2) - itc_inelg["samt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("sgst_account")), {}).get("amount"), 2) - itc_inelg["csamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cess_account")), {}).get("amount"), 2) + for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]: + itc_inelg[key[0]] = flt(itc_details.get(("Ineligible", "N", account_head.get(key[1])), {}).get("amount"), 2) def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"): From 359e934cd23ca31839a540791c5694680203543b Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 15 Jun 2020 11:32:42 +0530 Subject: [PATCH 08/12] fix: Travis --- erpnext/buying/doctype/purchase_order/test_purchase_order.py | 1 + erpnext/controllers/accounts_controller.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 3d6cba891a6..813286f7fa4 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -200,6 +200,7 @@ class TestPurchaseOrder(unittest.TestCase): # add new item trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}]) self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name) + frappe.set_user("Administrator") def test_update_qty(self): po = create_purchase_order() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c75aff9264e..837ffe3c8cb 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1234,7 +1234,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil if parent_doctype == 'Sales Order': prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date") elif parent_doctype == 'Purchase Order': - prev_date, new_date = child_item.get("schedule_date") == d.get("schedule_date") + prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date") rate_unchanged = prev_rate == new_rate qty_unchanged = prev_qty == new_qty From b7e94cc742c6407bb5f223ce9f75ef52583332f0 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 15 Jun 2020 11:32:42 +0530 Subject: [PATCH 09/12] fix: Travis --- erpnext/buying/doctype/purchase_order/test_purchase_order.py | 1 + erpnext/controllers/accounts_controller.py | 2 +- erpnext/selling/doctype/sales_order/test_sales_order.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 3d6cba891a6..813286f7fa4 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -200,6 +200,7 @@ class TestPurchaseOrder(unittest.TestCase): # add new item trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}]) self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name) + frappe.set_user("Administrator") def test_update_qty(self): po = create_purchase_order() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c75aff9264e..837ffe3c8cb 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1234,7 +1234,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil if parent_doctype == 'Sales Order': prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date") elif parent_doctype == 'Purchase Order': - prev_date, new_date = child_item.get("schedule_date") == d.get("schedule_date") + prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date") rate_unchanged = prev_rate == new_rate qty_unchanged = prev_qty == new_qty diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 90f9b094d1e..74e742fabbb 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -415,6 +415,7 @@ class TestSalesOrder(unittest.TestCase): # add new item trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}]) self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) + frappe.set_user("Administrator") def test_warehouse_user(self): frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com") From 8d5380a2f0a2e0a160e05092b872302d7d940894 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 18 Jun 2020 14:06:31 +0530 Subject: [PATCH 10/12] fix: incorrect variable used --- erpnext/controllers/accounts_controller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 837ffe3c8cb..f54b593022c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1197,11 +1197,11 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil action = "add" if perm_type == 'create' else "update" frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions")) - def get_new_child_item(): + def get_new_child_item(item_row): if parent_doctype == "Sales Order": - return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) + return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row) if parent_doctype == "Purchase Order": - return set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) + return set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row) def validate_quantity(child_item, d): if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): @@ -1222,7 +1222,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil if not d.get("docname"): new_child_flag = True check_permissions(parent, 'create') - child_item = get_new_child_item() + child_item = get_new_child_item(d) else: check_permissions(parent, 'write') child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) From 34d2bfbb3e6ce28de2690812191b8885b4f8f6ab Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 18 Jun 2020 09:58:55 +0000 Subject: [PATCH 11/12] refactor: handle exceptions when updating addresses (#22307) * refactor: handle exceptions when updating addresses * refactor: fold common statements in a loop --- .../connectors/woocommerce_connection.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py index 1b0c9f60b6e..6dedaa8c530 100644 --- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py +++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py @@ -73,10 +73,16 @@ def link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name if customer_exists: frappe.rename_doc("Customer", old_name, customer_name) - billing_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Billing"}) - shipping_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Shipping"}) - rename_address(billing_address, customer) - rename_address(shipping_address, customer) + for address_type in ("Billing", "Shipping",): + try: + address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": address_type}) + rename_address(address, customer) + except ( + frappe.DoesNotExistError, + frappe.DuplicateEntryError, + frappe.ValidationError, + ): + pass else: create_address(raw_billing_data, customer, "Billing") create_address(raw_shipping_data, customer, "Shipping") From a529d71ca0bf7517dc9d76e1dfd52359b411b7a8 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 18 Jun 2020 15:38:43 +0530 Subject: [PATCH 12/12] fix(patch): add patch for setting issue metrics (#22296) * patch: set issue metrics in Issue documents * fix: commit after every 100 records --- erpnext/patches.txt | 1 + erpnext/patches/v13_0/update_issue_metrics.py | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 erpnext/patches/v13_0/update_issue_metrics.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 279c453e350..d74dc244f3a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -699,3 +699,4 @@ erpnext.patches.v13_0.delete_old_purchase_reports erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions erpnext.patches.v13_0.update_sla_enhancements erpnext.patches.v12_0.update_address_template_for_india +erpnext.patches.v13_0.update_issue_metrics diff --git a/erpnext/patches/v13_0/update_issue_metrics.py b/erpnext/patches/v13_0/update_issue_metrics.py new file mode 100644 index 00000000000..6d7623565f0 --- /dev/null +++ b/erpnext/patches/v13_0/update_issue_metrics.py @@ -0,0 +1,33 @@ +from __future__ import unicode_literals +import frappe + +from frappe.core.doctype.communication.communication import set_avg_response_time +from erpnext.support.doctype.issue.issue import set_resolution_time, set_user_resolution_time + +def execute(): + if frappe.db.exists('DocType', 'Issue'): + frappe.reload_doctype('Issue') + + count = 0 + for parent in frappe.get_all('Issue', order_by='creation desc'): + parent_doc = frappe.get_doc('Issue', parent.name) + + communication = frappe.get_all('Communication', filters={ + 'reference_doctype': 'Issue', + 'reference_name': parent.name, + 'communication_medium': 'Email', + 'sent_or_received': 'Sent' + }, order_by = 'creation asc', limit=1) + + if communication: + communication_doc = frappe.get_doc('Communication', communication[0].name) + set_avg_response_time(parent_doc, communication_doc) + + if parent_doc.status in ['Closed', 'Resolved']: + set_resolution_time(parent_doc) + set_user_resolution_time(parent_doc) + + # commit after every 100 records + count += 1 + if count % 100 == 0: + frappe.db.commit() \ No newline at end of file