From 25d208aa8a1fcb535696f7da450176aacc23d675 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 6 Sep 2021 10:37:41 +0530 Subject: [PATCH 001/185] fix: GL Entries on advance TDS allocation --- erpnext/controllers/accounts_controller.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b90db054b5..515d6fdef5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -832,7 +832,7 @@ class AccountsController(TransactionBase): if pe.reference_type == 'Payment Entry': pe = frappe.get_doc('Payment Entry', pe.reference_name) for tax in pe.get('taxes'): - allocated_amount = tax_map.get(tax.account_head) - allocated_tax_map.get(tax.account_head) + allocated_amount = flt(tax_map.get(tax.account_head)) - flt(allocated_tax_map.get(tax.account_head)) if allocated_amount > tax.tax_amount: allocated_amount = tax.tax_amount @@ -931,15 +931,19 @@ class AccountsController(TransactionBase): if pe.reference_type == "Payment Entry" and \ frappe.db.get_value('Payment Entry', pe.reference_name, 'advance_tax_account'): pe = frappe.get_doc("Payment Entry", pe.reference_name) + advance_tax_account = pe.advance_tax_account + for tax in pe.get("taxes"): account_currency = get_account_currency(tax.account_head) if self.doctype == "Purchase Invoice": - dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - else: dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + advance_tax_account = pe.advance_tax_account if pe.paid_from != pe.advance_tax_account \ + else self.credit_to + else: + dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" party = self.supplier if self.doctype == "Purchase Invoice" else self.customer unallocated_amount = tax.tax_amount - tax.allocated_amount @@ -961,13 +965,15 @@ class AccountsController(TransactionBase): gl_entries.append( self.get_gl_dict({ - "account": pe.advance_tax_account, + "account": advance_tax_account, "against": party, rev_dr_cr: unallocated_amount, rev_dr_cr + "_in_account_currency": unallocated_amount if account_currency==self.company_currency else unallocated_amount, - "cost_center": tax.cost_center + "cost_center": self.get('cost_center') if advance_tax_account == self.get('credit_to') else tax.cost_center, + "party_type": 'Supplier' if advance_tax_account == self.get('credit_to') else '', + "party": self.get('supplier') if advance_tax_account == self.get('credit_to') else '', }, account_currency, item=tax)) frappe.db.set_value("Advance Taxes and Charges", tax.name, "allocated_amount", From 99edc9c83a16137a73516d809b67ec398e46a921 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sat, 2 Oct 2021 02:03:29 +0530 Subject: [PATCH 002/185] fix: Display message to delete linked invoices in the draft state --- erpnext/selling/doctype/sales_order/sales_order.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 9367609421..c2107b6c57 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -241,7 +241,7 @@ class SalesOrder(SellingController): # Checks Sales Invoice submit_rv = frappe.db.sql_list("""select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 - where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus < 2""", + where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 1""", self.name) if submit_rv: @@ -249,6 +249,16 @@ class SalesOrder(SellingController): frappe.throw(_("Sales Invoice {0} must be cancelled before cancelling this Sales Order") .format(", ".join(submit_rv))) + draft_rv = frappe.db.sql_list("""select t1.name + from `tabSales Invoice` t1,`tabSales Invoice Item` t2 + where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 0""", + self.name) + + if draft_rv: + draft_rv = [get_link_to_form("Sales Invoice", si) for si in draft_rv] + frappe.throw(_("Sales Invoice {0} must be deleted before cancelling this Sales Order") + .format(", ".join(draft_rv))) + #check maintenance schedule submit_ms = frappe.db.sql_list(""" select t1.name From 6e5a3c0ea4d6949e138e1d76359b19ceeda6aa73 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sat, 2 Oct 2021 02:15:17 +0530 Subject: [PATCH 003/185] fix: Display draft invoices only once in error message --- erpnext/selling/doctype/sales_order/sales_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index c2107b6c57..b1a6893034 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -249,7 +249,7 @@ class SalesOrder(SellingController): frappe.throw(_("Sales Invoice {0} must be cancelled before cancelling this Sales Order") .format(", ".join(submit_rv))) - draft_rv = frappe.db.sql_list("""select t1.name + draft_rv = frappe.db.sql_list("""select distinct t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 0""", self.name) From 21798d883611101964ce6f69054c20c137cb6d33 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 26 Oct 2021 21:35:43 +0530 Subject: [PATCH 004/185] fix: Add test to check if SO can be cancelled when linked SI has been submitted --- .../selling/doctype/sales_order/test_sales_order.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index bbfe7c06d8..f9556c2021 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1279,6 +1279,19 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.ValidationError, so.cancel) + def test_so_cancellation_when_si_has_been_submitted(self): + """ + Test to check if Sales Order gets cancelled when linked Sales Invoice has been Submitted + Expected result: Sales Order should not get cancelled + """ + so = make_sales_order() + so.submit() + si = make_sales_invoice(so.name) + si.submit() + + so.load_from_db() + self.assertRaises(frappe.LinkExistsError, so.cancel) + def test_payment_terms_are_fetched_when_creating_sales_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, From aac574579abef8cb05e016b4a27e706149746d5a Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 26 Oct 2021 21:41:47 +0530 Subject: [PATCH 005/185] fix: Add test to check if SO can be cancelled when linked DN has been submitted --- .../doctype/sales_order/test_sales_order.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index f9556c2021..b30bc030ae 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1279,7 +1279,7 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.ValidationError, so.cancel) - def test_so_cancellation_when_si_has_been_submitted(self): + def test_so_cancellation_after_si_submission(self): """ Test to check if Sales Order gets cancelled when linked Sales Invoice has been Submitted Expected result: Sales Order should not get cancelled @@ -1292,6 +1292,19 @@ class TestSalesOrder(unittest.TestCase): so.load_from_db() self.assertRaises(frappe.LinkExistsError, so.cancel) + def test_so_cancellation_after_dn_submission(self): + """ + Test to check if Sales Order gets cancelled when linked Delivery Note has been Submitted + Expected result: Sales Order should not get cancelled + """ + so = make_sales_order() + so.submit() + dn = make_delivery_note(so.name) + dn.submit() + + so.load_from_db() + self.assertRaises(frappe.LinkExistsError, so.cancel) + def test_payment_terms_are_fetched_when_creating_sales_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, From 60119f5ce5c212e25934c2e34a1ac259e5fdcc77 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 26 Oct 2021 21:42:35 +0530 Subject: [PATCH 006/185] fix: Remove redundant validations --- .../doctype/sales_order/sales_order.py | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index b1a6893034..a8277ba78d 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -227,28 +227,6 @@ class SalesOrder(SellingController): check_credit_limit(self.customer, self.company) def check_nextdoc_docstatus(self): - # Checks Delivery Note - submit_dn = frappe.db.sql_list(""" - select t1.name - from `tabDelivery Note` t1,`tabDelivery Note Item` t2 - where t1.name = t2.parent and t2.against_sales_order = %s and t1.docstatus = 1""", self.name) - - if submit_dn: - submit_dn = [get_link_to_form("Delivery Note", dn) for dn in submit_dn] - frappe.throw(_("Delivery Notes {0} must be cancelled before cancelling this Sales Order") - .format(", ".join(submit_dn))) - - # Checks Sales Invoice - submit_rv = frappe.db.sql_list("""select t1.name - from `tabSales Invoice` t1,`tabSales Invoice Item` t2 - where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 1""", - self.name) - - if submit_rv: - submit_rv = [get_link_to_form("Sales Invoice", si) for si in submit_rv] - frappe.throw(_("Sales Invoice {0} must be cancelled before cancelling this Sales Order") - .format(", ".join(submit_rv))) - draft_rv = frappe.db.sql_list("""select distinct t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 0""", From 80ce3e6de722e1c64a4c881691316e432fdf44ea Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 04:17:21 +0530 Subject: [PATCH 007/185] fix: Add test to check if SO can be cancelled after linked Maintenance Schedule has been submitted --- .../doctype/sales_order/test_sales_order.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index b30bc030ae..ff25a3a46d 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1305,6 +1305,21 @@ class TestSalesOrder(unittest.TestCase): so.load_from_db() self.assertRaises(frappe.LinkExistsError, so.cancel) + def test_so_cancellation_after_maintenance_schedule_submission(self): + """ + Expected result: Sales Order should not get cancelled + """ + from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import make_maintenance_schedule + + so = make_sales_order() + so.submit() + ms = make_maintenance_schedule() + ms.items[0].sales_order = so.name + ms.submit() + + so.load_from_db() + self.assertRaises(frappe.LinkExistsError, so.cancel) + def test_payment_terms_are_fetched_when_creating_sales_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, From 9ec24d491513fb4643667fe9e252574b4569f1b2 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 04:46:59 +0530 Subject: [PATCH 008/185] fix: Add test to check if SO can be cancelled after linked Maintenance Visit has been submitted --- .../doctype/sales_order/test_sales_order.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index ff25a3a46d..57b0c023ce 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1320,6 +1320,22 @@ class TestSalesOrder(unittest.TestCase): so.load_from_db() self.assertRaises(frappe.LinkExistsError, so.cancel) + def test_so_cancellation_after_maintenance_visit_submission(self): + """ + Expected result: Sales Order should not get cancelled + """ + from erpnext.maintenance.doctype.maintenance_visit.test_maintenance_visit import make_maintenance_visit + + so = make_sales_order() + so.submit() + mv = make_maintenance_visit() + mv.purposes[0].prevdoc_doctype = "Sales Order" + mv.purposes[0].prevdoc_docname = so.name + mv.submit() + + so.load_from_db() + self.assertRaises(frappe.LinkExistsError, so.cancel) + def test_payment_terms_are_fetched_when_creating_sales_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, From 29fc4da4c62db251ac8a1e20ac7e7bf510883a80 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 04:47:26 +0530 Subject: [PATCH 009/185] fix: Create Maintenance Visit --- .../test_maintenance_visit.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py index 57e728d1b1..6f86f70eb7 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py @@ -4,8 +4,39 @@ from __future__ import unicode_literals import unittest +import frappe +from frappe.utils.data import today # test_records = frappe.get_test_records('Maintenance Visit') class TestMaintenanceVisit(unittest.TestCase): pass + +def make_maintenance_visit(): + mv = frappe.new_doc("Maintenance Visit") + mv.company = "_Test Company" + mv.customer = "_Test Customer" + mv.mntc_date = today() + mv.completion_status = "Partially Completed" + + sales_person = make_sales_person("Dwight Schrute") + + mv.append("purposes", { + "item_code": "_Test Item", + "sales_person": "Sales Team", + "description": "Test Item", + "work_done": "Test Work Done", + "service_person": sales_person.name + }) + mv.insert(ignore_permissions=True) + + return mv + +def make_sales_person(name): + sales_person = frappe.get_doc({ + 'doctype': "Sales Person", + 'sales_person_name': name + }) + sales_person.insert(ignore_if_duplicate = True) + + return sales_person From 69e011bf6ae7d3da6a86ee462bc26d6de63efc21 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 05:07:16 +0530 Subject: [PATCH 010/185] fix: Add test to check if SO can be cancelled after linked Work Order has been submitted --- .../selling/doctype/sales_order/test_sales_order.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 57b0c023ce..d8d7336211 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1336,6 +1336,19 @@ class TestSalesOrder(unittest.TestCase): so.load_from_db() self.assertRaises(frappe.LinkExistsError, so.cancel) + def test_so_cancellation_after_work_order_submission(self): + """ + Expected result: Sales Order should not get cancelled + """ + from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record + + so = make_sales_order(item_code="_Test FG Item", qty=10) + so.submit() + make_wo_order_test_record(sales_order=so.name) + + so.load_from_db() + self.assertRaises(frappe.LinkExistsError, so.cancel) + def test_payment_terms_are_fetched_when_creating_sales_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, From 7454167725247b29ef067719a0c3bef72dd0d7a5 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 05:08:11 +0530 Subject: [PATCH 011/185] fix: Remove redundant code --- .../doctype/sales_order/sales_order.py | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index a8277ba78d..8d69f7b080 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -237,39 +237,6 @@ class SalesOrder(SellingController): frappe.throw(_("Sales Invoice {0} must be deleted before cancelling this Sales Order") .format(", ".join(draft_rv))) - #check maintenance schedule - submit_ms = frappe.db.sql_list(""" - select t1.name - from `tabMaintenance Schedule` t1, `tabMaintenance Schedule Item` t2 - where t2.parent=t1.name and t2.sales_order = %s and t1.docstatus = 1""", self.name) - - if submit_ms: - submit_ms = [get_link_to_form("Maintenance Schedule", ms) for ms in submit_ms] - frappe.throw(_("Maintenance Schedule {0} must be cancelled before cancelling this Sales Order") - .format(", ".join(submit_ms))) - - # check maintenance visit - submit_mv = frappe.db.sql_list(""" - select t1.name - from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 - where t2.parent=t1.name and t2.prevdoc_docname = %s and t1.docstatus = 1""",self.name) - - if submit_mv: - submit_mv = [get_link_to_form("Maintenance Visit", mv) for mv in submit_mv] - frappe.throw(_("Maintenance Visit {0} must be cancelled before cancelling this Sales Order") - .format(", ".join(submit_mv))) - - # check work order - pro_order = frappe.db.sql_list(""" - select name - from `tabWork Order` - where sales_order = %s and docstatus = 1""", self.name) - - if pro_order: - pro_order = [get_link_to_form("Work Order", po) for po in pro_order] - frappe.throw(_("Work Order {0} must be cancelled before cancelling this Sales Order") - .format(", ".join(pro_order))) - def check_modified_date(self): mod_db = frappe.db.get_value("Sales Order", self.name, "modified") date_diff = frappe.db.sql("select TIMEDIFF('%s', '%s')" % From 6dbc47f2250c735130892821d0512e5f5e658611 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 05:09:44 +0530 Subject: [PATCH 012/185] fix: Rename variables --- erpnext/selling/doctype/sales_order/sales_order.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 8d69f7b080..79e40f65db 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -227,15 +227,15 @@ class SalesOrder(SellingController): check_credit_limit(self.customer, self.company) def check_nextdoc_docstatus(self): - draft_rv = frappe.db.sql_list("""select distinct t1.name + linked_invoices = frappe.db.sql_list("""select distinct t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 0""", self.name) - if draft_rv: - draft_rv = [get_link_to_form("Sales Invoice", si) for si in draft_rv] + if linked_invoices: + linked_invoices = [get_link_to_form("Sales Invoice", si) for si in linked_invoices] frappe.throw(_("Sales Invoice {0} must be deleted before cancelling this Sales Order") - .format(", ".join(draft_rv))) + .format(", ".join(linked_invoices))) def check_modified_date(self): mod_db = frappe.db.get_value("Sales Order", self.name, "modified") From 54a0f5d04f2800ad4d08cd9271b75145e1d822ee Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 05:15:44 +0530 Subject: [PATCH 013/185] fix: Make import statements fit conventions --- erpnext/selling/doctype/sales_order/test_sales_order.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index d8d7336211..b26ee768c4 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1309,7 +1309,9 @@ class TestSalesOrder(unittest.TestCase): """ Expected result: Sales Order should not get cancelled """ - from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import make_maintenance_schedule + from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( + make_maintenance_schedule + ) so = make_sales_order() so.submit() @@ -1324,7 +1326,9 @@ class TestSalesOrder(unittest.TestCase): """ Expected result: Sales Order should not get cancelled """ - from erpnext.maintenance.doctype.maintenance_visit.test_maintenance_visit import make_maintenance_visit + from erpnext.maintenance.doctype.maintenance_visit.test_maintenance_visit import ( + make_maintenance_visit + ) so = make_sales_order() so.submit() From 07b25c4275173d01bee700a8b129a43469c1a83b Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 05:20:31 +0530 Subject: [PATCH 014/185] fix: Import statements --- .../doctype/sales_order/test_sales_order.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index b26ee768c4..752d358e43 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -21,6 +21,12 @@ from erpnext.selling.doctype.sales_order.sales_order import ( make_sales_invoice, make_work_orders, ) +from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( + make_maintenance_schedule, +) +from erpnext.maintenance.doctype.maintenance_visit.test_maintenance_visit import ( + make_maintenance_visit, +) from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -1309,10 +1315,6 @@ class TestSalesOrder(unittest.TestCase): """ Expected result: Sales Order should not get cancelled """ - from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( - make_maintenance_schedule - ) - so = make_sales_order() so.submit() ms = make_maintenance_schedule() @@ -1326,10 +1328,6 @@ class TestSalesOrder(unittest.TestCase): """ Expected result: Sales Order should not get cancelled """ - from erpnext.maintenance.doctype.maintenance_visit.test_maintenance_visit import ( - make_maintenance_visit - ) - so = make_sales_order() so.submit() mv = make_maintenance_visit() From 4789c3423d30adb02024d305e3afc517058857f3 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 05:25:32 +0530 Subject: [PATCH 015/185] fix: Linters --- .../selling/doctype/sales_order/test_sales_order.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 752d358e43..ca696034d6 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -11,6 +11,12 @@ from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.utils import add_days, flt, getdate, nowdate from erpnext.controllers.accounts_controller import update_child_qty_rate +from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( + make_maintenance_schedule, +) +from erpnext.maintenance.doctype.maintenance_visit.test_maintenance_visit import ( + make_maintenance_visit, +) from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle from erpnext.selling.doctype.sales_order.sales_order import ( @@ -21,12 +27,6 @@ from erpnext.selling.doctype.sales_order.sales_order import ( make_sales_invoice, make_work_orders, ) -from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( - make_maintenance_schedule, -) -from erpnext.maintenance.doctype.maintenance_visit.test_maintenance_visit import ( - make_maintenance_visit, -) from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry From 78825f2c6c277f9229f663a31185fddfb8796b0e Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 06:18:24 +0530 Subject: [PATCH 016/185] fix: Add extra line --- .../doctype/maintenance_visit/test_maintenance_visit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py index 6f86f70eb7..7ebb4ba023 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import unittest + import frappe from frappe.utils.data import today From 27236b7e9ee7a325dfcf75c7588ad31747a04ec1 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 28 Oct 2021 16:45:23 +0530 Subject: [PATCH 017/185] fix: Remove RM Cost column as cost is not retrievable from Job card --- .../cost_of_poor_quality_report.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py index 0dcad448d7..c79ded6804 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -1,8 +1,6 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import frappe from frappe import _ from frappe.utils import flt @@ -31,7 +29,7 @@ def get_data(report_filters): for row in job_cards: row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0) - update_raw_material_cost(row, report_filters) + # update_raw_material_cost(row, report_filters) data.append(row) return data @@ -47,11 +45,11 @@ def get_filters(report_filters, operations): return filters -def update_raw_material_cost(row, filters): - row.rm_cost = 0.0 - for data in frappe.get_all("Job Card Item", fields = ["amount"], - filters={"parent": row.name, "docstatus": 1}): - row.rm_cost += data.amount +# def update_raw_material_cost(row, filters): +# row.rm_cost = 0.0 +# for data in frappe.get_all("Job Card Item", fields = ["amount"], +# filters={"parent": row.name, "docstatus": 1}): +# row.rm_cost += data.amount def get_columns(filters): return [ @@ -60,7 +58,7 @@ def get_columns(filters): "fieldtype": "Link", "fieldname": "name", "options": "Job Card", - "width": "100" + "width": "120" }, { "label": _("Work Order"), @@ -112,18 +110,18 @@ def get_columns(filters): "label": _("Operating Cost"), "fieldtype": "Currency", "fieldname": "operating_cost", - "width": "100" - }, - { - "label": _("Raw Material Cost"), - "fieldtype": "Currency", - "fieldname": "rm_cost", - "width": "100" + "width": "150" }, + # { + # "label": _("Raw Material Cost"), + # "fieldtype": "Currency", + # "fieldname": "rm_cost", + # "width": "100" + # }, { "label": _("Total Time (in Mins)"), "fieldtype": "Float", "fieldname": "total_time_in_mins", - "width": "100" + "width": "150" } ] From 8502ccb5b2bd9306ce0ecdb49f4710a66c11860a Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 28 Oct 2021 16:53:45 +0530 Subject: [PATCH 018/185] chore: Add comment hinting to reason --- .../cost_of_poor_quality_report/cost_of_poor_quality_report.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py index c79ded6804..e6666f00bf 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -45,6 +45,8 @@ def get_filters(report_filters, operations): return filters +# Check PR #28123 as to why this is commented + # def update_raw_material_cost(row, filters): # row.rm_cost = 0.0 # for data in frappe.get_all("Job Card Item", fields = ["amount"], From 23af036894ef61f590278fa7e84402f0be3ff50d Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 29 Oct 2021 17:27:17 +0530 Subject: [PATCH 019/185] feat: provision to close the Work Order --- .../doctype/work_order/work_order.js | 48 +++++++++++-------- .../doctype/work_order/work_order.json | 5 +- .../doctype/work_order/work_order.py | 31 +++++++++++- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 51c46f63ab..d4235731ae 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -135,24 +135,26 @@ frappe.ui.form.on("Work Order", { frm.set_intro(__("Submit this Work Order for further processing.")); } - if (frm.doc.docstatus===1) { - frm.trigger('show_progress_for_items'); - frm.trigger('show_progress_for_operations'); - } + if (frm.doc.status != "Closed") { + if (frm.doc.docstatus===1) { + frm.trigger('show_progress_for_items'); + frm.trigger('show_progress_for_operations'); + } - if (frm.doc.docstatus === 1 - && frm.doc.operations && frm.doc.operations.length) { + if (frm.doc.docstatus === 1 + && frm.doc.operations && frm.doc.operations.length) { - const not_completed = frm.doc.operations.filter(d => { - if(d.status != 'Completed') { - return true; + const not_completed = frm.doc.operations.filter(d => { + if(d.status != 'Completed') { + return true; + } + }); + + if(not_completed && not_completed.length) { + frm.add_custom_button(__('Create Job Card'), () => { + frm.trigger("make_job_card"); + }).addClass('btn-primary'); } - }); - - if(not_completed && not_completed.length) { - frm.add_custom_button(__('Create Job Card'), () => { - frm.trigger("make_job_card"); - }).addClass('btn-primary'); } } @@ -517,14 +519,19 @@ frappe.ui.form.on("Work Order Operation", { erpnext.work_order = { set_custom_buttons: function(frm) { var doc = frm.doc; - if (doc.docstatus === 1) { + if (doc.docstatus === 1 && doc.status != "Closed") { + console.log("check"); + frm.add_custom_button(__('Close'), function() { + erpnext.work_order.change_work_order_status(frm, "Closed"); + }, __("Status")); + if (doc.status != 'Stopped' && doc.status != 'Completed') { frm.add_custom_button(__('Stop'), function() { - erpnext.work_order.stop_work_order(frm, "Stopped"); + erpnext.work_order.change_work_order_status(frm, "Stopped"); }, __("Status")); } else if (doc.status == 'Stopped') { frm.add_custom_button(__('Re-open'), function() { - erpnext.work_order.stop_work_order(frm, "Resumed"); + erpnext.work_order.change_work_order_status(frm, "Resumed"); }, __("Status")); } @@ -713,9 +720,10 @@ erpnext.work_order = { }); }, - stop_work_order: function(frm, status) { + change_work_order_status: function(frm, status) { + let method_name = status=="Closed" ? "close_work_order" : "stop_unstop"; frappe.call({ - method: "erpnext.manufacturing.doctype.work_order.work_order.stop_unstop", + method: `erpnext.manufacturing.doctype.work_order.work_order.${method_name}`, freeze: true, freeze_message: __("Updating Work Order status"), args: { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 7f8e816a22..df7ee53b92 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -99,7 +99,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled", + "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nClosed\nCancelled", "read_only": 1, "reqd": 1, "search_index": 1 @@ -573,7 +573,8 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-10-27 19:21:35.139888", + "migration_hash": "a18118963f4fcdb7f9d326de5f4063ba", + "modified": "2021-10-29 15:12:32.203605", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index f881e1bf16..6901d71ad4 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -175,7 +175,7 @@ class WorkOrder(Document): def update_status(self, status=None): '''Update status of work order if unknown''' - if status != "Stopped": + if status != "Stopped" and status != "Closed": status = self.get_status(status) if status != self.status: @@ -624,7 +624,6 @@ class WorkOrder(Document): def validate_operation_time(self): for d in self.operations: if not d.time_in_mins > 0: - print(self.bom_no, self.production_item) frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation)) def update_required_items(self): @@ -967,6 +966,10 @@ def stop_unstop(work_order, status): frappe.throw(_("Not permitted"), frappe.PermissionError) pro_order = frappe.get_doc("Work Order", work_order) + + if pro_order.status == "Closed": + frappe.throw(_("Closed Work Order can not be stopped or Re-opened")) + pro_order.update_status(status) pro_order.update_planned_qty() frappe.msgprint(_("Work Order has been {0}").format(status)) @@ -1001,6 +1004,30 @@ def make_job_card(work_order, operations): if row.job_card_qty > 0: create_job_card(work_order, row, auto_create=True) +@frappe.whitelist() +def close_work_order(work_order, status): + if not frappe.has_permission("Work Order", "write"): + frappe.throw(_("Not permitted"), frappe.PermissionError) + + work_order = frappe.get_doc("Work Order", work_order) + if work_order.get("operations"): + job_cards = frappe.get_list("Job Card", + filters={ + "work_order": work_order.name, + "status": "Work In Progress" + }, + pluck='name') + + if job_cards: + job_cards = ", ".join(job_cards) + frappe.throw(_("Can not close Work Order. Since {0} Job Cards are in Work In Progress state.").format(job_cards)) + + work_order.update_status(status) + work_order.update_planned_qty() + frappe.msgprint(_("Work Order has been {0}").format(status)) + work_order.notify_update() + return work_order.status + def split_qty_based_on_batch_size(wo_doc, row, qty): if not cint(frappe.db.get_value("Operation", row.operation, "create_job_card_based_on_batch_size")): From 5d4c5652aff59d6f051adf34dd60094cce96af07 Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 31 Oct 2021 14:20:03 +0530 Subject: [PATCH 020/185] feat: added confirm dialog on closing of workorder --- erpnext/manufacturing/doctype/work_order/work_order.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index d4235731ae..9ab81619f9 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -520,9 +520,12 @@ erpnext.work_order = { set_custom_buttons: function(frm) { var doc = frm.doc; if (doc.docstatus === 1 && doc.status != "Closed") { - console.log("check"); frm.add_custom_button(__('Close'), function() { - erpnext.work_order.change_work_order_status(frm, "Closed"); + frappe.confirm(__("Once the Work Order is Closed. It can't be resumed."), + () => { + erpnext.work_order.change_work_order_status(frm, "Closed"); + } + ); }, __("Status")); if (doc.status != 'Stopped' && doc.status != 'Completed') { From e290fe0721a2db994171e0be5a98d7fce272a3c7 Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 31 Oct 2021 14:42:10 +0530 Subject: [PATCH 021/185] fix: sider issue --- erpnext/manufacturing/doctype/work_order/work_order.js | 4 ++-- erpnext/manufacturing/doctype/work_order/work_order.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 9ab81619f9..bfce1b8cbe 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -145,12 +145,12 @@ frappe.ui.form.on("Work Order", { && frm.doc.operations && frm.doc.operations.length) { const not_completed = frm.doc.operations.filter(d => { - if(d.status != 'Completed') { + if (d.status != 'Completed') { return true; } }); - if(not_completed && not_completed.length) { + if (not_completed && not_completed.length) { frm.add_custom_button(__('Create Job Card'), () => { frm.trigger("make_job_card"); }).addClass('btn-primary'); diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 6901d71ad4..af80c4ef0b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1015,8 +1015,7 @@ def close_work_order(work_order, status): filters={ "work_order": work_order.name, "status": "Work In Progress" - }, - pluck='name') + }, pluck='name') if job_cards: job_cards = ", ".join(job_cards) From 55e97dce8a7cffd12180b36586ee277b3188cda5 Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 31 Oct 2021 14:45:36 +0530 Subject: [PATCH 022/185] fix: sider issue --- erpnext/manufacturing/doctype/work_order/work_order.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index af80c4ef0b..117e2a227a 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1012,10 +1012,10 @@ def close_work_order(work_order, status): work_order = frappe.get_doc("Work Order", work_order) if work_order.get("operations"): job_cards = frappe.get_list("Job Card", - filters={ - "work_order": work_order.name, - "status": "Work In Progress" - }, pluck='name') + filters={ + "work_order": work_order.name, + "status": "Work In Progress" + }, pluck='name') if job_cards: job_cards = ", ".join(job_cards) From 264b0df9ffe5074542c28b46c6ead2c3436e27ed Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 31 Oct 2021 16:11:11 +0530 Subject: [PATCH 023/185] fix: sider issue --- erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 117e2a227a..48703bc621 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1015,7 +1015,7 @@ def close_work_order(work_order, status): filters={ "work_order": work_order.name, "status": "Work In Progress" - }, pluck='name') + }, pluck='name') if job_cards: job_cards = ", ".join(job_cards) From 530a0f481e8901d2b525a54c663251a477cc3758 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 2 Nov 2021 12:39:13 +0530 Subject: [PATCH 024/185] fix: added testcase --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 85b5bfb9bf..dc81729681 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -14,6 +14,7 @@ from erpnext.manufacturing.doctype.work_order.work_order import ( StockOverProductionError, make_stock_entry, stop_unstop, + close_work_order, ) from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import create_item, make_item @@ -800,6 +801,10 @@ class TestWorkOrder(unittest.TestCase): if row.is_scrap_item: self.assertEqual(row.qty, 1) + def test_close_work_order(self): + close_work_order(self.wo_order.name, "Stopped") + self.assertEqual(self.wo_order.status, "Closed") + def update_job_card(job_card): job_card_doc = frappe.get_doc('Job Card', job_card) job_card_doc.set('scrap_items', [ From 59e4fd980c1c411f82df1f588bc2c80e17d8e317 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 2 Nov 2021 12:46:00 +0530 Subject: [PATCH 025/185] fix: linter issues --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 2 +- erpnext/manufacturing/doctype/work_order/work_order.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index dc81729681..f5851dae36 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -12,9 +12,9 @@ from erpnext.manufacturing.doctype.work_order.work_order import ( ItemHasVariantError, OverProductionError, StockOverProductionError, + close_work_order, make_stock_entry, stop_unstop, - close_work_order, ) from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import create_item, make_item diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 48703bc621..36dae99a69 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1011,6 +1011,7 @@ def close_work_order(work_order, status): work_order = frappe.get_doc("Work Order", work_order) if work_order.get("operations"): + job_cards = frappe.get_list("Job Card", filters={ "work_order": work_order.name, From e36da4d13749afd0fb0e1523aa6d2ecc33ab730c Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 2 Nov 2021 12:53:00 +0530 Subject: [PATCH 026/185] fix: linter issues --- erpnext/manufacturing/doctype/work_order/work_order.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 36dae99a69..0090f4d04e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1011,8 +1011,7 @@ def close_work_order(work_order, status): work_order = frappe.get_doc("Work Order", work_order) if work_order.get("operations"): - - job_cards = frappe.get_list("Job Card", + job_cards = frappe.get_list("Job Card", filters={ "work_order": work_order.name, "status": "Work In Progress" From 9c0906f1b51431f67c3bc009a1410e1129a80744 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 2 Nov 2021 20:43:00 +0530 Subject: [PATCH 027/185] fix: test cases --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index f5851dae36..b6a8700bac 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -802,7 +802,7 @@ class TestWorkOrder(unittest.TestCase): self.assertEqual(row.qty, 1) def test_close_work_order(self): - close_work_order(self.wo_order.name, "Stopped") + close_work_order(self.wo_order.name, "Closed") self.assertEqual(self.wo_order.status, "Closed") def update_job_card(job_card): From 9b4c7e479650851228181cdf562647364ba51cf9 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 3 Nov 2021 13:27:50 +0530 Subject: [PATCH 028/185] fix: validate job card --- .../doctype/job_card/job_card.js | 5 +++ .../doctype/job_card/job_card.py | 14 +++++++ .../doctype/work_order/test_work_order.py | 39 ++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 35be38813e..df35028ca7 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -28,6 +28,11 @@ frappe.ui.form.on('Job Card', { frappe.flags.resume_job = 0; let has_items = frm.doc.items && frm.doc.items.length; + if (frm.doc.__onload.work_order_stopped) { + frm.disable_save(); + return + } + if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) { let to_request = frm.doc.for_quantity > frm.doc.transferred_qty; let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer; diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index e1d79be81c..dd1df20216 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -37,6 +37,7 @@ class JobCard(Document): def onload(self): excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") self.set_onload("job_card_excess_transfer", excess_transfer) + self.set_onload("work_order_stopped", self.is_work_order_stopped()) def validate(self): self.validate_time_logs() @@ -45,6 +46,7 @@ class JobCard(Document): self.validate_sequence_id() self.set_sub_operations() self.update_sub_operation_status() + self.validate_work_order() def set_sub_operations(self): if self.operation: @@ -549,6 +551,18 @@ class JobCard(Document): frappe.throw(_("{0}, complete the operation {1} before the operation {2}.") .format(message, bold(row.operation), bold(self.operation)), OperationSequenceError) + def validate_work_order(self): + if self.is_work_order_stopped(): + frappe.throw(_("You can't make any changes to Job Card since Work Order is stopped.")) + + def is_work_order_stopped(self): + if self.work_order: + status = frappe.get_value('Work Order', self.work_order) + + if status == "Closed": + return True + + return False @frappe.whitelist() def make_time_log(args): diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index b6a8700bac..3dbbf31757 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -802,8 +802,43 @@ class TestWorkOrder(unittest.TestCase): self.assertEqual(row.qty, 1) def test_close_work_order(self): - close_work_order(self.wo_order.name, "Closed") - self.assertEqual(self.wo_order.status, "Closed") + items = ['Test FG Item for Closed WO', 'Test RM Item 1 for Closed WO', + 'Test RM Item 2 for Closed WO'] + + company = '_Test Company with perpetual inventory' + for item_code in items: + create_item(item_code = item_code, is_stock_item = 1, + is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1') + + item = 'Test FG Item for Closed WO' + raw_materials = ['Test RM Item 1 for Closed WO', 'Test RM Item 2 for Closed WO'] + if not frappe.db.get_value('BOM', {'item': item}): + bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True) + bom.with_operations = 1 + bom.append('operations', { + 'operation': '_Test Operation 1', + 'workstation': '_Test Workstation 1', + 'hour_rate': 20, + 'time_in_mins': 60 + }) + + bom.submit() + + wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) + job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') + + for jc in job_cards: + job_card_doc = frappe.get_doc('Job Card', jc) + job_card_doc.append('time_logs', { + 'from_time': now(), + 'time_in_mins': 60, + 'completed_qty': job_card_doc.for_quantity + }) + + job_card_doc.submit() + + close_work_order(wo_order, "Closed") + self.assertEqual(wo_order.get('status'), "Closed") def update_job_card(job_card): job_card_doc = frappe.get_doc('Job Card', job_card) From ba47bd02b6999411736aa864303a181addc225f3 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 3 Nov 2021 13:41:22 +0530 Subject: [PATCH 029/185] fix: sider isuue --- erpnext/manufacturing/doctype/job_card/job_card.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index df35028ca7..e3eed92d7e 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -30,7 +30,7 @@ frappe.ui.form.on('Job Card', { if (frm.doc.__onload.work_order_stopped) { frm.disable_save(); - return + return; } if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) { From cc15cf6ae2915ba01188a36c427ab19d7fd89e2b Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 3 Nov 2021 13:54:46 +0530 Subject: [PATCH 030/185] fix: linter isuue --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 3dbbf31757..4f67507755 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -826,7 +826,6 @@ class TestWorkOrder(unittest.TestCase): wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') - for jc in job_cards: job_card_doc = frappe.get_doc('Job Card', jc) job_card_doc.append('time_logs', { From 3da03028f30eb7171724e86d618e16dadbb5543e Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 3 Nov 2021 17:03:12 +0530 Subject: [PATCH 031/185] fix: Pull Items that are in JC in Stock Entry against JC - Check if items pulled in stock entry are present in Job Card - Code cleanup and removed redundant checks Co-authored-by: Gavin D'souza --- .../stock/doctype/stock_entry/stock_entry.py | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index bd7d22bcbc..6baf900349 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1464,29 +1464,60 @@ class StockEntry(StockController): return item_dict def get_pro_order_required_items(self, backflush_based_on=None): - item_dict = frappe._dict() - pro_order = frappe.get_doc("Work Order", self.work_order) - if not frappe.db.get_value("Warehouse", pro_order.wip_warehouse, "is_group"): - wip_warehouse = pro_order.wip_warehouse + """ + Gets Work Order Required Items only if Stock Entry purpose is **Material Transferred for Manufacture**. + """ + item_dict, job_card_items = frappe._dict(), [] + work_order = frappe.get_doc("Work Order", self.work_order) + + consider_job_card = work_order.transfer_material_against == "Job Card" and self.get("job_card") + if consider_job_card: + job_card_items = self.get_job_card_item_codes(self.get("job_card")) + + if not frappe.db.get_value("Warehouse", work_order.wip_warehouse, "is_group"): + wip_warehouse = work_order.wip_warehouse else: wip_warehouse = None - for d in pro_order.get("required_items"): - if ( ((flt(d.required_qty) > flt(d.transferred_qty)) or - (backflush_based_on == "Material Transferred for Manufacture")) and - (d.include_item_in_manufacturing or self.purpose != "Material Transfer for Manufacture")): + for d in work_order.get("required_items"): + if consider_job_card and (d.item_code not in job_card_items): + continue + + transfer_pending = flt(d.required_qty) > flt(d.transferred_qty) + can_transfer = transfer_pending or (backflush_based_on == "Material Transferred for Manufacture") + + if not can_transfer: + continue + + if d.include_item_in_manufacturing: item_row = d.as_dict() + item_row["idx"] = len(item_dict) + 1 + if d.source_warehouse and not frappe.db.get_value("Warehouse", d.source_warehouse, "is_group"): item_row["from_warehouse"] = d.source_warehouse item_row["to_warehouse"] = wip_warehouse if item_row["allow_alternative_item"]: - item_row["allow_alternative_item"] = pro_order.allow_alternative_item + item_row["allow_alternative_item"] = work_order.allow_alternative_item item_dict.setdefault(d.item_code, item_row) return item_dict + def get_job_card_item_codes(self, job_card=None): + if not job_card: + return [] + + job_card_items = frappe.get_all( + "Job Card Item", + filters={ + "parent": job_card + }, + fields=["item_code"], + distinct=True + ) + return [d.item_code for d in job_card_items] + def add_to_stock_entry_detail(self, item_dict, bom_no=None): for d in item_dict: stock_uom = item_dict[d].get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom") From 7044ae5e39669a0edf59f3abd19b5b52e1de233b Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 3 Nov 2021 17:41:04 +0530 Subject: [PATCH 032/185] fix: testcases: --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 4f67507755..c3f3917b5c 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -826,6 +826,7 @@ class TestWorkOrder(unittest.TestCase): wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') + self.assertEqual(len(job_cards), len(bom.operations)) for jc in job_cards: job_card_doc = frappe.get_doc('Job Card', jc) job_card_doc.append('time_logs', { From b7a44fe0a3e9756eef5ac9000913b0e7d7b86db0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 3 Nov 2021 18:17:31 +0530 Subject: [PATCH 033/185] perf: improve financial statement loading time --- erpnext/accounts/report/financial_statements.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 2cb8a6802a..352b1c8392 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -425,8 +425,7 @@ def set_gl_entries_by_account( {additional_conditions} and posting_date <= %(to_date)s and is_cancelled = 0 - {distributed_cost_center_query} - order by account, posting_date""".format( + {distributed_cost_center_query}""".format( additional_conditions=additional_conditions, distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec From 0aa237f38c946216a1d120c0c737a24afafa4b54 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 4 Nov 2021 20:14:28 +0530 Subject: [PATCH 034/185] test: Stock Entry from JC correctness (items mapping and qty) --- .../doctype/job_card/test_job_card.py | 99 ++++++++++++++++++- .../doctype/operation/test_operation.py | 8 +- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index d7992833ef..daf6d4a679 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -18,8 +18,17 @@ class TestJobCard(unittest.TestCase): def setUp(self): transfer_material_against, source_warehouse = None, None - tests_that_transfer_against_jc = ("test_job_card_multiple_materials_transfer", - "test_job_card_excess_material_transfer") + + tests_that_skip_setup = ( + "test_job_card_material_transfer_correctness" + ) + tests_that_transfer_against_jc = ( + "test_job_card_multiple_materials_transfer", + "test_job_card_excess_material_transfer" + ) + + if self._testMethodName in tests_that_skip_setup: + return if self._testMethodName in tests_that_transfer_against_jc: transfer_material_against = "Job Card" @@ -190,4 +199,88 @@ class TestJobCard(unittest.TestCase): job_card.submit() # JC is Completed with excess transfer - self.assertEqual(job_card.status, "Completed") \ No newline at end of file + self.assertEqual(job_card.status, "Completed") + + def test_job_card_material_transfer_correctness(self): + """ + 1. Test if only current Job Card Items are pulled in a Stock Entry against a Job Card + 2. Test impact of changing 'For Qty' in such a Stock Entry + """ + bom = create_bom_with_multiple_operations() + work_order = make_wo_with_transfer_against_jc() + + job_card_name = frappe.db.get_value( + "Job Card", + {"work_order": work_order.name,"operation": "Test Operation A"} + ) + job_card = frappe.get_doc("Job Card", job_card_name) + + self.assertEqual(len(job_card.items), 1) + self.assertEqual(job_card.items[0].item_code, "_Test Item") + + # check if right items are mapped in transfer entry + transfer_entry = make_stock_entry_from_jc(job_card_name) + transfer_entry.insert() + + self.assertEqual(len(transfer_entry.items), 1) + self.assertEqual(transfer_entry.items[0].item_code, "_Test Item") + self.assertEqual(transfer_entry.items[0].qty, 4) + + # change 'For Qty' and check impact on items table + # no.of items should be the same with qty change + transfer_entry.fg_completed_qty = 2 + transfer_entry.get_items() + + self.assertEqual(len(transfer_entry.items), 1) + self.assertEqual(transfer_entry.items[0].item_code, "_Test Item") + self.assertEqual(transfer_entry.items[0].qty, 2) + + # teardown + transfer_entry.delete() + frappe.db.delete("Job Card", {"work_order": work_order.name}) + work_order.cancel() + bom.cancel() + + +def create_bom_with_multiple_operations(): + from erpnext.manufacturing.doctype.operation.test_operation import make_operation + + test_record = frappe.get_test_records("BOM")[2] + bom_doc = frappe.get_doc(test_record) + + make_operation({ + "operation": "Test Operation A", + "workstation": "_Test Workstation A", + "hour_rate_rent": 300, + "time_in_mins": 60 + }) + + bom_doc.append("operations", { + "operation": "Test Operation A", + "description": "Test Operation A", + "workstation": "_Test Workstation A", + "hour_rate": 300, + "time_in_mins": 60, + "operating_cost": 100 + }) + + bom_doc.save() + bom_doc.submit() + + return bom_doc + +def make_wo_with_transfer_against_jc(): + "Create a WO with multiple operations and Material Transfer against Job Card" + + work_order = make_wo_order_test_record( + item="_Test FG Item 2", + qty=4, + transfer_material_against="Job Card", + source_warehouse="Stores - _TC", + do_not_submit=True + ) + work_order.required_items[0].operation = "Test Operation A" + work_order.required_items[1].operation = "_Test Operation 1" + work_order.submit() + + return work_order \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/operation/test_operation.py b/erpnext/manufacturing/doctype/operation/test_operation.py index 804cc3fa0d..e511084e7d 100644 --- a/erpnext/manufacturing/doctype/operation/test_operation.py +++ b/erpnext/manufacturing/doctype/operation/test_operation.py @@ -17,15 +17,13 @@ def make_operation(*args, **kwargs): args = frappe._dict(args) - try: + if not frappe.db.exists("Operation", args.operation): doc = frappe.get_doc({ "doctype": "Operation", "name": args.operation, "workstation": args.workstation }) - doc.insert() - return doc - except frappe.DuplicateEntryError: - return frappe.get_doc("Operation", args.operation) + + return frappe.get_doc("Operation", args.operation) From 5fb5a757cf0099f74d62f1a65a1269522af65894 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 8 Nov 2021 13:22:57 +0530 Subject: [PATCH 035/185] refactor: (ux) Accepted/Rejected/Received Qty UX --- erpnext/controllers/buying_controller.py | 11 ++--- erpnext/public/js/controllers/buying.js | 48 +++++++------------ .../purchase_receipt_item.json | 10 +++- 3 files changed, 29 insertions(+), 40 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 9965c87c81..4ec4df8800 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -360,17 +360,12 @@ class BuyingController(StockController, Subcontracting): def validate_accepted_rejected_qty(self): for d in self.get("items"): self.validate_negative_quantity(d, ["received_qty","qty", "rejected_qty"]) - if not flt(d.received_qty) and flt(d.qty): - d.received_qty = flt(d.qty) - flt(d.rejected_qty) - elif not flt(d.qty) and flt(d.rejected_qty): - d.qty = flt(d.received_qty) - flt(d.rejected_qty) + if not flt(d.received_qty) and (flt(d.qty) or flt(d.rejected_qty)): + d.received_qty = flt(d.qty) + flt(d.rejected_qty) - elif not flt(d.rejected_qty): - d.rejected_qty = flt(d.received_qty) - flt(d.qty) - - val = flt(d.qty) + flt(d.rejected_qty) # Check Received Qty = Accepted Qty + Rejected Qty + val = flt(d.qty) + flt(d.rejected_qty) if (flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty"))): frappe.throw(_("Accepted + Rejected Qty must be equal to Received quantity for Item {0}").format(d.item_code)) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 86dadd36d6..d696ef55ae 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -165,45 +165,33 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac } qty(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && (doc.update_stock || doc.is_return))) { - frappe.model.round_floats_in(item, ["qty", "received_qty"]); - - if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["qty", "received_qty"])){ return } - - if(!item.rejected_qty && item.qty) { - item.received_qty = item.qty; - } - - frappe.model.round_floats_in(item, ["qty", "received_qty"]); - item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item)); - item.received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(item.received_qty); + this.calculate_received_qty(doc, cdt, cdn) } super.qty(doc, cdt, cdn); } + rejected_qty(doc, cdt, cdn) { + this.calculate_received_qty(doc, cdt, cdn) + } + + calculate_received_qty(doc, cdt, cdn){ + var item = frappe.get_doc(cdt, cdn); + frappe.model.round_floats_in(item, ["qty", "rejected_qty"]); + + if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["qty", "rejected_qty"])){ return } + + let received_qty = flt(item.qty + item.rejected_qty, precision("received_qty", item)); + let received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(received_qty); + + frappe.model.set_value(cdt, cdn, "received_qty", received_qty); + frappe.model.set_value(cdt, cdn, "received_stock_qty", received_stock_qty); + } + batch_no(doc, cdt, cdn) { super.batch_no(doc, cdt, cdn); } - received_qty(doc, cdt, cdn) { - this.calculate_accepted_qty(doc, cdt, cdn) - } - - rejected_qty(doc, cdt, cdn) { - this.calculate_accepted_qty(doc, cdt, cdn) - } - - calculate_accepted_qty(doc, cdt, cdn){ - var item = frappe.get_doc(cdt, cdn); - frappe.model.round_floats_in(item, ["received_qty", "rejected_qty"]); - - if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["received_qty", "rejected_qty"])){ return } - - item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item)); - this.qty(doc, cdt, cdn); - } - validate_negative_quantity(cdt, cdn, item, fieldnames){ if(!item || !fieldnames) { return } diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 3efa66e02e..3ddb635102 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -197,6 +197,7 @@ }, { "bold": 1, + "default": "0", "fieldname": "received_qty", "fieldtype": "Float", "label": "Received Quantity", @@ -204,6 +205,7 @@ "oldfieldtype": "Currency", "print_hide": 1, "print_width": "100px", + "read_only": 1, "reqd": 1, "width": "100px" }, @@ -543,6 +545,7 @@ "fieldname": "stock_qty", "fieldtype": "Float", "label": "Accepted Qty in Stock UOM", + "no_copy": 1, "oldfieldname": "stock_qty", "oldfieldtype": "Currency", "print_hide": 1, @@ -882,7 +885,9 @@ "fieldname": "received_stock_qty", "fieldtype": "Float", "label": "Received Qty in Stock UOM", - "print_hide": 1 + "no_copy": 1, + "print_hide": 1, + "read_only": 1 }, { "depends_on": "eval: doc.uom != doc.stock_uom", @@ -969,10 +974,11 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-09-01 16:02:40.338597", + "modified": "2021-11-03 17:56:53.916921", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "quick_entry": 1, From 27709a1c71a9943cb95deb8ef1b563c465489dcb Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 9 Nov 2021 09:56:58 +0530 Subject: [PATCH 036/185] fix: test cases --- .../doctype/work_order/test_work_order.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index c3f3917b5c..f4a88dc459 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -826,19 +826,20 @@ class TestWorkOrder(unittest.TestCase): wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') - self.assertEqual(len(job_cards), len(bom.operations)) - for jc in job_cards: - job_card_doc = frappe.get_doc('Job Card', jc) - job_card_doc.append('time_logs', { - 'from_time': now(), - 'time_in_mins': 60, - 'completed_qty': job_card_doc.for_quantity - }) - job_card_doc.submit() + if len(job_cards) == len(bom.operations): + for jc in job_cards: + job_card_doc = frappe.get_doc('Job Card', jc) + job_card_doc.append('time_logs', { + 'from_time': now(), + 'time_in_mins': 60, + 'completed_qty': job_card_doc.for_quantity + }) - close_work_order(wo_order, "Closed") - self.assertEqual(wo_order.get('status'), "Closed") + job_card_doc.submit() + + close_work_order(wo_order, "Closed") + self.assertEqual(wo_order.get('status'), "Closed") def update_job_card(job_card): job_card_doc = frappe.get_doc('Job Card', job_card) From 663a7afe4df81fe3956af4eb18ca1eaa0614b626 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 9 Nov 2021 10:50:38 +0530 Subject: [PATCH 037/185] fix: get_planned_qty chnages --- erpnext/stock/stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 1cb0f0d0a4..51c20b0250 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -161,7 +161,7 @@ def get_ordered_qty(item_code, warehouse): def get_planned_qty(item_code, warehouse): planned_qty = frappe.db.sql(""" select sum(qty - produced_qty) from `tabWork Order` - where production_item = %s and fg_warehouse = %s and status not in ("Stopped", "Completed") + where production_item = %s and fg_warehouse = %s and status not in ("Stopped", "Completed", "Closed") and docstatus=1 and qty > produced_qty""", (item_code, warehouse)) return flt(planned_qty[0][0]) if planned_qty else 0 From 4a3cef6436287b7d0be69b5e0965c4f989957931 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 9 Nov 2021 14:16:38 +0530 Subject: [PATCH 038/185] fix: update 'current_invoice_end' after processing invoice (#28278) --- erpnext/accounts/doctype/subscription/subscription.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 63b714e68c..1dae87f2a4 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -519,8 +519,6 @@ class Subscription(Document): 2. Change the `Subscription` status to 'Past Due Date' 3. Change the `Subscription` status to 'Cancelled' """ - if getdate() > getdate(self.current_invoice_end) and self.is_prepaid_to_invoice(): - self.update_subscription_period(add_days(self.current_invoice_end, 1)) if not self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \ and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): @@ -528,6 +526,9 @@ class Subscription(Document): prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) + if getdate() > getdate(self.current_invoice_end) and self.is_prepaid_to_invoice(): + self.update_subscription_period(add_days(self.current_invoice_end, 1)) + if self.cancel_at_period_end and getdate() > getdate(self.current_invoice_end): self.cancel_subscription_at_period_end() From c78b8b7897152ea5daba9bfa0005a294a40c5670 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Tue, 9 Nov 2021 14:46:45 +0530 Subject: [PATCH 039/185] fix: Shipping Rule picking up old net_rate --- erpnext/public/js/controllers/taxes_and_totals.js | 3 +++ erpnext/public/js/controllers/transaction.js | 8 -------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index b5a6d8fdf6..0ada6601bf 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -268,6 +268,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { }); frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); + if(frappe.meta.get_docfield(this.frm.doc.doctype,"shipping_rule",this.frm.doc.name)) { + this.shipping_rule() + } } add_taxes_from_item_tax_template(item_tax_map) { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 63fd8a1c67..0cfc008c13 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1085,16 +1085,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe return this.frm.call({ doc: this.frm.doc, method: "apply_shipping_rule", - callback: function(r) { - if(!r.exc) { - me.calculate_taxes_and_totals(); - } - } }).fail(() => this.frm.set_value('shipping_rule', '')); } - else { - me.calculate_taxes_and_totals(); - } } set_margin_amount_based_on_currency(exchange_rate) { From 17acb08545a5bab991a5eb2f0f3e720886006bc4 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:21:51 +0530 Subject: [PATCH 040/185] fix: sum of components in salary register (#28237) * fix: sum of components in salary register * fix: sum of deduction components Co-authored-by: Rucha Mahabal --- .../report/salary_register/salary_register.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/payroll/report/salary_register/salary_register.py b/erpnext/payroll/report/salary_register/salary_register.py index 13ee691d87..78deb22778 100644 --- a/erpnext/payroll/report/salary_register/salary_register.py +++ b/erpnext/payroll/report/salary_register/salary_register.py @@ -134,11 +134,11 @@ def get_ss_earning_map(salary_slips, currency, company_currency): ss_earning_map = {} for d in ss_earnings: - ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, []) + ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0) if currency == company_currency: - ss_earning_map[d.parent][d.salary_component] = flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1) + ss_earning_map[d.parent][d.salary_component] += flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1) else: - ss_earning_map[d.parent][d.salary_component] = flt(d.amount) + ss_earning_map[d.parent][d.salary_component] += flt(d.amount) return ss_earning_map @@ -149,10 +149,10 @@ def get_ss_ded_map(salary_slips, currency, company_currency): ss_ded_map = {} for d in ss_deductions: - ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, []) + ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0) if currency == company_currency: - ss_ded_map[d.parent][d.salary_component] = flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1) + ss_ded_map[d.parent][d.salary_component] += flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1) else: - ss_ded_map[d.parent][d.salary_component] = flt(d.amount) + ss_ded_map[d.parent][d.salary_component] += flt(d.amount) return ss_ded_map From da22744e0fd7e19a6536aa653ebd78a83fafbac1 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 9 Nov 2021 15:49:32 +0530 Subject: [PATCH 041/185] fix: specify fields to be set in Lead (#28288) --- erpnext/shopping_cart/cart.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index a7d90ea201..ebbe233ca3 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -194,7 +194,9 @@ def add_new_address(doc): def create_lead_for_item_inquiry(lead, subject, message): lead = frappe.parse_json(lead) lead_doc = frappe.new_doc('Lead') - lead_doc.update(lead) + for fieldname in ("lead_name", "company_name", "email_id", "phone"): + lead_doc.set(fieldname, lead.get(fieldname)) + lead_doc.set('lead_owner', '') if not frappe.db.exists('Lead Source', 'Product Inquiry'): @@ -202,6 +204,7 @@ def create_lead_for_item_inquiry(lead, subject, message): 'doctype': 'Lead Source', 'source_name' : 'Product Inquiry' }).insert(ignore_permissions=True) + lead_doc.set('source', 'Product Inquiry') try: From 88b5bda34b80fb83e71f94a984184304e5b4c853 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 9 Nov 2021 16:04:52 +0530 Subject: [PATCH 042/185] fix(India setup): setup company independent fixtures for patch --- erpnext/regional/india/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 42ee27171f..316bb6b197 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -12,7 +12,7 @@ from frappe.utils import today def setup(company=None, patch=True): # Company independent fixtures should be called only once at the first company setup - if frappe.db.count('Company', {'country': 'India'}) <=1: + if patch or frappe.db.count('Company', {'country': 'India'}) <=1: setup_company_independent_fixtures(patch=patch) if not patch: From e8d0c25dffc913ba9cabe14f21cd5997b20d819a Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 9 Nov 2021 17:29:29 +0530 Subject: [PATCH 043/185] fix: Partial Trabsfers against JC - Fixed transferred qty not back updating on JC if partial transfer - Partial transfer not mapping pending qty from JC correctly in SE - tests for above cases - minor code cleanup --- .../doctype/job_card/job_card.py | 9 +++- .../doctype/job_card/test_job_card.py | 45 +++++++++++++++-- .../doctype/workstation/test_workstation.py | 6 +-- .../stock/doctype/stock_entry/stock_entry.py | 50 +++++++++++-------- 4 files changed, 82 insertions(+), 28 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index ff4feaa5dc..55197f120e 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -615,17 +615,22 @@ def make_material_request(source_name, target_doc=None): @frappe.whitelist() def make_stock_entry(source_name, target_doc=None): - def update_item(obj, target, source_parent): + def update_item(source, target, source_parent): target.t_warehouse = source_parent.wip_warehouse + if not target.conversion_factor: target.conversion_factor = 1 + pending_rm_qty = flt(source.required_qty) - flt(source.transferred_qty) + if pending_rm_qty > 0: + target.qty = pending_rm_qty + def set_missing_values(source, target): target.purpose = "Material Transfer for Manufacture" target.from_bom = 1 # avoid negative 'For Quantity' - pending_fg_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0) + pending_fg_qty = flt(source.get('for_quantity', 0)) - flt(source.get('transferred_qty', 0)) target.fg_completed_qty = pending_fg_qty if pending_fg_qty > 0 else 0 target.set_transfer_qty() diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index daf6d4a679..e16c4802fe 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -24,7 +24,8 @@ class TestJobCard(unittest.TestCase): ) tests_that_transfer_against_jc = ( "test_job_card_multiple_materials_transfer", - "test_job_card_excess_material_transfer" + "test_job_card_excess_material_transfer", + "test_job_card_partial_material_transfer" ) if self._testMethodName in tests_that_skip_setup: @@ -201,6 +202,42 @@ class TestJobCard(unittest.TestCase): # JC is Completed with excess transfer self.assertEqual(job_card.status, "Completed") + def test_job_card_partial_material_transfer(self): + "Test partial material transfer against Job Card" + + make_stock_entry(item_code="_Test Item", target="Stores - _TC", + qty=25, basic_rate=100) + make_stock_entry(item_code="_Test Item Home Desktop Manufactured", + target="Stores - _TC", qty=15, basic_rate=100) + + job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name}) + job_card = frappe.get_doc("Job Card", job_card_name) + + # partially transfer + transfer_entry = make_stock_entry_from_jc(job_card_name) + transfer_entry.fg_completed_qty = 1 + transfer_entry.get_items() + transfer_entry.insert() + transfer_entry.submit() + + job_card.reload() + self.assertEqual(job_card.transferred_qty, 1) + self.assertEqual(transfer_entry.items[0].qty, 5) + self.assertEqual(transfer_entry.items[1].qty, 3) + + # transfer remaining + transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + + self.assertEqual(transfer_entry_2.fg_completed_qty, 1) + self.assertEqual(transfer_entry_2.items[0].qty, 5) + self.assertEqual(transfer_entry_2.items[1].qty, 3) + + transfer_entry_2.insert() + transfer_entry_2.submit() + + job_card.reload() + self.assertEqual(job_card.transferred_qty, 2) + def test_job_card_material_transfer_correctness(self): """ 1. Test if only current Job Card Items are pulled in a Stock Entry against a Job Card @@ -248,12 +285,14 @@ def create_bom_with_multiple_operations(): test_record = frappe.get_test_records("BOM")[2] bom_doc = frappe.get_doc(test_record) - make_operation({ + row = { "operation": "Test Operation A", "workstation": "_Test Workstation A", "hour_rate_rent": 300, "time_in_mins": 60 - }) + } + make_workstation(row) + make_operation(row) bom_doc.append("operations", { "operation": "Test Operation A", diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py index c77cef2895..5ed5153528 100644 --- a/erpnext/manufacturing/doctype/workstation/test_workstation.py +++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py @@ -89,7 +89,7 @@ def make_workstation(*args, **kwargs): args = frappe._dict(args) workstation_name = args.workstation_name or args.workstation - try: + if not frappe.db.exists("Workstation", workstation_name): doc = frappe.get_doc({ "doctype": "Workstation", "workstation_name": workstation_name @@ -99,5 +99,5 @@ def make_workstation(*args, **kwargs): doc.insert() return doc - except frappe.DuplicateEntryError: - return frappe.get_doc("Workstation", workstation_name) + + return frappe.get_doc("Workstation", workstation_name) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 67a700cda1..70df82b223 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1491,6 +1491,16 @@ class StockEntry(StockController): item_row = d.as_dict() item_row["idx"] = len(item_dict) + 1 + if consider_job_card: + job_card_item = frappe.db.get_value( + "Job Card Item", + { + "item_code": d.item_code, + "parent": self.get("job_card") + } + ) + item_row["job_card_item"] = job_card_item or None + if d.source_warehouse and not frappe.db.get_value("Warehouse", d.source_warehouse, "is_group"): item_row["from_warehouse"] = d.source_warehouse @@ -1518,27 +1528,28 @@ class StockEntry(StockController): def add_to_stock_entry_detail(self, item_dict, bom_no=None): for d in item_dict: - stock_uom = item_dict[d].get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom") + item_row = item_dict[d] + stock_uom = item_row.get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom") se_child = self.append('items') - se_child.s_warehouse = item_dict[d].get("from_warehouse") - se_child.t_warehouse = item_dict[d].get("to_warehouse") - se_child.item_code = item_dict[d].get('item_code') or cstr(d) - se_child.uom = item_dict[d]["uom"] if item_dict[d].get("uom") else stock_uom + se_child.s_warehouse = item_row.get("from_warehouse") + se_child.t_warehouse = item_row.get("to_warehouse") + se_child.item_code = item_row.get('item_code') or cstr(d) + se_child.uom = item_row["uom"] if item_row.get("uom") else stock_uom se_child.stock_uom = stock_uom - se_child.qty = flt(item_dict[d]["qty"], se_child.precision("qty")) - se_child.allow_alternative_item = item_dict[d].get("allow_alternative_item", 0) - se_child.subcontracted_item = item_dict[d].get("main_item_code") - se_child.cost_center = (item_dict[d].get("cost_center") or - get_default_cost_center(item_dict[d], company = self.company)) - se_child.is_finished_item = item_dict[d].get("is_finished_item", 0) - se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0) - se_child.is_process_loss = item_dict[d].get("is_process_loss", 0) + se_child.qty = flt(item_row["qty"], se_child.precision("qty")) + se_child.allow_alternative_item = item_row.get("allow_alternative_item", 0) + se_child.subcontracted_item = item_row.get("main_item_code") + se_child.cost_center = (item_row.get("cost_center") or + get_default_cost_center(item_row, company = self.company)) + se_child.is_finished_item = item_row.get("is_finished_item", 0) + se_child.is_scrap_item = item_row.get("is_scrap_item", 0) + se_child.is_process_loss = item_row.get("is_process_loss", 0) for field in ["idx", "po_detail", "original_item", "expense_account", "description", "item_name", "serial_no", "batch_no", "allow_zero_valuation_rate"]: - if item_dict[d].get(field): - se_child.set(field, item_dict[d].get(field)) + if item_row.get(field): + se_child.set(field, item_row.get(field)) if se_child.s_warehouse==None: se_child.s_warehouse = self.from_warehouse @@ -1546,12 +1557,11 @@ class StockEntry(StockController): se_child.t_warehouse = self.to_warehouse # in stock uom - se_child.conversion_factor = flt(item_dict[d].get("conversion_factor")) or 1 - se_child.transfer_qty = flt(item_dict[d]["qty"]*se_child.conversion_factor, se_child.precision("qty")) + se_child.conversion_factor = flt(item_row.get("conversion_factor")) or 1 + se_child.transfer_qty = flt(item_row["qty"]*se_child.conversion_factor, se_child.precision("qty")) - - # to be assigned for finished item - se_child.bom_no = bom_no + se_child.bom_no = bom_no # to be assigned for finished item + se_child.job_card_item = item_row.get("job_card_item") if self.get("job_card") else None def validate_with_material_request(self): for item in self.get("items"): From 6907ad8adb2e15239598df8908eec16b56e106f1 Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Tue, 9 Nov 2021 13:02:57 +0100 Subject: [PATCH 044/185] fix: add Email option to contact email field (#28296) * Update warranty_claim.json Added the Email in option field of Contact Email so you are able to create a notification mapping to this field as reciever. * Update warranty_claim.json --- erpnext/support/doctype/warranty_claim/warranty_claim.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.json b/erpnext/support/doctype/warranty_claim/warranty_claim.json index 88ee4a3beb..45485ca2c2 100644 --- a/erpnext/support/doctype/warranty_claim/warranty_claim.json +++ b/erpnext/support/doctype/warranty_claim/warranty_claim.json @@ -256,6 +256,7 @@ "fieldname": "contact_email", "fieldtype": "Data", "label": "Contact Email", + "options": "Email", "read_only": 1 }, { @@ -361,7 +362,7 @@ ], "icon": "fa fa-bug", "idx": 1, - "modified": "2020-09-18 17:26:09.703215", + "modified": "2021-11-09 17:26:09.703215", "modified_by": "Administrator", "module": "Support", "name": "Warranty Claim", @@ -385,4 +386,4 @@ "sort_order": "DESC", "timeline_field": "customer", "title_field": "customer_name" -} \ No newline at end of file +} From 37799fe3dd5beed8b05ab0ea83139f3e5f200179 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 3 Nov 2021 16:33:28 +0530 Subject: [PATCH 045/185] fix: use completion qty instead of transfer quantity for JC status --- erpnext/manufacturing/doctype/job_card/job_card.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index f0f18cf7dd..5eea032be5 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -504,13 +504,11 @@ class JobCard(Document): self.status = 'Work In Progress' if (self.docstatus == 1 and - (self.for_quantity <= self.transferred_qty or not self.items)): - # consider excess transfer - # completed qty is checked via separate validation + (self.for_quantity <= self.total_completed_qty or not self.items)): self.status = 'Completed' if self.status != 'Completed': - if self.for_quantity == self.transferred_qty: + if self.for_quantity <= self.transferred_qty: self.status = 'Material Transferred' if update_status: From fdfa39c231ed3648d6e8b7cf0cda1934062b49da Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 3 Nov 2021 12:48:57 +0530 Subject: [PATCH 046/185] fix: avoid mutating iterator while iterating over it --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index fa2436bd27..46c9576a9c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1450,7 +1450,7 @@ class StockEntry(StockController): item_dict[item]["qty"] = 0 # delete items with 0 qty - list_of_items = item_dict.keys() + list_of_items = list(item_dict.keys()) for item in list_of_items: if not item_dict[item]["qty"]: del item_dict[item] From ccf84ae88a752d94d459e369fa5c1afaab9e49f1 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 7 Nov 2021 18:06:37 +0530 Subject: [PATCH 047/185] fix: patch to update job card status --- erpnext/patches.txt | 1 + .../patches/v13_0/update_job_card_status.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 erpnext/patches/v13_0/update_job_card_status.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1006e2180f..778cbdf65b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -303,6 +303,7 @@ erpnext.patches.v13_0.set_status_in_maintenance_schedule_table erpnext.patches.v13_0.add_default_interview_notification_templates erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts +erpnext.patches.v13_0.update_job_card_status erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.healthcare_deprecation_warning erpnext.patches.v14_0.delete_healthcare_doctypes diff --git a/erpnext/patches/v13_0/update_job_card_status.py b/erpnext/patches/v13_0/update_job_card_status.py new file mode 100644 index 0000000000..797a3e2ae3 --- /dev/null +++ b/erpnext/patches/v13_0/update_job_card_status.py @@ -0,0 +1,18 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + + +def execute(): + + job_card = frappe.qb.DocType("Job Card") + (frappe.qb + .update(job_card) + .set(job_card.status, "Completed") + .where( + (job_card.docstatus == 1) + & (job_card.for_quantity <= job_card.total_completed_qty) + & (job_card.status.isin(["Work In Progress", "Material Transferred"])) + ) + ).run() From 6954dd6329f2af0db88413fe933c5e1a111bcb9b Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Tue, 9 Nov 2021 19:36:27 +0530 Subject: [PATCH 048/185] feat: Competitor Tagging in Opportunity and Quotation (#28050) * feat: Competitor Tagging in Opportunity and Quotation * fix: review changes * fix: linter issue * fix: section label Co-authored-by: Rucha Mahabal --- erpnext/crm/doctype/competitor/__init__.py | 0 erpnext/crm/doctype/competitor/competitor.js | 8 +++ .../crm/doctype/competitor/competitor.json | 68 +++++++++++++++++++ erpnext/crm/doctype/competitor/competitor.py | 9 +++ .../crm/doctype/competitor/test_competitor.py | 9 +++ .../crm/doctype/competitor_detail/__init__.py | 0 .../competitor_detail/competitor_detail.json | 33 +++++++++ .../competitor_detail/competitor_detail.py | 9 +++ .../crm/doctype/opportunity/opportunity.json | 31 +++++++-- .../crm/doctype/opportunity/opportunity.py | 10 ++- .../selling/doctype/quotation/quotation.json | 15 +++- .../selling/doctype/quotation/quotation.py | 5 +- erpnext/selling/sales_common.js | 18 +++-- 13 files changed, 197 insertions(+), 18 deletions(-) create mode 100644 erpnext/crm/doctype/competitor/__init__.py create mode 100644 erpnext/crm/doctype/competitor/competitor.js create mode 100644 erpnext/crm/doctype/competitor/competitor.json create mode 100644 erpnext/crm/doctype/competitor/competitor.py create mode 100644 erpnext/crm/doctype/competitor/test_competitor.py create mode 100644 erpnext/crm/doctype/competitor_detail/__init__.py create mode 100644 erpnext/crm/doctype/competitor_detail/competitor_detail.json create mode 100644 erpnext/crm/doctype/competitor_detail/competitor_detail.py diff --git a/erpnext/crm/doctype/competitor/__init__.py b/erpnext/crm/doctype/competitor/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/crm/doctype/competitor/competitor.js b/erpnext/crm/doctype/competitor/competitor.js new file mode 100644 index 0000000000..a5b617dc74 --- /dev/null +++ b/erpnext/crm/doctype/competitor/competitor.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Competitor', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/crm/doctype/competitor/competitor.json b/erpnext/crm/doctype/competitor/competitor.json new file mode 100644 index 0000000000..280441f16f --- /dev/null +++ b/erpnext/crm/doctype/competitor/competitor.json @@ -0,0 +1,68 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:competitor_name", + "creation": "2021-10-21 10:28:52.071316", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "competitor_name", + "website" + ], + "fields": [ + { + "fieldname": "competitor_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Competitor Name", + "reqd": 1, + "unique": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "website", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Website", + "options": "URL" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-10-21 12:43:59.106807", + "modified_by": "Administrator", + "module": "CRM", + "name": "Competitor", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/crm/doctype/competitor/competitor.py b/erpnext/crm/doctype/competitor/competitor.py new file mode 100644 index 0000000000..a292e46104 --- /dev/null +++ b/erpnext/crm/doctype/competitor/competitor.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class Competitor(Document): + pass diff --git a/erpnext/crm/doctype/competitor/test_competitor.py b/erpnext/crm/doctype/competitor/test_competitor.py new file mode 100644 index 0000000000..f77d7e658d --- /dev/null +++ b/erpnext/crm/doctype/competitor/test_competitor.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestCompetitor(unittest.TestCase): + pass diff --git a/erpnext/crm/doctype/competitor_detail/__init__.py b/erpnext/crm/doctype/competitor_detail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/crm/doctype/competitor_detail/competitor_detail.json b/erpnext/crm/doctype/competitor_detail/competitor_detail.json new file mode 100644 index 0000000000..9512b22a3f --- /dev/null +++ b/erpnext/crm/doctype/competitor_detail/competitor_detail.json @@ -0,0 +1,33 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2021-10-21 10:34:58.841689", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "competitor" + ], + "fields": [ + { + "fieldname": "competitor", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Competitor", + "options": "Competitor", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-10-21 10:34:58.841689", + "modified_by": "Administrator", + "module": "CRM", + "name": "Competitor Detail", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/crm/doctype/competitor_detail/competitor_detail.py b/erpnext/crm/doctype/competitor_detail/competitor_detail.py new file mode 100644 index 0000000000..0ef75605b7 --- /dev/null +++ b/erpnext/crm/doctype/competitor_detail/competitor_detail.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CompetitorDetail(Document): + pass diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index dc886b51b4..feb6044d64 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -23,7 +23,6 @@ "status", "converted_by", "sales_stage", - "order_lost_reason", "first_response_time", "expected_closing", "next_contact", @@ -64,7 +63,11 @@ "transaction_date", "language", "amended_from", - "lost_reasons" + "lost_detail_section", + "lost_reasons", + "order_lost_reason", + "column_break_56", + "competitors" ], "fields": [ { @@ -154,10 +157,9 @@ "reqd": 1 }, { - "depends_on": "eval:doc.status===\"Lost\"", "fieldname": "order_lost_reason", "fieldtype": "Small Text", - "label": "Lost Reason", + "label": "Detailed Reason", "no_copy": 1, "read_only": 1 }, @@ -409,6 +411,7 @@ "width": "150px" }, { + "depends_on": "eval:doc.status===\"Lost\"", "fieldname": "lost_reasons", "fieldtype": "Table MultiSelect", "label": "Lost Reasons", @@ -486,15 +489,33 @@ "label": "Grand Total", "options": "currency", "read_only": 1 + }, + { + "fieldname": "lost_detail_section", + "fieldtype": "Section Break", + "label": "Lost Reasons" + }, + { + "fieldname": "column_break_56", + "fieldtype": "Column Break" + }, + { + "fieldname": "competitors", + "fieldtype": "Table MultiSelect", + "label": "Competitors", + "options": "Competitor Detail", + "read_only": 1 } ], "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2021-09-06 10:02:18.609136", + "migration_hash": "d87c646ea2579b6900197fd41e6c5c5a", + "modified": "2021-10-21 11:04:30.151379", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 90eae8dcb9..0bef80a749 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -116,16 +116,20 @@ class Opportunity(TransactionBase): self.party_name = lead_name @frappe.whitelist() - def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None): + def declare_enquiry_lost(self, lost_reasons_list, competitors, detailed_reason=None): if not self.has_active_quotation(): - frappe.db.set(self, 'status', 'Lost') + self.status = 'Lost' + self.lost_reasons = self.competitors = [] if detailed_reason: - frappe.db.set(self, 'order_lost_reason', detailed_reason) + self.order_lost_reason = detailed_reason for reason in lost_reasons_list: self.append('lost_reasons', reason) + for competitor in competitors: + self.append('competitors', competitor) + self.save() else: diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 43a44900fc..ad788e5c8b 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -110,7 +110,8 @@ "enq_det", "supplier_quotation", "opportunity", - "lost_reasons" + "lost_reasons", + "competitors" ], "fields": [ { @@ -946,6 +947,14 @@ "label": "Bundle Items", "options": "fa fa-suitcase", "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "competitors", + "fieldtype": "Table MultiSelect", + "label": "Competitors", + "options": "Competitor Detail", + "read_only": 1 } ], "icon": "fa fa-shopping-cart", @@ -953,10 +962,12 @@ "is_submittable": 1, "links": [], "max_attachments": 1, - "modified": "2021-08-27 20:10:07.864951", + "migration_hash": "75a86a19f062c2257bcbc8e6e31c7f1e", + "modified": "2021-10-21 12:58:55.514512", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 31b62d6da5..c4752aebb5 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -68,7 +68,7 @@ class Quotation(SellingController): opp.set_status(status=status, update=True) @frappe.whitelist() - def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None): + def declare_enquiry_lost(self, lost_reasons_list, competitors, detailed_reason=None): if not self.has_sales_order(): get_lost_reasons = frappe.get_list('Quotation Lost Reason', fields = ["name"]) @@ -84,6 +84,9 @@ class Quotation(SellingController): else: frappe.throw(_("Invalid lost reason {0}, please create a new lost reason").format(frappe.bold(reason.get('lost_reason')))) + for competitor in competitors: + self.append('competitors', competitor) + self.update_opportunity('Lost') self.update_lead() self.save() diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index a86e60494e..20504789aa 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -473,6 +473,12 @@ frappe.ui.form.on(cur_frm.doctype, { "options": frm.doctype === 'Opportunity' ? 'Opportunity Lost Reason Detail': 'Quotation Lost Reason Detail', "reqd": 1 }, + { + "fieldtype": "Table MultiSelect", + "label": __("Competitors"), + "fieldname": "competitors", + "options": "Competitor Detail" + }, { "fieldtype": "Text", "label": __("Detailed Reason"), @@ -480,27 +486,25 @@ frappe.ui.form.on(cur_frm.doctype, { }, ], primary_action: function() { - var values = dialog.get_values(); - var reasons = values["lost_reason"]; - var detailed_reason = values["detailed_reason"]; + let values = dialog.get_values(); frm.call({ doc: frm.doc, method: 'declare_enquiry_lost', args: { - 'lost_reasons_list': reasons, - 'detailed_reason': detailed_reason + 'lost_reasons_list': values.lost_reason, + 'competitors': values.competitors, + 'detailed_reason': values.detailed_reason }, callback: function(r) { dialog.hide(); frm.reload_doc(); }, }); - refresh_field("lost_reason"); }, primary_action_label: __('Declare Lost') }); dialog.show(); } -}) +}) \ No newline at end of file From 1eb3ca2b86a8faa5a322b9ec2c97a69cfe476e26 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 9 Nov 2021 23:07:28 +0530 Subject: [PATCH 049/185] fix: (travis) Production Plan Summary Report breaks if no WO - `get_cached_value` throws a DoesNotExistError if non-existent value, used `get_value` instead - accomodate production plan items that dont have WO/PO against them as well (blank values) - added some None value handling to avoid AttributeError --- .../production_plan_summary.py | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py index 43ef12e7b4..55b1a3f2f9 100644 --- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py @@ -28,8 +28,15 @@ def get_production_plan_item_details(filters, data, order_details): production_plan_doc = frappe.get_cached_doc("Production Plan", filters.get("production_plan")) for row in production_plan_doc.po_items: - work_order = frappe.get_cached_value("Work Order", {"production_plan_item": row.name, - "bom_no": row.bom_no, "production_item": row.item_code}, "name") + work_order = frappe.get_value( + "Work Order", + { + "production_plan_item": row.name, + "bom_no": row.bom_no, + "production_item": row.item_code + }, + "name" + ) if row.item_code not in itemwise_indent: itemwise_indent.setdefault(row.item_code, {}) @@ -40,10 +47,10 @@ def get_production_plan_item_details(filters, data, order_details): "item_name": frappe.get_cached_value("Item", row.item_code, "item_name"), "qty": row.planned_qty, "document_type": "Work Order", - "document_name": work_order, + "document_name": work_order or "", "bom_level": frappe.get_cached_value("BOM", row.bom_no, "bom_level"), - "produced_qty": order_details.get((work_order, row.item_code)).get("produced_qty"), - "pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code)).get("produced_qty")) + "produced_qty": order_details.get((work_order, row.item_code), {}).get("produced_qty", 0), + "pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code), {}).get("produced_qty", 0)) }) get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details) @@ -54,11 +61,23 @@ def get_production_plan_sub_assembly_item_details(filters, row, production_plan_ subcontracted_item = (item.type_of_manufacturing == 'Subcontract') if subcontracted_item: - docname = frappe.get_cached_value("Purchase Order Item", - {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "parent") + docname = frappe.get_value( + "Purchase Order Item", + { + "production_plan_sub_assembly_item": item.name, + "docstatus": ("<", 2) + }, + "parent" + ) else: - docname = frappe.get_cached_value("Work Order", - {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "name") + docname = frappe.get_value( + "Work Order", + { + "production_plan_sub_assembly_item": item.name, + "docstatus": ("<", 2) + }, + "name" + ) data.append({ "indent": 1, @@ -66,10 +85,10 @@ def get_production_plan_sub_assembly_item_details(filters, row, production_plan_ "item_name": item.item_name, "qty": item.qty, "document_type": "Work Order" if not subcontracted_item else "Purchase Order", - "document_name": docname, + "document_name": docname or "", "bom_level": item.bom_level, - "produced_qty": order_details.get((docname, item.production_item)).get("produced_qty"), - "pending_qty": flt(item.qty) - flt(order_details.get((docname, item.production_item)).get("produced_qty")) + "produced_qty": order_details.get((docname, item.production_item), {}).get("produced_qty", 0), + "pending_qty": flt(item.qty) - flt(order_details.get((docname, item.production_item), {}).get("produced_qty", 0)) }) def get_work_order_details(filters, order_details): From 4d4bfa2333f78b666fc3e6d8821a56d165bfb047 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 10 Nov 2021 00:00:38 +0530 Subject: [PATCH 050/185] fix: commision rate not fetch from sales person --- erpnext/accounts/party.py | 3 +- .../doctype/sales_team/sales_team.json | 321 +++++------------- 2 files changed, 89 insertions(+), 235 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 7ea6ccee5b..85aa9f5080 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -85,7 +85,8 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= if party_type=="Customer": party_details["sales_team"] = [{ "sales_person": d.sales_person, - "allocated_percentage": d.allocated_percentage or None + "allocated_percentage": d.allocated_percentage or None, + "commission_rate": d.commission_rate } for d in party.get("sales_team")] # supplier tax withholding category diff --git a/erpnext/selling/doctype/sales_team/sales_team.json b/erpnext/selling/doctype/sales_team/sales_team.json index 876789135c..cac5b763ff 100644 --- a/erpnext/selling/doctype/sales_team/sales_team.json +++ b/erpnext/selling/doctype/sales_team/sales_team.json @@ -1,247 +1,100 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-04-19 13:30:51", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2013-04-19 13:30:51", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "sales_person", + "contact_no", + "allocated_percentage", + "allocated_amount", + "commission_rate", + "incentives" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_person", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Sales Person", - "length": 0, - "no_copy": 0, - "oldfieldname": "sales_person", - "oldfieldtype": "Link", - "options": "Sales Person", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "200px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_on_submit": 1, + "fieldname": "sales_person", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Sales Person", + "oldfieldname": "sales_person", + "oldfieldtype": "Link", + "options": "Sales Person", + "print_width": "200px", + "reqd": 1, + "search_index": 1, "width": "200px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_no", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Contact No.", - "length": 0, - "no_copy": 0, - "oldfieldname": "contact_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_on_submit": 1, + "fieldname": "contact_no", + "fieldtype": "Data", + "hidden": 1, + "in_list_view": 1, + "label": "Contact No.", + "oldfieldname": "contact_no", + "oldfieldtype": "Data", + "print_width": "100px", "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "allocated_percentage", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Contribution (%)", - "length": 0, - "no_copy": 0, - "oldfieldname": "allocated_percentage", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_on_submit": 1, + "fieldname": "allocated_percentage", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Contribution (%)", + "oldfieldname": "allocated_percentage", + "oldfieldtype": "Currency", + "print_width": "100px", "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "allocated_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Contribution to Net Total", - "length": 0, - "no_copy": 0, - "oldfieldname": "allocated_amount", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "120px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_on_submit": 1, + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Contribution to Net Total", + "oldfieldname": "allocated_amount", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_width": "120px", + "read_only": 1, "width": "120px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "commission_rate", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Commission Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "sales_person.commission_rate", + "fetch_if_empty": 1, + "fieldname": "commission_rate", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Commission Rate", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "incentives", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Incentives", - "length": 0, - "no_copy": 0, - "oldfieldname": "incentives", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "allow_on_submit": 1, + "fieldname": "incentives", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Incentives", + "oldfieldname": "incentives", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-09-17 13:03:14.755974", - "modified_by": "Administrator", - "module": "Selling", - "name": "Sales Team", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2021-11-09 23:55:20.670475", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Team", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file From 68dc42fb1fe47185777babff93a8e24ee99b0762 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 10 Nov 2021 10:12:29 +0530 Subject: [PATCH 051/185] fix: function name [is_work_order_stopped -> is_work_order_closed] --- erpnext/manufacturing/doctype/job_card/job_card.js | 2 +- erpnext/manufacturing/doctype/job_card/job_card.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index e3eed92d7e..453ad50e8e 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -28,7 +28,7 @@ frappe.ui.form.on('Job Card', { frappe.flags.resume_job = 0; let has_items = frm.doc.items && frm.doc.items.length; - if (frm.doc.__onload.work_order_stopped) { + if (frm.doc.__onload.work_order_closed) { frm.disable_save(); return; } diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 5eea032be5..d6ae48995b 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -36,7 +36,7 @@ class JobCard(Document): def onload(self): excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") self.set_onload("job_card_excess_transfer", excess_transfer) - self.set_onload("work_order_stopped", self.is_work_order_stopped()) + self.set_onload("work_order_closed", self.is_work_order_closed()) def validate(self): self.validate_time_logs() @@ -549,10 +549,10 @@ class JobCard(Document): .format(message, bold(row.operation), bold(self.operation)), OperationSequenceError) def validate_work_order(self): - if self.is_work_order_stopped(): + if self.is_work_order_closed(): frappe.throw(_("You can't make any changes to Job Card since Work Order is stopped.")) - def is_work_order_stopped(self): + def is_work_order_closed(self): if self.work_order: status = frappe.get_value('Work Order', self.work_order) From e498389b00a2edadff47454e2cc55465d5d1bd52 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 10 Nov 2021 12:02:07 +0530 Subject: [PATCH 052/185] fix: ignore cancelled entries in incorrect balance qty report --- .../incorrect_balance_qty_after_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py index a381820ca2..6aa12ac2c9 100644 --- a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py +++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py @@ -46,7 +46,7 @@ def get_incorrect_data(data): return row def get_stock_ledger_entries(report_filters): - filters = {} + filters = {"is_cancelled": 0} fields = ['name', 'voucher_type', 'voucher_no', 'item_code', 'actual_qty', 'posting_date', 'posting_time', 'company', 'warehouse', 'qty_after_transaction', 'batch_no'] From 2f004693c36f3dc34c2ef96889fb17e28f2bd385 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Wed, 10 Nov 2021 12:36:54 +0530 Subject: [PATCH 053/185] fix(Bank Reconciliation): get credit amount for bank account of type liability --- .../bank_reconciliation_tool.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index 002e312a39..5cbf00b2c6 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -342,7 +342,15 @@ def get_pe_matching_query(amount_condition, account_from_to, transaction): def get_je_matching_query(amount_condition, transaction): # get matching journal entry query - cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit" + + company_account = frappe.get_value("Bank Account", transaction.bank_account, "account") + root_type = frappe.get_value("Account", company_account, "root_type") + + if root_type == "Liability": + cr_or_dr = "debit" if transaction.withdrawal > 0 else "credit" + else: + cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit" + return f""" SELECT From 12e81df2b79a0c7fb7cedb3e467837b60540be04 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 10 Nov 2021 12:36:00 +0530 Subject: [PATCH 054/185] fix: default value for allow neg stock in repost_item_valuation Negative stock can be toggled back after queuing transactions, this causes failure when repost is executed. Now allow_negative_stock stock is set at time of queuing the repost job. This means setting changes done afterwards won't affect already submitted reposts. --- .../doctype/repost_item_valuation/repost_item_valuation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 86b4b860f8..170aa7f76c 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -31,6 +31,9 @@ class RepostItemValuation(Document): self.voucher_type = None self.voucher_no = None + self.allow_negative_stock = self.allow_negative_stock or \ + cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) + def set_company(self): if self.voucher_type and self.voucher_no: self.company = frappe.get_cached_value(self.voucher_type, self.voucher_no, "company") From 6d05bb527432c7fb70794634c7c51692b25bdbfe Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 10 Nov 2021 13:06:55 +0530 Subject: [PATCH 055/185] fix(pos): get mode of payments query (#28321) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index cd270f505e..59d46fc2e8 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2029,7 +2029,7 @@ def get_mode_of_payments_info(mode_of_payments, company): mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and - mp.name in (%s) + mp.name in %s group by mp.name """, (company, mode_of_payments), as_dict=1) From 1a7851b1ad53808b1ec73dca841261864e41ae4c Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 10 Nov 2021 14:55:13 +0530 Subject: [PATCH 056/185] fix: error message --- erpnext/manufacturing/doctype/job_card/job_card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index d6ae48995b..90792c3c53 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -550,7 +550,7 @@ class JobCard(Document): def validate_work_order(self): if self.is_work_order_closed(): - frappe.throw(_("You can't make any changes to Job Card since Work Order is stopped.")) + frappe.throw(_("You can't make any changes to Job Card since Work Order is closed.")) def is_work_order_closed(self): if self.work_order: From 18ae03d9675ada7f0ab7affff72604709ce0aa76 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Wed, 10 Nov 2021 15:57:41 +0530 Subject: [PATCH 057/185] fix: calling shipping rule method during net_total calculation in taxes_adn_totals.py --- erpnext/controllers/taxes_and_totals.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 70cc8a58bf..8cca7ddc72 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -259,6 +259,10 @@ class calculate_taxes_and_totals(object): self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"]) + if hasattr(self.doc, "shipping_rule"): + shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule) + shipping_rule.apply(self.doc) + def calculate_taxes(self): self.doc.rounding_adjustment = 0 # maintain actual tax rate based on idx From af1fce0419bddd0e63f58cde3c2610d73752ef81 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Wed, 10 Nov 2021 16:49:12 +0530 Subject: [PATCH 058/185] fix: check if shipping rule value exists --- erpnext/controllers/taxes_and_totals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 8cca7ddc72..1b192b7a32 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -259,7 +259,7 @@ class calculate_taxes_and_totals(object): self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"]) - if hasattr(self.doc, "shipping_rule"): + if hasattr(self.doc, "shipping_rule") and self.doc.shipping_rule: shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule) shipping_rule.apply(self.doc) From b728597ef41dc27ddca4531f22109834bb4268ab Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 10 Nov 2021 17:59:07 +0530 Subject: [PATCH 059/185] fix: use hotfix branches for patch tests --- .github/workflows/patch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index f8abb6c774..97bccf5d56 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -93,7 +93,7 @@ jobs: for version in $(seq 12 13) do echo "Updating to v$version" - branch_name="version-$version" + branch_name="version-$version-hotfix" git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name From c96b5492dbe860e40f82497a6972cc1cb29071a5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 10 Nov 2021 19:04:37 +0530 Subject: [PATCH 060/185] fix: reload doctype forcefully --- erpnext/patches/v13_0/create_pan_field_for_india.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v13_0/create_pan_field_for_india.py b/erpnext/patches/v13_0/create_pan_field_for_india.py index c37651aaa3..6df6e1eb32 100644 --- a/erpnext/patches/v13_0/create_pan_field_for_india.py +++ b/erpnext/patches/v13_0/create_pan_field_for_india.py @@ -5,6 +5,7 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def execute(): frappe.reload_doc('buying', 'doctype', 'supplier', force=True) frappe.reload_doc('selling', 'doctype', 'customer', force=True) + frappe.reload_doc('core', 'doctype', 'doctype', force=True) custom_fields = { 'Supplier': [ From 0dca97eb9f56a9a45b736c42cbf48b4a3a7ee834 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 10 Nov 2021 20:54:12 +0530 Subject: [PATCH 061/185] fix(India): Sales Invoice with duplicate items not showing correct taxable value --- erpnext/regional/report/gstr_1/gstr_1.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 27cfaf7614..11b684d3f6 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -248,18 +248,17 @@ class Gstr1Report(object): """ % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) for d in items: - if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) - self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) + self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) - item_tax_rate = {} + item_tax_rate = {} - if d.item_tax_rate: - item_tax_rate = json.loads(d.item_tax_rate) + if d.item_tax_rate: + item_tax_rate = json.loads(d.item_tax_rate) - for account, rate in item_tax_rate.items(): - tax_rate_dict = self.item_tax_rate.setdefault(d.parent, {}).setdefault(d.item_code, []) - tax_rate_dict.append(rate) + for account, rate in item_tax_rate.items(): + tax_rate_dict = self.item_tax_rate.setdefault(d.parent, {}).setdefault(d.item_code, []) + tax_rate_dict.append(rate) def get_items_based_on_tax_rate(self): self.tax_details = frappe.db.sql(""" From ecbe4b16b8dedacb6a4d5bbdf43fa20570095009 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 11 Nov 2021 13:55:21 +0530 Subject: [PATCH 062/185] perf(minor): general ledger report (#27987) --- .../report/general_ledger/general_ledger.py | 65 ++++++++++++------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 050403d21e..385c8b2b6e 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -6,7 +6,7 @@ from collections import OrderedDict import frappe from frappe import _, _dict -from frappe.utils import cstr, flt, getdate +from frappe.utils import cstr, getdate from erpnext import get_company_currency, get_default_company from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -17,6 +17,8 @@ from erpnext.accounts.report.financial_statements import get_cost_centers_with_c from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency from erpnext.accounts.utils import get_account_currency +# to cache translations +TRANSLATIONS = frappe._dict() def execute(filters=None): if not filters: @@ -42,10 +44,20 @@ def execute(filters=None): columns = get_columns(filters) + update_translations() + res = get_result(filters, account_details) return columns, res +def update_translations(): + TRANSLATIONS.update( + dict( + OPENING = _('Opening'), + TOTAL = _('Total'), + CLOSING_TOTAL = _('Closing (Opening + Total)') + ) + ) def validate_filters(filters, account_details): if not filters.get("company"): @@ -351,9 +363,9 @@ def get_totals_dict(): credit_in_account_currency=0.0 ) return _dict( - opening = _get_debit_credit_dict(_('Opening')), - total = _get_debit_credit_dict(_('Total')), - closing = _get_debit_credit_dict(_('Closing (Opening + Total)')) + opening = _get_debit_credit_dict(TRANSLATIONS.OPENING), + total = _get_debit_credit_dict(TRANSLATIONS.TOTAL), + closing = _get_debit_credit_dict(TRANSLATIONS.CLOSING_TOTAL) ) def group_by_field(group_by): @@ -378,22 +390,23 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): entries = [] consolidated_gle = OrderedDict() group_by = group_by_field(filters.get('group_by')) + group_by_voucher_consolidated = filters.get("group_by") == 'Group by Voucher (Consolidated)' if filters.get('show_net_values_in_party_account'): account_type_map = get_account_type_map(filters.get('company')) def update_value_in_dict(data, key, gle): - data[key].debit += flt(gle.debit) - data[key].credit += flt(gle.credit) + data[key].debit += gle.debit + data[key].credit += gle.credit - data[key].debit_in_account_currency += flt(gle.debit_in_account_currency) - data[key].credit_in_account_currency += flt(gle.credit_in_account_currency) + data[key].debit_in_account_currency += gle.debit_in_account_currency + data[key].credit_in_account_currency += gle.credit_in_account_currency if filters.get('show_net_values_in_party_account') and \ account_type_map.get(data[key].account) in ('Receivable', 'Payable'): - net_value = flt(data[key].debit) - flt(data[key].credit) - net_value_in_account_currency = flt(data[key].debit_in_account_currency) \ - - flt(data[key].credit_in_account_currency) + net_value = data[key].debit - data[key].credit + net_value_in_account_currency = data[key].debit_in_account_currency \ + - data[key].credit_in_account_currency if net_value < 0: dr_or_cr = 'credit' @@ -411,19 +424,29 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): data[key].against_voucher += ', ' + gle.against_voucher from_date, to_date = getdate(filters.from_date), getdate(filters.to_date) - for gle in gl_entries: - if (gle.posting_date < from_date or - (cstr(gle.is_opening) == "Yes" and not filters.get("show_opening_entries"))): - update_value_in_dict(gle_map[gle.get(group_by)].totals, 'opening', gle) - update_value_in_dict(totals, 'opening', gle) + show_opening_entries = filters.get("show_opening_entries") - update_value_in_dict(gle_map[gle.get(group_by)].totals, 'closing', gle) + for gle in gl_entries: + group_by_value = gle.get(group_by) + + if (gle.posting_date < from_date or (cstr(gle.is_opening) == "Yes" and not show_opening_entries)): + if not group_by_voucher_consolidated: + update_value_in_dict(gle_map[group_by_value].totals, 'opening', gle) + update_value_in_dict(gle_map[group_by_value].totals, 'closing', gle) + + update_value_in_dict(totals, 'opening', gle) update_value_in_dict(totals, 'closing', gle) elif gle.posting_date <= to_date: - if filters.get("group_by") != 'Group by Voucher (Consolidated)': - gle_map[gle.get(group_by)].entries.append(gle) - elif filters.get("group_by") == 'Group by Voucher (Consolidated)': + if not group_by_voucher_consolidated: + update_value_in_dict(gle_map[group_by_value].totals, 'total', gle) + update_value_in_dict(gle_map[group_by_value].totals, 'closing', gle) + update_value_in_dict(totals, 'total', gle) + update_value_in_dict(totals, 'closing', gle) + + gle_map[group_by_value].entries.append(gle) + + elif group_by_voucher_consolidated: keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")] for dim in accounting_dimensions: keylist.append(gle.get(dim)) @@ -435,9 +458,7 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): update_value_in_dict(consolidated_gle, key, gle) for key, value in consolidated_gle.items(): - update_value_in_dict(gle_map[value.get(group_by)].totals, 'total', value) update_value_in_dict(totals, 'total', value) - update_value_in_dict(gle_map[value.get(group_by)].totals, 'closing', value) update_value_in_dict(totals, 'closing', value) entries.append(value) From 1aed8c4b2fca6000b036773539a75f0c697e83a8 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 11 Nov 2021 18:40:38 +0530 Subject: [PATCH 063/185] feat: Add Serial No field --- .../asset_repair_consumed_item.json | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json index 528f0ec986..f63add1235 100644 --- a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json +++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json @@ -5,19 +5,13 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "item", + "item_code", "valuation_rate", "consumed_quantity", - "total_value" + "total_value", + "serial_no" ], "fields": [ - { - "fieldname": "item", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item", - "options": "Item" - }, { "fetch_from": "item.valuation_rate", "fieldname": "valuation_rate", @@ -38,12 +32,24 @@ "in_list_view": 1, "label": "Total Value", "read_only": 1 + }, + { + "fieldname": "serial_no", + "fieldtype": "Small Text", + "label": "Serial No" + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item", + "options": "Item" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-05-12 03:19:55.006300", + "modified": "2021-11-11 18:23:00.492483", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair Consumed Item", From abb535540a0910bb6193ad0985e28116123e38e7 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 11 Nov 2021 18:41:16 +0530 Subject: [PATCH 064/185] fix: Rename item to item_code --- erpnext/assets/doctype/asset_repair/asset_repair.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 99a7d9bfbf..1131197bd2 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -120,7 +120,7 @@ class AssetRepair(AccountsController): for stock_item in self.get('stock_items'): stock_entry.append('items', { "s_warehouse": self.warehouse, - "item_code": stock_item.item, + "item_code": stock_item.item_code, "qty": stock_item.consumed_quantity, "basic_rate": stock_item.valuation_rate }) From 4668bb4be0320580f10608cdc74d23fc4a775a88 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 11 Nov 2021 18:42:25 +0530 Subject: [PATCH 065/185] feat: Add 'Add Serial No' button in the Stock Items table --- erpnext/assets/doctype/asset_repair/asset_repair.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index 18a56d33e6..d554d52a71 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -60,6 +60,10 @@ frappe.ui.form.on('Asset Repair', { if (frm.doc.repair_status == "Completed") { frm.set_value('completion_date', frappe.datetime.now_datetime()); } + }, + + stock_items_on_form_rendered() { + erpnext.setup_serial_or_batch_no(); } }); From 1393f97ad52902cc57a87d2a700ce3c169a5707c Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 11 Nov 2021 18:48:23 +0530 Subject: [PATCH 066/185] fix: Add serial no to Stock Entry doc to decrease quantity for Stock Items consumed during repair --- erpnext/assets/doctype/asset_repair/asset_repair.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 1131197bd2..2c01ed7748 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -122,7 +122,8 @@ class AssetRepair(AccountsController): "s_warehouse": self.warehouse, "item_code": stock_item.item_code, "qty": stock_item.consumed_quantity, - "basic_rate": stock_item.valuation_rate + "basic_rate": stock_item.valuation_rate, + "serial_no": stock_item.serial_no }) stock_entry.insert() From 944bf8da71284a4fd72afa4410e421ded63992fa Mon Sep 17 00:00:00 2001 From: Sagar Sharma <63660334+s-aga-r@users.noreply.github.com> Date: Thu, 11 Nov 2021 19:49:41 +0530 Subject: [PATCH 067/185] fix: Unable to edit supplier scorecard criteria name once created (#28348) --- .../supplier_scorecard_criteria.json | 228 +++++------------- 1 file changed, 57 insertions(+), 171 deletions(-) diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.json b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.json index 2623585aea..3668b2505f 100644 --- a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.json +++ b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.json @@ -1,184 +1,70 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:criteria_name", - "beta": 0, - "creation": "2017-05-29 01:32:43.064891", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_rename": 1, + "autoname": "field:criteria_name", + "creation": "2017-05-29 01:32:43.064891", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "criteria_name", + "max_score", + "formula", + "weight" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "criteria_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Criteria Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, + "fieldname": "criteria_name", + "fieldtype": "Data", + "label": "Criteria Name", + "reqd": 1, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "100", - "fieldname": "max_score", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Max Score", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "default": "100", + "fieldname": "max_score", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Max Score", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "formula", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Criteria Formula", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "formula", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Criteria Formula", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "weight", - "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Criteria Weight", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "weight", + "fieldtype": "Percent", + "label": "Criteria Weight" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-22 10:47:00.000822", - "modified_by": "Administrator", - "module": "Buying", - "name": "Supplier Scorecard Criteria", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2021-11-11 18:34:58.477648", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Scorecard Criteria", + "naming_rule": "By fieldname", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file From 88648570d78ebba2894357af6873adb4fc55f728 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 12 Nov 2021 12:39:30 +0530 Subject: [PATCH 068/185] fix: Default party account getting overriden in invoices --- .../doctype/purchase_invoice/purchase_invoice.js | 13 +++++++++++-- .../accounts/doctype/sales_invoice/sales_invoice.js | 13 +++++++++++-- erpnext/accounts/party.py | 9 ++++++--- erpnext/controllers/accounts_controller.py | 4 ++-- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 3526e488e1..1a398aba2e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -592,8 +592,17 @@ frappe.ui.form.on("Purchase Invoice", { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); if (frm.doc.company) { - frappe.db.get_value('Company', frm.doc.company, 'default_payable_account', (r) => { - frm.set_value('credit_to', r.default_payable_account); + frappe.call({ + method: + "erpnext.accounts.party.get_party_account", + args: { + party_type: 'Supplier', + party: frm.doc.supplier, + company: frm.doc.company + }, + callback: (response) => { + if (response) frm.set_value("credit_to", response.message); + }, }); } }, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index bee153b7b8..a1f3ee4b06 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -15,8 +15,17 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e let me = this; if (this.frm.doc.company) { - frappe.db.get_value('Company', this.frm.doc.company, 'default_receivable_account', (r) => { - me.frm.set_value('debit_to', r.default_receivable_account); + frappe.call({ + method: + "erpnext.accounts.party.get_party_account", + args: { + party_type: 'Customer', + party: this.frm.doc.customer, + company: this.frm.doc.company + }, + callback: (response) => { + if (response) me.frm.set_value("debit_to", response.message); + }, }); } } diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 2108bc158d..1c9d19d8dc 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -218,7 +218,7 @@ def set_account_and_due_date(party, account, party_type, company, posting_date, return out @frappe.whitelist() -def get_party_account(party_type, party, company=None): +def get_party_account(party_type, party=None, company=None): """Returns the account for the given `party`. Will first search in party (Customer / Supplier) record, if not found, will search in group (Customer Group / Supplier Group), @@ -226,8 +226,11 @@ def get_party_account(party_type, party, company=None): if not company: frappe.throw(_("Please select a Company")) - if not party: - return + if not party and party_type in ['Customer', 'Supplier']: + default_account_name = "default_receivable_account" \ + if party_type=="Customer" else "default_payable_account" + + return frappe.get_cached_value('Company', company, default_account_name) account = frappe.db.get_value("Party Account", {"parenttype": party_type, "parent": party, "company": company}, "account") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e0551a4ca8..3190feac70 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1402,8 +1402,8 @@ class AccountsController(TransactionBase): grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - if flt(total, self.precision("grand_total")) != flt(grand_total, self.precision("grand_total")) or \ - flt(base_total, self.precision("base_grand_total")) != flt(base_grand_total, self.precision("base_grand_total")): + if flt(total, self.precision("grand_total")) - flt(grand_total, self.precision("grand_total")) > 0.1 or \ + flt(base_total, self.precision("base_grand_total")) - flt(base_grand_total, self.precision("base_grand_total")) > 0.1: frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) def is_rounded_total_disabled(self): From a424310581fc7c4306fafd3038e7deaa2c584706 Mon Sep 17 00:00:00 2001 From: Sagar Sharma <63660334+s-aga-r@users.noreply.github.com> Date: Fri, 12 Nov 2021 13:50:30 +0530 Subject: [PATCH 069/185] fix: Collapse Scrap Items in Job Card (#28362) --- erpnext/manufacturing/doctype/job_card/job_card.js | 6 ++++++ erpnext/manufacturing/doctype/job_card/job_card.json | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index e3eed92d7e..f9259fbdf4 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -23,6 +23,12 @@ frappe.ui.form.on('Job Card', { ); }, + onload: function(frm) { + if (frm.doc.scrap_items.length == 0) { + frm.fields_dict['scrap_items_section'].collapse(); + } + }, + refresh: function(frm) { frappe.flags.pause_job = 0; frappe.flags.resume_job = 0; diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 7dd38f4673..6528199d82 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -396,6 +396,7 @@ "options": "Batch" }, { + "collapsible": 1, "fieldname": "scrap_items_section", "fieldtype": "Section Break", "label": "Scrap Items" @@ -411,7 +412,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-09-14 00:38:46.873105", + "modified": "2021-11-12 10:15:03.572401", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", From 24b048925bb758cad3cb0fa59a571165320132f8 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 12 Nov 2021 14:08:02 +0530 Subject: [PATCH 070/185] fix(WooCommerce): always expect signature in webhook requests (#28367) --- .../erpnext_integrations/connectors/woocommerce_connection.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py index f5f9ce3b16..9409485a9f 100644 --- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py +++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py @@ -19,8 +19,7 @@ def verify_request(): ) if frappe.request.data and \ - frappe.get_request_header("X-Wc-Webhook-Signature") and \ - not sig == bytes(frappe.get_request_header("X-Wc-Webhook-Signature").encode()): + not sig == frappe.get_request_header("X-Wc-Webhook-Signature", "").encode(): frappe.throw(_("Unverified Webhook Data")) frappe.set_user(woocommerce_settings.creation_user) From 6d3e9bce5f6bcecd6f4eec555cbdffd8f1df62e3 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 12 Nov 2021 14:24:33 +0530 Subject: [PATCH 071/185] fix(M-Pesa): validate type before executing `get_doc` (#28369) --- .../doctype/mpesa_settings/mpesa_settings.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py index d2b6190a29..e7b4a30e0a 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py @@ -141,6 +141,9 @@ def verify_transaction(**kwargs): transaction_response = frappe._dict(kwargs["Body"]["stkCallback"]) checkout_id = getattr(transaction_response, "CheckoutRequestID", "") + if not isinstance(checkout_id, str): + frappe.throw(_("Invalid Checkout Request ID")) + integration_request = frappe.get_doc("Integration Request", checkout_id) transaction_data = frappe._dict(loads(integration_request.data)) total_paid = 0 # for multiple integration request made against a pos invoice @@ -231,6 +234,9 @@ def process_balance_info(**kwargs): account_balance_response = frappe._dict(kwargs["Result"]) conversation_id = getattr(account_balance_response, "ConversationID", "") + if not isinstance(conversation_id, str): + frappe.throw(_("Invalid Conversation ID")) + request = frappe.get_doc("Integration Request", conversation_id) if request.status == "Completed": From c0f06bc8e382a0543a476f119989f7422a21add0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 12 Nov 2021 14:45:51 +0530 Subject: [PATCH 072/185] fix: validate hmac unconditionally (#28372) --- erpnext/erpnext_integrations/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index b52c3fc2a8..d922d875fd 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -23,7 +23,6 @@ def validate_webhooks_request(doctype, hmac_key, secret_key='secret'): ) if frappe.request.data and \ - frappe.get_request_header(hmac_key) and \ not sig == bytes(frappe.get_request_header(hmac_key).encode()): frappe.throw(_("Unverified Webhook Data")) frappe.set_user(settings.modified_by) From 520f33b02fcd77b4fab31da35198f2195800d83f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sun, 14 Nov 2021 08:10:53 +0530 Subject: [PATCH 073/185] fix(regional): hsn_wise as false returns item_code --- erpnext/regional/india/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 1733220c0a..e09c38fa45 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -572,17 +572,17 @@ def get_item_list(data, doc, hsn_wise=False): } item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol'] hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True, hsn_wise=hsn_wise) - for hsn_code, taxable_amount in hsn_taxable_amount.items(): + for item_or_hsn, taxable_amount in hsn_taxable_amount.items(): item_data = frappe._dict() - if not hsn_code: + if not item_or_hsn: frappe.throw(_('GST HSN Code does not exist for one or more items')) - item_data.hsnCode = int(hsn_code) + item_data.hsnCode = int(item_or_hsn) if hsn_wise else item_or_hsn item_data.taxableAmount = taxable_amount item_data.qtyUnit = "" for attr in item_data_attrs: item_data[attr] = 0 - for account, tax_detail in hsn_wise_charges.get(hsn_code, {}).items(): + for account, tax_detail in hsn_wise_charges.get(item_or_hsn, {}).items(): account_type = gst_accounts.get(account, '') for tax_acc, attrs in tax_map.items(): if account_type == tax_acc: From 03370c63c59226afbea3e8f2e41f69c7747fe219 Mon Sep 17 00:00:00 2001 From: Conor Date: Sat, 13 Nov 2021 22:16:31 -0600 Subject: [PATCH 074/185] chore: minor typo correction in CONTRIBUTING (#28386) --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 511b682f37..1cf9a5bd9b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -23,7 +23,7 @@ If your issue is not clear or does not meet the guidelines, then it will be clos 1. **Steps to Reproduce:** The bug report must have a list of steps needed to reproduce a bug. If we cannot reproduce it, then we cannot solve it. 1. **Version Number:** Please add the version number in your report. Often a bug is fixed in the latest version 1. **Clear Title:** Add a clear subject to your bug report like "Unable to submit Purchase Order without Basic Rate" instead of just "Cannot Submit" -1. **Screenshots:** Screenshots are a great way of communicating the issues. Try adding annotations or using LiceCAP to take a screencast in `gif`. +1. **Screenshots:** Screenshots are a great way of communicating issues. Try adding annotations or using LiceCAP to take a screencast in `gif`. ### Feature Request Guidelines From bb561ba7a8f6410f34409ba5f5d7a95653a7d1e4 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 15 Nov 2021 15:21:20 +0530 Subject: [PATCH 075/185] fix: Server side test - make `tests_that_skip_setup` a tuple (added comma) - remove manual teardown in `test_job_card_material_transfer_correctness` to avoid premature committing - transfer_material_against = "Job Card" while making BOM with mulitple operations --- .../manufacturing/doctype/job_card/test_job_card.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index e16c4802fe..51df35beab 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -20,7 +20,7 @@ class TestJobCard(unittest.TestCase): transfer_material_against, source_warehouse = None, None tests_that_skip_setup = ( - "test_job_card_material_transfer_correctness" + "test_job_card_material_transfer_correctness", ) tests_that_transfer_against_jc = ( "test_job_card_multiple_materials_transfer", @@ -272,14 +272,10 @@ class TestJobCard(unittest.TestCase): self.assertEqual(transfer_entry.items[0].item_code, "_Test Item") self.assertEqual(transfer_entry.items[0].qty, 2) - # teardown - transfer_entry.delete() - frappe.db.delete("Job Card", {"work_order": work_order.name}) - work_order.cancel() - bom.cancel() - + # rollback via tearDown method def create_bom_with_multiple_operations(): + "Create a BOM with multiple operations and Material Transfer against Job Card" from erpnext.manufacturing.doctype.operation.test_operation import make_operation test_record = frappe.get_test_records("BOM")[2] @@ -303,6 +299,7 @@ def create_bom_with_multiple_operations(): "operating_cost": 100 }) + bom_doc.transfer_material_against = "Job Card" bom_doc.save() bom_doc.submit() From 2eccb7a1ca626b05bca9d3f7a8621a9162cbd599 Mon Sep 17 00:00:00 2001 From: Sagar Sharma <63660334+s-aga-r@users.noreply.github.com> Date: Mon, 15 Nov 2021 02:38:15 -0800 Subject: [PATCH 076/185] fix: Work order creation from sales order (#28388) * fix: Work order creation from sales order * chore: formatting Co-authored-by: Ankush Menat --- erpnext/selling/doctype/sales_order/sales_order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index d46c46f90e..79e9e17e41 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -319,7 +319,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex title: __('Select Items to Manufacture'), fields: fields, primary_action: function() { - var data = d.get_values(); + var data = {items: d.fields_dict.items.grid.get_selected_children()}; me.frm.call({ method: 'make_work_orders', args: { From afe1c45f7f20b115585504929a20baf6eed28fc2 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 15 Nov 2021 17:17:29 +0530 Subject: [PATCH 077/185] fix: Sider + Fields UX - remove extra space (sider) - PR: Bring rejected qty in grid view - PI: Rename `Stock Qty` to `Accepted Qty in Stock UOM` - PI: Move `Accepted Qty in Stock UOM` under Stock UOM --- .../purchase_invoice_item/purchase_invoice_item.json | 10 ++++++---- erpnext/controllers/buying_controller.py | 2 +- .../purchase_receipt_item/purchase_receipt_item.json | 6 ++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index d39a9fc058..f9b2efd053 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -22,10 +22,10 @@ "received_qty", "qty", "rejected_qty", - "stock_uom", "col_break2", "uom", "conversion_factor", + "stock_uom", "stock_qty", "sec_break1", "price_list_rate", @@ -175,7 +175,8 @@ { "fieldname": "received_qty", "fieldtype": "Float", - "label": "Received Qty" + "label": "Received Qty", + "read_only": 1 }, { "bold": 1, @@ -223,7 +224,7 @@ { "fieldname": "stock_qty", "fieldtype": "Float", - "label": "Stock Qty", + "label": "Accepted Qty in Stock UOM", "print_hide": 1, "read_only": 1, "reqd": 1 @@ -870,10 +871,11 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-09-01 16:04:03.538643", + "modified": "2021-11-15 17:04:07.191013", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 4ec4df8800..6fc040e2de 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -365,7 +365,7 @@ class BuyingController(StockController, Subcontracting): d.received_qty = flt(d.qty) + flt(d.rejected_qty) # Check Received Qty = Accepted Qty + Rejected Qty - val = flt(d.qty) + flt(d.rejected_qty) + val = flt(d.qty) + flt(d.rejected_qty) if (flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty"))): frappe.throw(_("Accepted + Rejected Qty must be equal to Received quantity for Item {0}").format(d.item_code)) diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 3ddb635102..30ea1c3cad 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -221,8 +221,10 @@ "width": "100px" }, { + "columns": 1, "fieldname": "rejected_qty", "fieldtype": "Float", + "in_list_view": 1, "label": "Rejected Quantity", "oldfieldname": "rejected_qty", "oldfieldtype": "Currency", @@ -329,7 +331,7 @@ }, { "bold": 1, - "columns": 3, + "columns": 2, "fieldname": "rate", "fieldtype": "Currency", "in_list_view": 1, @@ -974,7 +976,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-11-03 17:56:53.916921", + "modified": "2021-11-15 15:46:10.591600", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 05ec7cce82f4ad7029c96b9f81d4c472718f791c Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 15 Nov 2021 17:49:14 +0530 Subject: [PATCH 078/185] test: Purchase Receipt Received Qty --- erpnext/controllers/buying_controller.py | 6 ++++-- .../purchase_receipt/test_purchase_receipt.py | 21 +++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 6fc040e2de..d0defcc9fa 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -3,7 +3,7 @@ import frappe -from frappe import _, msgprint +from frappe import _, msgprint, ValidationError from frappe.contacts.doctype.address.address import get_address_display from frappe.utils import cint, cstr, flt, getdate @@ -16,6 +16,7 @@ from erpnext.controllers.subcontracting import Subcontracting from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.utils import get_incoming_rate +class QtyMismatchError(ValidationError): pass class BuyingController(StockController, Subcontracting): @@ -367,7 +368,8 @@ class BuyingController(StockController, Subcontracting): # Check Received Qty = Accepted Qty + Rejected Qty val = flt(d.qty) + flt(d.rejected_qty) if (flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty"))): - frappe.throw(_("Accepted + Rejected Qty must be equal to Received quantity for Item {0}").format(d.item_code)) + message = _("Row #{0}: Received Qty must be equal to Accepted + Rejected Qty for Item {1}").format(d.idx, d.item_code) + frappe.throw(msg=message, title=_("Mismatch"), exc=QtyMismatchError) def validate_negative_quantity(self, item_row, field_list): if self.is_return: diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 102730b055..8968b18536 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -15,6 +15,7 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchas from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction +from erpnext.controllers.buying_controller import QtyMismatchError from erpnext.tests.utils import ERPNextTestCase @@ -22,6 +23,22 @@ class TestPurchaseReceipt(ERPNextTestCase): def setUp(self): frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) + def test_purchase_receipt_received_qty(self): + """ + 1. Test if received qty is validated against accepted + rejected + 2. Test if received qty is auto set on save + """ + pr = make_purchase_receipt(qty=1, rejected_qty=1, received_qty=3, + item_code="_Test Item Home Desktop 200", do_not_save=True) + self.assertRaises(QtyMismatchError, pr.save) + + pr.items[0].received_qty = 0 + pr.save() + self.assertEqual(pr.items[0].received_qty, 2) + + # teardown + pr.delete() + def test_reverse_purchase_receipt_sle(self): pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200") @@ -1225,8 +1242,8 @@ def make_purchase_receipt(**args): pr.return_against = args.return_against pr.apply_putaway_rule = args.apply_putaway_rule qty = args.qty or 5 - received_qty = args.received_qty or qty - rejected_qty = args.rejected_qty or flt(received_qty) - flt(qty) + rejected_qty = args.rejected_qty or 0 + received_qty = args.received_qty or flt(rejected_qty) + flt(qty) item_code = args.item or args.item_code or "_Test Item" uom = args.uom or frappe.db.get_value("Item", item_code, "stock_uom") or "_Test UOM" From 7fcaeca403c229f0101a8f89ca4cd64f6d5bcdc0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 15 Nov 2021 17:11:33 +0530 Subject: [PATCH 079/185] fix: don't make naming series mandatory for items Item variants are an exception, hence this needs to be checked conditionally. --- erpnext/setup/doctype/naming_series/naming_series.py | 4 ++-- erpnext/stock/doctype/stock_settings/stock_settings.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index 4def6eb9e8..986b4e87ff 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -180,11 +180,11 @@ class NamingSeries(Document): prefix = parse_naming_series(parts) return prefix -def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True): +def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True, make_mandatory=1): from frappe.custom.doctype.property_setter.property_setter import make_property_setter if naming_series: make_property_setter(doctype, "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False) - make_property_setter(doctype, "naming_series", "reqd", 1, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "naming_series", "reqd", make_mandatory, "Check", validate_fields_for_doctype=False) # set values for mandatory try: diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index 2dd103d607..1de48b6f1f 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -20,7 +20,7 @@ class StockSettings(Document): from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series set_by_naming_series("Item", "item_code", - self.get("item_naming_by")=="Naming Series", hide_name_field=True) + self.get("item_naming_by")=="Naming Series", hide_name_field=True, make_mandatory=0) stock_frozen_limit = 356 submitted_stock_frozen = self.stock_frozen_upto_days or 0 From 54184e54ed352a6264957e3efe659868a4523a11 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 15 Nov 2021 17:54:53 +0530 Subject: [PATCH 080/185] fix: patch for naming series property setter --- erpnext/patches.txt | 1 + .../patches/v13_0/item_naming_series_not_mandatory.py | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 erpnext/patches/v13_0/item_naming_series_not_mandatory.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 778cbdf65b..e475229125 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -306,6 +306,7 @@ erpnext.patches.v13_0.requeue_failed_reposts erpnext.patches.v13_0.update_job_card_status erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.healthcare_deprecation_warning +erpnext.patches.v13_0.item_naming_series_not_mandatory erpnext.patches.v14_0.delete_healthcare_doctypes erpnext.patches.v13_0.update_category_in_ltds_certificate erpnext.patches.v13_0.create_pan_field_for_india #2 diff --git a/erpnext/patches/v13_0/item_naming_series_not_mandatory.py b/erpnext/patches/v13_0/item_naming_series_not_mandatory.py new file mode 100644 index 0000000000..5fe85a4830 --- /dev/null +++ b/erpnext/patches/v13_0/item_naming_series_not_mandatory.py @@ -0,0 +1,11 @@ +import frappe + +from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series + + +def execute(): + + stock_settings = frappe.get_doc("Stock Settings") + + set_by_naming_series("Item", "item_code", + stock_settings.get("item_naming_by")=="Naming Series", hide_name_field=True, make_mandatory=0) From 043e3255d66d3919fb7317218760fa181be87c5f Mon Sep 17 00:00:00 2001 From: Rohan Date: Mon, 15 Nov 2021 19:41:17 +0530 Subject: [PATCH 081/185] fix: remove item-item group name validation (#28392) --- erpnext/setup/doctype/item_group/item_group.py | 5 ----- erpnext/stock/doctype/item/item.py | 7 ------- 2 files changed, 12 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 4c42b61f01..c94b3463fc 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -43,7 +43,6 @@ class ItemGroup(NestedSet, WebsiteGenerator): def on_update(self): NestedSet.on_update(self) invalidate_cache_for(self) - self.validate_name_with_item() self.validate_one_root() self.delete_child_item_groups_key() @@ -67,10 +66,6 @@ class ItemGroup(NestedSet, WebsiteGenerator): WebsiteGenerator.on_trash(self) self.delete_child_item_groups_key() - def validate_name_with_item(self): - if frappe.db.exists("Item", self.name): - frappe.throw(frappe._("An item exists with same name ({0}), please change the item group name or rename the item").format(self.name), frappe.NameError) - def get_context(self, context): context.show_search=True context.page_length = cint(frappe.db.get_single_value('Products Settings', 'products_per_page')) or 6 diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index fa42c7d934..5daabe817b 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -152,7 +152,6 @@ class Item(WebsiteGenerator): def on_update(self): invalidate_cache_for_item(self) - self.validate_name_with_item_group() self.update_variants() self.update_item_price() self.update_template_item() @@ -628,12 +627,6 @@ class Item(WebsiteGenerator): where item_code = %s and is_cancelled = 0 limit 1""", self.name)) return self._stock_ledger_created - def validate_name_with_item_group(self): - # causes problem with tree build - if frappe.db.exists("Item Group", self.name): - frappe.throw( - _("An Item Group exists with same name, please change the item name or rename the item group")) - def update_item_price(self): frappe.db.sql(""" UPDATE `tabItem Price` From 3aaf7cb4084892810cca8e7952419ed4a1c42237 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 16 Nov 2021 02:33:12 +0530 Subject: [PATCH 082/185] fix: Remove default Totals row --- erpnext/accounts/report/gross_profit/gross_profit.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.json b/erpnext/accounts/report/gross_profit/gross_profit.json index 5fff3fdba7..76c560ad24 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.json +++ b/erpnext/accounts/report/gross_profit/gross_profit.json @@ -9,7 +9,7 @@ "filters": [], "idx": 3, "is_standard": "Yes", - "modified": "2021-08-19 18:57:07.468202", + "modified": "2021-11-13 19:14:23.730198", "modified_by": "Administrator", "module": "Accounts", "name": "Gross Profit", From 582a7ae9641b7693899a63809598bb2d9abafd46 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 16 Nov 2021 02:33:51 +0530 Subject: [PATCH 083/185] fix: Add totals row when Grouped By Invoice --- .../report/gross_profit/gross_profit.py | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 9d5a24227c..5716350739 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -245,19 +245,28 @@ class GrossProfitGenerator(object): self.add_to_totals(new_row) else: for i, row in enumerate(self.grouped[key]): - if row.parent in self.returned_invoices \ - and row.item_code in self.returned_invoices[row.parent]: - returned_item_rows = self.returned_invoices[row.parent][row.item_code] - for returned_item_row in returned_item_rows: - row.qty += flt(returned_item_row.qty) - row.base_amount += flt(returned_item_row.base_amount, self.currency_precision) - row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision) - if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row): - row = self.set_average_rate(row) - self.grouped_data.append(row) - self.add_to_totals(row) + if row.indent == 1.0: + if row.parent in self.returned_invoices \ + and row.item_code in self.returned_invoices[row.parent]: + returned_item_rows = self.returned_invoices[row.parent][row.item_code] + for returned_item_row in returned_item_rows: + row.qty += flt(returned_item_row.qty) + row.base_amount += flt(returned_item_row.base_amount, self.currency_precision) + row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision) + if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row): + row = self.set_average_rate(row) + self.grouped_data.append(row) + self.add_to_totals(row) + self.set_average_gross_profit(self.totals) - self.grouped_data.append(self.totals) + + if self.filters.get("group_by") == "Invoice": + self.totals.indent = 0.0 + self.totals.parent_invoice = "" + self.totals.parent = "Totals" + self.si_list.append(self.totals) + else: + self.grouped_data.append(self.totals) def is_not_invoice_row(self, row): return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice" From c7ffe3a7b423d08a16ef442abd1594983a3e4887 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 16 Nov 2021 02:36:58 +0530 Subject: [PATCH 084/185] fix: Remove unnecessary condition --- erpnext/accounts/report/gross_profit/gross_profit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 5716350739..c7d794ea1e 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -253,7 +253,7 @@ class GrossProfitGenerator(object): row.qty += flt(returned_item_row.qty) row.base_amount += flt(returned_item_row.base_amount, self.currency_precision) row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision) - if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row): + if (flt(row.qty) or row.base_amount): row = self.set_average_rate(row) self.grouped_data.append(row) self.add_to_totals(row) From 508a2301ee041e806a26bc3a09b01ac115ed8204 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 16 Nov 2021 03:22:04 +0530 Subject: [PATCH 085/185] fix: Display totals row in bold --- erpnext/accounts/report/gross_profit/gross_profit.js | 2 +- erpnext/accounts/report/gross_profit/gross_profit.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js index 856b97d164..685f2d6176 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.js +++ b/erpnext/accounts/report/gross_profit/gross_profit.js @@ -44,7 +44,7 @@ frappe.query_reports["Gross Profit"] = { "formatter": function(value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (data && data.indent == 0.0) { + if (data && (data.indent == 0.0 || row[1].content == "Total")) { value = $(`${value}`); var $value = $(value).css("font-weight", "bold"); value = $value.wrap("

").parent().html(); diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index c7d794ea1e..7e4a1dfa0f 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -77,7 +77,8 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_ row.append(filters.currency) if idx == len(gross_profit_data.grouped_data)-1: - row[0] = frappe.bold("Total") + row[0] = "Total" + data.append(row) def get_columns(group_wise_columns, filters): From 1affa12b74443a3c7078f767503db2cdc6ee0826 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 16 Nov 2021 03:36:47 +0530 Subject: [PATCH 086/185] fix: Replace 'Totals' with 'Total' --- erpnext/accounts/report/gross_profit/gross_profit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 7e4a1dfa0f..a6fb6f505b 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -264,7 +264,7 @@ class GrossProfitGenerator(object): if self.filters.get("group_by") == "Invoice": self.totals.indent = 0.0 self.totals.parent_invoice = "" - self.totals.parent = "Totals" + self.totals.parent = "Total" self.si_list.append(self.totals) else: self.grouped_data.append(self.totals) From 8c85012a70d19468b1977ef6e0708af3809d2ec5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 11 Nov 2021 14:46:19 +0530 Subject: [PATCH 087/185] fix: can not cancel stock reconciliation with sr no --- erpnext/stock/doctype/serial_no/serial_no.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index d9d1957c0b..38291d19ec 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -342,7 +342,7 @@ def check_serial_no_validity_on_cancel(serial_no, sle): is_stock_reco = sle.voucher_type == "Stock Reconciliation" msg = None - if sr and (actual_qty < 0 or is_stock_reco) and sr.warehouse != sle.warehouse: + if sr and (actual_qty < 0 or is_stock_reco) and (sr.warehouse and sr.warehouse != sle.warehouse): # receipt(inward) is being cancelled msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the warehouse {3}").format( sle.voucher_type, doc_link, sr_link, frappe.bold(sle.warehouse)) From ed99aca36f642bdb708a3aaf01a427cf60bab43f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 15 Nov 2021 19:42:31 +0530 Subject: [PATCH 088/185] test: basic test for serialize reco cancel --- .../test_stock_reconciliation.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index de89b2b1c4..48e339ae56 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -399,6 +399,34 @@ class TestStockReconciliation(ERPNextTestCase): , do_not_submit=True) self.assertRaises(frappe.ValidationError, sr.submit) + def test_serial_no_cancellation(self): + + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + item = create_item("Stock-Reco-Serial-Item-9", is_stock_item=1) + if not item.has_serial_no: + item.has_serial_no = 1 + item.serial_no_series = "SRS9.####" + item.save() + + item_code = item.name + warehouse = "_Test Warehouse - _TC" + + se1 = make_stock_entry(item_code=item_code, target=warehouse, qty=10, basic_rate=700) + + serial_nos = get_serial_nos(se1.items[0].serial_no) + # reduce 1 item + serial_nos.pop() + new_serial_nos = "\n".join(serial_nos) + + sr = create_stock_reconciliation(item_code=item.name, warehouse=warehouse, serial_no=new_serial_nos, qty=9) + sr.cancel() + + active_sr_no = frappe.get_all("Serial No", + filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"}) + + self.assertEqual(len(active_sr_no), 10) + + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) if not batch_item_doc.has_batch_no: From d82910b08ab0c42b90cf6cf837514b54b6d5bd14 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 15 Nov 2021 19:31:27 +0530 Subject: [PATCH 089/185] fix: Pricing Rule not created against the Promotional Scheme --- .../promotional_scheme/promotional_scheme.py | 113 +++++++++++++----- .../test_promotional_scheme.py | 59 ++++++++- .../promotional_scheme_price_discount.json | 4 +- 3 files changed, 141 insertions(+), 35 deletions(-) diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index e3fac072b4..5fbe93ee68 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -20,6 +20,9 @@ price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discoun product_discount_fields = ['free_item', 'free_qty', 'free_item_uom', 'free_item_rate', 'same_item', 'is_recursive', 'apply_multiple_pricing_rules'] +class TransactionExists(frappe.ValidationError): + pass + class PromotionalScheme(Document): def validate(self): if not self.selling and not self.buying: @@ -28,6 +31,40 @@ class PromotionalScheme(Document): or self.product_discount_slabs): frappe.throw(_("Price or product discount slabs are required")) + self.validate_applicable_for() + self.validate_pricing_rules() + + def validate_applicable_for(self): + if self.applicable_for: + applicable_for = frappe.scrub(self.applicable_for) + + if not self.get(applicable_for): + msg = (f'The field {frappe.bold(self.applicable_for)} is required') + frappe.throw(_(msg)) + + def validate_pricing_rules(self): + if self.is_new(): + return + + transaction_exists = False + docnames = [] + + # If user has changed applicable for + if self._doc_before_save.applicable_for == self.applicable_for: + return + + docnames = frappe.get_all('Pricing Rule', + filters= {'promotional_scheme': self.name}) + + for docname in docnames: + if frappe.db.exists('Pricing Rule Detail', + {'pricing_rule': docname.name, 'docstatus': ('<', 2)}): + raise_for_transaction_exists(self.name) + + if docnames and not transaction_exists: + for docname in docnames: + frappe.delete_doc('Pricing Rule', docname.name) + def on_update(self): pricing_rules = frappe.get_all( 'Pricing Rule', @@ -67,6 +104,13 @@ class PromotionalScheme(Document): {'promotional_scheme': self.name}): frappe.delete_doc('Pricing Rule', rule.name) +def raise_for_transaction_exists(name): + msg = (f"""You can't change the {frappe.bold(_('Applicable For'))} + because transactions are present against the Promotional Scheme {frappe.bold(name)}. """) + msg += 'Kindly disable this Promotional Scheme and create new for new Applicable For.' + + frappe.throw(_(msg), TransactionExists) + def get_pricing_rules(doc, rules=None): if rules is None: rules = {} @@ -84,45 +128,59 @@ def _get_pricing_rules(doc, child_doc, discount_fields, rules=None): new_doc = [] args = get_args_for_pricing_rule(doc) applicable_for = frappe.scrub(doc.get('applicable_for')) + for idx, d in enumerate(doc.get(child_doc)): if d.name in rules: - for applicable_for_value in args.get(applicable_for): - temp_args = args.copy() - docname = frappe.get_all( - 'Pricing Rule', - fields = ["promotional_scheme_id", "name", applicable_for], - filters = { - 'promotional_scheme_id': d.name, - applicable_for: applicable_for_value - } - ) - - if docname: - pr = frappe.get_doc('Pricing Rule', docname[0].get('name')) - temp_args[applicable_for] = applicable_for_value - pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d) - else: - pr = frappe.new_doc("Pricing Rule") - pr.title = doc.name - temp_args[applicable_for] = applicable_for_value - pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d) - + if not args.get(applicable_for): + docname = get_pricing_rule_docname(d) + pr = prepare_pricing_rule(args, doc, child_doc, discount_fields, d, docname) new_doc.append(pr) + else: + for applicable_for_value in args.get(applicable_for): + docname = get_pricing_rule_docname(d, applicable_for, applicable_for_value) + pr = prepare_pricing_rule(args, doc, child_doc, discount_fields, + d, docname, applicable_for, applicable_for_value) + new_doc.append(pr) - else: + elif args.get(applicable_for): applicable_for_values = args.get(applicable_for) or [] for applicable_for_value in applicable_for_values: - pr = frappe.new_doc("Pricing Rule") - pr.title = doc.name - temp_args = args.copy() - temp_args[applicable_for] = applicable_for_value - pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d) + pr = prepare_pricing_rule(args, doc, child_doc, discount_fields, + d, applicable_for=applicable_for, value= applicable_for_value) + new_doc.append(pr) + else: + pr = prepare_pricing_rule(args, doc, child_doc, discount_fields, d) + new_doc.append(pr) return new_doc +def get_pricing_rule_docname(row: dict, applicable_for: str = None, applicable_for_value: str = None) -> str: + fields = ['promotional_scheme_id', 'name'] + filters = { + 'promotional_scheme_id': row.name + } + if applicable_for: + fields.append(applicable_for) + filters[applicable_for] = applicable_for_value + docname = frappe.get_all('Pricing Rule', fields = fields, filters = filters) + return docname[0].name if docname else '' + +def prepare_pricing_rule(args, doc, child_doc, discount_fields, d, docname=None, applicable_for=None, value=None): + if docname: + pr = frappe.get_doc("Pricing Rule", docname) + else: + pr = frappe.new_doc("Pricing Rule") + + pr.title = doc.name + temp_args = args.copy() + + if value: + temp_args[applicable_for] = value + + return set_args(temp_args, pr, doc, child_doc, discount_fields, d) def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields): pr.update(args) @@ -145,6 +203,7 @@ def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields): apply_on: d.get(apply_on), 'uom': d.uom }) + return pr def get_args_for_pricing_rule(doc): diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py index e1852ae2b2..49192a45f8 100644 --- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py @@ -5,10 +5,17 @@ import unittest import frappe +from erpnext.accounts.doctype.promotional_scheme.promotional_scheme import TransactionExists +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + class TestPromotionalScheme(unittest.TestCase): + def setUp(self): + if frappe.db.exists('Promotional Scheme', '_Test Scheme'): + frappe.delete_doc('Promotional Scheme', '_Test Scheme') + def test_promotional_scheme(self): - ps = make_promotional_scheme() + ps = make_promotional_scheme(applicable_for='Customer', customer='_Test Customer') price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name", "creation"], filters = {'promotional_scheme': ps.name}) self.assertTrue(len(price_rules),1) @@ -39,22 +46,62 @@ class TestPromotionalScheme(unittest.TestCase): filters = {'promotional_scheme': ps.name}) self.assertEqual(price_rules, []) -def make_promotional_scheme(): + def test_promotional_scheme_without_applicable_for(self): + ps = make_promotional_scheme() + price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name}) + + self.assertTrue(len(price_rules), 1) + frappe.delete_doc('Promotional Scheme', ps.name) + + price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name}) + self.assertEqual(price_rules, []) + + def test_change_applicable_for_in_promotional_scheme(self): + ps = make_promotional_scheme() + price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name}) + self.assertTrue(len(price_rules), 1) + + so = make_sales_order(qty=5, currency='USD', do_not_save=True) + so.set_missing_values() + so.save() + self.assertEqual(price_rules[0].name, so.pricing_rules[0].pricing_rule) + + ps.applicable_for = 'Customer' + ps.append('customer', { + 'customer': '_Test Customer' + }) + + self.assertRaises(TransactionExists, ps.save) + + frappe.delete_doc('Sales Order', so.name) + frappe.delete_doc('Promotional Scheme', ps.name) + price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name}) + self.assertEqual(price_rules, []) + +def make_promotional_scheme(**args): + args = frappe._dict(args) + ps = frappe.new_doc('Promotional Scheme') ps.name = '_Test Scheme' ps.append('items',{ 'item_code': '_Test Item' }) + ps.selling = 1 ps.append('price_discount_slabs',{ 'min_qty': 4, + 'validate_applied_rule': 0, 'discount_percentage': 20, 'rule_description': 'Test' }) - ps.applicable_for = 'Customer' - ps.append('customer',{ - 'customer': "_Test Customer" - }) + + ps.company = '_Test Company' + if args.applicable_for: + ps.applicable_for = args.applicable_for + ps.append(frappe.scrub(args.applicable_for), { + frappe.scrub(args.applicable_for): args.get(frappe.scrub(args.applicable_for)) + }) + ps.save() return ps diff --git a/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json index a70d5c9d43..aa3696d216 100644 --- a/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json +++ b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json @@ -136,7 +136,7 @@ "label": "Threshold for Suggestion" }, { - "default": "1", + "default": "0", "fieldname": "validate_applied_rule", "fieldtype": "Check", "label": "Validate Applied Rule" @@ -169,7 +169,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-08-19 15:49:29.598727", + "modified": "2021-11-16 00:25:33.843996", "modified_by": "Administrator", "module": "Accounts", "name": "Promotional Scheme Price Discount", From e9c75d6eead9dcdc905d49afec2ed308067d14d5 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 16 Nov 2021 11:55:34 +0530 Subject: [PATCH 090/185] test: Purchase Invoice Received Qty --- .../purchase_invoice/test_purchase_invoice.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 6e81c6d8b8..c950626ddf 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -21,6 +21,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import ( make_purchase_receipt, ) from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction +from erpnext.controllers.buying_controller import QtyMismatchError test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"] test_ignore = ["Serial No"] @@ -35,6 +36,27 @@ class TestPurchaseInvoice(unittest.TestCase): def tearDownClass(self): unlink_payment_on_cancel_of_invoice(0) + def test_purchase_invoice_received_qty(self): + """ + 1. Test if received qty is validated against accepted + rejected + 2. Test if received qty is auto set on save + """ + pi = make_purchase_invoice( + qty=1, + rejected_qty=1, + received_qty=3, + item_code="_Test Item Home Desktop 200", + rejected_warehouse = "_Test Rejected Warehouse - _TC", + update_stock=True, do_not_save=True) + self.assertRaises(QtyMismatchError, pi.save) + + pi.items[0].received_qty = 0 + pi.save() + self.assertEqual(pi.items[0].received_qty, 2) + + # teardown + pi.delete() + def test_gl_entries_without_perpetual_inventory(self): frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC") pi = frappe.copy_doc(test_records[0]) From f786a596f80f94465711584e29f20f8f7032a3aa Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Tue, 16 Nov 2021 12:01:26 +0530 Subject: [PATCH 091/185] chore: add docker pull count to readme (#28405) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1105a97005..96093531d3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml) [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext) [![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext) +[![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext-worker.svg)](https://hub.docker.com/r/frappe/erpnext-worker) [https://erpnext.com](https://erpnext.com) From 952fc87c99f8b3a084d132cde52124f975ef6f95 Mon Sep 17 00:00:00 2001 From: Ahmed Shareef <46922290+penieldev@users.noreply.github.com> Date: Tue, 16 Nov 2021 13:00:13 +0400 Subject: [PATCH 092/185] refactor: fix help section background in dark mode (#28406) --- .../accounting_dimension_filter/accounting_dimension_filter.js | 2 +- erpnext/accounts/doctype/loyalty_program/loyalty_program.js | 2 +- erpnext/accounts/doctype/pricing_rule/pricing_rule.js | 2 +- .../manufacturing/doctype/production_plan/production_plan.js | 2 +- .../stock/doctype/landed_cost_voucher/landed_cost_voucher.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js index 9dd882a311..750e129ba7 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js @@ -8,7 +8,7 @@ frappe.ui.form.on('Accounting Dimension Filter', { } let help_content = - ` + `

diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.js b/erpnext/accounts/doctype/loyalty_program/loyalty_program.js index f90f86728d..6951b2a2b3 100644 --- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.js +++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.js @@ -6,7 +6,7 @@ frappe.provide("erpnext.accounts.dimensions"); frappe.ui.form.on('Loyalty Program', { setup: function(frm) { var help_content = - ` + `

diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js index d79ad5f528..826758245a 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js @@ -38,7 +38,7 @@ frappe.ui.form.on('Pricing Rule', { refresh: function(frm) { var help_content = - ` + `

diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 2bd02dabd8..b171086b7c 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -105,7 +105,7 @@ frappe.ui.form.on('Production Plan', { } frm.trigger("material_requirement"); - const projected_qty_formula = ` + const projected_qty_formula = `

diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index 433f78adc9..9c1a809f4d 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -35,7 +35,7 @@ erpnext.stock.LandedCostVoucher = class LandedCostVoucher extends erpnext.stock. refresh() { var help_content = `

- +

From 293f6cbafd5552b501d8a48cd2ccc149dd5f477c Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 16 Nov 2021 14:22:26 +0530 Subject: [PATCH 093/185] fix: Sider, Linter and Server Side Test - Fix Server side PR test - linter: re-arrange imports - sider: avoid single line multi statement - Code cleanup: Improve code readability and avoid horizontal scroll in test_purchase_receipt - Removed unused variables in test_purchase_receipt --- .../purchase_invoice/test_purchase_invoice.py | 2 +- erpnext/controllers/buying_controller.py | 6 +- .../purchase_receipt/test_purchase_receipt.py | 538 ++++++++++++++---- 3 files changed, 427 insertions(+), 119 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index c950626ddf..f68122da87 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -13,6 +13,7 @@ from erpnext.accounts.doctype.account.test_account import create_account, get_in from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.buying.doctype.supplier.test_supplier import create_supplier from erpnext.controllers.accounts_controller import get_payment_terms +from erpnext.controllers.buying_controller import QtyMismatchError from erpnext.exceptions import InvalidCurrency from erpnext.projects.doctype.project.test_project import make_project from erpnext.stock.doctype.item.test_item import create_item @@ -21,7 +22,6 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import ( make_purchase_receipt, ) from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction -from erpnext.controllers.buying_controller import QtyMismatchError test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"] test_ignore = ["Serial No"] diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index d0defcc9fa..a3d2502268 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -3,7 +3,7 @@ import frappe -from frappe import _, msgprint, ValidationError +from frappe import ValidationError, _, msgprint from frappe.contacts.doctype.address.address import get_address_display from frappe.utils import cint, cstr, flt, getdate @@ -16,7 +16,9 @@ from erpnext.controllers.subcontracting import Subcontracting from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.utils import get_incoming_rate -class QtyMismatchError(ValidationError): pass + +class QtyMismatchError(ValidationError): + pass class BuyingController(StockController, Subcontracting): diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 8968b18536..2909a2d2e7 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -10,12 +10,12 @@ from frappe.utils import add_days, cint, cstr, flt, today import erpnext from erpnext.accounts.doctype.account.test_account import get_inventory_account +from erpnext.controllers.buying_controller import QtyMismatchError from erpnext.stock.doctype.item.test_item import create_item, make_item from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction -from erpnext.controllers.buying_controller import QtyMismatchError from erpnext.tests.utils import ERPNextTestCase @@ -28,8 +28,13 @@ class TestPurchaseReceipt(ERPNextTestCase): 1. Test if received qty is validated against accepted + rejected 2. Test if received qty is auto set on save """ - pr = make_purchase_receipt(qty=1, rejected_qty=1, received_qty=3, - item_code="_Test Item Home Desktop 200", do_not_save=True) + pr = make_purchase_receipt( + qty=1, + rejected_qty=1, + received_qty=3, + item_code="_Test Item Home Desktop 200", + do_not_save=True + ) self.assertRaises(QtyMismatchError, pr.save) pr.items[0].received_qty = 0 @@ -43,16 +48,29 @@ class TestPurchaseReceipt(ERPNextTestCase): pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200") - sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", - "voucher_no": pr.name}, ['actual_qty']) + sl_entry = frappe.db.get_all( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name + }, + ['actual_qty'] + ) self.assertEqual(len(sl_entry), 1) self.assertEqual(sl_entry[0].actual_qty, 0.5) pr.cancel() - sl_entry_cancelled = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", - "voucher_no": pr.name}, ['actual_qty'], order_by='creation') + sl_entry_cancelled = frappe.db.get_all( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name + }, + ['actual_qty'], + order_by='creation' + ) self.assertEqual(len(sl_entry_cancelled), 2) self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5) @@ -78,8 +96,15 @@ class TestPurchaseReceipt(ERPNextTestCase): }] }).insert() - template = frappe.db.get_value('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice') - old_template_in_supplier = frappe.db.get_value("Supplier", "_Test Supplier", "payment_terms") + template = frappe.db.get_value( + "Payment Terms Template", + "_Test Payment Terms Template For Purchase Invoice" + ) + old_template_in_supplier = frappe.db.get_value( + "Supplier", + "_Test Supplier", + "payment_terms" + ) frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", template) pr = make_purchase_receipt(do_not_save=True) @@ -105,30 +130,59 @@ class TestPurchaseReceipt(ERPNextTestCase): # teardown pi.delete() # draft PI pr.cancel() - frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", old_template_in_supplier) - frappe.get_doc('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice').delete() + frappe.db.set_value( + "Supplier", + "_Test Supplier", + "payment_terms", + old_template_in_supplier + ) + frappe.get_doc( + "Payment Terms Template", + "_Test Payment Terms Template For Purchase Invoice" + ).delete() def test_purchase_receipt_no_gl_entry(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - - existing_bin_qty, existing_bin_stock_value = frappe.db.get_value("Bin", {"item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC"}, ["actual_qty", "stock_value"]) + existing_bin_qty, existing_bin_stock_value = frappe.db.get_value( + "Bin", + { + "item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC" + }, + ["actual_qty", "stock_value"] + ) if existing_bin_qty < 0: - make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=abs(existing_bin_qty)) + make_stock_entry( + item_code="_Test Item", + target="_Test Warehouse - _TC", + qty=abs(existing_bin_qty) + ) pr = make_purchase_receipt() - stock_value_difference = frappe.db.get_value("Stock Ledger Entry", - {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, - "item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, "stock_value_difference") + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC" + }, + "stock_value_difference" + ) self.assertEqual(stock_value_difference, 250) - current_bin_stock_value = frappe.db.get_value("Bin", {"item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC"}, "stock_value") + current_bin_stock_value = frappe.db.get_value( + "Bin", + { + "item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC" + }, + "stock_value" + ) self.assertEqual(current_bin_stock_value, existing_bin_stock_value + 250) self.assertFalse(get_gl_entries("Purchase Receipt", pr.name)) @@ -150,13 +204,17 @@ class TestPurchaseReceipt(ERPNextTestCase): pr = make_purchase_receipt(item_code=item.name, qty=5, rate=500) - self.assertTrue(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name})) + self.assertTrue( + frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name}) + ) pr.load_from_db() batch_no = pr.items[0].batch_no pr.cancel() - self.assertFalse(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name})) + self.assertFalse( + frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name}) + ) self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no})) def test_duplicate_serial_nos(self): @@ -175,42 +233,78 @@ class TestPurchaseReceipt(ERPNextTestCase): pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500) pr.load_from_db() - serial_nos = frappe.db.get_value('Stock Ledger Entry', - {'voucher_type': 'Purchase Receipt', 'voucher_no': pr.name, 'item_code': item.name}, 'serial_no') + serial_nos = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "item_code": item.name + }, + "serial_no" + ) serial_nos = get_serial_nos(serial_nos) self.assertEquals(get_serial_nos(pr.items[0].serial_no), serial_nos) # Then tried to receive same serial nos in difference company - pr_different_company = make_purchase_receipt(item_code=item.name, qty=2, rate=500, - serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True, - warehouse = 'Stores - _TC1') + pr_different_company = make_purchase_receipt( + item_code=item.name, + qty=2, + rate=500, + serial_no='\n'.join(serial_nos), + company='_Test Company 1', + do_not_submit=True, + warehouse = 'Stores - _TC1' + ) self.assertRaises(SerialNoDuplicateError, pr_different_company.submit) # Then made delivery note to remove the serial nos from stock - dn = create_delivery_note(item_code=item.name, qty=2, rate = 1500, serial_no='\n'.join(serial_nos)) + dn = create_delivery_note( + item_code=item.name, + qty=2, + rate=1500, + serial_no='\n'.join(serial_nos) + ) dn.load_from_db() self.assertEquals(get_serial_nos(dn.items[0].serial_no), serial_nos) posting_date = add_days(today(), -3) # Try to receive same serial nos again in the same company with backdated. - pr1 = make_purchase_receipt(item_code=item.name, qty=2, rate=500, - posting_date=posting_date, serial_no='\n'.join(serial_nos), do_not_submit=True) + pr1 = make_purchase_receipt( + item_code=item.name, + qty=2, + rate=500, + posting_date=posting_date, + serial_no='\n'.join(serial_nos), + do_not_submit=True + ) self.assertRaises(SerialNoExistsInFutureTransaction, pr1.submit) # Try to receive same serial nos with different company with backdated. - pr2 = make_purchase_receipt(item_code=item.name, qty=2, rate=500, - posting_date=posting_date, serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True, - warehouse = 'Stores - _TC1') + pr2 = make_purchase_receipt( + item_code=item.name, + qty=2, + rate=500, + posting_date=posting_date, + serial_no='\n'.join(serial_nos), + company="_Test Company 1", + do_not_submit=True, + warehouse="Stores - _TC1" + ) self.assertRaises(SerialNoExistsInFutureTransaction, pr2.submit) # Receive the same serial nos after the delivery note posting date and time - make_purchase_receipt(item_code=item.name, qty=2, rate=500, serial_no='\n'.join(serial_nos)) + make_purchase_receipt( + item_code=item.name, + qty=2, + rate=500, + serial_no='\n'.join(serial_nos) + ) # Raise the error for backdated deliver note entry cancel self.assertRaises(SerialNoExistsInFutureTransaction, dn.cancel) @@ -253,11 +347,23 @@ class TestPurchaseReceipt(ERPNextTestCase): def test_subcontracting(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM") - make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100) - make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC", - qty=100, basic_rate=100) - pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted="Yes") + frappe.db.set_value( + "Buying Settings", None, + "backflush_raw_materials_of_subcontract_based_on", "BOM" + ) + + make_stock_entry( + item_code="_Test Item", qty=100, + target="_Test Warehouse 1 - _TC", basic_rate=100 + ) + make_stock_entry( + item_code="_Test Item Home Desktop 100", qty=100, + target="_Test Warehouse 1 - _TC", basic_rate=100 + ) + pr = make_purchase_receipt( + item_code="_Test FG Item", qty=10, + rate=500, is_subcontracted="Yes" + ) self.assertEqual(len(pr.get("supplied_items")), 2) rm_supp_cost = sum(d.amount for d in pr.get("supplied_items")) @@ -267,17 +373,33 @@ class TestPurchaseReceipt(ERPNextTestCase): def test_subcontracting_gle_fg_item_rate_zero(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM") + frappe.db.set_value( + "Buying Settings", None, + "backflush_raw_materials_of_subcontract_based_on", "BOM" + ) - se1 = make_stock_entry(item_code="_Test Item", target="Work In Progress - TCP1", - qty=100, basic_rate=100, company="_Test Company with perpetual inventory") + se1 = make_stock_entry( + item_code="_Test Item", + target="Work In Progress - TCP1", + qty=100, basic_rate=100, + company="_Test Company with perpetual inventory" + ) - se2 = make_stock_entry(item_code="_Test Item Home Desktop 100", target="Work In Progress - TCP1", - qty=100, basic_rate=100, company="_Test Company with perpetual inventory") + se2 = make_stock_entry( + item_code="_Test Item Home Desktop 100", + target="Work In Progress - TCP1", + qty=100, basic_rate=100, + company="_Test Company with perpetual inventory" + ) - pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=0, is_subcontracted="Yes", - company="_Test Company with perpetual inventory", warehouse='Stores - TCP1', - supplier_warehouse='Work In Progress - TCP1') + pr = make_purchase_receipt( + item_code="_Test FG Item", + qty=10, rate=0, + is_subcontracted="Yes", + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + supplier_warehouse="Work In Progress - TCP1" + ) gl_entries = get_gl_entries("Purchase Receipt", pr.name) @@ -311,13 +433,23 @@ class TestPurchaseReceipt(ERPNextTestCase): po = create_purchase_order(item_code=item_code, qty=1, include_exploded_items=0, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") - #stock raw materials in a warehouse before transfer - se1 = make_stock_entry(target="_Test Warehouse - _TC", - item_code = "Test Extra Item 1", qty=10, basic_rate=100) - se2 = make_stock_entry(target="_Test Warehouse - _TC", - item_code = "_Test FG Item", qty=1, basic_rate=100) - se3 = make_stock_entry(target="_Test Warehouse - _TC", - item_code = "Test Extra Item 2", qty=1, basic_rate=100) + # stock raw materials in a warehouse before transfer + make_stock_entry( + target="_Test Warehouse - _TC", + item_code = "Test Extra Item 1", + qty=10, basic_rate=100 + ) + make_stock_entry( + target="_Test Warehouse - _TC", + item_code = "_Test FG Item", + qty=1, basic_rate=100 + ) + make_stock_entry( + target="_Test Warehouse - _TC", + item_code = "Test Extra Item 2", + qty=1, basic_rate=100 + ) + rm_items = [ { "item_code": item_code, @@ -351,11 +483,17 @@ class TestPurchaseReceipt(ERPNextTestCase): def test_serial_no_supplier(self): pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) - self.assertEqual(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "supplier"), - pr.supplier) + pr_row_1_serial_no = pr.get("items")[0].serial_no + + self.assertEqual( + frappe.db.get_value("Serial No", pr_row_1_serial_no, "supplier"), + pr.supplier + ) pr.cancel() - self.assertFalse(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "warehouse")) + self.assertFalse( + frappe.db.get_value("Serial No", pr_row_1_serial_no, "warehouse") + ) def test_rejected_serial_no(self): pr = frappe.copy_doc(test_records[0]) @@ -382,18 +520,33 @@ class TestPurchaseReceipt(ERPNextTestCase): pr.cancel() def test_purchase_return_partial(self): - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", - warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", + supplier_warehouse = "Work in Progress - TCP1" + ) - return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", - warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", - is_return=1, return_against=pr.name, qty=-2, do_not_submit=1) + return_pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", + supplier_warehouse = "Work in Progress - TCP1", + is_return=1, + return_against=pr.name, + qty=-2, + do_not_submit=1 + ) return_pr.items[0].purchase_receipt_item = pr.items[0].name return_pr.submit() # check sle - outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", - "voucher_no": return_pr.name}, "outgoing_rate") + outgoing_rate = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": return_pr.name + }, + "outgoing_rate" + ) self.assertEqual(outgoing_rate, 50) @@ -457,11 +610,21 @@ class TestPurchaseReceipt(ERPNextTestCase): pr.cancel() def test_purchase_return_full(self): - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", - supplier_warehouse = "Work in Progress - TCP1") + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", + supplier_warehouse = "Work in Progress - TCP1" + ) - return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", - supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-5, do_not_submit=1) + return_pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", + supplier_warehouse = "Work in Progress - TCP1", + is_return=1, + return_against=pr.name, + qty=-5, + do_not_submit=1 + ) return_pr.items[0].purchase_receipt_item = pr.items[0].name return_pr.submit() @@ -483,15 +646,41 @@ class TestPurchaseReceipt(ERPNextTestCase): rejected_warehouse="_Test Rejected Warehouse - TCP1" if not frappe.db.exists("Warehouse", rejected_warehouse): - get_warehouse(company = "_Test Company with perpetual inventory", - abbr = " - TCP1", warehouse_name = "_Test Rejected Warehouse").name + get_warehouse( + company = "_Test Company with perpetual inventory", + abbr = " - TCP1", + warehouse_name = "_Test Rejected Warehouse" + ).name - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", received_qty=4, qty=2, rejected_warehouse=rejected_warehouse) + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", + supplier_warehouse = "Work in Progress - TCP1", + qty=2, + rejected_qty=2, + rejected_warehouse=rejected_warehouse + ) - return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, received_qty = -4, qty=-2, rejected_warehouse=rejected_warehouse) + return_pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", + supplier_warehouse = "Work in Progress - TCP1", + is_return=1, + return_against=pr.name, + qty=-2, + rejected_qty = -2, + rejected_warehouse=rejected_warehouse + ) - actual_qty = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", - "voucher_no": return_pr.name, 'warehouse': return_pr.items[0].rejected_warehouse}, "actual_qty") + actual_qty = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": return_pr.name, + "warehouse": return_pr.items[0].rejected_warehouse + }, + "actual_qty" + ) self.assertEqual(actual_qty, -2) @@ -516,8 +705,13 @@ class TestPurchaseReceipt(ERPNextTestCase): "purchase_document_no": pr.name }) - return_pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=-1, - is_return=1, return_against=pr.name, serial_no=serial_no) + return_pr = make_purchase_receipt( + item_code="_Test Serialized Item With Series", + qty=-1, + is_return=1, + return_against=pr.name, + serial_no=serial_no + ) _check_serial_no_values(serial_no, { "warehouse": "", @@ -539,9 +733,21 @@ class TestPurchaseReceipt(ERPNextTestCase): }) row.db_update() - pr = make_purchase_receipt(item_code=item_code, qty=1, uom="Box", conversion_factor=1.0) - return_pr = make_purchase_receipt(item_code=item_code, qty=-10, uom="Unit", - stock_uom="Box", conversion_factor=0.1, is_return=1, return_against=pr.name) + pr = make_purchase_receipt( + item_code=item_code, + qty=1, + uom="Box", + conversion_factor=1.0 + ) + return_pr = make_purchase_receipt( + item_code=item_code, + qty=-10, + uom="Unit", + stock_uom="Box", + conversion_factor=0.1, + is_return=1, + return_against=pr.name + ) self.assertEqual(abs(return_pr.items[0].stock_qty), 1.0) @@ -557,13 +763,19 @@ class TestPurchaseReceipt(ERPNextTestCase): pr.submit() update_purchase_receipt_status(pr.name, "Closed") - self.assertEqual(frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed") + self.assertEqual( + frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed" + ) pr.reload() pr.cancel() def test_pr_billing_status(self): - # PO -> PR1 -> PI and PO -> PI and PO -> PR2 + """Flow: + 1. PO -> PR1 -> PI + 2. PO -> PI + 3. PO -> PR2. + """ from erpnext.buying.doctype.purchase_order.purchase_order import ( make_purchase_invoice as make_purchase_invoice_from_po, ) @@ -627,21 +839,39 @@ class TestPurchaseReceipt(ERPNextTestCase): pr_doc = make_purchase_receipt(item_code=item_code, qty=1, serial_no = serial_no) - self.assertEqual(serial_no, frappe.db.get_value("Serial No", - {"purchase_document_type": "Purchase Receipt", "purchase_document_no": pr_doc.name}, "name")) + self.assertEqual( + serial_no, + frappe.db.get_value( + "Serial No", + { + "purchase_document_type": "Purchase Receipt", + "purchase_document_no": pr_doc.name + }, + "name" + ) + ) pr_doc.cancel() - #check for the auto created serial nos + # check for the auto created serial nos item_code = "Test Auto Created Serial No" if not frappe.db.exists("Item", item_code): - item = make_item(item_code, dict(has_serial_no=1, serial_no_series="KLJL.###")) + make_item(item_code, dict(has_serial_no=1, serial_no_series="KLJL.###")) new_pr_doc = make_purchase_receipt(item_code=item_code, qty=1) serial_no = get_serial_nos(new_pr_doc.items[0].serial_no)[0] - self.assertEqual(serial_no, frappe.db.get_value("Serial No", - {"purchase_document_type": "Purchase Receipt", "purchase_document_no": new_pr_doc.name}, "name")) + self.assertEqual( + serial_no, + frappe.db.get_value( + "Serial No", + { + "purchase_document_type": "Purchase Receipt", + "purchase_document_no": new_pr_doc.name + }, + "name" + ) + ) new_pr_doc.cancel() @@ -717,8 +947,12 @@ class TestPurchaseReceipt(ERPNextTestCase): def test_purchase_receipt_cost_center(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center + cost_center = "_Test Cost Center for BS Account - TCP1" - create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory") + create_cost_center( + cost_center_name="_Test Cost Center for BS Account", + company="_Test Company with perpetual inventory" + ) if not frappe.db.exists('Location', 'Test Location'): frappe.get_doc({ @@ -726,10 +960,16 @@ class TestPurchaseReceipt(ERPNextTestCase): 'location_name': 'Test Location' }).insert() - pr = make_purchase_receipt(cost_center=cost_center, company="_Test Company with perpetual inventory", - warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") + pr = make_purchase_receipt( + cost_center=cost_center, + company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", + supplier_warehouse = "Work in Progress - TCP1" + ) - stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse) + stock_in_hand_account = get_inventory_account( + pr.company, pr.get("items")[0].warehouse + ) gl_entries = get_gl_entries("Purchase Receipt", pr.name) self.assertTrue(gl_entries) @@ -753,9 +993,16 @@ class TestPurchaseReceipt(ERPNextTestCase): 'doctype': 'Location', 'location_name': 'Test Location' }).insert() - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") - stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse) + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", + supplier_warehouse = "Work in Progress - TCP1" + ) + + stock_in_hand_account = get_inventory_account( + pr.company, pr.get("items")[0].warehouse + ) gl_entries = get_gl_entries("Purchase Receipt", pr.name) self.assertTrue(gl_entries) @@ -783,7 +1030,11 @@ class TestPurchaseReceipt(ERPNextTestCase): 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 = make_purchase_receipt( + qty=-1, + is_return=1, return_against=pr.name, + do_not_submit=True + ) pr1.items[0].purchase_order = po.name pr1.items[0].purchase_order_item = po.items[0].name pr1.items[0].purchase_receipt_item = pr.items[0].name @@ -816,7 +1067,11 @@ class TestPurchaseReceipt(ERPNextTestCase): pi1.save() pi1.submit() - pr2 = make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2, do_not_submit=True) + pr2 = make_purchase_receipt( + qty=-2, + is_return=1, return_against=pr1.name, + do_not_submit=True + ) pr2.items[0].purchase_receipt_item = pr1.items[0].name pr2.submit() @@ -858,14 +1113,22 @@ class TestPurchaseReceipt(ERPNextTestCase): pr1.cancel() def test_stock_transfer_from_purchase_receipt_with_valuation(self): - create_warehouse("_Test Warehouse for Valuation", company="_Test Company with perpetual inventory", - properties={"account": '_Test Account Stock In Hand - TCP1'}) + create_warehouse( + "_Test Warehouse for Valuation", + company="_Test Company with perpetual inventory", + properties={"account": '_Test Account Stock In Hand - TCP1'} + ) - pr1 = make_purchase_receipt(warehouse = '_Test Warehouse for Valuation - TCP1', - company="_Test Company with perpetual inventory") + pr1 = make_purchase_receipt( + warehouse = '_Test Warehouse for Valuation - TCP1', + company="_Test Company with perpetual inventory" + ) - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", - warehouse = "Stores - TCP1", do_not_save=1) + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", + do_not_save=1 + ) pr.items[0].from_warehouse = '_Test Warehouse for Valuation - TCP1' pr.supplier_warehouse = '' @@ -947,10 +1210,24 @@ class TestPurchaseReceipt(ERPNextTestCase): } rm_items = [ - {"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item", - "qty":300,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name}, - {"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item", - "qty":200,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name} + { + "item_code":item_code, + "rm_item_code":"Sub Contracted Raw Material 3", + "item_name":"_Test Item", + "qty":300, + "warehouse":"_Test Warehouse - _TC", + "stock_uom":"Nos", + "name": po.supplied_items[0].name + }, + { + "item_code":item_code, + "rm_item_code":"Sub Contracted Raw Material 3", + "item_name":"_Test Item", + "qty":200, + "warehouse":"_Test Warehouse - _TC", + "stock_uom":"Nos", + "name": po.supplied_items[0].name + } ] rm_item_string = json.dumps(rm_items) @@ -960,8 +1237,14 @@ class TestPurchaseReceipt(ERPNextTestCase): se.items[1].batch_no = ste2.items[0].batch_no se.submit() - supplied_qty = frappe.db.get_value("Purchase Order Item Supplied", - {"parent": po.name, "rm_item_code": "Sub Contracted Raw Material 3"}, "supplied_qty") + supplied_qty = frappe.db.get_value( + "Purchase Order Item Supplied", + { + "parent": po.name, + "rm_item_code": "Sub Contracted Raw Material 3" + }, + "supplied_qty" + ) self.assertEqual(supplied_qty, 500.00) @@ -1033,10 +1316,18 @@ class TestPurchaseReceipt(ERPNextTestCase): company = '_Test Company with perpetual inventory' service_item = '_Test Non Stock Item' - before_test_value = frappe.db.get_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items') - frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', 1) + before_test_value = frappe.db.get_value( + 'Company', company, 'enable_perpetual_inventory_for_non_stock_items' + ) + frappe.db.set_value( + 'Company', company, + 'enable_perpetual_inventory_for_non_stock_items', 1 + ) srbnb_account = 'Stock Received But Not Billed - TCP1' - frappe.db.set_value('Company', company, 'service_received_but_not_billed', srbnb_account) + frappe.db.set_value( + 'Company', company, + 'service_received_but_not_billed', srbnb_account + ) pr = make_purchase_receipt( company=company, item=service_item, @@ -1068,7 +1359,10 @@ class TestPurchaseReceipt(ERPNextTestCase): self.assertEqual(len(item_one_gl_entry), 1) self.assertEqual(len(item_two_gl_entry), 1) - frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', before_test_value) + frappe.db.set_value( + 'Company', company, + 'enable_perpetual_inventory_for_non_stock_items', before_test_value + ) def test_purchase_receipt_with_exchange_rate_difference(self): from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import ( @@ -1093,10 +1387,19 @@ class TestPurchaseReceipt(ERPNextTestCase): pr.submit() # Get exchnage gain and loss account - exchange_gain_loss_account = frappe.db.get_value('Company', pr.company, 'exchange_gain_loss_account') + exchange_gain_loss_account = frappe.db.get_value( + 'Company', pr.company, 'exchange_gain_loss_account' + ) # fetching the latest GL Entry with exchange gain and loss account account - amount = frappe.db.get_value('GL Entry', {'account': exchange_gain_loss_account, 'voucher_no': pr.name}, 'credit') + amount = frappe.db.get_value( + 'GL Entry', + { + 'account': exchange_gain_loss_account, + 'voucher_no': pr.name + }, + 'credit' + ) discrepancy_caused_by_exchange_rate_diff = abs(pi.items[0].base_net_amount - pr.items[0].base_net_amount) self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) @@ -1266,9 +1569,12 @@ def make_purchase_receipt(**args): if args.get_multiple_items: pr.items = [] - for item in get_items(warehouse= args.warehouse, cost_center = args.cost_center or frappe.get_cached_value('Company', pr.company, 'cost_center')): - pr.append("items", item) + company_cost_center = frappe.get_cached_value('Company', pr.company, 'cost_center') + cost_center = args.cost_center or company_cost_center + + for item in get_items(warehouse=args.warehouse, cost_center=cost_center): + pr.append("items", item) if args.get_taxes_and_charges: for tax in get_taxes(): From a5f8274d79bf2a03e6845bad8938b9aa7dc68fce Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 16 Nov 2021 15:13:19 +0530 Subject: [PATCH 094/185] fix: `test_job_card_partial_material_transfer` test - Use a specific BOM for JC tests - Utility to create said BOM - Sider: unused variable --- .../doctype/job_card/test_job_card.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 51df35beab..9b4fc8b8b7 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -15,8 +15,9 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry class TestJobCard(unittest.TestCase): - def setUp(self): + make_bom_for_jc_tests() + transfer_material_against, source_warehouse = None, None tests_that_skip_setup = ( @@ -243,7 +244,7 @@ class TestJobCard(unittest.TestCase): 1. Test if only current Job Card Items are pulled in a Stock Entry against a Job Card 2. Test impact of changing 'For Qty' in such a Stock Entry """ - bom = create_bom_with_multiple_operations() + create_bom_with_multiple_operations() work_order = make_wo_with_transfer_against_jc() job_card_name = frappe.db.get_value( @@ -319,4 +320,13 @@ def make_wo_with_transfer_against_jc(): work_order.required_items[1].operation = "_Test Operation 1" work_order.submit() - return work_order \ No newline at end of file + return work_order + +def make_bom_for_jc_tests(): + test_records = frappe.get_test_records('BOM') + bom = frappe.copy_doc(test_records[2]) + bom.set_rate_of_sub_assembly_item_based_on_bom = 0 + bom.rm_cost_as_per = "Valuation Rate" + bom.items[0].uom = "_Test UOM 1" + bom.items[0].conversion_factor = 5 + bom.insert() \ No newline at end of file From a1807a2f98984a6474bc6d2bbb43052cd38eed36 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Tue, 16 Nov 2021 17:46:26 +0530 Subject: [PATCH 095/185] fix: POS idx issue in taxes table while merging (#28389) --- .../pos_invoice_merge_log.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 843497019d..0720d9b2e9 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -110,9 +110,15 @@ class POSInvoiceMergeLog(Document): def merge_pos_invoice_into(self, invoice, data): items, payments, taxes = [], [], [] + loyalty_amount_sum, loyalty_points_sum = 0, 0 + rounding_adjustment, base_rounding_adjustment = 0, 0 rounded_total, base_rounded_total = 0, 0 + + loyalty_amount_sum, loyalty_points_sum, idx = 0, 0, 1 + + for doc in data: map_doc(doc, invoice, table_map={ "doctype": invoice.doctype }) @@ -146,6 +152,8 @@ class POSInvoiceMergeLog(Document): found = True if not found: tax.charge_type = 'Actual' + tax.idx = idx + idx += 1 tax.included_in_print_rate = 0 tax.tax_amount = tax.tax_amount_after_discount_amount tax.base_tax_amount = tax.base_tax_amount_after_discount_amount @@ -163,8 +171,8 @@ class POSInvoiceMergeLog(Document): payments.append(payment) rounding_adjustment += doc.rounding_adjustment rounded_total += doc.rounded_total - base_rounding_adjustment += doc.rounding_adjustment - base_rounded_total += doc.rounded_total + base_rounding_adjustment += doc.base_rounding_adjustment + base_rounded_total += doc.base_rounded_total if loyalty_points_sum: @@ -176,9 +184,9 @@ class POSInvoiceMergeLog(Document): invoice.set('payments', payments) invoice.set('taxes', taxes) invoice.set('rounding_adjustment',rounding_adjustment) - invoice.set('rounding_adjustment',base_rounding_adjustment) - invoice.set('base_rounded_total',base_rounded_total) + invoice.set('base_rounding_adjustment',base_rounding_adjustment) invoice.set('rounded_total',rounded_total) + invoice.set('base_rounded_total',base_rounded_total) invoice.additional_discount_percentage = 0 invoice.discount_amount = 0.0 invoice.taxes_and_charges = None From a8e2c02e146a197590547d70ce2a93eda04f3a8f Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Tue, 16 Nov 2021 19:06:49 +0530 Subject: [PATCH 096/185] fix: fixed tests, separated a method for shipping charges --- .../purchase_invoice/test_purchase_invoice.py | 21 ++----------------- .../sales_invoice/test_sales_invoice.py | 20 ++---------------- erpnext/controllers/taxes_and_totals.py | 2 ++ .../public/js/controllers/taxes_and_totals.js | 3 +++ 4 files changed, 9 insertions(+), 37 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 6e81c6d8b8..774d70ba63 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -811,29 +811,12 @@ class TestPurchaseInvoice(unittest.TestCase): pi.shipping_rule = shipping_rule.name pi.insert() - - shipping_amount = 0.0 - for condition in shipping_rule.get("conditions"): - if not condition.to_value or (flt(condition.from_value) <= pi.net_total <= flt(condition.to_value)): - shipping_amount = condition.shipping_amount - - shipping_charge = { - "doctype": "Purchase Taxes and Charges", - "category": "Valuation and Total", - "charge_type": "Actual", - "account_head": shipping_rule.account, - "cost_center": shipping_rule.cost_center, - "tax_amount": shipping_amount, - "description": shipping_rule.name, - "add_deduct_tax": "Add" - } - pi.append("taxes", shipping_charge) pi.save() self.assertEqual(pi.net_total, 1250) - self.assertEqual(pi.total_taxes_and_charges, 462.3) - self.assertEqual(pi.grand_total, 1712.3) + self.assertEqual(pi.total_taxes_and_charges, 354.1) + self.assertEqual(pi.grand_total, 1604.1) def test_make_pi_without_terms(self): pi = make_purchase_invoice(do_not_save=1) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 969756addf..02e2416c26 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1603,28 +1603,12 @@ class TestSalesInvoice(unittest.TestCase): si.shipping_rule = shipping_rule.name si.insert() - - shipping_amount = 0.0 - for condition in shipping_rule.get("conditions"): - if not condition.to_value or (flt(condition.from_value) <= si.net_total <= flt(condition.to_value)): - shipping_amount = condition.shipping_amount - - shipping_charge = { - "doctype": "Sales Taxes and Charges", - "category": "Valuation and Total", - "charge_type": "Actual", - "account_head": shipping_rule.account, - "cost_center": shipping_rule.cost_center, - "tax_amount": shipping_amount, - "description": shipping_rule.name - } - si.append("taxes", shipping_charge) si.save() self.assertEqual(si.net_total, 1250) - self.assertEqual(si.total_taxes_and_charges, 577.05) - self.assertEqual(si.grand_total, 1827.05) + self.assertEqual(si.total_taxes_and_charges, 468.85) + self.assertEqual(si.grand_total, 1718.85) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index e30a0a1967..746c6fd9a4 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -50,6 +50,7 @@ class calculate_taxes_and_totals(object): self.initialize_taxes() self.determine_exclusive_rate() self.calculate_net_total() + self.calculate_shipping_charges() self.calculate_taxes() self.manipulate_grand_total_for_inclusive_tax() self.calculate_totals() @@ -258,6 +259,7 @@ class calculate_taxes_and_totals(object): self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"]) + def calculate_shipping_charges(self): if hasattr(self.doc, "shipping_rule") and self.doc.shipping_rule: shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule) shipping_rule.apply(self.doc) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 0ada6601bf..f4c4c6be95 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -81,6 +81,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.initialize_taxes(); this.determine_exclusive_rate(); this.calculate_net_total(); + calculate_shipping_charges(); this.calculate_taxes(); this.manipulate_grand_total_for_inclusive_tax(); this.calculate_totals(); @@ -267,6 +268,8 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { me.frm.doc.base_net_total += item.base_net_amount; }); + } + calculate_shipping_charges() { frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); if(frappe.meta.get_docfield(this.frm.doc.doctype,"shipping_rule",this.frm.doc.name)) { this.shipping_rule() From c00d0a3e24a019915bc0e5d4fc4941267497c3d7 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 16 Nov 2021 20:31:18 +0530 Subject: [PATCH 097/185] fix: better validation for Integration Request (#28186) --- .../doctype/payment_request/payment_request.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 03bb72b6ae..6a84a65e71 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -548,10 +548,14 @@ def make_payment_order(source_name, target_doc=None): return doclist -def validate_payment(doc, method=""): - if not frappe.db.has_column(doc.reference_doctype, 'status'): +def validate_payment(doc, method=None): + if doc.reference_doctype != "Payment Request" or ( + frappe.db.get_value(doc.reference_doctype, doc.reference_docname, 'status') + != "Paid" + ): return - status = frappe.db.get_value(doc.reference_doctype, doc.reference_docname, 'status') - if status == 'Paid': - frappe.throw(_("The Payment Request {0} is already paid, cannot process payment twice").format(doc.reference_docname)) \ No newline at end of file + frappe.throw( + _("The Payment Request {0} is already paid, cannot process payment twice") + .format(doc.reference_docname) + ) From e7b4204c35abb33d0101c96d0ade9ca85acb875a Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Tue, 16 Nov 2021 20:39:58 +0530 Subject: [PATCH 098/185] fix: sider issues --- erpnext/public/js/controllers/taxes_and_totals.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index f4c4c6be95..7c1c8c7e46 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -81,7 +81,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.initialize_taxes(); this.determine_exclusive_rate(); this.calculate_net_total(); - calculate_shipping_charges(); + this.calculate_shipping_charges(); this.calculate_taxes(); this.manipulate_grand_total_for_inclusive_tax(); this.calculate_totals(); @@ -267,12 +267,12 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { me.frm.doc.net_total += item.net_amount; me.frm.doc.base_net_total += item.base_net_amount; }); + } - } calculate_shipping_charges() { frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); - if(frappe.meta.get_docfield(this.frm.doc.doctype,"shipping_rule",this.frm.doc.name)) { - this.shipping_rule() + if (frappe.meta.get_docfield(this.frm.doc.doctype, "shipping_rule", this.frm.doc.name)) { + this.shipping_rule(); } } From 7472760ea3aa7b8153f4929088c2c5fdb5fdd980 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 17 Nov 2021 11:07:44 +0530 Subject: [PATCH 099/185] fix: performance to submit the JV --- erpnext/selling/doctype/customer/customer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index d4ad719534..2e2b8b77d3 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -463,11 +463,14 @@ def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None): def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, extra_amount=0): + credit_limit = get_credit_limit(customer, company) + if not credit_limit: + return + customer_outstanding = get_customer_outstanding(customer, company, ignore_outstanding_sales_order) if extra_amount > 0: customer_outstanding += flt(extra_amount) - credit_limit = get_credit_limit(customer, company) if credit_limit > 0 and flt(customer_outstanding) > credit_limit: msgprint(_("Credit limit has been crossed for customer {0} ({1}/{2})") .format(customer, customer_outstanding, credit_limit)) From 43aeb541c271df4ba4fbba92a51811dc35023ca6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 16 Nov 2021 19:46:28 +0530 Subject: [PATCH 100/185] fix: currency wise pricing rule not working --- .../doctype/pricing_rule/test_pricing_rule.py | 69 +++++++++++++++++++ .../accounts/doctype/pricing_rule/utils.py | 5 ++ 2 files changed, 74 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index e05e4a14e8..d8b860671f 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -543,6 +543,75 @@ class TestPricingRule(unittest.TestCase): frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete() item.delete() + def test_pricing_rule_for_different_currency(self): + make_item("Test Sanitizer Item") + + pricing_rule_record = { + "doctype": "Pricing Rule", + "title": "_Test Sanitizer Rule", + "apply_on": "Item Code", + "items": [{ + "item_code": "Test Sanitizer Item", + }], + "selling": 1, + "currency": "INR", + "rate_or_discount": "Rate", + "rate": 0, + "priority": 2, + "margin_type": "Percentage", + "margin_rate_or_amount": 0.0, + "company": "_Test Company" + } + + rule = frappe.get_doc(pricing_rule_record) + rule.rate_or_discount = 'Rate' + rule.rate = 100.0 + rule.insert() + + rule1 = frappe.get_doc(pricing_rule_record) + rule1.currency = 'USD' + rule1.rate_or_discount = 'Rate' + rule1.rate = 2.0 + rule1.priority = 1 + rule1.insert() + + args = frappe._dict({ + "item_code": "Test Sanitizer Item", + "company": "_Test Company", + "price_list": "_Test Price List", + "currency": "USD", + "doctype": "Sales Invoice", + "conversion_rate": 1, + "price_list_currency": "_Test Currency", + "plc_conversion_rate": 1, + "order_type": "Sales", + "customer": "_Test Customer", + "name": None, + "transaction_date": frappe.utils.nowdate() + }) + + details = get_item_details(args) + self.assertEqual(details.price_list_rate, 2.0) + + + args = frappe._dict({ + "item_code": "Test Sanitizer Item", + "company": "_Test Company", + "price_list": "_Test Price List", + "currency": "INR", + "doctype": "Sales Invoice", + "conversion_rate": 1, + "price_list_currency": "_Test Currency", + "plc_conversion_rate": 1, + "order_type": "Sales", + "customer": "_Test Customer", + "name": None, + "transaction_date": frappe.utils.nowdate() + }) + + details = get_item_details(args) + self.assertEqual(details.price_list_rate, 100.0) + def test_pricing_rule_for_transaction(self): make_item("Water Flask 1") frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 9655ac40b1..02bfc9defd 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -264,6 +264,11 @@ def filter_pricing_rules(args, pricing_rules, doc=None): else: p.variant_of = None + if len(pricing_rules) > 1: + filtered_rules = list(filter(lambda x: x.currency==args.get('currency'), pricing_rules)) + if filtered_rules: + pricing_rules = filtered_rules + # find pricing rule with highest priority if pricing_rules: max_priority = max(cint(p.priority) for p in pricing_rules) From 23486a929d2c7ec40c97da2ac8c2404c2df0ac9a Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 17 Nov 2021 14:21:37 +0530 Subject: [PATCH 101/185] fix: (style) Warehouse Capacity Dashboard UI - Made refresh button icon visible - Edit Capacity button size and alignment - Medium font on dashboard --- .../warehouse_capacity_summary.html | 27 ++++++++++--------- .../warehouse_capacity_summary.js | 2 +- .../warehouse_capacity_summary_header.html | 10 +++---- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html index de7e38e7d3..adab478640 100644 --- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html +++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html @@ -1,19 +1,19 @@ {% for d in data %}
-
+ -
+ -
+
{{ d.stock_capacity }}
-
+
{{ d.actual_qty }}
-
+
-
+
{{ d.percent_occupied }}%
{% if can_write %} -
-
{% endif %}
diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js index c0ffdc9d51..ea27dd251d 100644 --- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js +++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js @@ -4,7 +4,7 @@ frappe.pages['warehouse-capacity-summary'].on_page_load = function(wrapper) { title: 'Warehouse Capacity Summary', single_column: true }); - page.set_secondary_action('Refresh', () => page.capacity_dashboard.refresh(), 'octicon octicon-sync'); + page.set_secondary_action('Refresh', () => page.capacity_dashboard.refresh(), 'refresh'); page.start = 0; page.company_field = page.add_field({ diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html index 7ac5e64030..1183ad4496 100644 --- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html +++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html @@ -1,18 +1,18 @@
-
+
Warehouse
-
+
Item
-
+
Stock Capacity
-
+
Balance Stock Qty
-
+
% Occupied
From 402205278c718ee66cbb01d23c9772d75652ec4b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 17 Nov 2021 16:31:59 +0530 Subject: [PATCH 102/185] chore: remove support email from error message. [skip ci] --- erpnext/setup/doctype/company/company.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 5ebfa04942..dedd2d3f55 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -413,7 +413,7 @@ def install_country_fixtures(company, country): frappe.get_attr(module_name)(company, False) except Exception as e: frappe.log_error() - frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(country))) + frappe.throw(_("Failed to setup defaults for country {0}. Please contact support.").format(frappe.bold(country))) def update_company_current_month_sales(company): From 8102e4a2104a46584e092832e6895c4556b5112b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 17 Nov 2021 17:05:07 +0530 Subject: [PATCH 103/185] chore: remove manual collapsing of section (#28435) --- erpnext/manufacturing/doctype/job_card/job_card.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index f9259fbdf4..e3eed92d7e 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -23,12 +23,6 @@ frappe.ui.form.on('Job Card', { ); }, - onload: function(frm) { - if (frm.doc.scrap_items.length == 0) { - frm.fields_dict['scrap_items_section'].collapse(); - } - }, - refresh: function(frm) { frappe.flags.pause_job = 0; frappe.flags.resume_job = 0; From 488d34af1f88724dd5a6bd298c949ac3c39e5e1b Mon Sep 17 00:00:00 2001 From: Sagar Sharma <63660334+s-aga-r@users.noreply.github.com> Date: Thu, 18 Nov 2021 13:57:13 +0530 Subject: [PATCH 104/185] fix: unchecking the "With Operations" in BOM clears operations table #28446 --- erpnext/manufacturing/doctype/bom/bom.js | 6 ------ erpnext/manufacturing/doctype/bom/bom.json | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 5f5c20a595..6d35d65bea 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -680,12 +680,6 @@ frappe.ui.form.on("BOM Item", "items_remove", function(frm) { erpnext.bom.calculate_total(frm.doc); }); -frappe.ui.form.on("BOM", "with_operations", function(frm) { - if(!cint(frm.doc.with_operations)) { - frm.set_value("operations", []); - } -}); - frappe.tour['BOM'] = [ { fieldname: "item", diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 62187077f3..218ac64d8d 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -237,6 +237,7 @@ "options": "Price List" }, { + "depends_on": "with_operations", "fieldname": "operations_section", "fieldtype": "Section Break", "hide_border": 1, @@ -539,7 +540,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-10-27 14:52:04.500251", + "modified": "2021-11-18 13:04:16.271975", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", From f8e371af14ebc4d25786ab88d33be7a574231cec Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 11 Nov 2021 20:04:11 +0530 Subject: [PATCH 105/185] fix(ux): ordering fields for better tab order --- .../course_scheduling_tool.json | 773 ++++-------------- 1 file changed, 140 insertions(+), 633 deletions(-) diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.json b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.json index 2926fe8af3..13dfe38eea 100644 --- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.json +++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.json @@ -1,661 +1,168 @@ { - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2015-09-23 15:37:38.108475", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "engine": "InnoDB", + "actions": [], + "allow_copy": 1, + "creation": "2015-09-23 15:37:38.108475", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "student_group", + "course", + "program", + "column_break_3", + "academic_year", + "academic_term", + "section_break_6", + "instructor", + "instructor_name", + "column_break_9", + "room", + "section_break_7", + "course_start_date", + "course_end_date", + "day", + "reschedule", + "column_break_15", + "from_time", + "to_time" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "student_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Student Group", - "length": 0, - "no_copy": 0, - "options": "Student Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "student_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student Group", + "options": "Student Group", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "course", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Course", - "length": 0, - "no_copy": 0, - "options": "Course", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Course", + "options": "Course", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "program", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Program", - "length": 0, - "no_copy": 0, - "options": "Program", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "program", + "fieldtype": "Link", + "label": "Program", + "options": "Program", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "academic_year", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Academic Year", - "length": 0, - "no_copy": 0, - "options": "Academic Year", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "academic_year", + "fieldtype": "Link", + "label": "Academic Year", + "options": "Academic Year", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "academic_term", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Academic Term", - "length": 0, - "no_copy": 0, - "options": "Academic Term", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "academic_term", + "fieldtype": "Link", + "label": "Academic Term", + "options": "Academic Term", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "instructor", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Instructor", - "length": 0, - "no_copy": 0, - "options": "Instructor", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "instructor", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Instructor", + "options": "Instructor", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "instructor.instructor_name", - "fieldname": "instructor_name", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Instructor Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "instructor_name", + "fieldtype": "Read Only", + "label": "Instructor Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_9", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "room", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Room", - "length": 0, - "no_copy": 0, - "options": "Room", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "room", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Room", + "options": "Room", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "from_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "From Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "from_time", + "fieldtype": "Time", + "label": "From Time", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "course_start_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Course Start Date", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course_start_date", + "fieldtype": "Date", + "label": "Course Start Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "day", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Day", - "length": 0, - "no_copy": 0, - "options": "\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "day", + "fieldtype": "Select", + "label": "Day", + "options": "\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reschedule", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reschedule", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "reschedule", + "fieldtype": "Check", + "label": "Reschedule" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_15", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "To TIme", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "to_time", + "fieldtype": "Time", + "label": "To TIme", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "course_end_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Course End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "course_end_date", + "fieldtype": "Date", + "label": "Course End Date", + "reqd": 1 } - ], - "has_web_view": 0, - "hide_heading": 1, - "hide_toolbar": 1, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2018-05-16 22:43:29.363798", - "modified_by": "Administrator", - "module": "Education", - "name": "Course Scheduling Tool", - "name_case": "", - "owner": "Administrator", + ], + "hide_toolbar": 1, + "issingle": 1, + "links": [], + "modified": "2021-11-11 09:33:18.874445", + "modified_by": "Administrator", + "module": "Education", + "name": "Course Scheduling Tool", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Academics User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "create": 1, + "read": 1, + "role": "Academics User", "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Education", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file From a38aca5a548fa297ba5cff60b2ac1b8d6aae564e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 19 Nov 2021 11:03:13 +0530 Subject: [PATCH 106/185] fix(India): GST category not getting auto updated (cherry picked from commit f8a26a9fac2c91b036f1dd4033f3fb4797285043) --- erpnext/regional/india/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 54d592a650..4bd9195191 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -74,11 +74,11 @@ def validate_tax_category(doc, method): frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state)) def update_gst_category(doc, method): - if hasattr(doc, 'gst_category'): - for link in doc.links: - if link.link_doctype in ['Customer', 'Supplier']: - if doc.get('gstin'): - frappe.db.set_value(link.link_doctype, {'name': link.link_name, 'gst_category': 'Unregistered'}, 'gst_category', 'Registered Regular') + for link in doc.links: + if link.link_doctype in ['Customer', 'Supplier']: + meta = frappe.get_meta(link.link_doctype) + if doc.get('gstin') and meta.has_field('gst_category'): + frappe.db.set_value(link.link_doctype, {'name': link.link_name, 'gst_category': 'Unregistered'}, 'gst_category', 'Registered Regular') def set_gst_state_and_state_number(doc): if not doc.gst_state: From ff65399ae9c7ead884cfcfcc7289efd2baff6a95 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 19 Nov 2021 11:58:44 +0530 Subject: [PATCH 107/185] fix: Add test for gst category check (cherry picked from commit cdbc991e3f97c7ed3e120b2e4720298402d1de77) --- .../gstr_3b_report/test_gstr_3b_report.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index 0f0c0b9915..e12e3d7b80 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -103,6 +103,45 @@ class TestGSTR3BReport(unittest.TestCase): gst_settings.round_off_gst_values = 1 gst_settings.save() + def test_gst_category_auto_update(self): + if not frappe.db.exists("Customer", "_Test GST Customer With GSTIN"): + customer = frappe.get_doc({ + "customer_group": "_Test Customer Group", + "customer_name": "_Test GST Customer With GSTIN", + "customer_type": "Individual", + "doctype": "Customer", + "territory": "_Test Territory" + }).insert() + + self.assertEqual(customer.gst_category, 'Unregistered') + + if not frappe.db.exists('Address', '_Test GST Category-1-Billing'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test GST Category-1", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+91 0000000000", + "gstin": "29AZWPS7135H1ZG", + "gst_state": "Karnataka", + "gst_state_number": "29" + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test GST Customer With GSTIN" + }) + + address.save() + + customer.load_from_db() + self.assertEqual(customer.gst_category, 'Registered Regular') + + def make_sales_invoice(): si = create_sales_invoice(company="_Test Company GST", customer = '_Test GST Customer', From 9d319c2205c475f4f886bc0bd7c32721993ba06a Mon Sep 17 00:00:00 2001 From: Mohammed Redah Date: Fri, 19 Nov 2021 15:13:23 +0300 Subject: [PATCH 108/185] fix:Change QR Code Triggerr event This fixes the bug if the user changes the date after insertion it will show the wrong values --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 2a277ee035..4f00ef817a 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -248,10 +248,10 @@ doc_events = { "validate": "erpnext.regional.india.utils.validate_tax_category" }, "Sales Invoice": { - "after_insert": "erpnext.regional.saudi_arabia.utils.create_qr_code", "on_submit": [ "erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit", + "erpnext.regional.saudi_arabia.utils.create_qr_code", "erpnext.erpnext_integrations.taxjar_integration.create_transaction" ], "on_cancel": [ From 350ed1a6c21a3ca63de02b1a8ed741c51b254ad1 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Fri, 19 Nov 2021 21:05:25 +0530 Subject: [PATCH 109/185] fix: FY query returning None for new company --- erpnext/regional/india/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 316bb6b197..5865424028 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -791,7 +791,7 @@ def set_tax_withholding_category(company): accounts = [dict(company=company, account=tds_account)] try: - fiscal_year_details = get_fiscal_year(today(), verbose=0, company=company) + fiscal_year_details = get_fiscal_year(today(), verbose=0) except FiscalYearError: pass From ac27391a1f5e3cd31baa4c372e3b24ecdd0e05a3 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 20 Nov 2021 11:22:19 +0530 Subject: [PATCH 110/185] fix: bug with qrcode generation due to default print format name --- erpnext/regional/saudi_arabia/utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index cc6c0af7a5..0c036f905c 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -28,14 +28,22 @@ def create_qr_code(doc, method): for field in meta.get_image_fields(): if field.fieldname == 'qr_code': + from urllib.parse import urlencode + # Creating public url to print format default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") # System Language language = frappe.get_system_settings('language') + params = urlencode({ + 'format': default_print_format or 'Standard', + '_lang': language, + 'key': doc.get_signature() + }) + # creating qr code for the url - url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" + url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?{ params }" qr_image = io.BytesIO() url = qr_create(url, error='L') url.png(qr_image, scale=2, quiet_zone=1) From 08ad93d82f875e1098b54e58f77d46061f82381b Mon Sep 17 00:00:00 2001 From: Saqib Date: Sat, 20 Nov 2021 15:15:46 +0530 Subject: [PATCH 111/185] fix: coa balance rendering bug (#28468) --- erpnext/accounts/doctype/account/account_tree.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index a4b6e0b45a..b9ebb58b43 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -78,6 +78,7 @@ frappe.treeview_settings["Account"] = { const format = (value, currency) => format_currency(Math.abs(value), currency); if (account.balance!==undefined) { + node.parent && node.parent.find('.balance-area').remove(); $('' + (account.balance_in_account_currency ? (format(account.balance_in_account_currency, account.account_currency) + " / ") : "") From 624e58d1dee7f35bcd99c57baa46b2bf3f1d4aa3 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Mon, 22 Nov 2021 11:36:35 +0530 Subject: [PATCH 112/185] fix: Filter out cancelled and non-depreciable Assets in Asset Value Adjustment (#28443) --- .../asset_value_adjustment/asset_value_adjustment.js | 8 ++++++++ .../asset_value_adjustment/asset_value_adjustment.py | 11 +++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js index 79c8861bcd..36f510b18e 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js @@ -14,6 +14,14 @@ frappe.ui.form.on('Asset Value Adjustment', { } } }); + frm.set_query('asset', function() { + return { + filters: { + calculate_depreciation: 1, + docstatus: 1 + } + }; + }); }, onload: function(frm) { diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index b93f474a78..0b646ed4ed 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -10,7 +10,11 @@ from frappe.utils import cint, date_diff, flt, formatdate, getdate from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_checks_for_pl_and_bs_accounts, ) +from erpnext.assets.doctype.asset.asset import get_depreciation_amount from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts +from erpnext.regional.india.utils import ( + get_depreciation_amount as get_depreciation_amount_for_india, +) class AssetValueAdjustment(Document): @@ -90,6 +94,7 @@ class AssetValueAdjustment(Document): def reschedule_depreciations(self, asset_value): asset = frappe.get_doc('Asset', self.asset) + country = frappe.get_value('Company', self.company, 'country') for d in asset.finance_books: d.value_after_depreciation = asset_value @@ -111,8 +116,10 @@ class AssetValueAdjustment(Document): depreciation_amount = days * rate_per_day from_date = data.schedule_date else: - depreciation_amount = asset.get_depreciation_amount(value_after_depreciation, - no_of_depreciations, d) + if country == "India": + depreciation_amount = get_depreciation_amount_for_india(asset, value_after_depreciation, d) + else: + depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d) if depreciation_amount: value_after_depreciation -= flt(depreciation_amount) From 3a2074ce05d3b89b5f740ddca9d2edbfb39a6c06 Mon Sep 17 00:00:00 2001 From: Ahmed Shareef Date: Mon, 22 Nov 2021 10:16:58 +0400 Subject: [PATCH 113/185] fix: add child button in coa tree (#28413) --- erpnext/accounts/doctype/account/account_tree.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index b9ebb58b43..a3ef38465e 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -176,7 +176,7 @@ frappe.treeview_settings["Account"] = { && node.expandable && !node.hide_add; }, click: function() { - var me = frappe.treeview_settings['Account'].treeview; + var me = frappe.views.trees['Account']; me.new_node(); }, btnClass: "hidden-xs" From 78851ecb70a46024b7a1995252f5312dfd6e8aac Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 22 Nov 2021 12:23:23 +0530 Subject: [PATCH 114/185] feat: create party link from customer/supplier (#28387) * feat: create party link from customer/supplier * test: create_party_link method --- .../accounts_settings/accounts_settings.py | 3 ++ .../accounts/doctype/party_link/party_link.py | 14 ++++++ .../sales_invoice/test_sales_invoice.py | 9 +--- erpnext/buying/doctype/supplier/supplier.js | 43 +++++++++++++++++++ erpnext/selling/doctype/customer/customer.js | 43 +++++++++++++++++++ 5 files changed, 105 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index aa132a07d0..745191712b 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -19,6 +19,9 @@ class AccountsSettings(Document): frappe.db.set_default("add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)) + frappe.db.set_default("enable_common_party_accounting", + self.get("enable_common_party_accounting", 0)) + self.validate_stale_days() self.enable_payment_schedule_in_print() self.toggle_discount_accounting_fields() diff --git a/erpnext/accounts/doctype/party_link/party_link.py b/erpnext/accounts/doctype/party_link/party_link.py index daf667caf0..e9f813c17c 100644 --- a/erpnext/accounts/doctype/party_link/party_link.py +++ b/erpnext/accounts/doctype/party_link/party_link.py @@ -25,3 +25,17 @@ class PartyLink(Document): if existing_party_link: frappe.throw(_('{} {} is already linked with another {}') .format(self.primary_role, self.primary_party, existing_party_link[0])) + + +@frappe.whitelist() +def create_party_link(primary_role, primary_party, secondary_party): + party_link = frappe.new_doc('Party Link') + party_link.primary_role = primary_role + party_link.primary_party = primary_party + party_link.secondary_role = 'Customer' if primary_role == 'Supplier' else 'Supplier' + party_link.secondary_party = secondary_party + + party_link.save(ignore_permissions=True) + + return party_link + diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 02e2416c26..b5453ac56e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2300,6 +2300,7 @@ class TestSalesInvoice(unittest.TestCase): from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( make_customer, ) + from erpnext.accounts.doctype.party_link.party_link import create_party_link from erpnext.buying.doctype.supplier.test_supplier import create_supplier # create a customer @@ -2308,13 +2309,7 @@ class TestSalesInvoice(unittest.TestCase): supplier = create_supplier(supplier_name="_Test Common Supplier").name # create a party link between customer & supplier - # set primary role as supplier - party_link = frappe.new_doc("Party Link") - party_link.primary_role = "Supplier" - party_link.primary_party = supplier - party_link.secondary_role = "Customer" - party_link.secondary_party = customer - party_link.save() + party_link = create_party_link("Supplier", supplier, customer) # enable common party accounting frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1) diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index 7ee91961ca..f0899b06b5 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -83,6 +83,12 @@ frappe.ui.form.on("Supplier", { frm.trigger("get_supplier_group_details"); }, __('Actions')); + if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) { + frm.add_custom_button(__('Link with Customer'), function () { + frm.trigger('show_party_link_dialog'); + }, __('Actions')); + } + // indicators erpnext.utils.set_party_dashboard_indicators(frm); } @@ -128,5 +134,42 @@ frappe.ui.form.on("Supplier", { else { frm.toggle_reqd("represents_company", false); } + }, + show_party_link_dialog: function(frm) { + const dialog = new frappe.ui.Dialog({ + title: __('Select a Customer'), + fields: [{ + fieldtype: 'Link', label: __('Customer'), + options: 'Customer', fieldname: 'customer', reqd: 1 + }], + primary_action: function({ customer }) { + frappe.call({ + method: 'erpnext.accounts.doctype.party_link.party_link.create_party_link', + args: { + primary_role: 'Supplier', + primary_party: frm.doc.name, + secondary_party: customer + }, + freeze: true, + callback: function() { + dialog.hide(); + frappe.msgprint({ + message: __('Successfully linked to Customer'), + alert: true + }); + }, + error: function() { + dialog.hide(); + frappe.msgprint({ + message: __('Linking to Customer Failed. Please try again.'), + title: __('Linking Failed'), + indicator: 'red' + }); + } + }); + }, + primary_action_label: __('Create Link') + }); + dialog.show(); } }); diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 4b0bbd5a11..107e4a4759 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -134,6 +134,12 @@ frappe.ui.form.on("Customer", { frm.trigger("get_customer_group_details"); }, __('Actions')); + if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) { + frm.add_custom_button(__('Link with Supplier'), function () { + frm.trigger('show_party_link_dialog'); + }, __('Actions')); + } + // indicator erpnext.utils.set_party_dashboard_indicators(frm); @@ -158,5 +164,42 @@ frappe.ui.form.on("Customer", { } }); + }, + show_party_link_dialog: function(frm) { + const dialog = new frappe.ui.Dialog({ + title: __('Select a Supplier'), + fields: [{ + fieldtype: 'Link', label: __('Supplier'), + options: 'Supplier', fieldname: 'supplier', reqd: 1 + }], + primary_action: function({ supplier }) { + frappe.call({ + method: 'erpnext.accounts.doctype.party_link.party_link.create_party_link', + args: { + primary_role: 'Customer', + primary_party: frm.doc.name, + secondary_party: supplier + }, + freeze: true, + callback: function() { + dialog.hide(); + frappe.msgprint({ + message: __('Successfully linked to Supplier'), + alert: true + }); + }, + error: function() { + dialog.hide(); + frappe.msgprint({ + message: __('Linking to Supplier Failed. Please try again.'), + title: __('Linking Failed'), + indicator: 'red' + }); + } + }); + }, + primary_action_label: __('Create Link') + }); + dialog.show(); } }); From cb52e6dd6bb437b3cfe34d22a7b27b24e00b0580 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 22 Nov 2021 12:24:32 +0530 Subject: [PATCH 115/185] fix: cost center wise ledger posting for pcv (#28477) --- .../period_closing_voucher.py | 3 ++- .../test_period_closing_voucher.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 34572fdfbf..d0e555e40c 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -88,9 +88,10 @@ class PeriodClosingVoucher(AccountsController): for acc in pl_accounts: if flt(acc.bal_in_company_currency): + cost_center = acc.cost_center if self.cost_center_wise_pnl else company_cost_center gl_entry = self.get_gl_dict({ "account": self.closing_account_head, - "cost_center": acc.cost_center or company_cost_center, + "cost_center": cost_center, "finance_book": acc.finance_book, "account_currency": acc.account_currency, "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0, diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 0e29755aed..030b4caf7c 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -66,8 +66,8 @@ class TestPeriodClosingVoucher(unittest.TestCase): company = create_company() surplus_account = create_account() - cost_center1 = create_cost_center("Test Cost Center 1") - cost_center2 = create_cost_center("Test Cost Center 2") + cost_center1 = create_cost_center("Main") + cost_center2 = create_cost_center("Western Branch") create_sales_invoice( company=company, @@ -86,7 +86,10 @@ class TestPeriodClosingVoucher(unittest.TestCase): debit_to="Debtors - TPC" ) - pcv = self.make_period_closing_voucher() + pcv = self.make_period_closing_voucher(submit=False) + pcv.cost_center_wise_pnl = 1 + pcv.save() + pcv.submit() surplus_account = pcv.closing_account_head expected_gle = ( @@ -149,7 +152,7 @@ class TestPeriodClosingVoucher(unittest.TestCase): self.assertEqual(pcv_gle, expected_gle) - def make_period_closing_voucher(self): + def make_period_closing_voucher(self, submit=True): surplus_account = create_account() cost_center = create_cost_center("Test Cost Center 1") pcv = frappe.get_doc({ @@ -163,7 +166,8 @@ class TestPeriodClosingVoucher(unittest.TestCase): "remarks": "test" }) pcv.insert() - pcv.submit() + if submit: + pcv.submit() return pcv From 7d0558e202e1ef3f3a9adf540e3eb32bff986506 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Mon, 22 Nov 2021 12:34:46 +0530 Subject: [PATCH 116/185] fix: Add extra column to display Invoice or Item when grouped by Invoice (#28380) --- .../accounts/report/gross_profit/gross_profit.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index a6fb6f505b..20bc3ec115 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -19,7 +19,7 @@ def execute(filters=None): data = [] group_wise_columns = frappe._dict({ - "invoice": ["parent", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description", \ + "invoice": ["invoice_or_item", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description", "warehouse", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent", "project"], "item_code": ["item_code", "item_name", "brand", "description", "qty", "base_rate", @@ -85,6 +85,7 @@ def get_columns(group_wise_columns, filters): columns = [] column_map = frappe._dict({ "parent": _("Sales Invoice") + ":Link/Sales Invoice:120", + "invoice_or_item": _("Sales Invoice") + ":Link/Sales Invoice:120", "posting_date": _("Posting Date") + ":Date:100", "posting_time": _("Posting Time") + ":Data:100", "item_code": _("Item Code") + ":Link/Item:100", @@ -123,7 +124,7 @@ def get_columns(group_wise_columns, filters): def get_column_names(): return frappe._dict({ - 'parent': 'sales_invoice', + 'invoice_or_item': 'sales_invoice', 'customer': 'customer', 'customer_group': 'customer_group', 'posting_date': 'posting_date', @@ -456,7 +457,7 @@ class GrossProfitGenerator(object): if not row.indent: row.indent = 1.0 row.parent_invoice = row.parent - row.parent = row.item_code + row.invoice_or_item = row.item_code if frappe.db.exists('Product Bundle', row.item_code): self.add_bundle_items(row, index) @@ -465,7 +466,8 @@ class GrossProfitGenerator(object): return frappe._dict({ 'parent_invoice': "", 'indent': 0.0, - 'parent': row.parent, + 'invoice_or_item': row.parent, + 'parent': None, 'posting_date': row.posting_date, 'posting_time': row.posting_time, 'project': row.project, @@ -509,7 +511,8 @@ class GrossProfitGenerator(object): return frappe._dict({ 'parent_invoice': product_bundle.item_code, 'indent': product_bundle.indent + 1, - 'parent': item.item_code, + 'parent': None, + 'invoice_or_item': item.item_code, 'posting_date': product_bundle.posting_date, 'posting_time': product_bundle.posting_time, 'project': product_bundle.project, From 42f1dd98dde623f42d7eae431b22cbde83fd357f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 18 Nov 2021 12:48:36 +0530 Subject: [PATCH 117/185] fix: restrict repost valuation to manager roles --- .../repost_item_valuation.json | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index a800bf8701..3ff0f60b3e 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -177,10 +177,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-07-22 18:59:43.057878", + "modified": "2021-11-18 02:18:10.524560", "modified_by": "Administrator", "module": "Stock", "name": "Repost Item Valuation", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -197,20 +198,6 @@ "submit": 1, "write": 1 }, - { - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock User", - "share": 1, - "submit": 1, - "write": 1 - }, { "cancel": 1, "create": 1, @@ -226,7 +213,6 @@ "write": 1 }, { - "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -234,7 +220,7 @@ "print": 1, "read": 1, "report": 1, - "role": "Accounts User", + "role": "Accounts Manager", "share": 1, "submit": 1, "write": 1 From aa024fc9da3e3f93baebb7ccf9ddf220c782157e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 18 Nov 2021 12:51:26 +0530 Subject: [PATCH 118/185] fix: ignore permissions while creating repost If user reached this code then they already have permission to create stock transaction, hence ignore permission checks while creating/cancelling repost item valuation entries. --- erpnext/controllers/stock_controller.py | 1 + erpnext/stock/stock_ledger.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 08d422d3bc..aba15b47e3 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -676,5 +676,6 @@ def create_repost_item_valuation_entry(args): repost_entry.company = args.company repost_entry.allow_zero_rate = args.allow_zero_rate repost_entry.flags.ignore_links = True + repost_entry.flags.ignore_permissions = True repost_entry.save() repost_entry.submit() diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9c4c676192..9d409827db 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -111,6 +111,7 @@ def validate_cancellation(args): frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet.")) if repost_entry.status == 'Queued': doc = frappe.get_doc("Repost Item Valuation", repost_entry.name) + doc.flags.ignore_permissions = True doc.cancel() doc.delete() From c436e30ee6895797abec3649e5f08b1003164b52 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 18 Nov 2021 12:56:49 +0530 Subject: [PATCH 119/185] fix: don't use cached doc while reposting --- .../doctype/repost_item_valuation/repost_item_valuation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 170aa7f76c..59d191fa07 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -133,7 +133,7 @@ def repost_entries(): riv_entries = get_repost_item_valuation_entries() for row in riv_entries: - doc = frappe.get_cached_doc('Repost Item Valuation', row.name) + doc = frappe.get_doc('Repost Item Valuation', row.name) repost(doc) riv_entries = get_repost_item_valuation_entries() From aa689874e371d29c4295146a2eefb48acb847b23 Mon Sep 17 00:00:00 2001 From: Sagar Sharma <63660334+s-aga-r@users.noreply.github.com> Date: Mon, 22 Nov 2021 13:06:59 +0530 Subject: [PATCH 120/185] fix: Closed status error in Work Order Summary (#28460) * fix: Closed status error in Work Order Summary * chore: use get_meta to get status options * refactor: simplify code Co-authored-by: Ankush Menat --- .../work_order_summary/work_order_summary.js | 2 +- .../work_order_summary/work_order_summary.py | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js index eb23f17c47..832be2301c 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js @@ -51,7 +51,7 @@ frappe.query_reports["Work Order Summary"] = { label: __("Status"), fieldname: "status", fieldtype: "Select", - options: ["", "Not Started", "In Process", "Completed", "Stopped"] + options: ["", "Not Started", "In Process", "Completed", "Stopped", "Closed"] }, { label: __("Sales Orders"), diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py index 6207904a0f..d7469ddfdd 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -1,6 +1,7 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +from collections import defaultdict import frappe from frappe import _ @@ -58,21 +59,16 @@ def get_chart_data(data, filters): return get_chart_based_on_qty(data, filters) def get_chart_based_on_status(data): - labels = ["Completed", "In Process", "Stopped", "Not Started"] + labels = frappe.get_meta("Work Order").get_options("status").split("\n") + if "" in labels: + labels.remove("") - status_wise_data = { - "Not Started": 0, - "In Process": 0, - "Stopped": 0, - "Completed": 0, - "Draft": 0 - } + status_wise_data = defaultdict(int) for d in data: status_wise_data[d.status] += 1 - values = [status_wise_data["Completed"], status_wise_data["In Process"], - status_wise_data["Stopped"], status_wise_data["Not Started"]] + values = [status_wise_data[label] for label in labels] chart = { "data": { From 42c40b33bcd2906d806a2e60a81037a73fb32989 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 22 Nov 2021 13:20:43 +0530 Subject: [PATCH 121/185] fix: KSA Invoice print format for multicurrency invoices --- .../print_format/ksa_vat_invoice/ksa_vat_invoice.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json index 36d653616b..681f72fd30 100644 --- a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json +++ b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json @@ -2,7 +2,7 @@ "absolute_value": 0, "align_labels_right": 0, "creation": "2021-10-29 22:46:26.039023", - "css": ".qr-code{\n float:right;\n}\n\n.invoice-heading {\n margin: 0;\n}\n\n.ksa-invoice-table {\n border: 1px solid #888a8e;\n border-collapse: collapse;\n width: 100%;\n margin: 20px 0;\n color: #888a8e;\n font-size: 16px;\n}\n\n.ksa-invoice-table.two-columns td:nth-child(2) {\n direction: rtl;\n}\n\n.ksa-invoice-table th {\n border: 1px solid #888a8e;\n max-width: 50%;\n background-color: #265e4a !important;\n color: #fff;\n padding: 8px;\n}\n\n.ksa-invoice-table td {\n padding: 5px;\n border: 1px solid #888a8e;\n max-width: 50%;\n}\n\n.ksa-invoice-table thead,\n.ksa-invoice-table tfoot {\n text-transform: uppercase;\n}\n\n.qr-rtl {\n direction: rtl;\n}\n\n.qr-flex{\n display: flex;\n justify-content: space-between;\n}", + "css": ".qr-code{\n float:right;\n}\n\n.invoice-heading {\n margin: 0;\n}\n\n.ksa-invoice-table {\n border: 1px solid #888a8e;\n border-collapse: collapse;\n width: 100%;\n margin: 20px 0;\n font-size: 16px;\n}\n\n.ksa-invoice-table.two-columns td:nth-child(2) {\n direction: rtl;\n}\n\n.ksa-invoice-table th {\n border: 1px solid #888a8e;\n max-width: 50%;\n padding: 8px;\n}\n\n.ksa-invoice-table td {\n padding: 5px;\n border: 1px solid #888a8e;\n max-width: 50%;\n}\n\n.ksa-invoice-table thead,\n.ksa-invoice-table tfoot {\n text-transform: uppercase;\n}\n\n.qr-rtl {\n direction: rtl;\n}\n\n.qr-flex{\n display: flex;\n justify-content: space-between;\n}", "custom_format": 1, "default_print_language": "en", "disabled": 0, @@ -10,14 +10,14 @@ "docstatus": 0, "doctype": "Print Format", "font_size": 14, - "html": "
\n
\n
\n

TAX INVOICE

\n

\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629

\n
\n \n \n
\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\t\t{% if (company.tax_id) %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n {% if(supplier_address_doc) %}\n \n \n \n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n {% if(customer_address) %}\n \n \n \n \n {% endif %}\n \n {% if(customer_shipping_address) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n
{{ company.name }}{{ company.company_name_in_arabic }}
Invoice#: {{doc.name}}\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}
Invoice Date: {{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}
Date of Supply:{{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}
Supplier:\u0627\u0644\u0645\u0648\u0631\u062f:
Supplier Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:
{{ company.tax_id }}{{ company.tax_id }}
{{ company.name }}{{ company.company_name_in_arabic }}
{{ supplier_address_doc.address_line1}} {{ supplier_address_doc.address_in_arabic}}
Phone: {{ supplier_address_doc.phone }}\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}
Email: {{ supplier_address_doc.email_id }}\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}
CUSTOMER:\u0639\u0645\u064a\u0644:
Customer Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:
{{ customer_tax_id }}{{ customer_tax_id }}
{{ doc.customer }} {{ doc.customer_name_in_arabic }}
{{ customer_address.address_line1}} {{ customer_address.address_in_arabic}}
SHIPPING ADDRESS:\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:
{{ customer_shipping_address.address_line1}} {{ customer_shipping_address.address_in_arabic}}
OTHER INFORMATION\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649
Purchase Order Number: {{ doc.po_no }}\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}
Payment Due Date: {{ doc.due_date}} \u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}
\n\n \n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n \n {% set total = namespace(amount = 0) %}\n \n \n \n \n \n \n \n \n {% for row in doc.taxes %}\n \n {% endfor %}\n \n \n \n \n \n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n \n \n \n \n \n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n \n {% endfor %}\n \n \n {%- endfor -%}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Nature of goods or services
\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a
\n Unit price
\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n
\n Quantity
\n \u0627\u0644\u0643\u0645\u064a\u0629\n
\n Taxable Amount
\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n
{{row.description}}\n Total
\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n
{{ item.item_code }}{{ item.get_formatted(\"rate\") }}{{ item.qty }}{{ item.get_formatted(\"amount\") }}\n
\n {%- if(data_object[item.item_code][0])-%}\n {{ frappe.format(data_object[item.item_code][0], {'fieldtype': 'Percent'}) }}\n {%- endif -%}\n \n {%- if(data_object[item.item_code][1])-%}\n {{ frappe.format(data_object[item.item_code][1], {'fieldtype': 'Currency'}) }}\n {% set total.amount = total.amount + data_object[item.item_code][1] %}\n {%- endif -%}\n
\n
{{ frappe.format(total.amount, {'fieldtype': 'Currency'}) }}
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n Total (Excluding VAT)\n
\n Total VAT\n
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
{{ doc.get_formatted(\"grand_total\") }}\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642Total Amount Due{{ doc.get_formatted(\"grand_total\") }}
\n\n\t{%- if doc.terms -%}\n

\n {{doc.terms}}\n

\n\t{%- endif -%}\n
\n", + "html": "
\n
\n
\n

TAX INVOICE

\n

\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629

\n
\n \n \n
\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\t\t{% if (company.tax_id) %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n {% if(supplier_address_doc) %}\n \n \n \n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n {% if(customer_address) %}\n \n \n \n \n {% endif %}\n \n {% if(customer_shipping_address) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n
{{ company.name }}{{ company.company_name_in_arabic }}
Invoice#: {{doc.name}}\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}
Invoice Date: {{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}
Date of Supply:{{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}
Supplier:\u0627\u0644\u0645\u0648\u0631\u062f:
Supplier Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:
{{ company.tax_id }}{{ company.tax_id }}
{{ company.name }}{{ company.company_name_in_arabic }}
{{ supplier_address_doc.address_line1}} {{ supplier_address_doc.address_in_arabic}}
Phone: {{ supplier_address_doc.phone }}\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}
Email: {{ supplier_address_doc.email_id }}\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}
CUSTOMER:\u0639\u0645\u064a\u0644:
Customer Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:
{{ customer_tax_id }}{{ customer_tax_id }}
{{ doc.customer }} {{ doc.customer_name_in_arabic }}
{{ customer_address.address_line1}} {{ customer_address.address_in_arabic}}
SHIPPING ADDRESS:\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:
{{ customer_shipping_address.address_line1}} {{ customer_shipping_address.address_in_arabic}}
OTHER INFORMATION\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649
Purchase Order Number: {{ doc.po_no }}\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}
Payment Due Date: {{ doc.due_date}} \u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}
\n\n \n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n \n {% set total = namespace(amount = 0) %}\n \n \n \n \n \n \n \n \n {% for row in doc.taxes %}\n \n {% endfor %}\n \n \n \n \n \n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n \n \n \n \n \n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n {% set tax_amount = frappe.utils.flt(data_object[item.item_code][1]/doc.conversion_rate, row.precision('tax_amount')) %}\n \n {% endfor %}\n \n \n {%- endfor -%}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Nature of goods or services
\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a
\n Unit price
\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n
\n Quantity
\n \u0627\u0644\u0643\u0645\u064a\u0629\n
\n Taxable Amount
\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n
{{row.description}}\n Total
\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n
{{ item.item_code }}{{ item.get_formatted(\"rate\") }}{{ item.qty }}{{ item.get_formatted(\"amount\") }}\n
\n {%- if(data_object[item.item_code][0])-%}\n {{ frappe.format(data_object[item.item_code][0], {'fieldtype': 'Percent'}) }}\n {%- endif -%}\n \n {%- if(data_object[item.item_code][1])-%}\n {{ frappe.format_value(tax_amount, currency=doc.currency) }}\n {% set total.amount = total.amount + tax_amount %}\n {%- endif -%}\n
\n
{{ frappe.format_value(frappe.utils.flt(total.amount, doc.precision('total_taxes_and_charges')), currency=doc.currency) }}
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n Total (Excluding VAT)\n
\n Total VAT\n
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
{{ doc.get_formatted(\"grand_total\") }}\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642Total Amount Due{{ doc.get_formatted(\"grand_total\") }}
\n\n\t{%- if doc.terms -%}\n

\n {{doc.terms}}\n

\n\t{%- endif -%}\n
\n", "idx": 0, "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2021-11-08 09:19:18.660806", + "modified": "2021-11-22 10:40:24.716932", "modified_by": "Administrator", "module": "Regional", "name": "KSA VAT Invoice", From fce4f34325375c47f80c0870e43e384d404e77d9 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 22 Nov 2021 13:34:26 +0530 Subject: [PATCH 122/185] fix: POS string translation (#28381) --- .../page/point_of_sale/pos_item_cart.js | 44 +++++++++---------- .../page/point_of_sale/pos_item_details.js | 5 ++- .../page/point_of_sale/pos_item_selector.js | 2 +- .../page/point_of_sale/pos_past_order_list.js | 2 +- .../point_of_sale/pos_past_order_summary.js | 19 ++++---- .../selling/page/point_of_sale/pos_payment.js | 10 ++--- 6 files changed, 42 insertions(+), 40 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 9d8338e5fe..b652fdcb35 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -49,11 +49,11 @@ erpnext.PointOfSale.ItemCart = class { this.$component.append( `
-
Item Cart
+
${__('Item Cart')}
-
Item
-
Qty
-
Amount
+
${__('Item')}
+
${__('Quantity')}
+
${__('Amount')}
@@ -78,7 +78,7 @@ erpnext.PointOfSale.ItemCart = class { make_no_items_placeholder() { this.$cart_header.css('display', 'none'); this.$cart_items_wrapper.html( - `
No items in cart
` + `
${__('No items in cart')}
` ); } @@ -98,19 +98,19 @@ erpnext.PointOfSale.ItemCart = class { this.$totals_section.append( `
- ${this.get_discount_icon()} Add Discount + ${this.get_discount_icon()} ${__('Add Discount')}
-
Net Total
+
${__("Net Total")}
0.00
-
Grand Total
+
${__('Grand Total')}
0.00
-
Checkout
-
Edit Cart
` +
${__('Checkout')}
+
${__('Edit Cart')}
` ) this.$add_discount_elem = this.$component.find(".add-discount-wrapper"); @@ -126,10 +126,10 @@ erpnext.PointOfSale.ItemCart = class { }, cols: 5, keys: [ - [ 1, 2, 3, 'Quantity' ], - [ 4, 5, 6, 'Discount' ], - [ 7, 8, 9, 'Rate' ], - [ '.', 0, 'Delete', 'Remove' ] + [ 1, 2, 3, __('Quantity') ], + [ 4, 5, 6, __('Discount') ], + [ 7, 8, 9, __('Rate') ], + [ '.', 0, __('Delete'), __('Remove') ] ], css_classes: [ [ '', '', '', 'col-span-2' ], @@ -148,7 +148,7 @@ erpnext.PointOfSale.ItemCart = class { ) this.$numpad_section.append( - `
Checkout
` + `
${__('Checkout')}
` ) } @@ -386,7 +386,7 @@ erpnext.PointOfSale.ItemCart = class { 'border': '1px dashed var(--gray-500)', 'padding': 'var(--padding-sm) var(--padding-md)' }); - me.$add_discount_elem.html(`${me.get_discount_icon()} Add Discount`); + me.$add_discount_elem.html(`${me.get_discount_icon()} ${__('Add Discount')}`); me.discount_field = undefined; } }, @@ -411,7 +411,7 @@ erpnext.PointOfSale.ItemCart = class { }); this.$add_discount_elem.html( `
- ${this.get_discount_icon()} Additional ${String(discount).bold()}% discount applied + ${this.get_discount_icon()} ${__("Additional")} ${String(discount).bold()}% ${__("discount applied")}
` ); } @@ -445,7 +445,7 @@ erpnext.PointOfSale.ItemCart = class { function get_customer_description() { if (!email_id && !mobile_no) { - return `
Click to add email / phone
`; + return `
${__('Click to add email / phone')}
`; } else if (email_id && !mobile_no) { return `
${email_id}
`; } else if (mobile_no && !email_id) { @@ -479,22 +479,22 @@ erpnext.PointOfSale.ItemCart = class { render_net_total(value) { const currency = this.events.get_frm().doc.currency; this.$totals_section.find('.net-total-container').html( - `
Net Total
${format_currency(value, currency)}
` + `
${__('Net Total')}
${format_currency(value, currency)}
` ) this.$numpad_section.find('.numpad-net-total').html( - `
Net Total: ${format_currency(value, currency)}
` + `
${__('Net Total')}: ${format_currency(value, currency)}
` ); } render_grand_total(value) { const currency = this.events.get_frm().doc.currency; this.$totals_section.find('.grand-total-container').html( - `
Grand Total
${format_currency(value, currency)}
` + `
${__('Grand Total')}
${format_currency(value, currency)}
` ) this.$numpad_section.find('.numpad-grand-total').html( - `
Grand Total: ${format_currency(value, currency)}
` + `
${__('Grand Total')}: ${format_currency(value, currency)}
` ); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index ec861d7c53..fb69b63f82 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -28,7 +28,7 @@ erpnext.PointOfSale.ItemDetails = class { init_child_components() { this.$component.html( `
-
Item Details
+
${__('Item Details')}
@@ -201,8 +201,9 @@ erpnext.PointOfSale.ItemDetails = class { `
` ); } + const label = __('Auto Fetch Serial Numbers'); this.$form_container.append( - `
Auto Fetch Serial Numbers
` + `
${label}
` ); this.$form_container.find('.serial_no-control').find('textarea').css('height', '6rem'); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 8352b148ac..496385248c 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -24,7 +24,7 @@ erpnext.PointOfSale.ItemSelector = class { this.wrapper.append( `
-
All Items
+
${__('All Items')}
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js index e0993e2e34..a0475c70d0 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_list.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js @@ -16,7 +16,7 @@ erpnext.PointOfSale.PastOrderList = class { this.wrapper.append( `
-
Recent Orders
+
${__('Recent Orders')}
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index dd9e05a0e6..eeb8523f19 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -17,16 +17,16 @@ erpnext.PointOfSale.PastOrderSummary = class { this.wrapper.append( `
- Select an invoice to load summary data + ${__('Select an invoice to load summary data')}
-
Items
+
${__('Items')}
-
Totals
+
${__('Totals')}
-
Payments
+
${__('Payments')}
@@ -82,7 +82,7 @@ erpnext.PointOfSale.PastOrderSummary = class { return `
${doc.customer}
${this.customer_email}
-
Sold by: ${doc.owner}
+
${__('Sold by')}: ${doc.owner}
@@ -121,7 +121,7 @@ erpnext.PointOfSale.PastOrderSummary = class { get_net_total_html(doc) { return `
-
Net Total
+
${__('Net Total')}
${format_currency(doc.net_total, doc.currency)}
`; } @@ -144,14 +144,14 @@ erpnext.PointOfSale.PastOrderSummary = class { get_grand_total_html(doc) { return `
-
Grand Total
+
${__('Grand Total')}
${format_currency(doc.grand_total, doc.currency)}
`; } get_payment_html(doc, payment) { return `
-
${payment.mode_of_payment}
+
${__(payment.mode_of_payment)}
${format_currency(payment.amount, doc.currency)}
`; } @@ -285,8 +285,9 @@ erpnext.PointOfSale.PastOrderSummary = class { if (m.condition) { m.visible_btns.forEach(b => { const class_name = b.split(' ')[0].toLowerCase(); + const btn = __(b); this.$summary_btns.append( - `
${b}
` + `
${btn}
` ); }); } diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index 7ddbf45fdb..b9b65591dc 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -18,11 +18,11 @@ erpnext.PointOfSale.Payment = class { prepare_dom() { this.wrapper.append( `
- +
- +
@@ -30,7 +30,7 @@ erpnext.PointOfSale.Payment = class {
-
Complete Order
+
${__("Complete Order")}
` ); this.$component = this.wrapper.find('.payment-container'); @@ -518,12 +518,12 @@ erpnext.PointOfSale.Payment = class { this.$totals.html( `
-
Grand Total
+
${__('Grand Total')}
${format_currency(grand_total, currency)}
-
Paid Amount
+
${__('Paid Amount')}
${format_currency(paid_amount, currency)}
From 73c56510d8fc197188612f1714144fb175af3960 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 22 Nov 2021 14:21:53 +0530 Subject: [PATCH 123/185] fix: Delete KSA vat setting on deleting company --- erpnext/hooks.py | 3 ++- erpnext/regional/saudi_arabia/utils.py | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 2a277ee035..e8aac1d199 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -306,7 +306,8 @@ doc_events = { 'validate': ["erpnext.erpnext_integrations.taxjar_integration.set_sales_tax"] }, "Company": { - "on_trash": "erpnext.regional.india.utils.delete_gst_settings_for_company" + "on_trash": ["erpnext.regional.india.utils.delete_gst_settings_for_company", + "erpnext.regional.saudi_arabia.utils.delete_vat_settings_for_company"] }, "Integration Request": { "validate": "erpnext.accounts.doctype.payment_request.payment_request.validate_payment" diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index cc6c0af7a5..031cf03d07 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -74,4 +74,11 @@ def delete_qr_code_file(doc, method): 'file_url': doc.get('qr_code') }) if len(file_doc): - frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file + frappe.delete_doc('File', file_doc[0].name) + +def delete_vat_settings_for_company(doc, method): + if doc.country != 'Saudi Arabia': + return + + settings_doc = frappe.get_doc('KSA VAT Setting', {'company': doc.name}) + settings_doc.delete() \ No newline at end of file From db9bd5b912e8d7072d6f362b64919a62a6f18f78 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 22 Nov 2021 17:55:48 +0530 Subject: [PATCH 124/185] feat: report to see the consumed materials against the work order --- .../work_order_consumed_materials/__init__.py | 0 .../work_order_consumed_materials.js | 70 +++++++++ .../work_order_consumed_materials.json | 30 ++++ .../work_order_consumed_materials.py | 131 +++++++++++++++++ .../manufacturing/manufacturing.json | 135 ++++++++++++++++-- 5 files changed, 351 insertions(+), 15 deletions(-) create mode 100644 erpnext/manufacturing/report/work_order_consumed_materials/__init__.py create mode 100644 erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js create mode 100644 erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json create mode 100644 erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/__init__.py b/erpnext/manufacturing/report/work_order_consumed_materials/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js new file mode 100644 index 0000000000..b2428e85b7 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js @@ -0,0 +1,70 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Work Order Consumed Materials"] = { + "filters": [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + reqd: 1 + }, + { + fieldname:"to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1 + }, + { + label: __("Work Order"), + fieldname: "name", + fieldtype: "Link", + options: "Work Order", + get_query: function() { + return { + filters: { + status: ["in", ["In Process", "Completed", "Stopped"]] + } + } + } + }, + { + label: __("Production Item"), + fieldname: "production_item", + fieldtype: "Link", + depends_on: "eval: !doc.name", + options: "Item" + }, + { + label: __("Status"), + fieldname: "status", + fieldtype: "Select", + options: ["In Process", "Completed", "Stopped"] + }, + { + label: __("Excess Materials Consumed"), + fieldname: "show_extra_consumed_materials", + fieldtype: "Check" + } + ], + "formatter": function(value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (column.fieldname == "raw_material_name" && data && data.extra_consumed_qty > 0 ) { + value = `
${value}
`; + } + + return value; + }, +}; diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json new file mode 100644 index 0000000000..2fc986aa36 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json @@ -0,0 +1,30 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-11-22 17:36:11.886939", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letter_head": "Gadgets International", + "modified": "2021-11-22 17:36:14.999091", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Work Order Consumed Materials", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Work Order", + "report_name": "Work Order Consumed Materials", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing User" + }, + { + "role": "Stock User" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py new file mode 100644 index 0000000000..8d2505ad95 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py @@ -0,0 +1,131 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +import json +from frappe import _ + +def execute(filters=None): + columns, data = [], [] + columns = get_columns() + data = get_data(filters) + + return columns, data + +def get_data(report_filters): + fields = get_fields() + filters = get_filter_condition(report_filters) + + wo_items = {} + for d in frappe.get_all("Work Order", filters = filters, fields=fields): + d.extra_consumed_qty = 0.0 + if d.consumed_qty and d.consumed_qty > d.required_qty: + d.extra_consumed_qty = d.consumed_qty - d.required_qty + + if d.extra_consumed_qty or not report_filters.show_extra_consumed_materials: + wo_items.setdefault((d.name, d.production_item), []).append(d) + + data = [] + for key, wo_data in wo_items.items(): + for index, row in enumerate(wo_data): + if index != 0: + #If one work order has multiple raw materials then show parent data in the first row only + for field in ["name", "status", "production_item", "qty", "produced_qty"]: + row[field] = "" + + data.append(row) + + return data + +def get_fields(): + return ["`tabWork Order Item`.`parent`", "`tabWork Order Item`.`item_code` as raw_material_item_code", + "`tabWork Order Item`.`item_name` as raw_material_name", "`tabWork Order Item`.`required_qty`", + "`tabWork Order Item`.`transferred_qty`", "`tabWork Order Item`.`consumed_qty`", "`tabWork Order`.`status`", + "`tabWork Order`.`name`", "`tabWork Order`.`production_item`", "`tabWork Order`.`qty`", + "`tabWork Order`.`produced_qty`"] + +def get_filter_condition(report_filters): + filters = { + "docstatus": 1, "status": ("in", ["In Process", "Completed", "Stopped"]), + "creation": ("between", [report_filters.from_date, report_filters.to_date]) + } + + for field in ["name", "production_item", "company", "status"]: + value = report_filters.get(field) + if value: + key = f"`{field}`" + filters.update({key: value}) + + return filters + +def get_columns(): + return [ + { + "label": _("Id"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Work Order", + "width": 80 + }, + { + "label": _("Status"), + "fieldname": "status", + "fieldtype": "Data", + "width": 80 + }, + { + "label": _("Production Item"), + "fieldname": "production_item", + "fieldtype": "Link", + "options": "Item", + "width": 130 + }, + { + "label": _("Qty to Produce"), + "fieldname": "qty", + "fieldtype": "Float", + "width": 120 + }, + { + "label": _("Produced Qty"), + "fieldname": "produced_qty", + "fieldtype": "Float", + "width": 110 + }, + { + "label": _("Raw Material Item"), + "fieldname": "raw_material_item_code", + "fieldtype": "Link", + "options": "Item", + "width": 150 + }, + { + "label": _("Item Name"), + "fieldname": "raw_material_name", + "width": 130 + }, + { + "label": _("Required Qty"), + "fieldname": "required_qty", + "fieldtype": "Float", + "width": 100 + }, + { + "label": _("Transferred Qty"), + "fieldname": "transferred_qty", + "fieldtype": "Float", + "width": 100 + }, + { + "label": _("Consumed Qty"), + "fieldname": "consumed_qty", + "fieldtype": "Float", + "width": 100 + }, + { + "label": _("Extra Consumed Qty"), + "fieldname": "extra_consumed_qty", + "fieldtype": "Float", + "width": 100 + } + ] \ No newline at end of file diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json index cfa80f8e9f..65b4d02639 100644 --- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json @@ -1,10 +1,6 @@ { - "charts": [ - { - "chart_name": "Produced Quantity" - } - ], - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Manufacturing\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Plan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Forecasting\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM Stock Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Planning Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Production\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bill of Materials\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", + "charts": [], + "content": "[{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order Summary\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]", "creation": "2020-03-02 17:11:37.032604", "docstatus": 0, "doctype": "Workspace", @@ -140,14 +136,6 @@ "onboard": 0, "type": "Link" }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Reports", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, { "dependencies": "Work Order", "hidden": 0, @@ -295,9 +283,126 @@ "link_type": "DocType", "onboard": 0, "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "link_count": 10, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "Production Planning Report", + "link_count": 0, + "link_to": "Production Planning Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "Work Order Summary", + "link_count": 0, + "link_to": "Work Order Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Quality Inspection", + "hidden": 0, + "is_query_report": 1, + "label": "Quality Inspection Summary", + "link_count": 0, + "link_to": "Quality Inspection Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Downtime Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Downtime Analysis", + "link_count": 0, + "link_to": "Downtime Analysis", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Job Card", + "hidden": 0, + "is_query_report": 1, + "label": "Job Card Summary", + "link_count": 0, + "link_to": "Job Card Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "BOM Search", + "link_count": 0, + "link_to": "BOM Search", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "BOM Stock Report", + "link_count": 0, + "link_to": "BOM Stock Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "Production Analytics", + "link_count": 0, + "link_to": "Production Analytics", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "BOM Operations Time", + "link_count": 0, + "link_to": "BOM Operations Time", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Work Order Consumed Materials", + "link_count": 0, + "link_to": "Work Order Consumed Materials", + "link_type": "Report", + "onboard": 0, + "type": "Link" } ], - "modified": "2021-08-05 12:16:00.825742", + "modified": "2021-11-22 17:55:03.524496", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", From e7eef9601d2348094d16a92736c30aca8686734e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 22 Nov 2021 20:13:15 +0530 Subject: [PATCH 125/185] fix(UX): Allocated Amount in Advances not updated on updating expense amount in Expense Claim (#28497) * fix(UX): Allocated Amount in Advances not updated on updating expense amount in Expense Claim * fix: Expense Claim Advance form labels --- erpnext/hr/doctype/expense_claim/expense_claim.js | 2 ++ erpnext/hr/doctype/expense_claim/expense_claim.json | 3 ++- .../expense_claim_advance/expense_claim_advance.json | 7 ++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 218e97d7fc..665556301b 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -389,7 +389,9 @@ frappe.ui.form.on("Expense Claim Detail", { sanctioned_amount: function(frm, cdt, cdn) { cur_frm.cscript.calculate_total(frm.doc, cdt, cdn); frm.trigger("get_taxes"); + frm.trigger("calculate_grand_total"); }, + cost_center: function(frm, cdt, cdn) { erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "expenses", "cost_center"); } diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index a268c15c70..45b78bfb54 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -379,11 +379,12 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-04 05:35:12.040199", + "modified": "2021-11-22 16:26:57.787838", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json b/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json index 45509257c1..aa479c8308 100644 --- a/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json +++ b/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2017-10-09 16:53:26.410762", "doctype": "DocType", "document_type": "Document", @@ -50,7 +51,7 @@ "fieldname": "unclaimed_amount", "fieldtype": "Currency", "in_list_view": 1, - "label": "Unclaimed amount", + "label": "Unclaimed Amount", "no_copy": 1, "oldfieldname": "advance_amount", "oldfieldtype": "Currency", @@ -65,7 +66,7 @@ "fieldname": "allocated_amount", "fieldtype": "Currency", "in_list_view": 1, - "label": "Allocated amount", + "label": "Allocated Amount", "no_copy": 1, "oldfieldname": "allocated_amount", "oldfieldtype": "Currency", @@ -87,7 +88,7 @@ ], "istable": 1, "links": [], - "modified": "2019-12-17 13:53:22.111766", + "modified": "2021-11-22 16:33:58.515819", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim Advance", From 5ba1bc15728541f7251c0b643152eeeb34166d85 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 23 Nov 2021 09:30:30 +0530 Subject: [PATCH 126/185] fix: Employee link formatter showing incorrect value for Employee Name (#28504) --- erpnext/public/js/utils.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 0323a426f0..f0facdd3a1 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -751,9 +751,13 @@ frappe.form.link_formatters['Item'] = function(value, doc) { } frappe.form.link_formatters['Employee'] = function(value, doc) { - if(doc && doc.employee_name && doc.employee_name !== value) { - return value? value + ': ' + doc.employee_name: doc.employee_name; + if (doc && value && doc.employee_name && doc.employee_name !== value && doc.employee === value) { + return value + ': ' + doc.employee_name; + } else if (!value && doc.doctype && doc.employee_name) { + // format blank value in child table + return doc.employee; } else { + // if value is blank in report view or project name and name are the same, return as is return value; } } From 1909bb569ec2652a89cc6bf063bb93824b1eab96 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Tue, 23 Nov 2021 10:35:43 +0530 Subject: [PATCH 127/185] fix: POS Item cart only taxes with amount displayed (#28501) --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index b652fdcb35..a5b2d50041 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -502,6 +502,7 @@ erpnext.PointOfSale.ItemCart = class { if (taxes.length) { const currency = this.events.get_frm().doc.currency; const taxes_html = taxes.map(t => { + if (t.tax_amount_after_discount_amount == 0.0) return; const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`; return `
${description}
From 5ef23300229a6af013d4599c8af07dd8889599ce Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 23 Nov 2021 12:28:41 +0530 Subject: [PATCH 128/185] fix: Replace 'parent' with 'invoice_or_item' (#28513) --- erpnext/accounts/report/gross_profit/gross_profit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 20bc3ec115..84effc0f46 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -265,7 +265,7 @@ class GrossProfitGenerator(object): if self.filters.get("group_by") == "Invoice": self.totals.indent = 0.0 self.totals.parent_invoice = "" - self.totals.parent = "Total" + self.totals.invoice_or_item = "Total" self.si_list.append(self.totals) else: self.grouped_data.append(self.totals) From 0df9cf9526ea440044a6c3ffc10a778ea4d6050b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 23 Nov 2021 15:08:51 +0530 Subject: [PATCH 129/185] chore: remove dead/irrelevant links from workspace (#28519) * chore: remove dead link to shopify settings * chore: unlink "debug" reports from stock dashboard --- .../erpnext_integrations_settings.json | 15 +----- erpnext/stock/workspace/stock/stock.json | 52 +------------------ 2 files changed, 3 insertions(+), 64 deletions(-) diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json index 5fe5afa2c4..5efafd67fe 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json @@ -29,17 +29,6 @@ "onboard": 0, "type": "Link" }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Shopify Settings", - "link_count": 0, - "link_to": "Shopify Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "dependencies": "", "hidden": 0, @@ -74,7 +63,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:58.951705", + "modified": "2021-11-23 04:30:33.106991", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "ERPNext Integrations Settings", @@ -86,4 +75,4 @@ "sequence_id": 11, "shortcuts": [], "title": "ERPNext Integrations Settings" -} +} \ No newline at end of file diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json index 9c805150f1..4df27f5dbf 100644 --- a/erpnext/stock/workspace/stock/stock.json +++ b/erpnext/stock/workspace/stock/stock.json @@ -704,59 +704,9 @@ "link_type": "Report", "onboard": 0, "type": "Link" - }, - { - "dependencies": "Stock Ledger Entry", - "hidden": 0, - "is_query_report": 1, - "label": "Stock and Account Value Comparison", - "link_count": 0, - "link_to": "Stock and Account Value Comparison", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Incorrect Data Report", - "link_count": 0, - "link_type": "DocType", - "onboard": 0, - "type": "Card Break" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Incorrect Serial No Qty and Valuation", - "link_count": 0, - "link_to": "Incorrect Serial No Valuation", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Incorrect Balance Qty After Transaction", - "link_count": 0, - "link_to": "Incorrect Balance Qty After Transaction", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Stock and Account Value Comparison", - "link_count": 0, - "link_to": "Stock and Account Value Comparison", - "link_type": "Report", - "onboard": 0, - "type": "Link" } ], - "modified": "2021-08-05 12:16:02.361519", + "modified": "2021-11-23 04:34:00.420870", "modified_by": "Administrator", "module": "Stock", "name": "Stock", From 34d1fec7ded836fb683a0a79293e1641cf6d674f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 17:53:26 +0530 Subject: [PATCH 130/185] fix: broken bom tree view and remove duplicate button (#28512) (#28527) * fix: broken bom tree view and remove duplicate button (cherry picked from commit 7ae1369d64ecaa829965d89915475fbd3211b5c2) Co-authored-by: Bhavesh Maheshwari <34086262+bhavesh95863@users.noreply.github.com> Co-authored-by: Ankush Menat --- .../doctype/bom/bom_item_preview.html | 17 +++-------------- erpnext/manufacturing/doctype/bom/bom_tree.js | 1 + 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom_item_preview.html b/erpnext/manufacturing/doctype/bom/bom_item_preview.html index e614a7ebaa..eb4135e03a 100644 --- a/erpnext/manufacturing/doctype/bom/bom_item_preview.html +++ b/erpnext/manufacturing/doctype/bom/bom_item_preview.html @@ -16,26 +16,15 @@

- {% if data.value %} - + {% if data.value && data.value != "BOM" %} + {{ __("Open BOM {0}", [data.value.bold()]) }} {% endif %} {% if data.item_code %} - + {{ __("Open Item {0}", [data.item_code.bold()]) }} {% endif %}

-
-

- {% if data.value %} - - {{ __("Open BOM {0}", [data.value.bold()]) }} - {% endif %} - {% if data.item_code %} - - {{ __("Open Item {0}", [data.item_code.bold()]) }} - {% endif %} -

diff --git a/erpnext/manufacturing/doctype/bom/bom_tree.js b/erpnext/manufacturing/doctype/bom/bom_tree.js index 6e2599e41b..fb99add12c 100644 --- a/erpnext/manufacturing/doctype/bom/bom_tree.js +++ b/erpnext/manufacturing/doctype/bom/bom_tree.js @@ -66,6 +66,7 @@ frappe.treeview_settings["BOM"] = { var bom = frappe.model.get_doc("BOM", node.data.value); node.data.image = escape(bom.image) || ""; node.data.description = bom.description || ""; + node.data.item_code = bom.item || ""; }); } }, From ab2c1f62a1b598529fd279aa56ecab23ccfd1dc3 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 23 Nov 2021 18:21:00 +0530 Subject: [PATCH 131/185] fix: correct module for reloading doc (#28523) --- .../item_reposting_for_incorrect_sl_and_gl.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index e4cb9ae7cd..0f2ac4b451 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -6,10 +6,19 @@ from erpnext.stock.stock_ledger import update_entries_after def execute(): - for doctype in ('repost_item_valuation', 'stock_entry_detail', 'purchase_receipt_item', - 'purchase_invoice_item', 'delivery_note_item', 'sales_invoice_item', 'packed_item'): - frappe.reload_doc('stock', 'doctype', doctype) - frappe.reload_doc('buying', 'doctype', 'purchase_receipt_item_supplied') + doctypes_to_reload = [ + ("stock", "repost_item_valuation"), + ("stock", "stock_entry_detail"), + ("stock", "purchase_receipt_item"), + ("stock", "delivery_note_item"), + ("stock", "packed_item"), + ("accounts", "sales_invoice_item"), + ("accounts", "purchase_invoice_item"), + ("buying", "purchase_receipt_item_supplied") + ] + + for module, doctype in doctypes_to_reload: + frappe.reload_doc(module, 'doctype', doctype) reposting_project_deployed_on = get_creation_time() posting_date = getdate(reposting_project_deployed_on) From 8d2abc4b86fe7d029f6e02eedb776ca0fbdbc35f Mon Sep 17 00:00:00 2001 From: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> Date: Wed, 24 Nov 2021 16:23:12 +0530 Subject: [PATCH 132/185] fix: fixes in work order doctype (#28217) * fix: fixes in work order doctype * fix: sider issues and disabled set only once property * fix: set default qty to manufacture * fix: dont manually collapse sections * fix: remove unnecessary messages * fix: make dependent fields read only Co-authored-by: Ankush Menat --- .../doctype/work_order/work_order.js | 2 +- .../doctype/work_order/work_order.json | 6 +- .../work_order_operation.json | 68 ++++++++----------- 3 files changed, 34 insertions(+), 42 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index bfce1b8cbe..5ffbb0374e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -442,7 +442,7 @@ frappe.ui.form.on("Work Order", { additional_operating_cost: function(frm) { erpnext.work_order.calculate_cost(frm.doc); erpnext.work_order.calculate_total_cost(frm); - } + }, }); frappe.ui.form.on("Work Order Item", { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index df7ee53b92..12cd58f418 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -326,6 +326,7 @@ "label": "Expected Delivery Date" }, { + "collapsible": 1, "fieldname": "operations_section", "fieldtype": "Section Break", "label": "Operations", @@ -337,7 +338,7 @@ "fieldname": "transfer_material_against", "fieldtype": "Select", "label": "Transfer Material Against", - "options": "\nWork Order\nJob Card" + "options": "Work Order\nJob Card" }, { "fieldname": "operations", @@ -573,8 +574,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "migration_hash": "a18118963f4fcdb7f9d326de5f4063ba", - "modified": "2021-10-29 15:12:32.203605", + "modified": "2021-11-08 17:36:07.016300", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index f7b8787a0b..647c14b33d 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -6,27 +6,27 @@ "field_order": [ "details", "operation", - "bom", - "column_break_4", - "description", - "sequence_id", - "col_break1", - "completed_qty", "status", + "completed_qty", + "column_break_4", + "bom", "workstation", + "sequence_id", + "section_break_10", + "description", "estimated_time_and_cost", "planned_start_time", - "planned_end_time", - "column_break_10", - "time_in_mins", "hour_rate", + "time_in_mins", + "column_break_10", + "planned_end_time", "batch_size", "planned_operating_cost", "section_break_9", "actual_start_time", - "actual_end_time", - "column_break_11", "actual_operation_time", + "column_break_11", + "actual_end_time", "actual_operating_cost" ], "fields": [ @@ -42,7 +42,6 @@ "oldfieldname": "operation_no", "oldfieldtype": "Data", "options": "Operation", - "read_only": 1, "reqd": 1 }, { @@ -52,20 +51,14 @@ "label": "BOM", "no_copy": 1, "options": "BOM", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "description", "fieldtype": "Text Editor", "label": "Operation Description", "oldfieldname": "opn_description", - "oldfieldtype": "Text", - "read_only": 1 - }, - { - "fieldname": "col_break1", - "fieldtype": "Column Break" + "oldfieldtype": "Text" }, { "columns": 1, @@ -74,19 +67,16 @@ "fieldtype": "Float", "in_list_view": 1, "label": "Completed Qty", - "no_copy": 1, - "read_only": 1 + "no_copy": 1 }, { "columns": 1, "default": "Pending", "fieldname": "status", "fieldtype": "Select", - "in_list_view": 1, "label": "Status", "no_copy": 1, - "options": "Pending\nWork in Progress\nCompleted", - "read_only": 1 + "options": "Pending\nWork in Progress\nCompleted" }, { "fieldname": "workstation", @@ -106,15 +96,13 @@ "fieldname": "planned_start_time", "fieldtype": "Datetime", "label": "Planned Start Time", - "no_copy": 1, - "read_only": 1 + "no_copy": 1 }, { "fieldname": "planned_end_time", "fieldtype": "Datetime", "label": "Planned End Time", - "no_copy": 1, - "read_only": 1 + "no_copy": 1 }, { "fieldname": "column_break_10", @@ -122,7 +110,7 @@ }, { "columns": 1, - "description": "in Minutes", + "description": "In Minutes", "fieldname": "time_in_mins", "fieldtype": "Float", "in_list_view": 1, @@ -152,6 +140,7 @@ "label": "Actual Time and Cost" }, { + "description": "Updated via 'Time Log' (In Minutes)", "fieldname": "actual_start_time", "fieldtype": "Datetime", "label": "Actual Start Time", @@ -159,7 +148,7 @@ "read_only": 1 }, { - "description": "Updated via 'Time Log'", + "description": "Updated via 'Time Log' (In Minutes)", "fieldname": "actual_end_time", "fieldtype": "Datetime", "label": "Actual End Time", @@ -171,7 +160,7 @@ "fieldtype": "Column Break" }, { - "description": "in Minutes\nUpdated via 'Time Log'", + "description": "Updated via 'Time Log' (In Minutes)", "fieldname": "actual_operation_time", "fieldtype": "Float", "label": "Actual Operation Time", @@ -190,25 +179,28 @@ { "fieldname": "batch_size", "fieldtype": "Int", - "label": "Batch Size", - "read_only": 1 + "label": "Batch Size" }, { "fieldname": "sequence_id", "fieldtype": "Int", + "hidden": 1, "label": "Sequence ID", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "column_break_4", "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-24 14:36:12.835543", + "modified": "2021-11-24 04:52:54.295168", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", @@ -217,4 +209,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file From 42395af22ad3f55a7bbef64f1202768abca77042 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 25 Nov 2021 13:28:52 +0530 Subject: [PATCH 133/185] fix: Remove commented code --- .../cost_of_poor_quality_report.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py index e6666f00bf..77418235b0 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -29,7 +29,6 @@ def get_data(report_filters): for row in job_cards: row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0) - # update_raw_material_cost(row, report_filters) data.append(row) return data @@ -45,14 +44,6 @@ def get_filters(report_filters, operations): return filters -# Check PR #28123 as to why this is commented - -# def update_raw_material_cost(row, filters): -# row.rm_cost = 0.0 -# for data in frappe.get_all("Job Card Item", fields = ["amount"], -# filters={"parent": row.name, "docstatus": 1}): -# row.rm_cost += data.amount - def get_columns(filters): return [ { @@ -114,12 +105,6 @@ def get_columns(filters): "fieldname": "operating_cost", "width": "150" }, - # { - # "label": _("Raw Material Cost"), - # "fieldtype": "Currency", - # "fieldname": "rm_cost", - # "width": "100" - # }, { "label": _("Total Time (in Mins)"), "fieldtype": "Float", From 5ba3b28d69c88675f41830b8a490882f5298dc07 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 25 Nov 2021 15:42:30 +0530 Subject: [PATCH 134/185] fix(refactor): Advance tds allocation to purchase invoice --- .../accounts/doctype/advance_tax/__init__.py | 0 .../doctype/advance_tax/advance_tax.json | 56 +++++++++++++ .../doctype/advance_tax/advance_tax.py | 9 ++ .../doctype/payment_entry/payment_entry.json | 12 +-- .../doctype/payment_entry/payment_entry.py | 33 +++----- .../purchase_invoice/purchase_invoice.json | 11 ++- .../purchase_invoice/purchase_invoice.py | 42 +++++++++- .../doctype/sales_invoice/sales_invoice.py | 2 - .../tax_withholding_category.py | 33 +++++++- erpnext/accounts/general_ledger.py | 20 +++++ erpnext/controllers/accounts_controller.py | 82 +------------------ 11 files changed, 179 insertions(+), 121 deletions(-) create mode 100644 erpnext/accounts/doctype/advance_tax/__init__.py create mode 100644 erpnext/accounts/doctype/advance_tax/advance_tax.json create mode 100644 erpnext/accounts/doctype/advance_tax/advance_tax.py diff --git a/erpnext/accounts/doctype/advance_tax/__init__.py b/erpnext/accounts/doctype/advance_tax/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/advance_tax/advance_tax.json b/erpnext/accounts/doctype/advance_tax/advance_tax.json new file mode 100644 index 0000000000..68706aba88 --- /dev/null +++ b/erpnext/accounts/doctype/advance_tax/advance_tax.json @@ -0,0 +1,56 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2021-11-25 10:24:39.836195", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "reference_type", + "reference_name", + "reference_detail", + "account_head", + "allocated_amount" + ], + "fields": [ + { + "fieldname": "reference_type", + "fieldtype": "Link", + "label": "Reference Type", + "options": "DocType" + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "label": "Reference Name", + "options": "reference_type" + }, + { + "fieldname": "reference_detail", + "fieldtype": "Data", + "label": "Reference Detail" + }, + { + "fieldname": "account_head", + "fieldtype": "Link", + "label": "Account Head", + "options": "Account" + }, + { + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "label": "Allocated Amount", + "options": "party_account_currency" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-11-25 10:27:51.712286", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Advance Tax", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/advance_tax/advance_tax.py b/erpnext/accounts/doctype/advance_tax/advance_tax.py new file mode 100644 index 0000000000..2e784efd8f --- /dev/null +++ b/erpnext/accounts/doctype/advance_tax/advance_tax.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class AdvanceTax(Document): + pass diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index ee2e319a6f..c8d1db91f5 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -61,7 +61,6 @@ "taxes_and_charges_section", "purchase_taxes_and_charges_template", "sales_taxes_and_charges_template", - "advance_tax_account", "column_break_55", "apply_tax_withholding_amount", "tax_withholding_category", @@ -685,15 +684,6 @@ "fieldtype": "Section Break", "hide_border": 1 }, - { - "depends_on": "eval:doc.apply_tax_withholding_amount", - "description": "Provisional tax account for advance tax. Taxes are parked in this account until payments are allocated to invoices", - "fieldname": "advance_tax_account", - "fieldtype": "Link", - "label": "Advance Tax Account", - "mandatory_depends_on": "eval:doc.apply_tax_withholding_amount", - "options": "Account" - }, { "depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'", "fieldname": "received_amount_after_tax", @@ -730,7 +720,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-10-22 17:50:24.632806", + "modified": "2021-11-24 18:58:24.919764", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 26fd16a4fd..e524592382 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -20,7 +20,7 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_ban from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) -from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map from erpnext.accounts.party import get_party_account from erpnext.accounts.utils import get_account_currency, get_balance_on, get_outstanding_invoices from erpnext.controllers.accounts_controller import ( @@ -433,9 +433,6 @@ class PaymentEntry(AccountsController): if not self.apply_tax_withholding_amount: return - if not self.advance_tax_account: - frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction")) - net_total = self.paid_amount for reference in self.get("references"): @@ -455,13 +452,12 @@ class PaymentEntry(AccountsController): 'net_total': net_total }) - tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category) + tax_withholding_details, tax_deducted_on_advances = get_party_tax_withholding_details(args, self.tax_withholding_category) if not tax_withholding_details: return tax_withholding_details.update({ - 'add_deduct_tax': 'Add', 'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company) }) @@ -689,6 +685,7 @@ class PaymentEntry(AccountsController): self.add_deductions_gl_entries(gl_entries) self.add_tax_gl_entries(gl_entries) + gl_entries = process_gl_map(gl_entries) make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj) def add_party_gl_entries(self, gl_entries): @@ -752,7 +749,8 @@ class PaymentEntry(AccountsController): "against": self.party if self.payment_type=="Pay" else self.paid_to, "credit_in_account_currency": self.paid_amount, "credit": self.base_paid_amount, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "post_net_value": True }, item=self) ) if self.payment_type in ("Receive", "Internal Transfer"): @@ -782,14 +780,10 @@ class PaymentEntry(AccountsController): rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit" against = self.party or self.paid_to - payment_or_advance_account = self.get_party_account_for_taxes() + payment_account = self.get_party_account_for_taxes() tax_amount = d.tax_amount base_tax_amount = d.base_tax_amount - if self.advance_tax_account: - tax_amount = -1 * tax_amount - base_tax_amount = -1 * base_tax_amount - gl_entries.append( self.get_gl_dict({ "account": d.account_head, @@ -798,19 +792,21 @@ class PaymentEntry(AccountsController): dr_or_cr + "_in_account_currency": base_tax_amount if account_currency==self.company_currency else d.tax_amount, - "cost_center": d.cost_center + "cost_center": d.cost_center, + "post_net_value": True, }, account_currency, item=d)) - if not d.included_in_paid_amount or self.advance_tax_account: + if not d.included_in_paid_amount: gl_entries.append( self.get_gl_dict({ - "account": payment_or_advance_account, + "account": payment_account, "against": against, rev_dr_or_cr: tax_amount, rev_dr_or_cr + "_in_account_currency": base_tax_amount if account_currency==self.company_currency else d.tax_amount, "cost_center": self.cost_center, + "post_net_value": True, }, account_currency, item=d)) def add_deductions_gl_entries(self, gl_entries): @@ -832,9 +828,7 @@ class PaymentEntry(AccountsController): ) def get_party_account_for_taxes(self): - if self.advance_tax_account: - return self.advance_tax_account - elif self.payment_type == 'Receive': + if self.payment_type == 'Receive': return self.paid_to elif self.payment_type in ('Pay', 'Internal Transfer'): return self.paid_from @@ -1603,9 +1597,6 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.apply_tax_withholding_amount = 1 pe.tax_withholding_category = doc.tax_withholding_category - if not pe.advance_tax_account: - pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account') - return pe def get_bank_cash_account(doc, bank_account): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 03cbc4acbc..bd0116443f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -130,6 +130,7 @@ "allocate_advances_automatically", "get_advances", "advances", + "advance_tax", "payment_schedule_section", "payment_terms_template", "ignore_default_payment_terms_template", @@ -1408,13 +1409,21 @@ { "fieldname": "column_break_147", "fieldtype": "Column Break" + }, + { + "fieldname": "advance_tax", + "fieldtype": "Table", + "hidden": 1, + "label": "Advance Tax", + "options": "Advance Tax", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-10-12 20:55:16.145651", + "modified": "2021-11-25 13:31:02.716727", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 62e3dc8346..516133ab63 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -427,6 +427,7 @@ class PurchaseInvoice(BuyingController): self.update_project() update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) + self.update_advance_tax_references() self.process_common_party_accounting() @@ -472,8 +473,6 @@ class PurchaseInvoice(BuyingController): self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) - self.allocate_advance_taxes(gl_entries) - gl_entries = make_regional_gl_entries(gl_entries, self) gl_entries = merge_similar_entries(gl_entries) @@ -1074,6 +1073,7 @@ class PurchaseInvoice(BuyingController): unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') + self.update_advance_tax_references(cancel=1) def update_project(self): project_list = [] @@ -1150,7 +1150,10 @@ class PurchaseInvoice(BuyingController): if not self.tax_withholding_category: return - tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category) + tax_withholding_details, advance_taxes = get_party_tax_withholding_details(self, self.tax_withholding_category) + + # Adjust TDS paid on advances + self.allocate_advance_tds(tax_withholding_details, advance_taxes) if not tax_withholding_details: return @@ -1174,6 +1177,39 @@ class PurchaseInvoice(BuyingController): # calculate totals again after applying TDS self.calculate_taxes_and_totals() + def allocate_advance_tds(self, tax_withholding_details, advance_taxes): + self.set('advance_tax', []) + for tax in advance_taxes: + allocated_amount = 0 + pending_amount = flt(tax.tax_amount - tax.allocated_amount) + if flt(tax_withholding_details.get('tax_amount')) >= pending_amount: + tax_withholding_details['tax_amount'] -= pending_amount + allocated_amount = pending_amount + elif flt(tax_withholding_details.get('tax_amount')) and flt(tax_withholding_details.get('tax_amount')) < pending_amount: + allocated_amount = tax_withholding_details['tax_amount'] + tax_withholding_details['tax_amount'] = 0 + + self.append('advance_tax', { + 'reference_type': 'Payment Entry', + 'reference_name': tax.parent, + 'reference_detail': tax.name, + 'account_head': tax.account_head, + 'allocated_amount': allocated_amount + }) + + def update_advance_tax_references(self, cancel=0): + for tax in self.get('advance_tax'): + at = frappe.qb.DocType("Advance Taxes and Charges").as_("at") + + if cancel: + frappe.qb.update(at).set( + at.allocated_amount, at.allocated_amount - tax.allocated_amount + ).where(at.name == tax.reference_detail).run() + else: + frappe.qb.update(at).set( + at.allocated_amount, at.allocated_amount + tax.allocated_amount + ).where(at.name == tax.reference_detail).run() + def set_status(self, update=False, status=None, update_modified=True): if self.is_new(): if self.get('amended_from'): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 59d46fc2e8..c4d59f1ef7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -842,8 +842,6 @@ class SalesInvoice(SellingController): self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) - self.allocate_advance_taxes(gl_entries) - self.make_item_gl_entries(gl_entries) self.make_discount_gl_entries(gl_entries) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index dc1818a052..fe156cb029 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -95,7 +95,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.') .format(tax_withholding_category, inv.company, party)) - tax_amount, tax_deducted = get_tax_amount( + tax_amount, tax_deducted, tax_deducted_on_advances = get_tax_amount( party_type, parties, inv, tax_details, posting_date, pan_no @@ -106,7 +106,10 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): else: tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted) - return tax_row + if inv.doctype == 'Purchase Invoice': + return tax_row, tax_deducted_on_advances + else: + return tax_row def get_tax_withholding_details(tax_withholding_category, posting_date, company): tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category) @@ -194,6 +197,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date, to_date=tax_details.to_date, party_type=party_type) taxable_vouchers = vouchers + advance_vouchers + tax_deducted_on_advances = get_taxes_deducted_on_advances_allocated(inv, tax_details) tax_deducted = 0 if taxable_vouchers: @@ -223,7 +227,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N if cint(tax_details.round_off_tax_amount): tax_amount = round(tax_amount) - return tax_amount, tax_deducted + return tax_amount, tax_deducted, tax_deducted_on_advances def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'): dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit' @@ -281,6 +285,29 @@ def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, pa return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""] +def get_taxes_deducted_on_advances_allocated(inv, tax_details): + advances = [d.reference_name for d in inv.get('advances')] + tax_info = [] + + if advances: + pe = frappe.qb.DocType("Payment Entry").as_("pe") + at = frappe.qb.DocType("Advance Taxes and Charges").as_("at") + + tax_info = frappe.qb.from_(at).inner_join(pe).on( + pe.name == at.parent + ).select( + at.parent, at.name, at.tax_amount, at.allocated_amount + ).where( + pe.tax_withholding_category == tax_details.get('tax_withholding_category') + ).where( + at.parent.isin(advances) + ).where( + at.account_head == tax_details.account_head + ).run(as_dict=True) + + return tax_info + + def get_deducted_tax(taxable_vouchers, tax_details): # check if TDS / TCS account is already charged on taxable vouchers filters = { diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 8ef7d7e103..1836db6477 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -73,8 +73,28 @@ def process_gl_map(gl_map, merge_entries=True, precision=None): flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency) entry.credit_in_account_currency = 0.0 + update_net_values(entry) + return gl_map +def update_net_values(entry): + # In some scenarios net value needs to be shown in the ledger + # This method updates net values as debit or credit + if entry.post_net_value and entry.debit and entry.credit: + if entry.debit > entry.credit: + entry.debit = entry.debit - entry.credit + entry.debit_in_account_currency = entry.debit_in_account_currency \ + - entry.credit_in_account_currency + entry.credit = 0 + entry.credit_in_account_currency = 0 + else: + entry.credit = entry.credit - entry.debit + entry.credit_in_account_currency = entry.credit_in_account_currency \ + - entry.debit_in_account_currency + + entry.debit = 0 + entry.debit_in_account_currency = 0 + def merge_similar_entries(gl_map, precision=None): merged_gl_map = [] accounting_dimensions = get_accounting_dimensions() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 77503a8ee5..9cf2e33916 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -524,7 +524,8 @@ class AccountsController(TransactionBase): 'is_opening': self.get("is_opening") or "No", 'party_type': None, 'party': None, - 'project': self.get("project") + 'project': self.get("project"), + 'post_net_value': args.get('post_net_value') }) accounting_dimensions = get_accounting_dimensions() @@ -805,7 +806,6 @@ class AccountsController(TransactionBase): from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries if self.doctype in ["Sales Invoice", "Purchase Invoice"]: - self.update_allocated_advance_taxes_on_cancel() if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'): unlink_ref_doc_from_payment_entries(self) @@ -853,29 +853,6 @@ class AccountsController(TransactionBase): return tax_map - def update_allocated_advance_taxes_on_cancel(self): - if self.get('advances'): - tax_accounts = [d.account_head for d in self.get('taxes')] - allocated_tax_map = frappe._dict(frappe.get_all('GL Entry', fields=['account', 'sum(credit - debit)'], - filters={'voucher_no': self.name, 'account': ('in', tax_accounts)}, - group_by='account', as_list=1)) - - tax_map = self.get_tax_map() - - for pe in self.get('advances'): - if pe.reference_type == 'Payment Entry': - pe = frappe.get_doc('Payment Entry', pe.reference_name) - for tax in pe.get('taxes'): - allocated_amount = flt(tax_map.get(tax.account_head)) - flt(allocated_tax_map.get(tax.account_head)) - if allocated_amount > tax.tax_amount: - allocated_amount = tax.tax_amount - - if allocated_amount: - frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', - tax.allocated_amount - allocated_amount) - tax_map[tax.account_head] -= allocated_amount - allocated_tax_map[tax.account_head] -= allocated_amount - def get_amount_and_base_amount(self, item, enable_discount_accounting): amount = item.net_amount base_amount = item.base_net_amount @@ -959,61 +936,6 @@ class AccountsController(TransactionBase): }, item=self) ) - def allocate_advance_taxes(self, gl_entries): - tax_map = self.get_tax_map() - for pe in self.get("advances"): - if pe.reference_type == "Payment Entry" and \ - frappe.db.get_value('Payment Entry', pe.reference_name, 'advance_tax_account'): - pe = frappe.get_doc("Payment Entry", pe.reference_name) - advance_tax_account = pe.advance_tax_account - - for tax in pe.get("taxes"): - account_currency = get_account_currency(tax.account_head) - - if self.doctype == "Purchase Invoice": - dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - advance_tax_account = pe.advance_tax_account if pe.paid_from != pe.advance_tax_account \ - else self.credit_to - else: - dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - - party = self.supplier if self.doctype == "Purchase Invoice" else self.customer - unallocated_amount = tax.tax_amount - tax.allocated_amount - if tax_map.get(tax.account_head): - amount = tax_map.get(tax.account_head) - if amount < unallocated_amount: - unallocated_amount = amount - - gl_entries.append( - self.get_gl_dict({ - "account": tax.account_head, - "against": party, - dr_or_cr: unallocated_amount, - dr_or_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - "cost_center": tax.cost_center - }, account_currency, item=tax)) - - gl_entries.append( - self.get_gl_dict({ - "account": advance_tax_account, - "against": party, - rev_dr_cr: unallocated_amount, - rev_dr_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - "cost_center": self.get('cost_center') if advance_tax_account == self.get('credit_to') else tax.cost_center, - "party_type": 'Supplier' if advance_tax_account == self.get('credit_to') else '', - "party": self.get('supplier') if advance_tax_account == self.get('credit_to') else '', - }, account_currency, item=tax)) - - frappe.db.set_value("Advance Taxes and Charges", tax.name, "allocated_amount", - tax.allocated_amount + unallocated_amount) - - tax_map[tax.account_head] -= unallocated_amount def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): from erpnext.controllers.status_updater import get_allowance_for From 6dc9b822bce20fc4520f6f5fe597a20c554ba025 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 28 Oct 2021 15:53:18 +0530 Subject: [PATCH 135/185] refactor: item-wh wise reposting by default --- erpnext/controllers/stock_controller.py | 38 ++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index aba15b47e3..6431388208 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -544,7 +544,7 @@ class StockController(AccountsController): "company": self.company }) if future_sle_exists(args): - create_repost_item_valuation_entry(args) + create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name) @frappe.whitelist() def make_quality_inspections(doctype, docname, items): @@ -679,3 +679,39 @@ def create_repost_item_valuation_entry(args): repost_entry.flags.ignore_permissions = True repost_entry.save() repost_entry.submit() + + +def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=False): + """Using a voucher create repost item valuation records for all item-warehouse pairs.""" + + stock_ledger_entries = frappe.db.get_all("Stock Ledger Entry", + filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, + fields=["item_code", "warehouse", "posting_date", "posting_time", "creation", "company"], + order_by="creation asc", + group_by="item_code, warehouse" + ) + distinct_item_warehouses = set() + + repost_entries = [] + + for sle in stock_ledger_entries: + item_wh = (sle.item_code, sle.warehouse) + if item_wh in distinct_item_warehouses: + continue + distinct_item_warehouses.add(item_wh) + + repost_entry = frappe.new_doc("Repost Item Valuation") + repost_entry.based_on = "Item and Warehouse" + repost_entry.voucher_type = voucher_type + repost_entry.voucher_no = voucher_no + + repost_entry.item_code = sle.item_code + repost_entry.warehouse = sle.warehouse + repost_entry.posting_date = sle.posting_date + repost_entry.posting_time = sle.posting_time + repost_entry.allow_zero_rate = allow_zero_rate + repost_entry.flags.ignore_links = True + repost_entry.submit() + repost_entries.append(repost_entry) + + return repost_entries From a36c249d3dcb413a4dff3d9552b6eaadf56d844e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 28 Oct 2021 16:03:37 +0530 Subject: [PATCH 136/185] test: item-wh repost creation --- erpnext/controllers/stock_controller.py | 1 - .../test_repost_item_valuation.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 6431388208..d241f3f7d0 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -691,7 +691,6 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa group_by="item_code, warehouse" ) distinct_item_warehouses = set() - repost_entries = [] for sle in stock_ledger_entries: diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index c086f938b5..7abda61a83 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -5,6 +5,8 @@ import unittest import frappe +from erpnext.controllers.stock_controller import create_item_wise_repost_entries +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import ( in_configured_timeslot, ) @@ -70,3 +72,15 @@ class TestRepostItemValuation(unittest.TestCase): in_configured_timeslot(repost_settings, case.get("current_time")), msg=f"Exepcted false from : {case}", ) + + def test_create_item_wise_repost_item_valuation_entries(self): + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", get_multiple_items = True) + + rivs = create_item_wise_repost_entries(pr.doctype, pr.name) + self.assertGreaterEqual(len(rivs), 2) + self.assertIn("_Test Item", [d.item_code for d in rivs]) + + for riv in rivs: + self.assertEqual(riv.company, "_Test Company with perpetual inventory") + self.assertEqual(riv.warehouse, "Stores - TCP1") From d220e08ba4c55c334a5586481dec192c4896ea13 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 28 Oct 2021 17:47:00 +0530 Subject: [PATCH 137/185] refactor: reuse get_items_to_be_repost function --- erpnext/controllers/stock_controller.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index d241f3f7d0..fe5d0c70cf 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -17,7 +17,7 @@ from erpnext.accounts.general_ledger import ( from erpnext.accounts.utils import get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController from erpnext.stock import get_warehouse_account_map -from erpnext.stock.stock_ledger import get_valuation_rate +from erpnext.stock.stock_ledger import get_items_to_be_repost, get_valuation_rate class QualityInspectionRequiredError(frappe.ValidationError): pass @@ -684,12 +684,8 @@ def create_repost_item_valuation_entry(args): def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=False): """Using a voucher create repost item valuation records for all item-warehouse pairs.""" - stock_ledger_entries = frappe.db.get_all("Stock Ledger Entry", - filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, - fields=["item_code", "warehouse", "posting_date", "posting_time", "creation", "company"], - order_by="creation asc", - group_by="item_code, warehouse" - ) + stock_ledger_entries = get_items_to_be_repost(voucher_type, voucher_no) + distinct_item_warehouses = set() repost_entries = [] From 45dd46be3d92201eb0195ae62fa5b2ba15616e5b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Nov 2021 10:50:52 +0530 Subject: [PATCH 138/185] feat: option to select reposting method In current implementation selecting Item-Warehouse based reposting is better for few users, who don't use depenent SLEs but have frequent transactions involving same items. This change lets them switch to item-warehouse based reposting if required. Only use this if you understand technicalities of stock reposting. This is experimental but will become mainstream in coming days. --- erpnext/controllers/stock_controller.py | 7 ++++++- .../stock_reposting_settings.json | 12 ++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index fe5d0c70cf..ca567fdc58 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -544,7 +544,12 @@ class StockController(AccountsController): "company": self.company }) if future_sle_exists(args): - create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name) + item_based_reposting = cint(frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")) + if item_based_reposting: + create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name) + else: + create_repost_item_valuation_entry(args) + @frappe.whitelist() def make_quality_inspections(doctype, docname, items): diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json index 2474059003..0facae8d3b 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json @@ -1,6 +1,7 @@ { "actions": [], "allow_rename": 1, + "beta": 1, "creation": "2021-10-01 10:56:30.814787", "doctype": "DocType", "editable_grid": 1, @@ -10,7 +11,8 @@ "limit_reposting_timeslot", "start_time", "end_time", - "limits_dont_apply_on" + "limits_dont_apply_on", + "item_based_reposting" ], "fields": [ { @@ -44,12 +46,18 @@ "fieldname": "limit_reposting_timeslot", "fieldtype": "Check", "label": "Limit timeslot for Stock Reposting" + }, + { + "default": "0", + "fieldname": "item_based_reposting", + "fieldtype": "Check", + "label": "Use Item based reposting" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-10-01 11:27:28.981594", + "modified": "2021-11-02 01:22:45.155841", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reposting Settings", From a5a8c9104fbb5279e6b25c529b6b4c0fc7ccef97 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Nov 2021 11:08:36 +0530 Subject: [PATCH 139/185] perf: index for item-sh on repost item valuation Item-WH based reposting requires querying existing similar repost. Assuming there is only 1 max extra entry with same params just indexing item-WH is sufficient to speed up the query. --- .../doctype/repost_item_valuation/repost_item_valuation.json | 4 ++-- .../doctype/repost_item_valuation/repost_item_valuation.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index 3ff0f60b3e..794c15ef33 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -177,7 +177,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-11-18 02:18:10.524560", + "modified": "2021-11-24 02:18:10.524560", "modified_by": "Administrator", "module": "Stock", "name": "Repost Item Valuation", @@ -228,4 +228,4 @@ ], "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 59d191fa07..475f525157 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -58,6 +58,11 @@ class RepostItemValuation(Document): frappe.enqueue(repost, timeout=1800, queue='long', job_name='repost_sle', now=True, doc=self) + +def on_doctype_update(): + frappe.db.add_index("Repost Item Valuation", ["warehouse", "item_code"], "item_warehouse") + + def repost(doc): try: if not frappe.db.exists("Repost Item Valuation", doc.name): From 1d3842f03a8f90d2110e43008091ea761f83fcb4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Nov 2021 15:22:39 +0530 Subject: [PATCH 140/185] fix: dont erase voucher_type and voucher_no for item_wh repost kept for tracability. --- .../doctype/repost_item_valuation/repost_item_valuation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 475f525157..64cea1db34 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -27,15 +27,12 @@ class RepostItemValuation(Document): if self.based_on == 'Transaction': self.item_code = None self.warehouse = None - else: - self.voucher_type = None - self.voucher_no = None self.allow_negative_stock = self.allow_negative_stock or \ cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) def set_company(self): - if self.voucher_type and self.voucher_no: + if self.based_on == "Transaction": self.company = frappe.get_cached_value(self.voucher_type, self.voucher_no, "company") elif self.warehouse: self.company = frappe.get_cached_value("Warehouse", self.warehouse, "company") From 0d0e24a5f51e566dfa04364787dfb883a9b7ad30 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Nov 2021 11:13:37 +0530 Subject: [PATCH 141/185] perf: skip unnecessary item-wh reposts Using basic idea that repost with older posting date will also take care of subsequent posting dates... When Item-WH reposts are queued: 1. If another repost with same item-wh but older posting date exists then skip current one. 2. If another repost with same item-wh but newer posting date exists then skip another one. --- .../repost_item_valuation.json | 2 +- .../repost_item_valuation.py | 58 +++++++++++++++++-- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index 794c15ef33..cd7e63b18b 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -64,7 +64,7 @@ "in_standard_filter": 1, "label": "Status", "no_copy": 1, - "options": "Queued\nIn Progress\nCompleted\nFailed", + "options": "Queued\nIn Progress\nCompleted\nSkipped\nFailed", "read_only": 1 }, { diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 64cea1db34..c0cb2c54b1 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -2,10 +2,21 @@ # For license information, please see license.txt +import datetime + import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form, get_weekday, now, nowtime, today +from frappe.utils import ( + cint, + get_datetime, + get_link_to_form, + get_time, + get_weekday, + now, + nowtime, + today, +) from frappe.utils.user import get_users_with_role from rq.timeouts import JobTimeoutException @@ -19,7 +30,7 @@ from erpnext.stock.stock_ledger import repost_future_sle class RepostItemValuation(Document): def validate(self): - self.set_status() + self.set_status(write=False) self.reset_field_values() self.set_company() @@ -37,12 +48,17 @@ class RepostItemValuation(Document): elif self.warehouse: self.company = frappe.get_cached_value("Warehouse", self.warehouse, "company") - def set_status(self, status=None): + def set_status(self, status=None, write=True): + status = status or self.status if not status: - status = 'Queued' - self.db_set('status', status) + self.status = 'Queued' + else: + self.status = status + if write: + self.db_set('status', self.status) def on_submit(self): + self.deduplicate_similar_repost() if not frappe.flags.in_test: return @@ -55,6 +71,35 @@ class RepostItemValuation(Document): frappe.enqueue(repost, timeout=1800, queue='long', job_name='repost_sle', now=True, doc=self) + def deduplicate_similar_repost(self): + """ Deduplicate similar reposts based on item-warehouse-posting combination.""" + if self.based_on != "Item and Warehouse": + return + + queued = frappe.db.get_value( + "Repost Item Valuation", + filters={ + "docstatus": 1, + "status": "Queued", + "item_code": self.item_code, + "warehouse": self.warehouse, + "based_on": self.based_on, + "name": ("!=", self.name) + }, + fieldname=["name", "posting_date", "posting_time"], + as_dict=True + ) + if not queued: + return + + posting_timestamp = datetime.datetime.combine(get_datetime(self.posting_date), get_time(self.posting_time)) + queued_timestamp = datetime.datetime.combine(get_datetime(queued.posting_date), get_time(queued.posting_time)) + + if posting_timestamp > queued_timestamp: + self.set_status("Skipped") + else: + frappe.db.set_value("Repost Item Valuation", queued.name, "status", "Skipped") + def on_doctype_update(): frappe.db.add_index("Repost Item Valuation", ["warehouse", "item_code"], "item_warehouse") @@ -136,7 +181,8 @@ def repost_entries(): for row in riv_entries: doc = frappe.get_doc('Repost Item Valuation', row.name) - repost(doc) + if doc.status in ('Queued', 'In Progress'): + repost(doc) riv_entries = get_repost_item_valuation_entries() if riv_entries: From 55631dd0d68fb3306bf285fa205e55e567b73837 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Nov 2021 18:03:43 +0530 Subject: [PATCH 142/185] test: item-wh deduplication in reposting --- .../repost_item_valuation.py | 2 +- .../test_repost_item_valuation.py | 55 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index c0cb2c54b1..ff490f8ecc 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -59,7 +59,7 @@ class RepostItemValuation(Document): def on_submit(self): self.deduplicate_similar_repost() - if not frappe.flags.in_test: + if not frappe.flags.in_test or self.flags.dont_run_in_test: return frappe.enqueue(repost, timeout=1800, queue='long', diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index 7abda61a83..ea79572bc5 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -74,8 +74,11 @@ class TestRepostItemValuation(unittest.TestCase): ) def test_create_item_wise_repost_item_valuation_entries(self): - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", - warehouse = "Stores - TCP1", get_multiple_items = True) + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + get_multiple_items=True, + ) rivs = create_item_wise_repost_entries(pr.doctype, pr.name) self.assertGreaterEqual(len(rivs), 2) @@ -84,3 +87,51 @@ class TestRepostItemValuation(unittest.TestCase): for riv in rivs: self.assertEqual(riv.company, "_Test Company with perpetual inventory") self.assertEqual(riv.warehouse, "Stores - TCP1") + + def test_deduplication(self): + def _assert_status(doc, status): + doc.load_from_db() + self.assertEqual(doc.status, status) + + riv_args = frappe._dict( + doctype="Repost Item Valuation", + item_code="_Test Item", + warehouse="_Test Warehouse - _TC", + based_on="Item and Warehouse", + voucher_type="Sales Invoice", + voucher_no="SI-1", + posting_date="2021-01-02", + posting_time="00:01:00", + ) + + # new repost without any duplicates + riv1 = frappe.get_doc(riv_args) + riv1.flags.dont_run_in_test = True + riv1.submit() + _assert_status(riv1, "Queued") + self.assertEqual(riv1.voucher_type, "Sales Invoice") # traceability + self.assertEqual(riv1.voucher_no, "SI-1") + + # newer than existing duplicate - riv1 + riv2 = frappe.get_doc(riv_args.update({"posting_date": "2021-01-03"})) + riv2.flags.dont_run_in_test = True + riv2.submit() + _assert_status(riv2, "Skipped") + + # older than exisitng duplicate - riv1 + riv3 = frappe.get_doc(riv_args.update({"posting_date": "2021-01-01"})) + riv3.flags.dont_run_in_test = True + riv3.submit() + _assert_status(riv3, "Queued") + _assert_status(riv1, "Skipped") + + # unrelated reposts, shouldn't do anything to others. + riv4 = frappe.get_doc(riv_args.update({"warehouse": "Stores - _TC"})) + riv4.flags.dont_run_in_test = True + riv4.submit() + _assert_status(riv4, "Queued") + _assert_status(riv3, "Queued") + + # to avoid breaking other tests accidentaly + riv4.set_status("Skipped") + riv3.set_status("Skipped") From ed94f0f3f26ecf71f9113d3d2d10aed05cf31265 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 24 Nov 2021 13:57:39 +0530 Subject: [PATCH 143/185] refactor: deduplicate during repost background job --- .../repost_item_valuation.py | 59 +++++++------------ .../test_repost_item_valuation.py | 3 + 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index ff490f8ecc..965a32d92d 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -1,22 +1,10 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - -import datetime - import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import ( - cint, - get_datetime, - get_link_to_form, - get_time, - get_weekday, - now, - nowtime, - today, -) +from frappe.utils import cint, get_link_to_form, get_weekday, now, nowtime, today from frappe.utils.user import get_users_with_role from rq.timeouts import JobTimeoutException @@ -58,7 +46,6 @@ class RepostItemValuation(Document): self.db_set('status', self.status) def on_submit(self): - self.deduplicate_similar_repost() if not frappe.flags.in_test or self.flags.dont_run_in_test: return @@ -76,30 +63,27 @@ class RepostItemValuation(Document): if self.based_on != "Item and Warehouse": return - queued = frappe.db.get_value( - "Repost Item Valuation", - filters={ - "docstatus": 1, - "status": "Queued", - "item_code": self.item_code, - "warehouse": self.warehouse, - "based_on": self.based_on, - "name": ("!=", self.name) - }, - fieldname=["name", "posting_date", "posting_time"], - as_dict=True - ) - if not queued: - return - - posting_timestamp = datetime.datetime.combine(get_datetime(self.posting_date), get_time(self.posting_time)) - queued_timestamp = datetime.datetime.combine(get_datetime(queued.posting_date), get_time(queued.posting_time)) - - if posting_timestamp > queued_timestamp: - self.set_status("Skipped") - else: - frappe.db.set_value("Repost Item Valuation", queued.name, "status", "Skipped") + filters = { + "item_code": self.item_code, + "warehouse": self.warehouse, + "name": self.name, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + } + frappe.db.sql(""" + update `tabRepost Item Valuation` + set status = 'Skipped' + WHERE item_code = %(item_code)s + and warehouse = %(warehouse)s + and name != %(name)s + and TIMESTAMP(posting_date, posting_time) > TIMESTAMP(%(posting_date)s, %(posting_time)s) + and docstatus = 1 + and status = 'Queued' + and based_on = 'Item and Warehouse' + """, + filters + ) def on_doctype_update(): frappe.db.add_index("Repost Item Valuation", ["warehouse", "item_code"], "item_warehouse") @@ -182,6 +166,7 @@ def repost_entries(): for row in riv_entries: doc = frappe.get_doc('Repost Item Valuation', row.name) if doc.status in ('Queued', 'In Progress'): + doc.deduplicate_similar_repost() repost(doc) riv_entries = get_repost_item_valuation_entries() diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index ea79572bc5..de793163fd 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -116,12 +116,14 @@ class TestRepostItemValuation(unittest.TestCase): riv2 = frappe.get_doc(riv_args.update({"posting_date": "2021-01-03"})) riv2.flags.dont_run_in_test = True riv2.submit() + riv1.deduplicate_similar_repost() _assert_status(riv2, "Skipped") # older than exisitng duplicate - riv1 riv3 = frappe.get_doc(riv_args.update({"posting_date": "2021-01-01"})) riv3.flags.dont_run_in_test = True riv3.submit() + riv3.deduplicate_similar_repost() _assert_status(riv3, "Queued") _assert_status(riv1, "Skipped") @@ -129,6 +131,7 @@ class TestRepostItemValuation(unittest.TestCase): riv4 = frappe.get_doc(riv_args.update({"warehouse": "Stores - _TC"})) riv4.flags.dont_run_in_test = True riv4.submit() + riv4.deduplicate_similar_repost() _assert_status(riv4, "Queued") _assert_status(riv3, "Queued") From 0a2964dc826d6f027a20f8d16ab62a1a59238853 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 24 Nov 2021 15:55:31 +0530 Subject: [PATCH 144/185] fix: ignore permissions while creating reposts --- erpnext/controllers/stock_controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index ca567fdc58..ae170945aa 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -711,6 +711,7 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa repost_entry.posting_time = sle.posting_time repost_entry.allow_zero_rate = allow_zero_rate repost_entry.flags.ignore_links = True + repost_entry.flags.ignore_permissions = True repost_entry.submit() repost_entries.append(repost_entry) From 8b33358660fac5ee66c2b56e966faca1f6ba0522 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 25 Nov 2021 17:24:59 +0530 Subject: [PATCH 145/185] fix: patch failure due to new doctype --- .../v12_0/move_target_distribution_from_parent_to_child.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py b/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py index 1e230a787c..36fe18d8b7 100644 --- a/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py +++ b/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py @@ -7,6 +7,7 @@ import frappe def execute(): frappe.reload_doc("setup", "doctype", "target_detail") + frappe.reload_doc("core", "doctype", "prepared_report") for d in ['Sales Person', 'Sales Partner', 'Territory']: frappe.db.sql(""" From c9e79ef1f232ac517277d4c3fb6bf9d8ace58abf Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 25 Nov 2021 18:53:48 +0530 Subject: [PATCH 146/185] fix: Replace 'item' with 'item_code' in tests --- erpnext/assets/doctype/asset_repair/test_asset_repair.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 81b4f6c449..ed5ae2cb4d 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -70,7 +70,7 @@ class TestAssetRepair(unittest.TestCase): self.assertEqual(stock_entry.stock_entry_type, "Material Issue") self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse) - self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item) + self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item_code) self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity) def test_increase_in_asset_value_due_to_stock_consumption(self): @@ -139,7 +139,7 @@ def create_asset_repair(**args): asset_repair.stock_consumption = 1 asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company) asset_repair.append("stock_items", { - "item": args.item or args.item_code or "_Test Item", + "item_code": args.item_code or "_Test Item", "valuation_rate": args.rate if args.get("rate") is not None else 100, "consumed_quantity": args.qty or 1 }) @@ -158,7 +158,7 @@ def create_asset_repair(**args): }) stock_entry.append('items', { "t_warehouse": asset_repair.warehouse, - "item_code": asset_repair.stock_items[0].item, + "item_code": asset_repair.stock_items[0].item_code, "qty": asset_repair.stock_items[0].consumed_quantity }) stock_entry.submit() From eea80b6c01df479fbbc6cbc13edc4a7e3fadc2c4 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 25 Nov 2021 19:01:22 +0530 Subject: [PATCH 147/185] fix: Create stock item --- erpnext/assets/doctype/asset_repair/test_asset_repair.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index ed5ae2cb4d..7efb03083e 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -11,12 +11,14 @@ from erpnext.assets.doctype.asset.test_asset import ( create_asset_data, set_depreciation_settings_in_company, ) +from erpnext.stock.doctype.item.test_item import create_item class TestAssetRepair(unittest.TestCase): def setUp(self): set_depreciation_settings_in_company() create_asset_data() + create_item("_Test Stock Item") frappe.db.sql("delete from `tabTax Rule`") def test_update_status(self): @@ -139,7 +141,7 @@ def create_asset_repair(**args): asset_repair.stock_consumption = 1 asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company) asset_repair.append("stock_items", { - "item_code": args.item_code or "_Test Item", + "item_code": args.item_code or "_Test Stock Item", "valuation_rate": args.rate if args.get("rate") is not None else 100, "consumed_quantity": args.qty or 1 }) From efac7b0904ff5cc24fdc525c399ada8509f07882 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 25 Nov 2021 19:02:01 +0530 Subject: [PATCH 148/185] fix: Create setUpClass --- erpnext/assets/doctype/asset_repair/test_asset_repair.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 7efb03083e..0ef2d800bb 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -15,7 +15,8 @@ from erpnext.stock.doctype.item.test_item import create_item class TestAssetRepair(unittest.TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): set_depreciation_settings_in_company() create_asset_data() create_item("_Test Stock Item") From 87f2dcfb597e45d9a7f970fbe4a644e1e70bca92 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 25 Nov 2021 19:38:44 +0530 Subject: [PATCH 149/185] fix: total stock summary UI glitch #28564 fix: total stock summary UI glitch --- .../total_stock_summary.js | 22 ++++--------------- .../total_stock_summary.py | 11 +++------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.js b/erpnext/stock/report/total_stock_summary/total_stock_summary.js index 90648f1b24..88054aaea7 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.js +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.js @@ -10,23 +10,8 @@ frappe.query_reports["Total Stock Summary"] = { "fieldtype": "Select", "width": "80", "reqd": 1, - "options": ["", "Warehouse", "Company"], - "change": function() { - let group_by = frappe.query_report.get_filter_value("group_by") - let company_filter = frappe.query_report.get_filter("company") - if (group_by == "Company") { - company_filter.df.reqd = 0; - company_filter.df.hidden = 1; - frappe.query_report.set_filter_value("company", ""); - company_filter.refresh(); - } - else { - company_filter.df.reqd = 1; - company_filter.df.hidden = 0; - company_filter.refresh(); - frappe.query_report.refresh(); - } - } + "options": ["Warehouse", "Company"], + "default": "Warehouse", }, { "fieldname": "company", @@ -34,8 +19,9 @@ frappe.query_reports["Total Stock Summary"] = { "fieldtype": "Link", "width": "80", "options": "Company", + "reqd": 1, "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 + "depends_on": "eval: doc.group_by != 'Company'", }, ] } diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.py b/erpnext/stock/report/total_stock_summary/total_stock_summary.py index 7e47b50b85..6f27558b88 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.py +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.py @@ -7,8 +7,9 @@ from frappe import _ def execute(filters=None): - if not filters: filters = {} - validate_filters(filters) + + if not filters: + filters = {} columns = get_columns() stock = get_total_stock(filters) @@ -53,9 +54,3 @@ def get_total_stock(filters): ON warehouse.name = ledger.warehouse WHERE ledger.actual_qty != 0 %s""" % (columns, conditions)) - -def validate_filters(filters): - if filters.get("group_by") == 'Company' and \ - filters.get("company"): - - frappe.throw(_("Please set Company filter blank if Group By is 'Company'")) From 6b75d1439f053a1d07ef54a3fdf55964aefa39c4 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 25 Nov 2021 22:10:24 +0530 Subject: [PATCH 150/185] fix: Add test for consumption of serialized Assets --- .../doctype/asset_repair/test_asset_repair.py | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 0ef2d800bb..34c7282ab2 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -5,6 +5,7 @@ import unittest import frappe from frappe.utils import flt, nowdate +from traceback import print_exc from erpnext.assets.doctype.asset.test_asset import ( create_asset, @@ -76,6 +77,25 @@ class TestAssetRepair(unittest.TestCase): self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item_code) self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity) + def test_serialized_item_consumption(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item + from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError + + stock_entry = make_serialized_item() + serial_nos = stock_entry.get("items")[0].serial_no + serial_no = serial_nos.split("\n")[0] + + # should not raise any error + create_asset_repair(stock_consumption = 1, item_code = stock_entry.get("items")[0].item_code, + warehouse = "_Test Warehouse - _TC", serial_no = serial_no, submit = 1) + + # should raise error + asset_repair = create_asset_repair(stock_consumption = 1, warehouse = "_Test Warehouse - _TC", + item_code = stock_entry.get("items")[0].item_code) + + asset_repair.repair_status = "Completed" + self.assertRaises(SerialNoRequiredError, asset_repair.submit) + def test_increase_in_asset_value_due_to_stock_consumption(self): asset = create_asset(calculate_depreciation = 1, submit=1) initial_asset_value = get_asset_value(asset) @@ -140,11 +160,12 @@ def create_asset_repair(**args): if args.stock_consumption: asset_repair.stock_consumption = 1 - asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company) + asset_repair.warehouse = args.warehouse or create_warehouse("Test Warehouse", company = asset.company) asset_repair.append("stock_items", { "item_code": args.item_code or "_Test Stock Item", "valuation_rate": args.rate if args.get("rate") is not None else 100, - "consumed_quantity": args.qty or 1 + "consumed_quantity": args.qty or 1, + "serial_no": args.serial_no }) asset_repair.insert(ignore_if_duplicate=True) From b28f137ee8d580a480bca0ed772283b62ae3c5b0 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 25 Nov 2021 22:17:54 +0530 Subject: [PATCH 151/185] fix: Remove unused import --- erpnext/assets/doctype/asset_repair/test_asset_repair.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 34c7282ab2..e54329c10b 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -5,7 +5,6 @@ import unittest import frappe from frappe.utils import flt, nowdate -from traceback import print_exc from erpnext.assets.doctype.asset.test_asset import ( create_asset, From c94f1ed39a40b019604a3476782f27a4ad4dab30 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 25 Nov 2021 22:18:57 +0530 Subject: [PATCH 152/185] fix: Change order of import statements --- erpnext/assets/doctype/asset_repair/test_asset_repair.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index e54329c10b..7c0d05748e 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -77,8 +77,8 @@ class TestAssetRepair(unittest.TestCase): self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity) def test_serialized_item_consumption(self): - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item stock_entry = make_serialized_item() serial_nos = stock_entry.get("items")[0].serial_no From f07f010962d51f43bc8f17048b4f3bb74cd925f1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 25 Nov 2021 23:58:16 +0530 Subject: [PATCH 153/185] fix: Add tests --- .../advance_taxes_and_charges.json | 11 ++-------- .../doctype/payment_entry/payment_entry.py | 16 ++------------ .../purchase_invoice/test_purchase_invoice.py | 22 ++++++++++--------- .../tax_withholding_category.py | 5 ++++- erpnext/controllers/accounts_controller.py | 9 ++++---- 5 files changed, 25 insertions(+), 38 deletions(-) diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json index 4d63499431..05b284ae16 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json @@ -25,8 +25,7 @@ "allocated_amount", "column_break_13", "base_tax_amount", - "base_total", - "base_allocated_amount" + "base_total" ], "fields": [ { @@ -168,12 +167,6 @@ "label": "Allocated Amount", "options": "currency" }, - { - "fieldname": "base_allocated_amount", - "fieldtype": "Currency", - "label": "Allocated Amount (Company Currency)", - "options": "Company:company:default_currency" - }, { "fetch_from": "account_head.account_currency", "fieldname": "currency", @@ -186,7 +179,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-09 11:46:58.373170", + "modified": "2021-11-25 11:10:10.945027", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Taxes and Charges", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e524592382..7aeb872b28 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -435,24 +435,16 @@ class PaymentEntry(AccountsController): net_total = self.paid_amount - for reference in self.get("references"): - net_total_for_tds = 0 - if reference.reference_doctype == 'Purchase Order': - net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total')) - - if net_total_for_tds: - net_total = net_total_for_tds - # Adding args as purchase invoice to get TDS amount args = frappe._dict({ 'company': self.company, - 'doctype': 'Purchase Invoice', + 'doctype': 'Payment Entry', 'supplier': self.party, 'posting_date': self.posting_date, 'net_total': net_total }) - tax_withholding_details, tax_deducted_on_advances = get_party_tax_withholding_details(args, self.tax_withholding_category) + tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category) if not tax_withholding_details: return @@ -1593,10 +1585,6 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= }) pe.set_difference_amount() - if doc.doctype == 'Purchase Order' and doc.apply_tds: - pe.apply_tax_withholding_amount = 1 - pe.tax_withholding_category = doc.tax_withholding_category - return pe def get_bank_cash_account(doc, bank_account): diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 78396a5e58..aa2408e092 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1160,25 +1160,21 @@ class TestPurchaseInvoice(unittest.TestCase): # Create Purchase Order with TDS applied po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item', posting_date='2021-09-15') - po.apply_tds = 1 - po.tax_withholding_category = 'TDS - 194 - Dividends - Individual' po.save() po.submit() - # Update Unrealized Profit / Loss Account which is used as default advance tax account - frappe.db.set_value('Company', '_Test Company', 'unrealized_profit_loss_account', '_Test Account Excise Duty - _TC') - # Create Payment Entry Against the order payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name) payment_entry.paid_from = 'Cash - _TC' + payment_entry.apply_tax_withholding_amount = 1 + payment_entry.tax_withholding_category = 'TDS - 194 - Dividends - Individual' payment_entry.save() payment_entry.submit() # Check GLE for Payment Entry expected_gle = [ - ['_Test Account Excise Duty - _TC', 3000, 0], ['Cash - _TC', 0, 27000], - ['Creditors - _TC', 27000, 0], + ['Creditors - _TC', 30000, 0], ['TDS Payable - _TC', 0, 3000], ] @@ -1204,9 +1200,7 @@ class TestPurchaseInvoice(unittest.TestCase): # Zero net effect on final TDS Payable on invoice expected_gle = [ ['_Test Account Cost for Goods Sold - _TC', 30000], - ['_Test Account Excise Duty - _TC', -3000], - ['Creditors - _TC', -27000], - ['TDS Payable - _TC', 0] + ['Creditors - _TC', -30000] ] gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount @@ -1219,6 +1213,14 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][1], gle.amount) + payment_entry.load_from_db() + self.assertEqual(payment_entry.taxes[0].allocated_amount, 3000) + + purchase_invoice.cancel() + + payment_entry.load_from_db() + self.assertEqual(payment_entry.taxes[0].allocated_amount, 0) + def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql("""select account, debit, credit, posting_date from `tabGL Entry` diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index fe156cb029..5bb9b93185 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -197,7 +197,10 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date, to_date=tax_details.to_date, party_type=party_type) taxable_vouchers = vouchers + advance_vouchers - tax_deducted_on_advances = get_taxes_deducted_on_advances_allocated(inv, tax_details) + tax_deducted_on_advances = 0 + + if inv.doctype == 'Purchase Invoice': + tax_deducted_on_advances = get_taxes_deducted_on_advances_allocated(inv, tax_details) tax_deducted = 0 if taxable_vouchers: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9cf2e33916..c5260295c4 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -145,15 +145,16 @@ class AccountsController(TransactionBase): self.validate_party() self.validate_currency() + if self.doctype in ['Purchase Invoice', 'Sales Invoice']: + pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid" + if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): + self.set_advances() + if self.doctype == 'Purchase Invoice': self.calculate_paid_amount() # apply tax withholding only if checked and applicable self.set_tax_withholding() - if self.doctype in ['Purchase Invoice', 'Sales Invoice']: - pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid" - if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): - self.set_advances() self.set_advance_gain_or_loss() From 7f06c8ca5768fcd16c077888324cb59244ccf032 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 26 Nov 2021 10:27:57 +0530 Subject: [PATCH 154/185] fix: Incorrect indentation --- erpnext/controllers/accounts_controller.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c5260295c4..56e03e15ec 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -150,12 +150,6 @@ class AccountsController(TransactionBase): if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): self.set_advances() - if self.doctype == 'Purchase Invoice': - self.calculate_paid_amount() - # apply tax withholding only if checked and applicable - self.set_tax_withholding() - - self.set_advance_gain_or_loss() if self.is_return: @@ -165,6 +159,11 @@ class AccountsController(TransactionBase): self.set_inter_company_account() + if self.doctype == 'Purchase Invoice': + self.calculate_paid_amount() + # apply tax withholding only if checked and applicable + self.set_tax_withholding() + validate_regional(self) if self.doctype != 'Material Request': From 73bfd59846de9a65b5d95f6204d4301f9e22e80b Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 26 Nov 2021 11:53:35 +0530 Subject: [PATCH 155/185] fix: checkbox triggers get_items and sub_assembly buttons (#28558) --- .../doctype/production_plan/production_plan.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index b171086b7c..0babf875e7 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -238,6 +238,12 @@ frappe.ui.form.on('Production Plan', { method: "get_items", freeze: true, doc: frm.doc, + callback: function() { + frm.refresh_field("po_items"); + if (frm.doc.sub_assembly_items.length > 0) { + frm.trigger("get_sub_assembly_items"); + } + } }); }, From 9c913c9b2dfbe8c87b9e08b16a4427d89bde689e Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 26 Nov 2021 12:00:13 +0530 Subject: [PATCH 156/185] fix: over billing validation (#28218) --- .../sales_invoice/test_sales_invoice.py | 26 +++++++++++ erpnext/controllers/accounts_controller.py | 45 ++++++++++++++++--- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index b5453ac56e..37bea70f16 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2397,6 +2397,32 @@ class TestSalesInvoice(unittest.TestCase): frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + def test_over_billing_case_against_delivery_note(self): + ''' + Test a case where duplicating the item with qty = 1 in the invoice + allows overbilling even if it is disabled + ''' + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + over_billing_allowance = frappe.db.get_single_value('Accounts Settings', 'over_billing_allowance') + frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', 0) + + dn = create_delivery_note() + dn.submit() + + si = make_sales_invoice(dn.name) + # make a copy of first item and add it to invoice + item_copy = frappe.copy_doc(si.items[0]) + si.append('items', item_copy) + si.save() + + with self.assertRaises(frappe.ValidationError) as err: + si.submit() + + self.assertTrue("cannot overbill" in str(err.exception).lower()) + + frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance) + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3190feac70..1654ac9c39 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1011,6 +1011,7 @@ class AccountsController(TransactionBase): def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): from erpnext.controllers.status_updater import get_allowance_for + item_allowance = {} global_qty_allowance, global_amount_allowance = None, None @@ -1031,12 +1032,7 @@ class AccountsController(TransactionBase): .format(item.item_code, ref_dt), title=_("Warning"), indicator="orange") continue - already_billed = frappe.db.sql(""" - select sum(%s) - from `tab%s` - where %s=%s and docstatus=1 and parent != %s - """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), - (item.get(item_ref_dn), self.name))[0][0] + already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on) total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), self.precision(based_on, item)) @@ -1064,6 +1060,43 @@ class AccountsController(TransactionBase): frappe.msgprint(_("Overbilling of {} ignored because you have {} role.") .format(total_overbilled_amt, role_allowed_to_over_bill), indicator="orange", alert=True) + def get_billed_amount_for_item(self, item, item_ref_dn, based_on): + ''' + Returns Sum of Amount of + Sales/Purchase Invoice Items + that are linked to `item_ref_dn` (`dn_detail` / `pr_detail`) + that are submitted OR not submitted but are under current invoice + ''' + + from frappe.query_builder import Criterion + from frappe.query_builder.functions import Sum + + item_doctype = frappe.qb.DocType(item.doctype) + based_on_field = frappe.qb.Field(based_on) + join_field = frappe.qb.Field(item_ref_dn) + + result = ( + frappe.qb.from_(item_doctype) + .select(Sum(based_on_field)) + .where( + join_field == item.get(item_ref_dn) + ).where( + Criterion.any([ # select all items from other invoices OR current invoices + Criterion.all([ # for selecting items from other invoices + item_doctype.docstatus == 1, + item_doctype.parent != self.name + ]), + Criterion.all([ # for selecting items from current invoice, that are linked to same reference + item_doctype.docstatus == 0, + item_doctype.parent == self.name, + item_doctype.name != item.name + ]) + ]) + ) + ).run() + + return result[0][0] if result else 0 + def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") .format(item.item_code, item.idx, max_allowed_amt)) From 2a5f663a1e9a63c6a3ac46de4643cfd038329c8a Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Fri, 26 Nov 2021 12:58:43 +0530 Subject: [PATCH 157/185] fix: Customer, Supplier heatmap data not rendering (#28553) * fix: adding get_timeline_data import on supplier.py, customer.py --- erpnext/buying/doctype/supplier/supplier.py | 6 +++++- erpnext/selling/doctype/customer/customer.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 32659d607b..14d2ccdb50 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -11,7 +11,11 @@ from frappe.contacts.address_and_contact import ( ) from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options -from erpnext.accounts.party import get_dashboard_info, validate_party_accounts +from erpnext.accounts.party import ( # noqa + get_dashboard_info, + get_timeline_data, + validate_party_accounts, +) from erpnext.utilities.transaction_base import TransactionBase diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 2e2b8b77d3..0c8c53aabe 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -18,7 +18,11 @@ from frappe.model.rename_doc import update_linked_doctypes from frappe.utils import cint, cstr, flt, get_formatted_email, today from frappe.utils.user import get_users_with_role -from erpnext.accounts.party import get_dashboard_info, validate_party_accounts +from erpnext.accounts.party import ( # noqa + get_dashboard_info, + get_timeline_data, + validate_party_accounts, +) from erpnext.utilities.transaction_base import TransactionBase From ca8dec0cf2d8a74eed1db939f5865f7536431a25 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Nov 2021 13:24:32 +0530 Subject: [PATCH 158/185] fix: Use `get_all` instead of `get_list` for child doctype (#28538) * fix(Student Attendance Tool): Use `get_all` instead of `get_list` for child doctype * fix(Course Schedule): incorrect fetch from value * fix: sider * fix(Gratuity): Use `get_all` instead of `get_list` for child doctype --- erpnext/education/api.py | 14 +- .../course_schedule/course_schedule.json | 449 ++---------------- .../student_attendance_tool.py | 30 +- erpnext/payroll/doctype/gratuity/gratuity.py | 2 +- .../payroll/doctype/gratuity/test_gratuity.py | 4 +- 5 files changed, 67 insertions(+), 432 deletions(-) diff --git a/erpnext/education/api.py b/erpnext/education/api.py index 53d1482f95..d9013b0816 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -125,7 +125,7 @@ def get_student_guardians(student): :param student: Student. """ - guardians = frappe.get_list("Student Guardian", fields=["guardian"] , + guardians = frappe.get_all("Student Guardian", fields=["guardian"] , filters={"parent": student}) return guardians @@ -137,10 +137,10 @@ def get_student_group_students(student_group, include_inactive=0): :param student_group: Student Group. """ if include_inactive: - students = frappe.get_list("Student Group Student", fields=["student", "student_name"] , + students = frappe.get_all("Student Group Student", fields=["student", "student_name"] , filters={"parent": student_group}, order_by= "group_roll_number") else: - students = frappe.get_list("Student Group Student", fields=["student", "student_name"] , + students = frappe.get_all("Student Group Student", fields=["student", "student_name"] , filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") return students @@ -164,7 +164,7 @@ def get_fee_components(fee_structure): :param fee_structure: Fee Structure. """ if fee_structure: - fs = frappe.get_list("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx") + fs = frappe.get_all("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx") return fs @@ -175,7 +175,7 @@ def get_fee_schedule(program, student_category=None): :param program: Program. :param student_category: Student Category """ - fs = frappe.get_list("Program Fee", fields=["academic_term", "fee_structure", "due_date", "amount"] , + fs = frappe.get_all("Program Fee", fields=["academic_term", "fee_structure", "due_date", "amount"] , filters={"parent": program, "student_category": student_category }, order_by= "idx") return fs @@ -220,7 +220,7 @@ def get_assessment_criteria(course): :param Course: Course """ - return frappe.get_list("Course Assessment Criteria", \ + return frappe.get_all("Course Assessment Criteria", fields=["assessment_criteria", "weightage"], filters={"parent": course}, order_by= "idx") @@ -253,7 +253,7 @@ def get_assessment_details(assessment_plan): :param Assessment Plan: Assessment Plan """ - return frappe.get_list("Assessment Plan Criteria", \ + return frappe.get_all("Assessment Plan Criteria", fields=["assessment_criteria", "maximum_score", "docstatus"], filters={"parent": assessment_plan}, order_by= "idx") diff --git a/erpnext/education/doctype/course_schedule/course_schedule.json b/erpnext/education/doctype/course_schedule/course_schedule.json index 8c6746bda8..38d9b508f0 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule.json +++ b/erpnext/education/doctype/course_schedule/course_schedule.json @@ -1,520 +1,143 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, + "actions": [], "allow_import": 1, - "allow_rename": 0, "autoname": "naming_series:", - "beta": 0, "creation": "2015-09-09 16:34:04.960369", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Document", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "student_group", + "instructor", + "instructor_name", + "column_break_2", + "naming_series", + "course", + "color", + "section_break_6", + "schedule_date", + "room", + "column_break_9", + "from_time", + "to_time", + "title" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "student_group", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, "in_standard_filter": 1, "label": "Student Group", - "length": 0, - "no_copy": 0, "options": "Student Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "instructor", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Instructor", - "length": 0, - "no_copy": 0, "options": "Instructor", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "instructor.Instructor_name", + "fetch_from": "instructor.instructor_name", "fieldname": "instructor_name", "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Instructor Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", "fieldname": "naming_series", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Naming Series", - "length": 0, - "no_copy": 0, "options": "EDU-CSH-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "course", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Course", - "length": 0, - "no_copy": 0, "options": "Course", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "color", "fieldtype": "Color", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Color", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Today", "fieldname": "schedule_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Schedule Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Schedule Date" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "room", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Room", - "length": 0, - "no_copy": 0, "options": "Room", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_9", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "from_time", "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "From Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "to_time", "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "To Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "title", "fieldtype": "Data", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Title" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2018-08-21 14:44:51.827225", + "links": [], + "modified": "2021-11-24 11:57:08.164449", "modified_by": "Administrator", "module": "Education", "name": "Course Schedule", - "name_case": "", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Academics User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "restrict_to_domain": "Education", - "show_name_in_global_search": 0, "sort_field": "schedule_date", "sort_order": "DESC", - "title_field": "title", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "title_field": "title" } \ No newline at end of file diff --git a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py index 4e3f98d655..7deb6b18da 100644 --- a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py +++ b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py @@ -14,24 +14,36 @@ def get_student_attendance_records(based_on, date=None, student_group=None, cour student_list = [] student_attendance_list = [] - if based_on=="Course Schedule": + if based_on == "Course Schedule": student_group = frappe.db.get_value("Course Schedule", course_schedule, "student_group") if student_group: - student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , \ + student_list = frappe.get_all("Student Group Student", fields=["student", "student_name", "group_roll_number"], filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") if not student_list: - student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , + student_list = frappe.get_all("Student Group Student", fields=["student", "student_name", "group_roll_number"], filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") + table = frappe.qb.DocType("Student Attendance") + if course_schedule: - student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \ - course_schedule= %s''', (course_schedule), as_dict=1) + student_attendance_list = ( + frappe.qb.from_(table) + .select(table.student, table.status) + .where( + (table.course_schedule == course_schedule) + ) + ).run(as_dict=True) else: - student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \ - student_group= %s and date= %s and \ - (course_schedule is Null or course_schedule='')''', - (student_group, date), as_dict=1) + student_attendance_list = ( + frappe.qb.from_(table) + .select(table.student, table.status) + .where( + (table.student_group == student_group) + & (table.date == date) + & (table.course_schedule == "") | (table.course_schedule.isnull()) + ) + ).run(as_dict=True) for attendance in student_attendance_list: for student in student_list: diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index f563c08a04..476990a88e 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -193,7 +193,7 @@ def get_total_applicable_component_amount(employee, applicable_earnings_componen sal_slip = get_last_salary_slip(employee) if not sal_slip: frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee))) - component_and_amounts = frappe.get_list("Salary Detail", + component_and_amounts = frappe.get_all("Salary Detail", filters={ "docstatus": 1, 'parent': sal_slip, diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index 78355cae64..93cba06da1 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -48,7 +48,7 @@ class TestGratuity(unittest.TestCase): self.assertEqual(floor(experience), gratuity.current_work_experience) #amount Calculation - component_amount = frappe.get_list("Salary Detail", + component_amount = frappe.get_all("Salary Detail", filters={ "docstatus": 1, 'parent': sal_slip, @@ -84,7 +84,7 @@ class TestGratuity(unittest.TestCase): self.assertEqual(floor(experience), gratuity.current_work_experience) #amount Calculation - component_amount = frappe.get_list("Salary Detail", + component_amount = frappe.get_all("Salary Detail", filters={ "docstatus": 1, 'parent': sal_slip, From efec85d5cd59eacd8cce4d519caf914740cc6c12 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 26 Nov 2021 16:29:21 +0530 Subject: [PATCH 159/185] chore: correct __version__ on develop branch. (#28582) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 9d99ebb7bb..a5de50fb06 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import frappe from erpnext.hooks import regional_overrides -__version__ = '13.9.0' +__version__ = '14.0.0-dev' def get_default_company(user=None): '''Get default company for user''' From 7ff30a4b2b64df191836f0d7695c7007167fb076 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 26 Nov 2021 17:33:57 +0530 Subject: [PATCH 160/185] fix: incorrect balance for warehouses (#28583) --- .../warehouse_wise_item_balance_age_and_value.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py index d3af5f61e3..4d1491b1b5 100644 --- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py +++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py @@ -46,8 +46,8 @@ def execute(filters=None): item_balance.setdefault((item, item_map[item]["item_group"]), []) total_stock_value = 0.00 for wh in warehouse_list: - row += [qty_dict.bal_qty] if wh.name in warehouse else [0.00] - total_stock_value += qty_dict.bal_val if wh.name in warehouse else 0.00 + row += [qty_dict.bal_qty] if wh.name == warehouse else [0.00] + total_stock_value += qty_dict.bal_val if wh.name == warehouse else 0.00 item_balance[(item, item_map[item]["item_group"])].append(row) item_value.setdefault((item, item_map[item]["item_group"]),[]) From 69a17b9e51cdf4c3214b8d70a2fed3e4e13a81b4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 27 Nov 2021 20:07:02 +0530 Subject: [PATCH 161/185] refactor: move Bin queries to qb/orm (#28522) --- erpnext/stock/doctype/bin/bin.py | 135 ++++++++++++++++++------------- 1 file changed, 81 insertions(+), 54 deletions(-) diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 48b1cc5396..da9c66d996 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -4,6 +4,8 @@ import frappe from frappe.model.document import Document +from frappe.query_builder import Case +from frappe.query_builder.functions import Coalesce, Sum from frappe.utils import flt, nowdate @@ -19,34 +21,42 @@ class Bin(Document): - flt(self.reserved_qty_for_production) - flt(self.reserved_qty_for_sub_contract)) def get_first_sle(self): - sle = frappe.db.sql(""" - select * from `tabStock Ledger Entry` - where item_code = %s - and warehouse = %s - order by timestamp(posting_date, posting_time) asc, creation asc - limit 1 - """, (self.item_code, self.warehouse), as_dict=1) - return sle and sle[0] or None + sle = frappe.qb.DocType("Stock Ledger Entry") + first_sle = ( + frappe.qb.from_(sle) + .select("*") + .where((sle.item_code == self.item_code) & (sle.warehouse == self.warehouse)) + .orderby(sle.posting_date, sle.posting_time, sle.creation) + .limit(1) + ).run(as_dict=True) + + return first_sle and first_sle[0] or None def update_reserved_qty_for_production(self): '''Update qty reserved for production from Production Item tables in open work orders''' - self.reserved_qty_for_production = frappe.db.sql(''' - SELECT - CASE WHEN ifnull(skip_transfer, 0) = 0 THEN - SUM(item.required_qty - item.transferred_qty) - ELSE - SUM(item.required_qty - item.consumed_qty) - END - FROM `tabWork Order` pro, `tabWork Order Item` item - WHERE - item.item_code = %s - and item.parent = pro.name - and pro.docstatus = 1 - and item.source_warehouse = %s - and pro.status not in ("Stopped", "Completed") - and (item.required_qty > item.transferred_qty or item.required_qty > item.consumed_qty) - ''', (self.item_code, self.warehouse))[0][0] + + wo = frappe.qb.DocType("Work Order") + wo_item = frappe.qb.DocType("Work Order Item") + + self.reserved_qty_for_production = ( + frappe.qb + .from_(wo) + .from_(wo_item) + .select(Case() + .when(wo.skip_transfer == 0, Sum(wo_item.required_qty - wo_item.transferred_qty)) + .else_(Sum(wo_item.required_qty - wo_item.consumed_qty)) + ) + .where( + (wo_item.item_code == self.item_code) + & (wo_item.parent == wo.name) + & (wo.docstatus == 1) + & (wo_item.source_warehouse == self.warehouse) + & (wo.status.notin(["Stopped", "Completed"])) + & ((wo_item.required_qty > wo_item.transferred_qty) + | (wo_item.required_qty > wo_item.consumed_qty)) + ) + ).run()[0][0] or 0.0 self.set_projected_qty() @@ -55,36 +65,53 @@ class Bin(Document): def update_reserved_qty_for_sub_contracting(self): #reserved qty - reserved_qty_for_sub_contract = frappe.db.sql(''' - select ifnull(sum(itemsup.required_qty),0) - from `tabPurchase Order` po, `tabPurchase Order Item Supplied` itemsup - where - itemsup.rm_item_code = %s - and itemsup.parent = po.name - and po.docstatus = 1 - and po.is_subcontracted = 'Yes' - and po.status != 'Closed' - and po.per_received < 100 - and itemsup.reserve_warehouse = %s''', (self.item_code, self.warehouse))[0][0] - #Get Transferred Entries - materials_transferred = frappe.db.sql(""" - select - ifnull(sum(CASE WHEN se.is_return = 1 THEN (transfer_qty * -1) ELSE transfer_qty END),0) - from - `tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po - where - se.docstatus=1 - and se.purpose='Send to Subcontractor' - and ifnull(se.purchase_order, '') !='' - and (sed.item_code = %(item)s or sed.original_item = %(item)s) - and se.name = sed.parent - and se.purchase_order = po.name - and po.docstatus = 1 - and po.is_subcontracted = 'Yes' - and po.status != 'Closed' - and po.per_received < 100 - """, {'item': self.item_code})[0][0] + po = frappe.qb.DocType("Purchase Order") + supplied_item = frappe.qb.DocType("Purchase Order Item Supplied") + + reserved_qty_for_sub_contract = ( + frappe.qb + .from_(po) + .from_(supplied_item) + .select(Sum(Coalesce(supplied_item.required_qty, 0))) + .where( + (supplied_item.rm_item_code == self.item_code) + & (po.name == supplied_item.parent) + & (po.docstatus == 1) + & (po.is_subcontracted == "Yes") + & (po.status != "Closed") + & (po.per_received < 100) + & (supplied_item.reserve_warehouse == self.warehouse) + ) + ).run()[0][0] or 0.0 + + se = frappe.qb.DocType("Stock Entry") + se_item = frappe.qb.DocType("Stock Entry Detail") + + materials_transferred = ( + frappe.qb + .from_(se) + .from_(se_item) + .from_(po) + .select(Sum( + Case() + .when(se.is_return == 1, se_item.transfer_qty * -1) + .else_(se_item.transfer_qty) + )) + .where( + (se.docstatus == 1) + & (se.purpose == "Send to Subcontractor") + & (Coalesce(se.purchase_order, "") != "") + & ((se_item.item_code == self.item_code) + | (se_item.original_item == self.item_code)) + & (se.name == se_item.parent) + & (po.name == se.purchase_order) + & (po.docstatus == 1) + & (po.is_subcontracted == "Yes") + & (po.status != "Closed") + & (po.per_received < 100) + ) + ).run()[0][0] or 0.0 if reserved_qty_for_sub_contract > materials_transferred: reserved_qty_for_sub_contract = reserved_qty_for_sub_contract - materials_transferred @@ -160,4 +187,4 @@ def update_qty(bin_name, args): 'indented_qty': indented_qty, 'planned_qty': planned_qty, 'projected_qty': projected_qty - }) \ No newline at end of file + }) From cdaf0a04cf92d27a5fae7a298ee8397b0b321491 Mon Sep 17 00:00:00 2001 From: xdlumertz Date: Sat, 27 Nov 2021 12:03:35 -0300 Subject: [PATCH 162/185] fix(ux): allow translations (#28455) * Translation * Translations Co-authored-by: xdlumertz --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +++--- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 4 ++-- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 6 +++--- erpnext/controllers/stock_controller.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 7aeb872b28..c1b056b9c7 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -339,7 +339,7 @@ class PaymentEntry(AccountsController): for k, v in no_oustanding_refs.items(): frappe.msgprint( _("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.") - .format(k, frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold("negative outstanding amount")) + .format(_(k), frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold(_("negative outstanding amount"))) + "

" + _("If this is undesirable please cancel the corresponding Payment Entry."), title=_("Warning"), indicator="orange") @@ -611,7 +611,7 @@ class PaymentEntry(AccountsController): if not total_negative_outstanding: frappe.throw(_("Cannot {0} {1} {2} without any negative outstanding invoice") - .format(self.payment_type, ("to" if self.party_type=="Customer" else "from"), + .format(_(self.payment_type), (_("to") if self.party_type=="Customer" else _("from")), self.party_type), InvalidPaymentEntry) elif paid_amount - additional_charges > total_negative_outstanding: @@ -1092,7 +1092,7 @@ def get_outstanding_reference_documents(args): if not data: frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.") - .format(args.get("party_type").lower(), frappe.bold(args.get("party")))) + .format(_(args.get("party_type")).lower(), frappe.bold(args.get("party")))) return data diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 516133ab63..48b5cb9459 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -728,7 +728,7 @@ class PurchaseInvoice(BuyingController): "account": self.stock_received_but_not_billed, "against": self.supplier, "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), - "remarks": self.remarks or "Accounting Entry for Stock", + "remarks": self.remarks or _("Accounting Entry for Stock"), "cost_center": self.cost_center, "project": item.project or self.project }, item=item) @@ -936,7 +936,7 @@ class PurchaseInvoice(BuyingController): "cost_center": tax.cost_center, "against": self.supplier, "credit": valuation_tax[tax.name], - "remarks": self.remarks or "Accounting Entry for Stock" + "remarks": self.remarks or _("Accounting Entry for Stock") }, item=tax)) @property diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index a1f3ee4b06..4538675d07 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -978,7 +978,7 @@ frappe.ui.form.on('Sales Invoice', { } if (frm.doc.is_debit_note) { - frm.set_df_property('return_against', 'label', 'Adjustment Against'); + frm.set_df_property('return_against', 'label', __('Adjustment Against')); } if (frappe.boot.active_domains.includes("Healthcare")) { @@ -988,10 +988,10 @@ frappe.ui.form.on('Sales Invoice', { if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) { frm.add_custom_button(__('Healthcare Services'), function() { get_healthcare_services_to_invoice(frm); - },"Get Items From"); + },__("Get Items From")); frm.add_custom_button(__('Prescriptions'), function() { get_drugs_to_invoice(frm); - },"Get Items From"); + },__("Get Items From")); } } else { diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index ae170945aa..7073e32f53 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -134,7 +134,7 @@ class StockController(AccountsController): "against": expense_account, "cost_center": item_row.cost_center, "project": item_row.project or self.get('project'), - "remarks": self.get("remarks") or "Accounting Entry for Stock", + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": flt(sle.stock_value_difference, precision), "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", }, warehouse_account[sle.warehouse]["account_currency"], item=item_row)) @@ -143,7 +143,7 @@ class StockController(AccountsController): "account": expense_account, "against": warehouse_account[sle.warehouse]["account"], "cost_center": item_row.cost_center, - "remarks": self.get("remarks") or "Accounting Entry for Stock", + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(sle.stock_value_difference, precision), "project": item_row.get("project") or self.get("project"), "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No" From 43038aab7952a9e613ed92b14f5b14212d6667ca Mon Sep 17 00:00:00 2001 From: hrwx Date: Sat, 27 Nov 2021 21:46:13 +0000 Subject: [PATCH 163/185] fix: do not add gst fields if no indian company --- erpnext/patches.txt | 2 +- .../v13_0/create_gst_payment_entry_fields.py | 45 ++++++++++--------- erpnext/www/shop-by-category/__init__.py | 0 3 files changed, 26 insertions(+), 21 deletions(-) create mode 100644 erpnext/www/shop-by-category/__init__.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e475229125..897e70ce25 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -287,7 +287,7 @@ erpnext.patches.v14_0.delete_einvoicing_doctypes erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021 erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.validate_options_for_data_field -erpnext.patches.v13_0.create_gst_payment_entry_fields +erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021 erpnext.patches.v14_0.delete_shopify_doctypes erpnext.patches.v13_0.fix_invoice_statuses erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item diff --git a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py index 7e6d67ce93..416694559c 100644 --- a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py +++ b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py @@ -9,24 +9,29 @@ def execute(): frappe.reload_doc('accounts', 'doctype', 'advance_taxes_and_charges') frappe.reload_doc('accounts', 'doctype', 'payment_entry') - custom_fields = { - 'Payment Entry': [ - dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions', - print_hide=1, collapsible=1), - dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section', - print_hide=1, options='Address'), - dict(fieldname='company_gstin', label='Company GSTIN', - fieldtype='Data', insert_after='company_address', - fetch_from='company_address.gstin', print_hide=1, read_only=1), - dict(fieldname='place_of_supply', label='Place of Supply', - fieldtype='Data', insert_after='company_gstin', - print_hide=1, read_only=1), - dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply', - print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'), - dict(fieldname='customer_gstin', label='Customer GSTIN', - fieldtype='Data', insert_after='customer_address', - fetch_from='customer_address.gstin', print_hide=1, read_only=1) - ] - } + if frappe.db.exists('Company', {'country': 'India'}): + custom_fields = { + 'Payment Entry': [ + dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions', + print_hide=1, collapsible=1), + dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section', + print_hide=1, options='Address'), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', insert_after='company_address', + fetch_from='company_address.gstin', print_hide=1, read_only=1), + dict(fieldname='place_of_supply', label='Place of Supply', + fieldtype='Data', insert_after='company_gstin', + print_hide=1, read_only=1), + dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply', + print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'), + dict(fieldname='customer_gstin', label='Customer GSTIN', + fieldtype='Data', insert_after='customer_address', + fetch_from='customer_address.gstin', print_hide=1, read_only=1) + ] + } - create_custom_fields(custom_fields, update=True) \ No newline at end of file + create_custom_fields(custom_fields, update=True) + else: + fields = ['gst_section', 'company_address', 'company_gstin', 'place_of_supply', 'customer_address', 'customer_gstin'] + for field in fields: + frappe.delete_doc_if_exists("Custom Field", f"Payment Entry-{field}") \ No newline at end of file diff --git a/erpnext/www/shop-by-category/__init__.py b/erpnext/www/shop-by-category/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From de6f104b740d4854663a6246a4a3b2a31b1034be Mon Sep 17 00:00:00 2001 From: Himanshu Date: Sat, 27 Nov 2021 23:16:34 +0000 Subject: [PATCH 164/185] Delete __init__.py --- erpnext/www/shop-by-category/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 erpnext/www/shop-by-category/__init__.py diff --git a/erpnext/www/shop-by-category/__init__.py b/erpnext/www/shop-by-category/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From f8623390241affb06f278ef5700aa0294b5cc726 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 24 Nov 2021 19:07:39 +0530 Subject: [PATCH 165/185] fix: use get_all instead of get_list for child tables --- .../bank_reconciliation_tool/bank_reconciliation_tool.py | 2 +- .../report/bom_stock_calculated/bom_stock_calculated.py | 2 +- erpnext/regional/report/eway_bill/eway_bill.py | 4 ++-- erpnext/regional/report/vat_audit_report/vat_audit_report.py | 2 +- erpnext/stock/doctype/item/test_item.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index 5cbf00b2c6..e7371fbe43 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -434,7 +434,7 @@ def get_pi_matching_query(amount_condition): def get_ec_matching_query(bank_account, company, amount_condition): # get matching Expense Claim query - mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account", + mode_of_payments = [x["parent"] for x in frappe.db.get_all("Mode of Payment Account", filters={"default_account": bank_account}, fields=["parent"])] mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )' company_currency = get_company_currency(company) diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py index cf19cbf6a2..090a3e74fc 100644 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py +++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py @@ -89,7 +89,7 @@ def get_bom_stock(filters): GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1) def get_manufacturer_records(): - details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"]) + details = frappe.get_all('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"]) manufacture_details = frappe._dict() for detail in details: dic = manufacture_details.setdefault(detail.get('parent'), {}) diff --git a/erpnext/regional/report/eway_bill/eway_bill.py b/erpnext/regional/report/eway_bill/eway_bill.py index 91a47674d7..f3fe5e8848 100644 --- a/erpnext/regional/report/eway_bill/eway_bill.py +++ b/erpnext/regional/report/eway_bill/eway_bill.py @@ -106,14 +106,14 @@ def set_address_details(row, special_characters): row.update({'ship_to_state': row.to_state}) def set_taxes(row, filters): - taxes = frappe.get_list("Sales Taxes and Charges", + taxes = frappe.get_all("Sales Taxes and Charges", filters={ 'parent': row.dn_id }, fields=('item_wise_tax_detail', 'account_head')) account_list = ["cgst_account", "sgst_account", "igst_account", "cess_account"] - taxes_list = frappe.get_list("GST Account", + taxes_list = frappe.get_all("GST Account", filters={ "parent": "GST Settings", "company": filters.company diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index 5a281a4cbb..17e50648b3 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -41,7 +41,7 @@ class VATAuditReport(object): return self.columns, self.data def get_sa_vat_accounts(self): - self.sa_vat_accounts = frappe.get_list("South Africa VAT Account", + self.sa_vat_accounts = frappe.get_all("South Africa VAT Account", filters = {"parent": self.filters.company}, pluck="account") if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings") diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 7237178b15..8b1224bd3e 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -488,7 +488,7 @@ class TestItem(ERPNextTestCase): item_doc.save() # Check values saved correctly - barcodes = frappe.get_list( + barcodes = frappe.get_all( 'Item Barcode', fields=['barcode', 'barcode_type'], filters={'parent': item_code}) From c7701ace80e5ce62e6a41db170b4e1f461ce970f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 29 Nov 2021 12:36:47 +0530 Subject: [PATCH 166/185] chore: correct docstrings --- erpnext/stock/doctype/item/item.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 5daabe817b..c9b8a3734e 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -222,10 +222,11 @@ class Item(WebsiteGenerator): 'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5)) def validate_website_image(self): + """Validate if the website image is a public file""" + if frappe.flags.in_import: return - """Validate if the website image is a public file""" auto_set_website_image = False if not self.website_image and self.image: auto_set_website_image = True @@ -255,10 +256,11 @@ class Item(WebsiteGenerator): self.website_image = None def make_thumbnail(self): + """Make a thumbnail of `website_image`""" + if frappe.flags.in_import: return - """Make a thumbnail of `website_image`""" import requests.exceptions if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"): From d1746caa02c84d22793b9a21c49582f372eca1a9 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Wed, 24 Nov 2021 02:54:43 +0100 Subject: [PATCH 167/185] refactor(KSA VAT): QR Code as per ZATKA specification --- erpnext/regional/saudi_arabia/utils.py | 85 ++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index a2f634ee22..558c7ee1d7 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -1,7 +1,10 @@ import io import os +from base64 import b64encode import frappe +from frappe import _ +from frappe.utils.data import add_to_date, get_time, getdate from pyqrcode import create as qr_create from erpnext import get_region @@ -28,24 +31,80 @@ def create_qr_code(doc, method): for field in meta.get_image_fields(): if field.fieldname == 'qr_code': - from urllib.parse import urlencode + ''' TLV conversion for + 1. Seller's Name + 2. VAT Number + 3. Time Stamp + 4. Invoice Amount + 5. VAT Amount + ''' + tlv_array = [] + # Sellers Name - # Creating public url to print format - default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") + '''TODO: Fix arabic conversion''' + # seller_name = frappe.db.get_value( + # 'Company', + # doc.company, + # 'company_name_in_arabic') - # System Language - language = frappe.get_system_settings('language') + # if not seller_name: + # frappe.throw(_('Arabic name missing for {} in the company document'.format(doc.company))) - params = urlencode({ - 'format': default_print_format or 'Standard', - '_lang': language, - 'key': doc.get_signature() - }) + seller_name = doc.company + tag = bytes([1]).hex() + length = bytes([len(seller_name)]).hex() + value = seller_name.encode('utf-8').hex() + tlv_array.append(''.join([tag, length, value])) + + # VAT Number + tax_id = frappe.db.get_value('Company', doc.company, 'tax_id') + if not tax_id: + frappe.throw(_('Tax ID missing for {} in the company document'.format(doc.company))) + + # tax_id = '310122393500003' # + tag = bytes([2]).hex() + length = bytes([len(tax_id)]).hex() + value = tax_id.encode('utf-8').hex() + tlv_array.append(''.join([tag, length, value])) + + # Time Stamp + posting_date = getdate(doc.posting_date) + time = get_time(doc.posting_time) + seconds = time.hour * 60 * 60 + time.minute * 60 + time.second + time_stamp = add_to_date(posting_date, seconds=seconds) + time_stamp = time_stamp.strftime('%Y-%m-%dT%H:%M:%SZ') + + # time_stamp = '2022-04-25T15:30:00Z' # + tag = bytes([3]).hex() + length = bytes([len(time_stamp)]).hex() + value = time_stamp.encode('utf-8').hex() + tlv_array.append(''.join([tag, length, value])) + + # Invoice Amount + invoice_amount = str(doc.total) + # invoice_amount = '1000.00' # + tag = bytes([4]).hex() + length = bytes([len(invoice_amount)]).hex() + value = invoice_amount.encode('utf-8').hex() + tlv_array.append(''.join([tag, length, value])) + + # VAT Amount + vat_amount = str(doc.total_taxes_and_charges) + + # vat_amount = '150.00' # + tag = bytes([5]).hex() + length = bytes([len(vat_amount)]).hex() + value = vat_amount.encode('utf-8').hex() + tlv_array.append(''.join([tag, length, value])) + + # Joining bytes into one + tlv_buff = ''.join(tlv_array) + + # base64 conversion for QR Code + base64_string = b64encode(bytes.fromhex(tlv_buff)).decode() - # creating qr code for the url - url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?{ params }" qr_image = io.BytesIO() - url = qr_create(url, error='L') + url = qr_create(base64_string, error='L') url.png(qr_image, scale=2, quiet_zone=1) # making file From de784d8bfe31008adf735ec065627ea4aea2cbd8 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Wed, 24 Nov 2021 03:03:17 +0100 Subject: [PATCH 168/185] refactor: comments removed --- erpnext/regional/saudi_arabia/utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index 558c7ee1d7..c0f4e31b23 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -61,7 +61,6 @@ def create_qr_code(doc, method): if not tax_id: frappe.throw(_('Tax ID missing for {} in the company document'.format(doc.company))) - # tax_id = '310122393500003' # tag = bytes([2]).hex() length = bytes([len(tax_id)]).hex() value = tax_id.encode('utf-8').hex() @@ -74,7 +73,6 @@ def create_qr_code(doc, method): time_stamp = add_to_date(posting_date, seconds=seconds) time_stamp = time_stamp.strftime('%Y-%m-%dT%H:%M:%SZ') - # time_stamp = '2022-04-25T15:30:00Z' # tag = bytes([3]).hex() length = bytes([len(time_stamp)]).hex() value = time_stamp.encode('utf-8').hex() @@ -82,7 +80,6 @@ def create_qr_code(doc, method): # Invoice Amount invoice_amount = str(doc.total) - # invoice_amount = '1000.00' # tag = bytes([4]).hex() length = bytes([len(invoice_amount)]).hex() value = invoice_amount.encode('utf-8').hex() @@ -91,7 +88,6 @@ def create_qr_code(doc, method): # VAT Amount vat_amount = str(doc.total_taxes_and_charges) - # vat_amount = '150.00' # tag = bytes([5]).hex() length = bytes([len(vat_amount)]).hex() value = vat_amount.encode('utf-8').hex() From 31b9b84fdf9dfefc2c629fc208bbe5330df43556 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Sat, 27 Nov 2021 14:28:13 +0100 Subject: [PATCH 169/185] fix: KSA VAT QR Code arabic conversion --- erpnext/regional/saudi_arabia/utils.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index c0f4e31b23..516b87c75d 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -41,18 +41,16 @@ def create_qr_code(doc, method): tlv_array = [] # Sellers Name - '''TODO: Fix arabic conversion''' - # seller_name = frappe.db.get_value( - # 'Company', - # doc.company, - # 'company_name_in_arabic') + seller_name = frappe.db.get_value( + 'Company', + doc.company, + 'company_name_in_arabic') - # if not seller_name: - # frappe.throw(_('Arabic name missing for {} in the company document'.format(doc.company))) + if not seller_name: + frappe.throw(_('Arabic name missing for {} in the company document'.format(doc.company))) - seller_name = doc.company tag = bytes([1]).hex() - length = bytes([len(seller_name)]).hex() + length = bytes([len(seller_name.encode('utf-8'))]).hex() value = seller_name.encode('utf-8').hex() tlv_array.append(''.join([tag, length, value])) From f3f7ed6f0d7d6e758da5eee77a22f48c6fd9bec3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 29 Nov 2021 13:43:56 +0530 Subject: [PATCH 170/185] fix: Translations --- erpnext/regional/saudi_arabia/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index 516b87c75d..1051315cbe 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -47,7 +47,7 @@ def create_qr_code(doc, method): 'company_name_in_arabic') if not seller_name: - frappe.throw(_('Arabic name missing for {} in the company document'.format(doc.company))) + frappe.throw(_('Arabic name missing for {} in the company document').format(doc.company)) tag = bytes([1]).hex() length = bytes([len(seller_name.encode('utf-8'))]).hex() @@ -57,7 +57,7 @@ def create_qr_code(doc, method): # VAT Number tax_id = frappe.db.get_value('Company', doc.company, 'tax_id') if not tax_id: - frappe.throw(_('Tax ID missing for {} in the company document'.format(doc.company))) + frappe.throw(_('Tax ID missing for {} in the company document').format(doc.company)) tag = bytes([2]).hex() length = bytes([len(tax_id)]).hex() From baf41fdc9c3b6280a766254c30673c534bf72878 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 29 Nov 2021 14:23:06 +0530 Subject: [PATCH 171/185] fix: Employee Advance paid amount not updated on PE cancellation (#28572) * fix: employee advance paid amount not updated on PE cancellation * fix: convert raw sql queries to qb * test: Employee Advance Paid Amount on PE cancellation * chore: disable no copy for sanctioned amount in Expense Claim --- .../employee_advance/employee_advance.py | 45 ++++++++++++------- .../employee_advance/test_employee_advance.py | 18 ++++++++ .../expense_claim_detail.json | 3 +- 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index 8a8e8dba74..7aac2b63ed 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -5,6 +5,7 @@ import frappe from frappe import _ from frappe.model.document import Document +from frappe.query_builder.functions import Sum from frappe.utils import flt, nowdate import erpnext @@ -41,24 +42,34 @@ class EmployeeAdvance(Document): self.status = "Cancelled" def set_total_advance_paid(self): - paid_amount = frappe.db.sql(""" - select ifnull(sum(debit), 0) as paid_amount - from `tabGL Entry` - where against_voucher_type = 'Employee Advance' - and against_voucher = %s - and party_type = 'Employee' - and party = %s - """, (self.name, self.employee), as_dict=1)[0].paid_amount + gle = frappe.qb.DocType("GL Entry") - return_amount = frappe.db.sql(""" - select ifnull(sum(credit), 0) as return_amount - from `tabGL Entry` - where against_voucher_type = 'Employee Advance' - and voucher_type != 'Expense Claim' - and against_voucher = %s - and party_type = 'Employee' - and party = %s - """, (self.name, self.employee), as_dict=1)[0].return_amount + paid_amount = ( + frappe.qb.from_(gle) + .select(Sum(gle.debit).as_("paid_amount")) + .where( + (gle.against_voucher_type == 'Employee Advance') + & (gle.against_voucher == self.name) + & (gle.party_type == 'Employee') + & (gle.party == self.employee) + & (gle.docstatus == 1) + & (gle.is_cancelled == 0) + ) + ).run(as_dict=True)[0].paid_amount or 0 + + return_amount = ( + frappe.qb.from_(gle) + .select(Sum(gle.credit).as_("return_amount")) + .where( + (gle.against_voucher_type == 'Employee Advance') + & (gle.voucher_type != 'Expense Claim') + & (gle.against_voucher == self.name) + & (gle.party_type == 'Employee') + & (gle.party == self.employee) + & (gle.docstatus == 1) + & (gle.is_cancelled == 0) + ) + ).run(as_dict=True)[0].return_amount or 0 if paid_amount != 0: paid_amount = flt(paid_amount) / flt(self.exchange_rate) diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py index 4ecfa60eb7..5f2e720eb4 100644 --- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py @@ -34,6 +34,24 @@ class TestEmployeeAdvance(unittest.TestCase): journal_entry1 = make_payment_entry(advance) self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit) + def test_paid_amount_on_pe_cancellation(self): + employee_name = make_employee("_T@employe.advance") + advance = make_employee_advance(employee_name) + + pe = make_payment_entry(advance) + pe.submit() + + advance.reload() + + self.assertEqual(advance.paid_amount, 1000) + self.assertEqual(advance.status, "Paid") + + pe.cancel() + advance.reload() + + self.assertEqual(advance.paid_amount, 0) + self.assertEqual(advance.status, "Unpaid") + def test_repay_unclaimed_amount_from_salary(self): employee_name = make_employee("_T@employe.advance") advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1}) diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json index 70a48f93b7..6edbcb5c39 100644 --- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json +++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json @@ -94,7 +94,6 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Sanctioned Amount", - "no_copy": 1, "oldfieldname": "sanctioned_amount", "oldfieldtype": "Currency", "options": "Company:company:default_currency", @@ -120,7 +119,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-09-18 17:26:09.703215", + "modified": "2021-11-26 14:23:45.539922", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim Detail", From c0cc72ec1d839db4bb510efb22ed17e2e4033e8a Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 29 Nov 2021 15:05:06 +0530 Subject: [PATCH 172/185] fix: incorrect discount amount set when item is replaced (#28556) --- erpnext/stock/get_item_details.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e00382bec1..cd180a42ca 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -299,7 +299,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): "warehouse": warehouse, "income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults), "expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) , - "discount_account": None or get_default_discount_account(args, item_defaults), + "discount_account": get_default_discount_account(args, item_defaults), "cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults), 'has_serial_no': item.has_serial_no, 'has_batch_no': item.has_batch_no, @@ -317,6 +317,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): "net_rate": 0.0, "net_amount": 0.0, "discount_percentage": 0.0, + "discount_amount": 0.0, "supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults), "update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0, "delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0, From af6fc2977098d51c990ee8a63fc6315122166be8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 29 Nov 2021 16:18:35 +0530 Subject: [PATCH 173/185] fix: KSA print format for invoices not having item codes --- .../print_format/ksa_vat_invoice/ksa_vat_invoice.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json index 681f72fd30..8e9a72897d 100644 --- a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json +++ b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json @@ -10,14 +10,14 @@ "docstatus": 0, "doctype": "Print Format", "font_size": 14, - "html": "
\n
\n
\n

TAX INVOICE

\n

\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629

\n
\n \n \n
\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\t\t{% if (company.tax_id) %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n {% if(supplier_address_doc) %}\n \n \n \n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n {% if(customer_address) %}\n \n \n \n \n {% endif %}\n \n {% if(customer_shipping_address) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n
{{ company.name }}{{ company.company_name_in_arabic }}
Invoice#: {{doc.name}}\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}
Invoice Date: {{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}
Date of Supply:{{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}
Supplier:\u0627\u0644\u0645\u0648\u0631\u062f:
Supplier Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:
{{ company.tax_id }}{{ company.tax_id }}
{{ company.name }}{{ company.company_name_in_arabic }}
{{ supplier_address_doc.address_line1}} {{ supplier_address_doc.address_in_arabic}}
Phone: {{ supplier_address_doc.phone }}\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}
Email: {{ supplier_address_doc.email_id }}\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}
CUSTOMER:\u0639\u0645\u064a\u0644:
Customer Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:
{{ customer_tax_id }}{{ customer_tax_id }}
{{ doc.customer }} {{ doc.customer_name_in_arabic }}
{{ customer_address.address_line1}} {{ customer_address.address_in_arabic}}
SHIPPING ADDRESS:\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:
{{ customer_shipping_address.address_line1}} {{ customer_shipping_address.address_in_arabic}}
OTHER INFORMATION\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649
Purchase Order Number: {{ doc.po_no }}\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}
Payment Due Date: {{ doc.due_date}} \u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}
\n\n \n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n \n {% set total = namespace(amount = 0) %}\n \n \n \n \n \n \n \n \n {% for row in doc.taxes %}\n \n {% endfor %}\n \n \n \n \n \n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n \n \n \n \n \n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n {% set tax_amount = frappe.utils.flt(data_object[item.item_code][1]/doc.conversion_rate, row.precision('tax_amount')) %}\n \n {% endfor %}\n \n \n {%- endfor -%}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Nature of goods or services
\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a
\n Unit price
\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n
\n Quantity
\n \u0627\u0644\u0643\u0645\u064a\u0629\n
\n Taxable Amount
\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n
{{row.description}}\n Total
\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n
{{ item.item_code }}{{ item.get_formatted(\"rate\") }}{{ item.qty }}{{ item.get_formatted(\"amount\") }}\n
\n {%- if(data_object[item.item_code][0])-%}\n {{ frappe.format(data_object[item.item_code][0], {'fieldtype': 'Percent'}) }}\n {%- endif -%}\n \n {%- if(data_object[item.item_code][1])-%}\n {{ frappe.format_value(tax_amount, currency=doc.currency) }}\n {% set total.amount = total.amount + tax_amount %}\n {%- endif -%}\n
\n
{{ frappe.format_value(frappe.utils.flt(total.amount, doc.precision('total_taxes_and_charges')), currency=doc.currency) }}
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n Total (Excluding VAT)\n
\n Total VAT\n
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
{{ doc.get_formatted(\"grand_total\") }}\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642Total Amount Due{{ doc.get_formatted(\"grand_total\") }}
\n\n\t{%- if doc.terms -%}\n

\n {{doc.terms}}\n

\n\t{%- endif -%}\n
\n", + "html": "
\n
\n
\n

TAX INVOICE

\n

\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629

\n
\n \n \n
\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\t\t{% if (company.tax_id) %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n {% if(supplier_address_doc) %}\n \n \n \n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n {% if(customer_address) %}\n \n \n \n \n {% endif %}\n \n {% if(customer_shipping_address) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n
{{ company.name }}{{ company.company_name_in_arabic }}
Invoice#: {{doc.name}}\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}
Invoice Date: {{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}
Date of Supply:{{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}
Supplier:\u0627\u0644\u0645\u0648\u0631\u062f:
Supplier Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:
{{ company.tax_id }}{{ company.tax_id }}
{{ company.name }}{{ company.company_name_in_arabic }}
{{ supplier_address_doc.address_line1}} {{ supplier_address_doc.address_in_arabic}}
Phone: {{ supplier_address_doc.phone }}\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}
Email: {{ supplier_address_doc.email_id }}\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}
CUSTOMER:\u0639\u0645\u064a\u0644:
Customer Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:
{{ customer_tax_id }}{{ customer_tax_id }}
{{ doc.customer }} {{ doc.customer_name_in_arabic }}
{{ customer_address.address_line1}} {{ customer_address.address_in_arabic}}
SHIPPING ADDRESS:\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:
{{ customer_shipping_address.address_line1}} {{ customer_shipping_address.address_in_arabic}}
OTHER INFORMATION\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649
Purchase Order Number: {{ doc.po_no }}\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}
Payment Due Date: {{ doc.due_date}} \u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}
\n\n \n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n \n {% set total = namespace(amount = 0) %}\n \n \n \n \n \n \n \n \n {% for row in doc.taxes %}\n \n {% endfor %}\n \n \n \n \n \n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n \n \n \n \n \n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n {% set key = item.item_code or item.item_name %}\n {% set tax_amount = frappe.utils.flt(data_object[key][1]/doc.conversion_rate, row.precision('tax_amount')) %}\n \n {% endfor %}\n \n \n {%- endfor -%}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Nature of goods or services
\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a
\n Unit price
\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n
\n Quantity
\n \u0627\u0644\u0643\u0645\u064a\u0629\n
\n Taxable Amount
\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n
{{row.description}}\n Total
\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n
{{ item.item_code or item.item_name }}{{ item.get_formatted(\"rate\") }}{{ item.qty }}{{ item.get_formatted(\"amount\") }}\n
\n {%- if(data_object[key][0])-%}\n {{ frappe.format(data_object[key][0], {'fieldtype': 'Percent'}) }}\n {%- endif -%}\n \n {%- if(data_object[key][1])-%}\n {{ frappe.format_value(tax_amount, currency=doc.currency) }}\n {% set total.amount = total.amount + tax_amount %}\n {%- endif -%}\n
\n
{{ frappe.format_value(frappe.utils.flt(total.amount, doc.precision('total_taxes_and_charges')), currency=doc.currency) }}
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n Total (Excluding VAT)\n
\n Total VAT\n
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
{{ doc.get_formatted(\"grand_total\") }}\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642Total Amount Due{{ doc.get_formatted(\"grand_total\") }}
\n\n\t{%- if doc.terms -%}\n

\n {{doc.terms}}\n

\n\t{%- endif -%}\n
\n", "idx": 0, "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2021-11-22 10:40:24.716932", + "modified": "2021-11-29 13:47:37.870818", "modified_by": "Administrator", "module": "Regional", "name": "KSA VAT Invoice", From 570ae422a84088a9d37fc1ef79264d0793a25505 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 29 Nov 2021 16:25:30 +0530 Subject: [PATCH 174/185] Update work_order_consumed_materials.py --- .../work_order_consumed_materials.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py index 8d2505ad95..a56c71064d 100644 --- a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py @@ -2,7 +2,6 @@ # For license information, please see license.txt import frappe -import json from frappe import _ def execute(filters=None): @@ -128,4 +127,4 @@ def get_columns(): "fieldtype": "Float", "width": 100 } - ] \ No newline at end of file + ] From 7fac9b8e9ccec5d310c701eb1b25a81f7c662745 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 29 Nov 2021 16:38:34 +0530 Subject: [PATCH 175/185] fix: changed fieldtype from int to float for the field Batch Size in the work order --- .../doctype/work_order_operation/work_order_operation.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index 647c14b33d..4e1a464cb0 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -178,8 +178,9 @@ }, { "fieldname": "batch_size", - "fieldtype": "Int", - "label": "Batch Size" + "fieldtype": "Float", + "label": "Batch Size", + "read_only": 1 }, { "fieldname": "sequence_id", @@ -200,7 +201,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-11-24 04:52:54.295168", + "modified": "2021-11-29 16:37:18.824489", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", From 3d2efb76d39910b271325554a9d841e74ea0230f Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 29 Nov 2021 17:52:55 +0530 Subject: [PATCH 176/185] fix: linters issue --- .../work_order_consumed_materials.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py index a56c71064d..052834807e 100644 --- a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py @@ -4,6 +4,7 @@ import frappe from frappe import _ + def execute(filters=None): columns, data = [], [] columns = get_columns() From 4382040fb64e333b33fefb2e97ad96ba3e5d52a3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 29 Nov 2021 18:25:47 +0530 Subject: [PATCH 177/185] fix: Move trigger from on trash to on cancel --- erpnext/hooks.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 4f00ef817a..6f9b08acb7 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -256,12 +256,10 @@ doc_events = { ], "on_cancel": [ "erpnext.regional.italy.utils.sales_invoice_on_cancel", - "erpnext.erpnext_integrations.taxjar_integration.delete_transaction" - ], - "on_trash": [ - "erpnext.regional.check_deletion_permission", + "erpnext.erpnext_integrations.taxjar_integration.delete_transaction", "erpnext.regional.saudi_arabia.utils.delete_qr_code_file" ], + "on_trash": "erpnext.regional.check_deletion_permission", "validate": [ "erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.update_taxable_values" From 2f6a6afaa8f839722cc2dd2a7efe442448250b96 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 29 Nov 2021 20:35:49 +0530 Subject: [PATCH 178/185] feat(pos): Total item qty field to POS screen (#28331) --- erpnext/public/scss/point-of-sale.scss | 5 +++++ .../page/point_of_sale/pos_item_cart.js | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index 1677e9b3de..7a3854cc61 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -495,6 +495,11 @@ font-size: var(--text-md); } + > .item-qty-total-container { + @extend .net-total-container; + padding: 5px 0px 0px 0px; + } + > .taxes-container { display: none; flex-direction: column; diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index a5b2d50041..4920584d95 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -100,6 +100,10 @@ erpnext.PointOfSale.ItemCart = class { `
${this.get_discount_icon()} ${__('Add Discount')}
+
+
${__('Total Items')}
+
0.00
+
${__("Net Total")}
0.00
@@ -142,6 +146,7 @@ erpnext.PointOfSale.ItemCart = class { this.$numpad_section.prepend( `
+
` @@ -470,6 +475,7 @@ erpnext.PointOfSale.ItemCart = class { if (!frm) frm = this.events.get_frm(); this.render_net_total(frm.doc.net_total); + this.render_total_item_qty(frm.doc.items); const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? frm.doc.grand_total : frm.doc.rounded_total; this.render_grand_total(grand_total); @@ -487,6 +493,21 @@ erpnext.PointOfSale.ItemCart = class { ); } + render_total_item_qty(items) { + var total_item_qty = 0; + items.map((item) => { + total_item_qty = total_item_qty + item.qty; + }); + + this.$totals_section.find('.item-qty-total-container').html( + `
${__('Total Quantity')}
${total_item_qty}
` + ); + + this.$numpad_section.find('.numpad-item-qty-total').html( + `
${__('Total Quantity')}: ${total_item_qty}
` + ); + } + render_grand_total(value) { const currency = this.events.get_frm().doc.currency; this.$totals_section.find('.grand-total-container').html( From 4458b2481351e6784492686e2e6ccf02ee5c8bc5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 30 Nov 2021 10:15:41 +0530 Subject: [PATCH 179/185] fix: allow creating Shift Assignment for same day (#28613) --- erpnext/hr/doctype/shift_assignment/shift_assignment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 4e829a3dbd..517730281f 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -19,8 +19,8 @@ class ShiftAssignment(Document): validate_active_employee(self.employee) self.validate_overlapping_dates() - if self.end_date and self.end_date <= self.start_date: - frappe.throw(_("End Date must not be lesser than Start Date")) + if self.end_date: + self.validate_from_to_dates('start_date', 'end_date') def validate_overlapping_dates(self): if not self.name: From aeb62af544090576944b51d667fc60ec42b42ab4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 30 Nov 2021 11:59:43 +0530 Subject: [PATCH 180/185] fix(ux): remove attachment limits --- erpnext/selling/doctype/quotation/quotation.json | 4 +--- erpnext/stock/doctype/item/item.json | 3 +-- .../doctype/stock_reconciliation/stock_reconciliation.json | 6 ++++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index ad788e5c8b..ee5b0ea760 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -961,9 +961,7 @@ "idx": 82, "is_submittable": 1, "links": [], - "max_attachments": 1, - "migration_hash": "75a86a19f062c2257bcbc8e6e31c7f1e", - "modified": "2021-10-21 12:58:55.514512", + "modified": "2021-11-30 01:33:21.106073", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 4b314a00a4..cd97ec31a4 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -1028,8 +1028,7 @@ "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "max_attachments": 1, - "modified": "2021-10-27 21:04:00.324786", + "modified": "2021-11-30 01:33:06.572442", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json index b7d1497319..3402972bb8 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2013-03-28 10:35:31", "description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.", @@ -153,11 +154,12 @@ "icon": "fa fa-upload-alt", "idx": 1, "is_submittable": 1, - "max_attachments": 1, - "modified": "2020-04-08 17:02:47.196206", + "links": [], + "modified": "2021-11-30 01:33:51.437194", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { From 9610086d0c084808455e38a4f950b8b9f68f90bc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 30 Nov 2021 13:24:23 +0530 Subject: [PATCH 181/185] feat: Show Zero Values filter in consolidated financial statement --- .../consolidated_financial_statement.js | 5 +++++ .../consolidated_financial_statement.py | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js index e24a5f9918..d3e836afd1 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js @@ -92,6 +92,11 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "label": __("Include Default Book Entries"), "fieldtype": "Check", "default": 1 + }, + { + "fieldname": "show_zero_values", + "label": __("Show zero values"), + "fieldtype": "Check" } ], "formatter": function(value, row, column, data, default_formatter) { diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index c71bc17ca7..01799d5804 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -22,7 +22,11 @@ from erpnext.accounts.report.cash_flow.cash_flow import ( get_cash_flow_accounts, ) from erpnext.accounts.report.cash_flow.cash_flow import get_report_summary as get_cash_flow_summary -from erpnext.accounts.report.financial_statements import get_fiscal_year_data, sort_accounts +from erpnext.accounts.report.financial_statements import ( + filter_out_zero_value_rows, + get_fiscal_year_data, + sort_accounts, +) from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import ( get_chart_data as get_pl_chart_data, ) @@ -265,7 +269,7 @@ def get_columns(companies, filters): return columns def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, ignore_closing_entries=False): - accounts, accounts_by_name = get_account_heads(root_type, + accounts, accounts_by_name, parent_children_map = get_account_heads(root_type, companies, filters) if not accounts: return [] @@ -294,6 +298,8 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters) + out = filter_out_zero_value_rows(out, parent_children_map, show_zero_values=filters.get("show_zero_values")) + if out: add_total_row(out, root_type, balance_must_be, companies, company_currency) @@ -370,7 +376,7 @@ def get_account_heads(root_type, companies, filters): accounts, accounts_by_name, parent_children_map = filter_accounts(accounts) - return accounts, accounts_by_name + return accounts, accounts_by_name, parent_children_map def update_parent_account_names(accounts): """Update parent_account_name in accounts list. From d0f4f03b668ed08e6685d46e95ee9aca4eea5b13 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 30 Nov 2021 14:04:47 +0530 Subject: [PATCH 182/185] fix: Employee Transfer and Project Profitability test cases (#28633) * fix: Employee Transfer testcases * fix: Project Profitability test case --- .../test_employee_transfer.py | 72 +++++++++---------- .../test_project_profitability.py | 19 ++--- 2 files changed, 43 insertions(+), 48 deletions(-) diff --git a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py index 287dfba35b..64eee402fe 100644 --- a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py @@ -2,7 +2,6 @@ # See license.txt import unittest -from datetime import date import frappe from frappe.utils import add_days, getdate @@ -12,16 +11,14 @@ from erpnext.hr.doctype.employee.test_employee import make_employee class TestEmployeeTransfer(unittest.TestCase): def setUp(self): - make_employee("employee2@transfers.com") - make_employee("employee3@transfers.com") create_company() - create_employee() - create_employee_transfer() def tearDown(self): frappe.db.rollback() def test_submit_before_transfer_date(self): + make_employee("employee2@transfers.com") + transfer_obj = frappe.get_doc({ "doctype": "Employee Transfer", "employee": frappe.get_value("Employee", {"user_id":"employee2@transfers.com"}, "name"), @@ -43,6 +40,8 @@ class TestEmployeeTransfer(unittest.TestCase): self.assertEqual(transfer.docstatus, 1) def test_new_employee_creation(self): + make_employee("employee3@transfers.com") + transfer = frappe.get_doc({ "doctype": "Employee Transfer", "employee": frappe.get_value("Employee", {"user_id":"employee3@transfers.com"}, "name"), @@ -63,60 +62,51 @@ class TestEmployeeTransfer(unittest.TestCase): self.assertEqual(frappe.get_value("Employee", transfer.employee, "status"), "Left") def test_employee_history(self): - name = frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name") - doc = frappe.get_doc("Employee",name) + employee = make_employee("employee4@transfers.com", + company="Test Company", + date_of_birth=getdate("30-09-1980"), + date_of_joining=getdate("01-10-2021"), + department="Accounts - TC", + designation="Accountant" + ) + transfer = create_employee_transfer(employee) + count = 0 department = ["Accounts - TC", "Management - TC"] designation = ["Accountant", "Manager"] - dt = [getdate("01-10-2021"), date.today()] + dt = [getdate("01-10-2021"), getdate()] - for data in doc.internal_work_history: + employee = frappe.get_doc("Employee", employee) + for data in employee.internal_work_history: self.assertEqual(data.department, department[count]) self.assertEqual(data.designation, designation[count]) self.assertEqual(data.from_date, dt[count]) count = count + 1 - data = frappe.db.get_list("Employee Transfer", filters={"employee":name}, fields=["*"]) - doc = frappe.get_doc("Employee Transfer", data[0]["name"]) - doc.cancel() - employee_doc = frappe.get_doc("Employee",name) + transfer.cancel() + employee.reload() - for data in employee_doc.internal_work_history: + for data in employee.internal_work_history: self.assertEqual(data.designation, designation[0]) self.assertEqual(data.department, department[0]) self.assertEqual(data.from_date, dt[0]) -def create_employee(): - doc = frappe.get_doc({ - "doctype": "Employee", - "first_name": "John", - "company": "Test Company", - "gender": "Male", - "date_of_birth": getdate("30-09-1980"), - "date_of_joining": getdate("01-10-2021"), - "department": "Accounts - TC", - "designation": "Accountant" - }) - - doc.save() def create_company(): - exists = frappe.db.exists("Company", "Test Company") - if not exists: - doc = frappe.get_doc({ - "doctype": "Company", - "company_name": "Test Company", - "default_currency": "INR", - "country": "India" - }) + if not frappe.db.exists("Company", "Test Company"): + frappe.get_doc({ + "doctype": "Company", + "company_name": "Test Company", + "default_currency": "INR", + "country": "India" + }).insert() - doc.save() -def create_employee_transfer(): +def create_employee_transfer(employee): doc = frappe.get_doc({ "doctype": "Employee Transfer", - "employee": frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name"), - "transfer_date": date.today(), + "employee": employee, + "transfer_date": getdate(), "transfer_details": [ { "property": "Designation", @@ -134,4 +124,6 @@ def create_employee_transfer(): }) doc.save() - doc.submit() \ No newline at end of file + doc.submit() + + return doc \ No newline at end of file diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index 2cf9d38bd0..04156902f7 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -1,7 +1,7 @@ import unittest import frappe -from frappe.utils import add_days, getdate, nowdate +from frappe.utils import add_days, getdate from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.projects.doctype.timesheet.test_timesheet import ( @@ -13,21 +13,26 @@ from erpnext.projects.report.project_profitability.project_profitability import class TestProjectProfitability(unittest.TestCase): - def setUp(self): + frappe.db.sql('delete from `tabTimesheet`') emp = make_employee('test_employee_9@salary.com', company='_Test Company') + if not frappe.db.exists('Salary Component', 'Timesheet Component'): frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert() + make_salary_structure_for_timesheet(emp, company='_Test Company') - self.timesheet = make_timesheet(emp, simulate = True, is_billable=1) + date = getdate() + + self.timesheet = make_timesheet(emp, is_billable=1) self.salary_slip = make_salary_slip(self.timesheet.name) - holidays = self.salary_slip.get_holidays_for_employee(nowdate(), nowdate()) + + holidays = self.salary_slip.get_holidays_for_employee(date, date) if holidays: frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 1) self.salary_slip.submit() self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer') - self.sales_invoice.due_date = nowdate() + self.sales_invoice.due_date = date self.sales_invoice.submit() frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8) @@ -63,6 +68,4 @@ class TestProjectProfitability(unittest.TestCase): self.assertEqual(fractional_cost, row.fractional_cost) def tearDown(self): - frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() - frappe.get_doc("Salary Slip", self.salary_slip.name).cancel() - frappe.get_doc("Timesheet", self.timesheet.name).cancel() + frappe.db.rollback() From e10ab1626c1264d9d5a25216068b5d064726d59e Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Tue, 30 Nov 2021 13:24:18 +0100 Subject: [PATCH 183/185] feat: Grant commission on certain items only (#27467) Co-authored-by: Sagar Vora --- .../doctype/pos_invoice/pos_invoice.json | 10 ++- .../pos_invoice_item/pos_invoice_item.json | 11 +++- .../doctype/sales_invoice/sales_invoice.json | 11 +++- .../sales_invoice/test_sales_invoice.py | 23 +++++++ .../sales_invoice_item.json | 11 +++- .../sales_partners_commission.json | 41 ++++++------ erpnext/controllers/accounts_controller.py | 7 ++- erpnext/controllers/selling_controller.py | 28 ++++++--- .../doctype/sales_order/sales_order.json | 10 ++- .../sales_order_item/sales_order_item.json | 11 +++- erpnext/selling/sales_common.js | 63 ++++++++++--------- .../doctype/delivery_note/delivery_note.json | 8 +++ .../delivery_note_item.json | 10 ++- erpnext/stock/doctype/item/item.json | 9 ++- erpnext/stock/get_item_details.py | 3 +- 15 files changed, 190 insertions(+), 66 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index bff8587278..0c6e7edeb0 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -171,6 +171,7 @@ "sales_team_section_break", "sales_partner", "column_break10", + "amount_eligible_for_commission", "commission_rate", "total_commission", "section_break2", @@ -1561,16 +1562,23 @@ "label": "Coupon Code", "options": "Coupon Code", "print_hide": 1 + }, + { + "fieldname": "amount_eligible_for_commission", + "fieldtype": "Currency", + "label": "Amount Eligible for Commission", + "read_only": 1 } ], "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2021-08-27 20:12:57.306772", + "modified": "2021-10-05 12:11:53.871828", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json index 8b71eb02fd..3f85668ede 100644 --- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json +++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json @@ -46,6 +46,7 @@ "base_amount", "pricing_rules", "is_free_item", + "grant_commission", "section_break_21", "net_rate", "net_amount", @@ -800,14 +801,22 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "grant_commission", + "fieldtype": "Check", + "label": "Grant Commission", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-01-04 17:34:49.924531", + "modified": "2021-10-05 12:23:47.506290", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 93e32f1a18..545abf77e6 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -182,6 +182,7 @@ "sales_team_section_break", "sales_partner", "column_break10", + "amount_eligible_for_commission", "commission_rate", "total_commission", "section_break2", @@ -2019,6 +2020,12 @@ "label": "Total Billing Hours", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "amount_eligible_for_commission", + "fieldtype": "Currency", + "label": "Amount Eligible for Commission", + "read_only": 1 } ], "icon": "fa fa-file-text", @@ -2031,7 +2038,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-10-11 20:19:38.667508", + "modified": "2021-10-21 20:19:38.667508", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", @@ -2086,4 +2093,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 37bea70f16..6a488ea96e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2385,6 +2385,29 @@ class TestSalesInvoice(unittest.TestCase): si.reload() self.assertEqual(si.status, "Paid") + def test_sales_commission(self): + si = frappe.copy_doc(test_records[0]) + item = copy.deepcopy(si.get('items')[0]) + item.update({ + "qty": 1, + "rate": 500, + "grant_commission": 1 + }) + si.append("items", item) + + # Test valid values + for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)): + si.commission_rate = commission_rate + si.save() + self.assertEqual(si.amount_eligible_for_commission, 500) + self.assertEqual(si.total_commission, total_commission) + + # Test invalid values + for commission_rate in (101, -1): + si.reload() + si.commission_rate = commission_rate + self.assertRaises(frappe.ValidationError, si.save) + def test_sales_invoice_submission_post_account_freezing_date(self): frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1)) si = create_sales_invoice(do_not_save=True) diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index b90f3f0904..ae9ac35729 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -47,6 +47,7 @@ "pricing_rules", "stock_uom_rate", "is_free_item", + "grant_commission", "section_break_21", "net_rate", "net_amount", @@ -828,15 +829,23 @@ "fieldtype": "Link", "label": "Discount Account", "options": "Account" + }, + { + "default": "0", + "fieldname": "grant_commission", + "fieldtype": "Check", + "label": "Grant Commission", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-08-19 13:41:53.435827", + "modified": "2021-10-05 12:24:54.968907", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json b/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json index a740de3572..9dd4e437f7 100644 --- a/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json +++ b/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json @@ -1,27 +1,30 @@ { - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2013-05-06 12:28:23", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 3, - "is_standard": "Yes", - "modified": "2017-03-06 05:52:57.645281", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Sales Partners Commission", - "owner": "Administrator", - "query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:150\",\n\tsum(base_net_total) as \"Invoiced Amount (Exclusive Tax):Currency:210\",\n\tsum(total_commission) as \"Total Commission:Currency:150\",\n\tsum(total_commission)*100/sum(base_net_total) as \"Average Commission Rate:Currency:170\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"", - "ref_doctype": "Sales Invoice", - "report_name": "Sales Partners Commission", - "report_type": "Query Report", + "add_total_row": 0, + "columns": [], + "creation": "2013-05-06 12:28:23", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 3, + "is_standard": "Yes", + "modified": "2021-10-06 06:26:07.881340", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Partners Commission", + "owner": "Administrator", + "prepared_report": 0, + "query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:220\",\n\tsum(base_net_total) as \"Invoiced Amount (Excl. Tax):Currency:220\",\n\tsum(amount_eligible_for_commission) as \"Amount Eligible for Commission:Currency:220\",\n\tsum(total_commission) as \"Total Commission:Currency:170\",\n\tsum(total_commission)*100/sum(amount_eligible_for_commission) as \"Average Commission Rate:Percent:220\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"", + "ref_doctype": "Sales Invoice", + "report_name": "Sales Partners Commission", + "report_type": "Query Report", "roles": [ { "role": "Accounts Manager" - }, + }, { "role": "Accounts User" } ] -} +} \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 0ee884e30c..2c92820a74 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -250,7 +250,12 @@ class AccountsController(TransactionBase): from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals calculate_taxes_and_totals(self) - if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: + if self.doctype in ( + 'Sales Order', + 'Delivery Note', + 'Sales Invoice', + 'POS Invoice', + ): self.calculate_commission() self.calculate_contribution() diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index dad3ed7093..cc773b7596 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -120,13 +120,27 @@ class SellingController(StockController): self.in_words = money_in_words(amount, self.currency) def calculate_commission(self): - if self.meta.get_field("commission_rate"): - self.round_floats_in(self, ["base_net_total", "commission_rate"]) - if self.commission_rate > 100.0: - throw(_("Commission rate cannot be greater than 100")) + if not self.meta.get_field("commission_rate"): + return - self.total_commission = flt(self.base_net_total * self.commission_rate / 100.0, - self.precision("total_commission")) + self.round_floats_in( + self, ("amount_eligible_for_commission", "commission_rate") + ) + + if not (0 <= self.commission_rate <= 100.0): + throw("{} {}".format( + _(self.meta.get_label("commission_rate")), + _("must be between 0 and 100"), + )) + + self.amount_eligible_for_commission = sum( + item.base_net_amount for item in self.items if item.grant_commission + ) + + self.total_commission = flt( + self.amount_eligible_for_commission * self.commission_rate / 100.0, + self.precision("total_commission") + ) def calculate_contribution(self): if not self.meta.get_field("sales_team"): @@ -138,7 +152,7 @@ class SellingController(StockController): self.round_floats_in(sales_person) sales_person.allocated_amount = flt( - self.base_net_total * sales_person.allocated_percentage / 100.0, + self.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0, self.precision("allocated_amount", sales_person)) if sales_person.commission_rate: diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 7c7ed9a960..7e99a06243 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -134,6 +134,7 @@ "sales_team_section_break", "sales_partner", "column_break7", + "amount_eligible_for_commission", "commission_rate", "total_commission", "section_break1", @@ -1507,16 +1508,23 @@ "fieldtype": "Small Text", "label": "Dispatch Address", "read_only": 1 + }, + { + "fieldname": "amount_eligible_for_commission", + "fieldtype": "Currency", + "label": "Amount Eligible for Commission", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-09-28 13:09:51.515542", + "modified": "2021-10-05 12:16:40.775704", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { 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 1e5590e748..95f6c4e96d 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -48,6 +48,7 @@ "pricing_rules", "stock_uom_rate", "is_free_item", + "grant_commission", "section_break_24", "net_rate", "net_amount", @@ -789,15 +790,23 @@ "no_copy": 1, "options": "currency", "read_only": 1 + }, + { + "default": "0", + "fieldname": "grant_commission", + "fieldtype": "Check", + "label": "Grant Commission", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-23 01:15:05.803091", + "modified": "2021-10-05 12:27:25.014789", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 20504789aa..e2e0db4044 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -157,25 +157,19 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran commission_rate() { this.calculate_commission(); - refresh_field("total_commission"); } total_commission() { - if(this.frm.doc.base_net_total) { - frappe.model.round_floats_in(this.frm.doc, ["base_net_total", "total_commission"]); + frappe.model.round_floats_in(this.frm.doc, ["amount_eligible_for_commission", "total_commission"]); - if(this.frm.doc.base_net_total < this.frm.doc.total_commission) { - var msg = (__("[Error]") + " " + - __(frappe.meta.get_label(this.frm.doc.doctype, "total_commission", - this.frm.doc.name)) + " > " + - __(frappe.meta.get_label(this.frm.doc.doctype, "base_net_total", this.frm.doc.name))); - frappe.msgprint(msg); - throw msg; - } + const { amount_eligible_for_commission } = this.frm.doc; + if(!amount_eligible_for_commission) return; - this.frm.set_value("commission_rate", - flt(this.frm.doc.total_commission * 100.0 / this.frm.doc.base_net_total)); - } + this.frm.set_value( + "commission_rate", flt( + this.frm.doc.total_commission * 100.0 / amount_eligible_for_commission + ) + ); } allocated_percentage(doc, cdt, cdn) { @@ -185,7 +179,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran sales_person.allocated_percentage = flt(sales_person.allocated_percentage, precision("allocated_percentage", sales_person)); - sales_person.allocated_amount = flt(this.frm.doc.base_net_total * + sales_person.allocated_amount = flt(this.frm.doc.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0, precision("allocated_amount", sales_person)); refresh_field(["allocated_amount"], sales_person); @@ -259,28 +253,39 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran } calculate_commission() { - if(this.frm.fields_dict.commission_rate) { - if(this.frm.doc.commission_rate > 100) { - var msg = __(frappe.meta.get_label(this.frm.doc.doctype, "commission_rate", this.frm.doc.name)) + - " " + __("cannot be greater than 100"); - frappe.msgprint(msg); - throw msg; - } + if(!this.frm.fields_dict.commission_rate) return; - this.frm.doc.total_commission = flt(this.frm.doc.base_net_total * this.frm.doc.commission_rate / 100.0, - precision("total_commission")); + if(this.frm.doc.commission_rate > 100) { + this.frm.set_value("commission_rate", 100); + frappe.throw(`${__(frappe.meta.get_label( + this.frm.doc.doctype, "commission_rate", this.frm.doc.name + ))} ${__("cannot be greater than 100")}`); } + + this.frm.doc.amount_eligible_for_commission = this.frm.doc.items.reduce( + (sum, item) => item.grant_commission ? sum + item.base_net_amount : sum, 0 + ) + + this.frm.doc.total_commission = flt( + this.frm.doc.amount_eligible_for_commission * this.frm.doc.commission_rate / 100.0, + precision("total_commission") + ); + + refresh_field(["amount_eligible_for_commission", "total_commission"]); } calculate_contribution() { var me = this; $.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) { frappe.model.round_floats_in(sales_person); - if(sales_person.allocated_percentage) { - sales_person.allocated_amount = flt( - me.frm.doc.base_net_total * sales_person.allocated_percentage / 100.0, - precision("allocated_amount", sales_person)); - } + if (!sales_person.allocated_percentage) return; + + sales_person.allocated_amount = flt( + me.frm.doc.amount_eligible_for_commission + * sales_person.allocated_percentage + / 100.0, + precision("allocated_amount", sales_person) + ); }); } diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index ad1b3b43ae..e78d5010eb 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -145,6 +145,7 @@ "sales_team_section_break", "sales_partner", "column_break7", + "amount_eligible_for_commission", "commission_rate", "total_commission", "section_break1", @@ -1302,6 +1303,12 @@ "label": "Dispatch Address", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "amount_eligible_for_commission", + "fieldtype": "Currency", + "label": "Amount Eligible for Commission", + "read_only": 1 } ], "icon": "fa fa-truck", @@ -1312,6 +1319,7 @@ "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index a96c29925e..51c88bed61 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -49,6 +49,7 @@ "pricing_rules", "stock_uom_rate", "is_free_item", + "grant_commission", "section_break_25", "net_rate", "net_amount", @@ -753,13 +754,20 @@ "no_copy": 1, "options": "currency", "read_only": 1 + }, + { + "default": "0", + "fieldname": "grant_commission", + "fieldtype": "Check", + "label": "Grant Commission", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-10-05 12:12:44.018872", + "modified": "2021-10-06 12:12:44.018872", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index cd97ec31a4..5469a9fc25 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -88,6 +88,7 @@ "sales_details", "sales_uom", "is_sales_item", + "grant_commission", "column_break3", "max_discount", "deferred_revenue", @@ -1020,6 +1021,12 @@ "fieldname": "website_image_alt", "fieldtype": "Data", "label": "Image Description" + }, + { + "default": "1", + "fieldname": "grant_commission", + "fieldtype": "Check", + "label": "Grant Commission" } ], "has_web_view": 1, @@ -1028,7 +1035,7 @@ "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-11-30 01:33:06.572442", + "modified": "2021-11-30 02:33:06.572442", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index cd180a42ca..9889a22b35 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -327,7 +327,8 @@ def get_basic_details(args, item, overwrite_warehouse=True): "against_blanket_order": args.get("against_blanket_order"), "bom_no": item.get("default_bom"), "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"), - "weight_uom": args.get("weight_uom") or item.get("weight_uom") + "weight_uom": args.get("weight_uom") or item.get("weight_uom"), + "grant_commission": item.get("grant_commission") }) if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): From 0854c183aaa6e443d30be72f8532954986a4d3e5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 30 Nov 2021 17:57:53 +0530 Subject: [PATCH 184/185] chore: remove duplicate code (#28646) [skip ci] --- erpnext/hr/doctype/employee/employee.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 79e8f6140a..88e5ca9d4c 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -96,15 +96,8 @@ class Employee(NestedSet): 'user': self.user_id }) - if employee_user_permission_exists: return - - employee_user_permission_exists = frappe.db.exists('User Permission', { - 'allow': 'Employee', - 'for_value': self.name, - 'user': self.user_id - }) - - if employee_user_permission_exists: return + if employee_user_permission_exists: + return add_user_permission("Employee", self.name, self.user_id) set_user_permission_if_allowed("Company", self.company, self.user_id) From 071118fa4ebac9ec9d7ed435331568f1feb196f0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 30 Nov 2021 13:15:20 +0000 Subject: [PATCH 185/185] fix: Unable to search project by project name in Sales Invoice (bp #28648) (cherry picked from commit 08b7c856b2ee94d1f8ac2c019c556eef4d7dd7da) Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- .../doctype/sales_invoice/sales_invoice.js | 9 --------- erpnext/controllers/queries.py | 14 +++++++++----- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 4538675d07..39dfd8d76d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -516,15 +516,6 @@ cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) { } } -// project name -//-------------------------- -cur_frm.fields_dict['project'].get_query = function(doc, cdt, cdn) { - return{ - query: "erpnext.controllers.queries.get_project_name", - filters: {'customer': doc.customer} - } -} - // Income Account in Details Table // -------------------------------- cur_frm.set_query("income_account", "items", function(doc) { diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 76a7d1a95f..dc04dab84c 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -539,6 +539,10 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters) dimension_filters = get_dimension_filter_map() dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account'))) query_filters = [] + or_filters = [] + fields = ['name'] + + searchfields = frappe.get_meta(doctype).get_search_fields() meta = frappe.get_meta(doctype) if meta.is_tree: @@ -550,8 +554,9 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters) if meta.has_field('company'): query_filters.append(['company', '=', filters.get('company')]) - if txt: - query_filters.append([searchfield, 'LIKE', "%%%s%%" % txt]) + for field in searchfields: + or_filters.append([field, 'LIKE', "%%%s%%" % txt]) + fields.append(field) if dimension_filters: if dimension_filters['allow_or_restrict'] == 'Allow': @@ -566,10 +571,9 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters) query_filters.append(['name', query_selector, dimensions]) - output = frappe.get_list(doctype, filters=query_filters) - result = [d.name for d in output] + output = frappe.get_list(doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1) - return [(d,) for d in set(result)] + return [tuple(d) for d in set(output)] @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs