From 25d208aa8a1fcb535696f7da450176aacc23d675 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 6 Sep 2021 10:37:41 +0530 Subject: [PATCH 001/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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/114] 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 3da03028f30eb7171724e86d618e16dadbb5543e Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 3 Nov 2021 17:03:12 +0530 Subject: [PATCH 019/114] 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 0aa237f38c946216a1d120c0c737a24afafa4b54 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 4 Nov 2021 20:14:28 +0530 Subject: [PATCH 020/114] 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 021/114] 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 c78b8b7897152ea5daba9bfa0005a294a40c5670 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Tue, 9 Nov 2021 14:46:45 +0530 Subject: [PATCH 022/114] 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 e8d0c25dffc913ba9cabe14f21cd5997b20d819a Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 9 Nov 2021 17:29:29 +0530 Subject: [PATCH 023/114] 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 1eb3ca2b86a8faa5a322b9ec2c97a69cfe476e26 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 9 Nov 2021 23:07:28 +0530 Subject: [PATCH 024/114] 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 68dc42fb1fe47185777babff93a8e24ee99b0762 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 10 Nov 2021 10:12:29 +0530 Subject: [PATCH 025/114] 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 1a7851b1ad53808b1ec73dca841261864e41ae4c Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 10 Nov 2021 14:55:13 +0530 Subject: [PATCH 026/114] 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 027/114] 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 028/114] 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 bb561ba7a8f6410f34409ba5f5d7a95653a7d1e4 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 15 Nov 2021 15:21:20 +0530 Subject: [PATCH 029/114] 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 030/114] 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 031/114] 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 032/114] 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 033/114] 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 034/114] 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 035/114] 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 036/114] 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 037/114] 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 038/114] 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 039/114] 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 040/114] 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 041/114] 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 042/114] 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 043/114] 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 044/114] 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 045/114] 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 046/114] 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 047/114] 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 048/114] 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 049/114] 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 050/114] 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 051/114] 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 052/114] 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 053/114] 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 054/114] 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 055/114] 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 056/114] 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 057/114] 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 058/114] 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 059/114] 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 060/114] 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 061/114] 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 350ed1a6c21a3ca63de02b1a8ed741c51b254ad1 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Fri, 19 Nov 2021 21:05:25 +0530 Subject: [PATCH 062/114] 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 063/114] 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 064/114] 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 065/114] 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 066/114] 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 067/114] 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 068/114] 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 069/114] 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 070/114] 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 071/114] 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 072/114] 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 073/114] 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 074/114] 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 075/114] 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 076/114] 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 e7eef9601d2348094d16a92736c30aca8686734e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 22 Nov 2021 20:13:15 +0530 Subject: [PATCH 077/114] 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 078/114] 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 079/114] 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 080/114] 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 081/114] 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 082/114] 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 083/114] 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 084/114] 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 085/114] 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 086/114] 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 087/114] 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 088/114] 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 089/114] 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 090/114] 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 091/114] 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 092/114] 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 093/114] 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 094/114] 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 095/114] 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 096/114] 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 097/114] 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 87f2dcfb597e45d9a7f970fbe4a644e1e70bca92 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 25 Nov 2021 19:38:44 +0530 Subject: [PATCH 098/114] 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 f07f010962d51f43bc8f17048b4f3bb74cd925f1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 25 Nov 2021 23:58:16 +0530 Subject: [PATCH 099/114] 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 100/114] 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 101/114] 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 102/114] 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 103/114] 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 104/114] 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 105/114] 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 106/114] 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 107/114] 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 108/114] 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 109/114] 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 110/114] 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 111/114] 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 112/114] 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 baf41fdc9c3b6280a766254c30673c534bf72878 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 29 Nov 2021 14:23:06 +0530 Subject: [PATCH 113/114] 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 114/114] 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,