From f54838ab56a258eb3d2d77fc9622076dc9b49fa7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 1 Dec 2022 21:23:17 +0530 Subject: [PATCH 01/41] fix: Buying and selling check in pricing rule --- erpnext/accounts/doctype/pricing_rule/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index bb54b23e26..2bdef1031f 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -250,6 +250,11 @@ def get_other_conditions(conditions, values, args): and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')""" values["transaction_date"] = args.get("transaction_date") + if args.get("doctype") in ["Sales Order", "Delivery Note", "Sales Invoice"]: + conditions += """ and ifnull(`tabPricing Rule`.selling, 0) = 1""" + else: + conditions += """ and ifnull(`tabPricing Rule`.buying, 0) = 1""" + return conditions From b741ae143c514cf832a435db1902986a915efde4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 2 Dec 2022 17:14:06 +0530 Subject: [PATCH 02/41] fix: Reapply pricing rule on qty change --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 4 ++-- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- erpnext/controllers/accounts_controller.py | 2 +- erpnext/public/js/controllers/transaction.js | 11 +++++++---- erpnext/stock/get_item_details.py | 4 ++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index ed46d85e3a..e2b015bf02 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -256,7 +256,7 @@ def apply_pricing_rule(args, doc=None): for item in item_list: args_copy = copy.deepcopy(args) args_copy.update(item) - data = get_pricing_rule_for_item(args_copy, item.get("price_list_rate"), doc=doc) + data = get_pricing_rule_for_item(args_copy, doc=doc) out.append(data) if ( @@ -293,7 +293,7 @@ def update_pricing_rule_uom(pricing_rule, args): pricing_rule.uom = row.uom -def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False): +def get_pricing_rule_for_item(args, doc=None, for_validate=False): from erpnext.accounts.doctype.pricing_rule.utils import ( get_applied_pricing_rules, get_pricing_rule_items, diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 2bdef1031f..e93b130879 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -674,7 +674,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): item_details.free_item_data.append(free_item_data_args) -def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): +def apply_pricing_rule_for_free_items(doc, pricing_rule_args): if pricing_rule_args: items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5a051e3baf..334a2d806d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -197,7 +197,7 @@ class AccountsController(TransactionBase): validate_einvoice_fields(self) - if self.doctype != "Material Request": + if self.doctype != "Material Request" and not self.ignore_pricing_rule: apply_pricing_rule_on_transaction(self) def before_cancel(self): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 46ac80895c..32268319f2 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1130,10 +1130,13 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe qty(doc, cdt, cdn) { let item = frappe.get_doc(cdt, cdn); - item.pricing_rules = '' - this.conversion_factor(doc, cdt, cdn, true); - this.calculate_stock_uom_rate(doc, cdt, cdn); - this.apply_pricing_rule(item, true); + // item.pricing_rules = '' + frappe.run_serially([ + () => this.remove_pricing_rule(item), + () => this.conversion_factor(doc, cdt, cdn, true), + () => this.calculate_stock_uom_rate(doc, cdt, cdn), + () => this.apply_pricing_rule(item, true) + ]); } calculate_stock_uom_rate(doc, cdt, cdn) { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 108611c09b..a9e8db0742 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -113,7 +113,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru if args.get(key) is None: args[key] = value - data = get_pricing_rule_for_item(args, out.price_list_rate, doc, for_validate=for_validate) + data = get_pricing_rule_for_item(args, doc, for_validate=for_validate) out.update(data) @@ -1305,7 +1305,7 @@ def apply_price_list_on_item(args): item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1) item_details = get_price_list_rate(args, item_doc) - item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate)) + item_details.update(get_pricing_rule_for_item(args)) return item_details From 5f821b93a5832ee3f37d37dc9192ac656be3224e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 5 Dec 2022 15:43:03 +0530 Subject: [PATCH 03/41] chore: Add POS Invoices --- erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py | 2 +- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index d27f65eba0..5bb366a770 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -1123,7 +1123,7 @@ def make_pricing_rule(**args): "apply_on": args.apply_on or "Item Code", "applicable_for": args.applicable_for, "selling": args.selling or 0, - "currency": "USD", + "currency": "INR", "apply_discount_on_rate": args.apply_discount_on_rate or 0, "buying": args.buying or 0, "min_qty": args.min_qty or 0.0, diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index e93b130879..ab1d4d3fa1 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -250,7 +250,7 @@ def get_other_conditions(conditions, values, args): and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')""" values["transaction_date"] = args.get("transaction_date") - if args.get("doctype") in ["Sales Order", "Delivery Note", "Sales Invoice"]: + if args.get("doctype") in ["Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]: conditions += """ and ifnull(`tabPricing Rule`.selling, 0) = 1""" else: conditions += """ and ifnull(`tabPricing Rule`.buying, 0) = 1""" From 05810009907560b02509fcb43ae65319daa54e90 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 5 Dec 2022 17:59:02 +0530 Subject: [PATCH 04/41] fix: Remove free items --- erpnext/public/js/controllers/transaction.js | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 32268319f2..58d8de2499 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1360,16 +1360,21 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe var item_list = []; $.each(this.frm.doc["items"] || [], function(i, d) { - if (d.item_code && !d.is_free_item) { - item_list.push({ - "doctype": d.doctype, - "name": d.name, - "item_code": d.item_code, - "pricing_rules": d.pricing_rules, - "parenttype": d.parenttype, - "parent": d.parent, - "price_list_rate": d.price_list_rate - }) + if (d.item_code) { + if (d.is_free_item) { + // Simply remove free items + me.frm.get_field("items").grid.grid_rows[i].remove(); + } else { + item_list.push({ + "doctype": d.doctype, + "name": d.name, + "item_code": d.item_code, + "pricing_rules": d.pricing_rules, + "parenttype": d.parenttype, + "parent": d.parent, + "price_list_rate": d.price_list_rate + }) + } } }); return this.frm.call({ From 6192af5cf04e2cec18cc6cb012adb779a0d2966b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 8 Dec 2022 18:04:40 +0530 Subject: [PATCH 05/41] chore: Update tests --- erpnext/accounts/doctype/pricing_rule/utils.py | 8 +++++++- erpnext/stock/get_item_details.py | 2 +- erpnext/utilities/product.py | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index ab1d4d3fa1..3989f8a8ac 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -250,7 +250,13 @@ def get_other_conditions(conditions, values, args): and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')""" values["transaction_date"] = args.get("transaction_date") - if args.get("doctype") in ["Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]: + if args.get("doctype") in [ + "Quotation", + "Sales Order", + "Delivery Note", + "Sales Invoice", + "POS Invoice", + ]: conditions += """ and ifnull(`tabPricing Rule`.selling, 0) = 1""" else: conditions += """ and ifnull(`tabPricing Rule`.buying, 0) = 1""" diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index a9e8db0742..31dccf6944 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -113,7 +113,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru if args.get(key) is None: args[key] = value - data = get_pricing_rule_for_item(args, doc, for_validate=for_validate) + data = get_pricing_rule_for_item(args, doc=doc, for_validate=for_validate) out.update(data) diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index 04ee0b3b1e..afe9654e8e 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -110,6 +110,7 @@ def get_price(item_code, price_list, customer_group, company, qty=1): "conversion_rate": 1, "for_shopping_cart": True, "currency": frappe.db.get_value("Price List", price_list, "currency"), + "doctype": "Quotation", } ) From 2010b1b6e847911cda7491c5c218d8a074b5d895 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Thu, 8 Dec 2022 16:26:07 -0500 Subject: [PATCH 06/41] fix: use highest precision for exchange rate. --- erpnext/accounts/doctype/payment_entry/payment_entry.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 3fc1adff2d..4a7a57b627 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -305,6 +305,7 @@ "fieldname": "source_exchange_rate", "fieldtype": "Float", "label": "Exchange Rate", + "precision": "9", "print_hide": 1, "reqd": 1 }, @@ -334,6 +335,7 @@ "fieldname": "target_exchange_rate", "fieldtype": "Float", "label": "Exchange Rate", + "precision": "9", "print_hide": 1, "reqd": 1 }, @@ -731,7 +733,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-02-23 20:08:39.559814", + "modified": "2022-12-08 16:25:43.824051", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", From 97ddfcfc7ca272922203d5423a21e777c62477d1 Mon Sep 17 00:00:00 2001 From: Tunde Akinyanmi Date: Fri, 9 Dec 2022 10:14:18 +0100 Subject: [PATCH 07/41] fix: Maintain Same Rate Throughout Sales Cycle doesn't work Issue #29976 was partly fixed by #32923 but the problem still persists. The reason is because an incorrect fieldname was passed to the `validate_rate_with_reference_doc` method. This commit fixes it --- erpnext/selling/doctype/quotation/test_quotation.py | 13 +++++++++++++ erpnext/selling/doctype/sales_order/sales_order.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 6f0b381fc1..5cd6545187 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -30,6 +30,19 @@ class TestQuotation(FrappeTestCase): self.assertTrue(sales_order.get("payment_schedule")) + def test_maintain_rate_in_sales_cycle_is_enforced(self): + from erpnext.selling.doctype.quotation.quotation import make_sales_order + + quotation = frappe.copy_doc(test_records[0]) + quotation.transaction_date = nowdate() + quotation.valid_till = add_months(quotation.transaction_date, 1) + quotation.insert() + quotation.submit() + + sales_order = make_sales_order(quotation.name) + sales_order.items[0].rate = 1 + self.assertRaises(frappe.ValidationError, sales_order.save) + def test_make_sales_order_with_different_currency(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 78e2370878..0013c95032 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -194,7 +194,7 @@ class SalesOrder(SellingController): ) if cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")): - self.validate_rate_with_reference_doc([["Quotation", "prev_docname", "quotation_item"]]) + self.validate_rate_with_reference_doc([["Quotation", "prevdoc_docname", "quotation_item"]]) def update_enquiry_status(self, prevdoc, flag): enq = frappe.db.sql( From d193a14b8f8e39367bb26e6595283ef5aa545dcc Mon Sep 17 00:00:00 2001 From: Tunde Akinyanmi Date: Fri, 9 Dec 2022 12:39:03 +0100 Subject: [PATCH 08/41] test: ensure test case sets Selling Settings --- erpnext/selling/doctype/quotation/test_quotation.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 5cd6545187..b3c21c46ed 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -33,6 +33,11 @@ class TestQuotation(FrappeTestCase): def test_maintain_rate_in_sales_cycle_is_enforced(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order + maintain_rate = frappe.db.get_value( + "Selling Settings", "Selling Settings", "maintain_same_sales_rate" + ) + frappe.db.set_value("Selling Settings", "Selling Settings", "maintain_same_sales_rate", 1) + quotation = frappe.copy_doc(test_records[0]) quotation.transaction_date = nowdate() quotation.valid_till = add_months(quotation.transaction_date, 1) @@ -43,6 +48,10 @@ class TestQuotation(FrappeTestCase): sales_order.items[0].rate = 1 self.assertRaises(frappe.ValidationError, sales_order.save) + frappe.db.set_value( + "Selling Settings", "Selling Settings", "maintain_same_sales_rate", maintain_rate + ) + def test_make_sales_order_with_different_currency(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order From 71aa8c5e1c8f9ac71fc3a5a8246f8a15f896a57d Mon Sep 17 00:00:00 2001 From: Tunde Akinyanmi Date: Fri, 9 Dec 2022 14:33:54 +0100 Subject: [PATCH 09/41] test: refactor test case --- erpnext/selling/doctype/quotation/test_quotation.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index b3c21c46ed..b151dd5e79 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -33,10 +33,8 @@ class TestQuotation(FrappeTestCase): def test_maintain_rate_in_sales_cycle_is_enforced(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order - maintain_rate = frappe.db.get_value( - "Selling Settings", "Selling Settings", "maintain_same_sales_rate" - ) - frappe.db.set_value("Selling Settings", "Selling Settings", "maintain_same_sales_rate", 1) + maintain_rate = frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate") + frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", 1) quotation = frappe.copy_doc(test_records[0]) quotation.transaction_date = nowdate() @@ -48,9 +46,7 @@ class TestQuotation(FrappeTestCase): sales_order.items[0].rate = 1 self.assertRaises(frappe.ValidationError, sales_order.save) - frappe.db.set_value( - "Selling Settings", "Selling Settings", "maintain_same_sales_rate", maintain_rate - ) + frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", maintain_rate) def test_make_sales_order_with_different_currency(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order From d512919f5c95605e98495d5b2f1b11f838622b79 Mon Sep 17 00:00:00 2001 From: artykbasar <78363421+artykbasar@users.noreply.github.com> Date: Sun, 11 Dec 2022 08:51:34 +0000 Subject: [PATCH 10/41] Subscription Cost center value is fixed to Default value(Bug fix) self.cost_center field value keeps changing to erpnext.get_default_cost_center(self.get("company")) after validation of this doctype. This overrides the user input. simple fix, it will first check if the field is empty, if empty then puts the company's default cost center value if not then user input will be saved. --- erpnext/accounts/doctype/subscription/subscription.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 9dab4e91fb..8708342b11 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -280,7 +280,8 @@ class Subscription(Document): self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval()) self.validate_end_date() self.validate_to_follow_calendar_months() - self.cost_center = erpnext.get_default_cost_center(self.get("company")) + if not self.cost_center: + self.cost_center = erpnext.get_default_cost_center(self.get("company")) def validate_trial_period(self): """ From 593626f5022bd5af1241e8c5ecb807c04943bd42 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 12 Dec 2022 12:58:11 +0530 Subject: [PATCH 11/41] perf: add indexes on payment entry reference (#33288) Adds index on: 1. reference doctype 2. reference name *Why not composite index?* There are three type of queries on this doctype - filtering ref_doctype - doctype index helps here - filtering ref_name - name index helps here - filtering both - name index helps here too. Since it has sufficiently high cardinality. Composite index wont help in case where ref_doctype isn't specfied. [skip ci] --- .../payment_entry_reference/payment_entry_reference.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index 8961167f01..3003c68196 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -25,7 +25,8 @@ "in_list_view": 1, "label": "Type", "options": "DocType", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "columns": 2, @@ -35,7 +36,8 @@ "in_list_view": 1, "label": "Name", "options": "reference_doctype", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "fieldname": "due_date", @@ -104,7 +106,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-26 17:06:55.597389", + "modified": "2022-12-12 12:31:44.919895", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", @@ -113,5 +115,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From 915e0347b0d5b07e1e526db5ea8840be76d2f437 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 12 Dec 2022 15:14:30 +0530 Subject: [PATCH 12/41] chore!: remove activity log feeds (#33294) - This contains little to no information and practically no one uses this. - Also causes a lot of problem by adding way too many feeds in activity log to the point where activity page doesn't even load. --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 3 --- erpnext/buying/doctype/supplier/supplier.py | 3 --- erpnext/controllers/buying_controller.py | 4 ---- erpnext/controllers/selling_controller.py | 3 --- erpnext/crm/doctype/lead/lead.py | 3 --- .../doctype/maintenance_visit/maintenance_visit.py | 3 --- erpnext/projects/doctype/project/project.py | 3 --- erpnext/projects/doctype/task/task.py | 3 --- erpnext/selling/doctype/customer/customer.py | 3 --- erpnext/stock/doctype/material_request/material_request.py | 3 --- erpnext/stock/doctype/stock_entry/stock_entry.py | 3 --- erpnext/support/doctype/issue/issue.py | 3 --- erpnext/support/doctype/warranty_claim/warranty_claim.py | 3 --- 13 files changed, 40 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 1714fffc16..b63d57c900 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -34,9 +34,6 @@ class JournalEntry(AccountsController): def __init__(self, *args, **kwargs): super(JournalEntry, self).__init__(*args, **kwargs) - def get_feed(self): - return self.voucher_type - def validate(self): if self.voucher_type == "Opening Entry": self.is_opening = "Yes" diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index bebff1c3ac..120b2f8bbe 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -20,9 +20,6 @@ from erpnext.utilities.transaction_base import TransactionBase class Supplier(TransactionBase): - def get_feed(self): - return self.supplier_name - def onload(self): """Load address and contacts in `__onload`""" load_address_and_contact(self) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 2efa545736..7989a40a3d 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -25,10 +25,6 @@ class BuyingController(SubcontractingController): def __setup__(self): self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"] - def get_feed(self): - if self.get("supplier_name"): - return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total) - def validate(self): super(BuyingController, self).validate() if getattr(self, "supplier", None) and not self.supplier_name: diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 0ebc8d4b4d..8b073a4320 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -19,9 +19,6 @@ class SellingController(StockController): def __setup__(self): self.flags.ignore_permlevel_for_fields = ["selling_price_list", "price_list_currency"] - def get_feed(self): - return _("To {0} | {1} {2}").format(self.customer_name, self.currency, self.grand_total) - def onload(self): super(SellingController, self).onload() if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"): diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 361e13cd1f..b0ff5d4c3b 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -14,9 +14,6 @@ from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_ class Lead(SellingController, CRMNote): - def get_feed(self): - return "{0}: {1}".format(_(self.status), self.lead_name) - def onload(self): customer = frappe.db.get_value("Customer", {"lead_name": self.name}) self.get("__onload").is_customer = customer diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 0d319bf742..b900b216e6 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -10,9 +10,6 @@ from erpnext.utilities.transaction_base import TransactionBase class MaintenanceVisit(TransactionBase): - def get_feed(self): - return _("To {0}").format(self.customer_name) - def validate_serial_no(self): for d in self.get("purposes"): if d.serial_no and not frappe.db.exists("Serial No", d.serial_no): diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index cbf2493d9a..4735f24e57 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -15,9 +15,6 @@ from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday class Project(Document): - def get_feed(self): - return "{0}: {1}".format(_(self.status), frappe.safe_decode(self.project_name)) - def onload(self): self.set_onload( "activity_summary", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 79f1b3adb4..2dde542d2d 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -20,9 +20,6 @@ class CircularReferenceError(frappe.ValidationError): class Task(NestedSet): nsm_parent_field = "parent_task" - def get_feed(self): - return "{0}: {1}".format(_(self.status), self.subject) - def get_customer_details(self): cust = frappe.db.sql("select customer_name from `tabCustomer` where name=%s", self.customer) if cust: diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 60c33567be..12ecb0112a 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -27,9 +27,6 @@ from erpnext.utilities.transaction_base import TransactionBase class Customer(TransactionBase): - def get_feed(self): - return self.customer_name - def onload(self): """Load address and contacts in `__onload`""" load_address_and_contact(self) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index afad7511be..94f63a599b 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -22,9 +22,6 @@ form_grid_templates = {"items": "templates/form_grid/material_request_grid.html" class MaterialRequest(BuyingController): - def get_feed(self): - return - def check_if_already_pulled(self): pass diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index f6c53f7bca..d9b9f12599 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -83,9 +83,6 @@ class StockEntry(StockController): } ) - def get_feed(self): - return self.stock_entry_type - def onload(self): for item in self.get("items"): item.update(get_bin_details(item.item_code, item.s_warehouse)) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 7f3e0cf4c2..f23419e98e 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -18,9 +18,6 @@ from frappe.utils.user import is_website_user class Issue(Document): - def get_feed(self): - return "{0}: {1}".format(_(self.status), self.subject) - def validate(self): if self.is_new() and self.via_customer_portal: self.flags.create_communication = True diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.py b/erpnext/support/doctype/warranty_claim/warranty_claim.py index c86356f2a2..ff63b77f9e 100644 --- a/erpnext/support/doctype/warranty_claim/warranty_claim.py +++ b/erpnext/support/doctype/warranty_claim/warranty_claim.py @@ -10,9 +10,6 @@ from erpnext.utilities.transaction_base import TransactionBase class WarrantyClaim(TransactionBase): - def get_feed(self): - return _("{0}: From {1}").format(self.status, self.customer_name) - def validate(self): if session["user"] != "Guest" and not self.customer: frappe.throw(_("Customer is required")) From 7d64bf78cf7f0f27f991d30ff04e90569dfbba20 Mon Sep 17 00:00:00 2001 From: Sabu Siyad Date: Mon, 12 Dec 2022 19:09:00 +0530 Subject: [PATCH 13/41] fix(pos): variable typo: `s_pos` -> `is_pos` Signed-off-by: Sabu Siyad --- erpnext/public/js/controllers/taxes_and_totals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 74810005ed..1f8a5e39f2 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -58,7 +58,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if ( in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) - && this.frm.doc.s_pos + && this.frm.doc.is_pos && this.frm.doc.is_return ) { this.set_total_amount_to_default_mop(); From 7b3316dc3175bcdd6c2c592d9abdb13ab477f015 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 12 Dec 2022 20:15:59 +0530 Subject: [PATCH 14/41] fix: incorrect balance on parent company due to key mismatch --- .../consolidated_financial_statement.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index e93fb6138a..d269e1f794 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -533,12 +533,13 @@ def get_accounts(root_type, companies): ], filters={"company": company, "root_type": root_type}, ): - if account.account_name not in added_accounts: + if account.account_number: + account_key = account.account_number + "-" + account.account_name + else: + account_key = account.account_name + + if account_key not in added_accounts: accounts.append(account) - if account.account_number: - account_key = account.account_number + "-" + account.account_name - else: - account_key = account.account_name added_accounts.append(account_key) return accounts From 78b438f6cf952829cbac907203f0f31584add668 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 9 Dec 2022 22:41:43 +0530 Subject: [PATCH 15/41] fix: `Material Request` reference in internal `Sales Order` --- erpnext/selling/doctype/sales_order/sales_order_dashboard.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py index ace2e29c2b..5c4b57813d 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py +++ b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py @@ -12,7 +12,10 @@ def get_data(): "Auto Repeat": "reference_document", "Maintenance Visit": "prevdoc_docname", }, - "internal_links": {"Quotation": ["items", "prevdoc_docname"]}, + "internal_links": { + "Quotation": ["items", "prevdoc_docname"], + "Material Request": ["items", "material_request"], + }, "transactions": [ { "label": _("Fulfillment"), From e057e1dfe7a9328d48ad7febdcf155d14c57cc83 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 12 Dec 2022 18:49:47 +0100 Subject: [PATCH 16/41] feat: incoterm named place --- .../doctype/purchase_invoice/purchase_invoice.json | 13 ++++++++++--- .../doctype/sales_invoice/sales_invoice.json | 13 ++++++++++--- .../doctype/purchase_order/purchase_order.json | 13 ++++++++++--- .../supplier_quotation/supplier_quotation.json | 13 ++++++++++--- erpnext/selling/doctype/quotation/quotation.json | 13 ++++++++++--- .../selling/doctype/sales_order/sales_order.json | 13 ++++++++++--- .../stock/doctype/delivery_note/delivery_note.json | 13 ++++++++++--- .../doctype/purchase_receipt/purchase_receipt.json | 13 ++++++++++--- 8 files changed, 80 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index a03157ebd4..6281400fbc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -64,12 +64,13 @@ "tax_withholding_net_total", "base_tax_withholding_net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_58", - "tax_category", - "column_break_49", "shipping_rule", + "column_break_49", "incoterm", + "named_place", "section_break_51", "taxes", "totals", @@ -1541,13 +1542,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2022-11-25 12:44:29.935567", + "modified": "2022-12-12 18:37:38.142688", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index b38bce7216..4729d9c3db 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -61,12 +61,13 @@ "total", "net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_38", "shipping_rule", - "incoterm", "column_break_55", - "tax_category", + "incoterm", + "named_place", "section_break_40", "taxes", "section_break_43", @@ -2122,6 +2123,12 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-file-text", @@ -2134,7 +2141,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-12-05 16:18:14.532114", + "modified": "2022-12-12 18:34:33.409895", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 93496261aa..ce7de874c5 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -62,12 +62,13 @@ "set_reserve_warehouse", "supplied_items", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_53", - "tax_category", - "column_break_50", "shipping_rule", + "column_break_50", "incoterm", + "named_place", "section_break_52", "taxes", "totals", @@ -1256,13 +1257,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:28:07.729943", + "modified": "2022-12-12 18:36:37.455134", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 7776ab8ec8..c5b369bedd 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -40,12 +40,13 @@ "total", "net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_34", - "tax_category", - "column_break_36", "shipping_rule", + "column_break_36", "incoterm", + "named_place", "section_break_38", "taxes", "totals", @@ -830,6 +831,12 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-shopping-cart", @@ -837,7 +844,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:27:32.179686", + "modified": "2022-12-12 18:35:39.740974", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 08918f4d61..eb2c0a48ac 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -43,12 +43,13 @@ "total", "net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_36", - "tax_category", - "column_break_34", "shipping_rule", + "column_break_34", "incoterm", + "named_place", "section_break_36", "taxes", "section_break_39", @@ -1059,13 +1060,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-shopping-cart", "idx": 82, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:20:54.984348", + "modified": "2022-12-12 18:32:28.671332", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 9ec32cbfc6..ccea8407ab 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -58,12 +58,13 @@ "total", "net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_38", - "tax_category", - "column_break_49", "shipping_rule", + "column_break_49", "incoterm", + "named_place", "section_break_40", "taxes", "section_break_43", @@ -1630,13 +1631,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:22:00.413878", + "modified": "2022-12-12 18:34:00.681780", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 80e4bcb640..165a56b783 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -57,12 +57,13 @@ "total", "net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_43", - "tax_category", - "column_break_39", "shipping_rule", + "column_break_39", "incoterm", + "named_place", "section_break_41", "taxes", "section_break_44", @@ -1388,13 +1389,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-truck", "idx": 146, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:22:42.860790", + "modified": "2022-12-12 18:38:53.067799", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index ab91d7c8c9..8f043585b8 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -58,12 +58,13 @@ "total", "net_total", "taxes_charges_section", + "tax_category", "taxes_and_charges", "shipping_col", - "tax_category", - "column_break_53", "shipping_rule", + "column_break_53", "incoterm", + "named_place", "taxes_section", "taxes", "totals", @@ -1225,13 +1226,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-truck", "idx": 261, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:29:30.067536", + "modified": "2022-12-12 18:40:32.447752", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From f20370c5ef6ed5cd3c146fa115e7d43527373969 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 13 Dec 2022 02:36:12 +0100 Subject: [PATCH 17/41] refactor: translatable strings and guard clause --- .../doctype/payment_entry/payment_entry.py | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9354e44d24..0a704ac442 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -684,35 +684,34 @@ class PaymentEntry(AccountsController): ) def validate_payment_against_negative_invoice(self): - if (self.payment_type == "Pay" and self.party_type == "Customer") or ( - self.payment_type == "Receive" and self.party_type == "Supplier" + if (self.payment_type != "Pay" or self.party_type != "Customer") and ( + self.payment_type != "Receive" or self.party_type != "Supplier" ): + return - total_negative_outstanding = sum( - abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0 + total_negative_outstanding = sum( + abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0 + ) + + paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount + additional_charges = sum(flt(d.amount) for d in self.deductions) + + if not total_negative_outstanding: + if self.party_type == "Customer": + msg = _("Cannot pay to Customer without any negative outstanding invoice") + else: + msg = _("Cannot receive from Supplier without any negative outstanding invoice") + + frappe.throw(msg, InvalidPaymentEntry) + + elif paid_amount - additional_charges > total_negative_outstanding: + frappe.throw( + _("Paid Amount cannot be greater than total negative outstanding amount {0}").format( + total_negative_outstanding + ), + InvalidPaymentEntry, ) - paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount - additional_charges = sum([flt(d.amount) for d in self.deductions]) - - 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")), - self.party_type, - ), - InvalidPaymentEntry, - ) - - elif paid_amount - additional_charges > total_negative_outstanding: - frappe.throw( - _("Paid Amount cannot be greater than total negative outstanding amount {0}").format( - total_negative_outstanding - ), - InvalidPaymentEntry, - ) - def set_title(self): if frappe.flags.in_import and self.title: # do not set title dynamically if title exists during data import. From aa787e403086c085bc1706d634b243a33be2d91c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 13 Dec 2022 13:16:24 +0530 Subject: [PATCH 18/41] fix: Permission issue in Tax Detail report --- erpnext/accounts/report/tax_detail/tax_detail.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/tax_detail/tax_detail.py b/erpnext/accounts/report/tax_detail/tax_detail.py index ba8d307228..ba733c2d18 100644 --- a/erpnext/accounts/report/tax_detail/tax_detail.py +++ b/erpnext/accounts/report/tax_detail/tax_detail.py @@ -234,8 +234,11 @@ def modify_report_columns(doctype, field, column): if field in ["item_tax_rate", "base_net_amount"]: return None - if doctype == "GL Entry" and field in ["debit", "credit"]: - column.update({"label": _("Amount"), "fieldname": "amount"}) + if doctype == "GL Entry": + if field in ["debit", "credit"]: + column.update({"label": _("Amount"), "fieldname": "amount"}) + elif field == "voucher_type": + column.update({"fieldtype": "Data", "options": ""}) if field == "taxes_and_charges": column.update({"label": _("Taxes and Charges Template")}) From 723c64ba738b027323487500f09dfd7cafe3468d Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 10 Dec 2022 14:00:29 +0530 Subject: [PATCH 19/41] fix: `Enough Parts to Build` in `BOM Stock Report` --- .../report/bom_stock_report/bom_stock_report.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py index 1e1b435600..cdf1541f88 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py @@ -4,7 +4,7 @@ import frappe from frappe import _ -from frappe.query_builder.functions import Floor, Sum +from frappe.query_builder.functions import Sum from pypika.terms import ExistsCriterion @@ -58,9 +58,9 @@ def get_bom_stock(filters): bom_item.description, bom_item.stock_qty, bom_item.stock_uom, - bom_item.stock_qty * qty_to_produce / bom.quantity, - Sum(bin.actual_qty).as_("actual_qty"), - Sum(Floor(bin.actual_qty / (bom_item.stock_qty * qty_to_produce / bom.quantity))), + (bom_item.stock_qty / bom.quantity) * qty_to_produce, + Sum(bin.actual_qty), + Sum(bin.actual_qty) / (bom_item.stock_qty / bom.quantity), ) .where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM")) .groupby(bom_item.item_code) From 15e3b7f218cd881d28f616a6286dca27a5223744 Mon Sep 17 00:00:00 2001 From: kunhi Date: Tue, 13 Dec 2022 12:41:56 +0400 Subject: [PATCH 20/41] fix: at create_customer_or_supplier on session creation --- erpnext/portal/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py index 7be8c5df18..c8b03e678b 100644 --- a/erpnext/portal/utils.py +++ b/erpnext/portal/utils.py @@ -102,7 +102,7 @@ def create_party_contact(doctype, fullname, user, party_name): contact = frappe.new_doc("Contact") contact.update({"first_name": fullname, "email_id": user}) contact.append("links", dict(link_doctype=doctype, link_name=party_name)) - contact.append("email_ids", dict(email_id=user)) + contact.append("email_ids", dict(email_id=user, is_primary=True)) contact.flags.ignore_mandatory = True contact.insert(ignore_permissions=True) From 36997d9788d7e677a1b5bb3a2c772689a9146c24 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 13 Dec 2022 18:59:20 +0100 Subject: [PATCH 21/41] fix: translation for warning on Overbilling/-receipt/-delivery --- erpnext/controllers/status_updater.py | 21 +++++++++++++-------- erpnext/translations/de.csv | 3 ++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index bf077282bf..d4972973d0 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -347,16 +347,21 @@ class StatusUpdater(Document): ) def warn_about_bypassing_with_role(self, item, qty_or_amount, role): - action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling") + if qty_or_amount == "qty": + msg = _("Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.") + else: + msg = _("Overbilling of {0} {1} ignored for item {2} because you have {3} role.") - msg = _("{0} of {1} {2} ignored for item {3} because you have {4} role.").format( - action, - _(item["target_ref_field"].title()), - frappe.bold(item["reduce_by"]), - frappe.bold(item.get("item_code")), - role, + frappe.msgprint( + msg.format( + _(item["target_ref_field"].title()), + frappe.bold(item["reduce_by"]), + frappe.bold(item.get("item_code")), + role, + ), + indicator="orange", + alert=True, ) - frappe.msgprint(msg, indicator="orange", alert=True) def update_qty(self, update_modified=True): """Updates qty or amount at row level diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index f1d830245f..1014e27d6c 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1849,6 +1849,8 @@ Outstanding Amt,Offener Betrag, Outstanding Cheques and Deposits to clear,Ausstehende Schecks und Anzahlungen zum verbuchen, Outstanding for {0} cannot be less than zero ({1}),Ausstände für {0} können nicht kleiner als Null sein ({1}), Outward taxable supplies(zero rated),Steuerpflichtige Lieferungen aus dem Ausland (null bewertet), +Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Annahme bzw. Lieferung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben." +Overbilling of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Abrechnung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben." Overdue,Überfällig, Overlap in scoring between {0} and {1},Überlappung beim Scoring zwischen {0} und {1}, Overlapping conditions found between:,Überlagernde Bedingungen gefunden zwischen:, @@ -9914,4 +9916,3 @@ Cost and Freight,Kosten und Fracht, Delivered at Place,Geliefert benannter Ort, Delivered at Place Unloaded,Geliefert benannter Ort entladen, Delivered Duty Paid,Geliefert verzollt, -{0} of {1} {2} ignored for item {3} because you have {4} role,"{0} von Artikel {3} mit {1} {2} wurde ignoriert, weil Sie die Rolle {4} haben." From 41fc3be339cc78dc7418776556830c29aa875999 Mon Sep 17 00:00:00 2001 From: Gokulnath17 Date: Wed, 14 Dec 2022 10:46:11 +0530 Subject: [PATCH 22/41] fixes in gross profit report --- 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 dacc809da0..99e86ae78e 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -503,7 +503,7 @@ class GrossProfitGenerator(object): invoice_portion = 100 elif row.invoice_portion: invoice_portion = row.invoice_portion - else: + elif row.payment_amount: invoice_portion = row.payment_amount * 100 / row.base_net_amount if i == 0: From 410a58b3deb060003bfaeac2fbff938aef1526dc Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 14 Dec 2022 15:30:28 +0530 Subject: [PATCH 23/41] fix: get_serial_nos_for_fg() missing 1 required positional argument: 'args' --- .../doctype/work_order/test_work_order.py | 30 +++++++++++++++++++ .../stock/doctype/stock_entry/stock_entry.py | 10 +++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 36466ff6d7..f568264c90 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1154,6 +1154,36 @@ class TestWorkOrder(FrappeTestCase): except frappe.MandatoryError: self.fail("Batch generation causing failing in Work Order") + @change_settings("Manufacturing Settings", {"make_serial_no_batch_from_work_order": 1}) + def test_auto_serial_no_creation(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + + fg_item = frappe.generate_hash(length=20) + child_item = frappe.generate_hash(length=20) + + bom_tree = {fg_item: {child_item: {}}} + + create_nested_bom(bom_tree, prefix="") + + item = frappe.get_doc("Item", fg_item) + item.has_serial_no = 1 + item.serial_no_series = f"{item.name}.#####" + item.save() + + try: + wo_order = make_wo_order_test_record(item=fg_item, qty=2, skip_transfer=True) + serial_nos = wo_order.serial_no + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) + stock_entry.set_work_order_details() + stock_entry.set_serial_no_batch_for_finished_good() + for row in stock_entry.items: + if row.item_code == fg_item: + self.assertTrue(row.serial_no) + self.assertEqual(sorted(get_serial_nos(row.serial_no)), sorted(get_serial_nos(serial_nos))) + + except frappe.MandatoryError: + self.fail("Batch generation causing failing in Work Order") + @change_settings( "Manufacturing Settings", {"backflush_raw_materials_based_on": "Material Transferred for Manufacture"}, diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index d9b9f12599..d401f818c6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2236,16 +2236,16 @@ class StockEntry(StockController): d.qty -= process_loss_dict[d.item_code][1] def set_serial_no_batch_for_finished_good(self): - serial_nos = "" + serial_nos = [] if self.pro_doc.serial_no: - serial_nos = self.get_serial_nos_for_fg() + serial_nos = self.get_serial_nos_for_fg() or [] for row in self.items: if row.is_finished_item and row.item_code == self.pro_doc.production_item: if serial_nos: row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)]) - def get_serial_nos_for_fg(self, args): + def get_serial_nos_for_fg(self): fields = [ "`tabStock Entry`.`name`", "`tabStock Entry Detail`.`qty`", @@ -2261,9 +2261,7 @@ class StockEntry(StockController): ] stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters) - - if self.pro_doc.serial_no: - return self.get_available_serial_nos(stock_entries) + return self.get_available_serial_nos(stock_entries) def get_available_serial_nos(self, stock_entries): used_serial_nos = [] From 6d9d730759af258eadf93bc105a0bd159f2b7ba2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 14 Dec 2022 16:05:15 +0530 Subject: [PATCH 24/41] fix: cost_center filter gives incorrect output filtering on cost center gives invoices that are reconciled as having outstanding --- .../doctype/payment_reconciliation/payment_reconciliation.py | 4 +++- erpnext/accounts/utils.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 52efd33fef..ff212f2a35 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -23,6 +23,7 @@ class PaymentReconciliation(Document): def __init__(self, *args, **kwargs): super(PaymentReconciliation, self).__init__(*args, **kwargs) self.common_filter_conditions = [] + self.accounting_dimension_filter_conditions = [] self.ple_posting_date_filter = [] @frappe.whitelist() @@ -193,6 +194,7 @@ class PaymentReconciliation(Document): posting_date=self.ple_posting_date_filter, min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None, max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None, + accounting_dimensions=self.accounting_dimension_filter_conditions, ) if self.invoice_limit: @@ -381,7 +383,7 @@ class PaymentReconciliation(Document): self.common_filter_conditions.append(ple.company == self.company) if self.get("cost_center") and (get_invoices or get_return_invoices): - self.common_filter_conditions.append(ple.cost_center == self.cost_center) + self.accounting_dimension_filter_conditions.append(ple.cost_center == self.cost_center) if get_invoices: if self.from_invoice_date: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 41702d65b4..1e573b01ba 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -836,6 +836,7 @@ def get_outstanding_invoices( posting_date=None, min_outstanding=None, max_outstanding=None, + accounting_dimensions=None, ): ple = qb.DocType("Payment Ledger Entry") @@ -866,6 +867,7 @@ def get_outstanding_invoices( min_outstanding=min_outstanding, max_outstanding=max_outstanding, get_invoices=True, + accounting_dimensions=accounting_dimensions or [], ) for d in invoice_list: @@ -1615,6 +1617,7 @@ class QueryPaymentLedger(object): .where(ple.delinked == 0) .where(Criterion.all(filter_on_voucher_no)) .where(Criterion.all(self.common_filter)) + .where(Criterion.all(self.dimensions_filter)) .where(Criterion.all(self.voucher_posting_date)) .groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party) ) @@ -1702,6 +1705,7 @@ class QueryPaymentLedger(object): max_outstanding=None, get_payments=False, get_invoices=False, + accounting_dimensions=None, ): """ Fetch voucher amount and outstanding amount from Payment Ledger using Database CTE @@ -1717,6 +1721,7 @@ class QueryPaymentLedger(object): self.reset() self.vouchers = vouchers self.common_filter = common_filter or [] + self.dimensions_filter = accounting_dimensions or [] self.voucher_posting_date = posting_date or [] self.min_outstanding = min_outstanding self.max_outstanding = max_outstanding From 8eb93004f7f5947ab535cb42c377f2d795dac255 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 14 Dec 2022 16:16:22 +0530 Subject: [PATCH 25/41] fix: cost_center filter fix for 'Get Outstanding Invoice' in PE --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9354e44d24..e7e7ce5c9d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1188,6 +1188,7 @@ def get_outstanding_reference_documents(args): ple = qb.DocType("Payment Ledger Entry") common_filter = [] + accounting_dimensions_filter = [] posting_and_due_date = [] # confirm that Supplier is not blocked @@ -1217,7 +1218,7 @@ def get_outstanding_reference_documents(args): # Add cost center condition if args.get("cost_center"): condition += " and cost_center='%s'" % args.get("cost_center") - common_filter.append(ple.cost_center == args.get("cost_center")) + accounting_dimensions_filter.append(ple.cost_center == args.get("cost_center")) date_fields_dict = { "posting_date": ["from_posting_date", "to_posting_date"], @@ -1243,6 +1244,7 @@ def get_outstanding_reference_documents(args): posting_date=posting_and_due_date, min_outstanding=args.get("outstanding_amt_greater_than"), max_outstanding=args.get("outstanding_amt_less_than"), + accounting_dimensions=accounting_dimensions_filter, ) outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) From ce9626fead61278085f681673feedcb0f45ff5b2 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 14 Dec 2022 14:03:36 +0100 Subject: [PATCH 26/41] feat: more control when printing RFQ --- .../request_for_quotation.js | 119 ++++++++++++------ .../request_for_quotation.py | 15 ++- 2 files changed, 95 insertions(+), 39 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 98c7dc9bd3..744dd70d62 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -57,44 +57,93 @@ frappe.ui.form.on("Request for Quotation",{ }); }, __("Tools")); - frm.add_custom_button(__('Download PDF'), () => { - var suppliers = []; - const fields = [{ - fieldtype: 'Link', - label: __('Select a Supplier'), - fieldname: 'supplier', - options: 'Supplier', - reqd: 1, - get_query: () => { - return { - filters: [ - ["Supplier", "name", "in", frm.doc.suppliers.map((row) => {return row.supplier;})] - ] - } - } - }]; - - frappe.prompt(fields, data => { - var child = locals[cdt][cdn] - - var w = window.open( - frappe.urllib.get_full_url("/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?" - +"doctype="+encodeURIComponent(frm.doc.doctype) - +"&name="+encodeURIComponent(frm.doc.name) - +"&supplier="+encodeURIComponent(data.supplier) - +"&no_letterhead=0")); - if(!w) { - frappe.msgprint(__("Please enable pop-ups")); return; - } + frm.add_custom_button( + __("Download PDF"), + () => { + frappe.prompt( + [ + { + fieldtype: "Link", + label: "Select a Supplier", + fieldname: "supplier", + options: "Supplier", + reqd: 1, + default: frm.doc.suppliers?.length == 1 ? frm.doc.suppliers[0].supplier : "", + get_query: () => { + return { + filters: [ + [ + "Supplier", + "name", + "in", + frm.doc.suppliers.map((row) => { + return row.supplier; + }), + ], + ], + }; + }, + }, + { + fieldtype: "Section Break", + label: "Print Settings", + fieldname: "print_settings", + collapsible: 1, + }, + { + fieldtype: "Link", + label: "Print Format", + fieldname: "print_format", + options: "Print Format", + get_query: () => { + return { + filters: { + doc_type: "Request for Quotation", + }, + }; + }, + }, + { + fieldtype: "Link", + label: "Language", + fieldname: "language", + options: "Language", + }, + { + fieldtype: "Link", + label: "Letter Head", + fieldname: "letter_head", + options: "Letter Head", + }, + ], + (data) => { + var w = window.open( + frappe.urllib.get_full_url( + "/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?" + + new URLSearchParams({ + doctype: frm.doc.doctype, + name: frm.doc.name, + supplier: data.supplier, + print_format: data.print_format || "Standard", + language: data.language || frappe.boot.lang, + letter_head: data.letter_head || "", + }).toString() + ) + ); + if (!w) { + frappe.msgprint(__("Please enable pop-ups")); + return; + } + }, + "Download PDF for Supplier", + "Download" + ); }, - 'Download PDF for Supplier', - 'Download'); - }, - __("Tools")); + __("Tools") + ); - frm.page.set_inner_btn_group_as_primary(__('Create')); + frm.page.set_inner_btn_group_as_primary(__("Create")); } - }, make_supplier_quotation: function(frm) { diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index bdbc9ce0b7..dbc3644957 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -389,10 +389,17 @@ def create_rfq_items(sq_doc, supplier, data): @frappe.whitelist() -def get_pdf(doctype, name, supplier): - doc = get_rfq_doc(doctype, name, supplier) - if doc: - download_pdf(doctype, name, doc=doc) +def get_pdf(doctype, name, supplier, print_format=None, language=None, letter_head=None): + # permissions get checked in `download_pdf` + if doc := get_rfq_doc(doctype, name, supplier): + download_pdf( + doctype, + name, + print_format, + doc=doc, + language=language, + letter_head=letter_head or None, + ) def get_rfq_doc(doctype, name, supplier): From 8717148d9bb96451162d83cc5fd7297aa45a4dc1 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 14 Dec 2022 14:14:29 +0100 Subject: [PATCH 27/41] feat: improve visibility of default values --- .../doctype/request_for_quotation/request_for_quotation.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 744dd70d62..a9f5afb2e9 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -95,6 +95,7 @@ frappe.ui.form.on("Request for Quotation",{ label: "Print Format", fieldname: "print_format", options: "Print Format", + placeholder: "Standard", get_query: () => { return { filters: { @@ -108,12 +109,14 @@ frappe.ui.form.on("Request for Quotation",{ label: "Language", fieldname: "language", options: "Language", + default: frappe.boot.lang, }, { fieldtype: "Link", label: "Letter Head", fieldname: "letter_head", options: "Letter Head", + default: frm.doc.letter_head, }, ], (data) => { @@ -126,7 +129,7 @@ frappe.ui.form.on("Request for Quotation",{ supplier: data.supplier, print_format: data.print_format || "Standard", language: data.language || frappe.boot.lang, - letter_head: data.letter_head || "", + letter_head: data.letter_head || frm.doc.letter_head || "", }).toString() ) ); From 973ef33eb5ba0e173978a9c7452bb0861b272077 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 14 Dec 2022 20:41:00 +0530 Subject: [PATCH 28/41] fix: Cost Center for tax withholding invoices --- .../tax_withholding_category.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 30ed91b974..a9d870da74 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -121,12 +121,24 @@ 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) + cost_center = get_cost_center(inv) + tax_row.update({"cost_center": cost_center}) + if inv.doctype == "Purchase Invoice": return tax_row, tax_deducted_on_advances, voucher_wise_amount else: return tax_row +def get_cost_center(inv): + cost_center = frappe.get_cached_value("Company", inv.company, "cost_center") + + if len(inv.get("taxes")) > 0: + cost_center = inv.get("taxes")[0].cost_center + + return cost_center + + def get_tax_withholding_details(tax_withholding_category, posting_date, company): tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category) From 26277cfcf3e7bbb6b14ceb42ee77e740f2825f20 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 14 Dec 2022 21:22:48 +0530 Subject: [PATCH 29/41] chore: resolve errors in test --- .../tax_withholding_category/tax_withholding_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a9d870da74..b834d1404d 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -133,7 +133,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): def get_cost_center(inv): cost_center = frappe.get_cached_value("Company", inv.company, "cost_center") - if len(inv.get("taxes")) > 0: + if len(inv.get("taxes", [])) > 0: cost_center = inv.get("taxes")[0].cost_center return cost_center From a998a8a2dad20e2671de5dbf70816c31c09e4c0e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 15 Dec 2022 12:57:53 +0530 Subject: [PATCH 30/41] test: cost center should not affect outstanding calculation --- .../test_payment_reconciliation.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index dae029b408..6030134fff 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -8,6 +8,8 @@ from frappe import qb from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, nowdate +from erpnext import get_default_cost_center +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.party import get_party_account @@ -20,6 +22,7 @@ class TestPaymentReconciliation(FrappeTestCase): self.create_item() self.create_customer() self.create_account() + self.create_cost_center() self.clear_old_entries() def tearDown(self): @@ -216,6 +219,22 @@ class TestPaymentReconciliation(FrappeTestCase): ) return je + def create_cost_center(self): + # Setup cost center + cc_name = "Sub" + + self.main_cc = frappe.get_doc("Cost Center", get_default_cost_center(self.company)) + + cc_exists = frappe.db.get_list("Cost Center", filters={"cost_center_name": cc_name}) + if cc_exists: + self.sub_cc = frappe.get_doc("Cost Center", cc_exists[0].name) + else: + sub_cc = frappe.new_doc("Cost Center") + sub_cc.cost_center_name = "Sub" + sub_cc.parent_cost_center = self.main_cc.parent_cost_center + sub_cc.company = self.main_cc.company + self.sub_cc = sub_cc.save() + def test_filter_min_max(self): # check filter condition minimum and maximum amount self.create_sales_invoice(qty=1, rate=300) @@ -578,3 +597,24 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(len(pr.payments), 1) self.assertEqual(pr.payments[0].amount, amount) self.assertEqual(pr.payments[0].currency, "EUR") + + def test_differing_cost_center_on_invoice_and_payment(self): + """ + Cost Center filter should not affect outstanding amount calculation + """ + + si = self.create_sales_invoice(qty=1, rate=100, do_not_submit=True) + si.cost_center = self.main_cc.name + si.submit() + pr = get_payment_entry(si.doctype, si.name) + pr.cost_center = self.sub_cc.name + pr = pr.save().submit() + + pr = self.create_payment_reconciliation() + pr.cost_center = self.main_cc.name + + pr.get_unreconciled_entries() + + # check PR tool output + self.assertEqual(len(pr.get("invoices")), 0) + self.assertEqual(len(pr.get("payments")), 0) From 0f28074e5a8b39773ccadf4427f83eaa1ed8821e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Dec 2022 16:34:06 +0530 Subject: [PATCH 31/41] fix: unsupported operand type(s) for +: 'int' and 'NoneType' --- erpnext/stock/get_item_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 31dccf6944..1741d65460 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -828,9 +828,9 @@ def insert_item_price(args): ): if frappe.has_permission("Item Price", "write"): price_list_rate = ( - (args.rate + args.discount_amount) / args.get("conversion_factor") + (flt(args.rate) + flt(args.discount_amount)) / args.get("conversion_factor") if args.get("conversion_factor") - else (args.rate + args.discount_amount) + else (flt(args.rate) + flt(args.discount_amount)) ) item_price = frappe.db.get_value( From ae31ff1c48cb4a9baab98f61bfe9ed85914a9034 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Dec 2022 17:05:53 +0530 Subject: [PATCH 32/41] fix: disabled items showing in the report 'Itemwise Recommended Reorder Level ' --- .../itemwise_recommended_reorder_level.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py index a6fc049cbd..c4358b809f 100644 --- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py +++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py @@ -82,7 +82,7 @@ def get_item_info(filters): item.safety_stock, item.lead_time_days, ) - .where(item.is_stock_item == 1) + .where((item.is_stock_item == 1) & (item.disabled == 0)) ) if brand := filters.get("brand"): From 1a40c04b72986232d40be1620788ed5b6b68ffab Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 15 Dec 2022 18:51:58 +0530 Subject: [PATCH 33/41] feat: Ignore company related doctype for other apps via hooks --- .../transaction_deletion_record.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index c18a4b2214..4256a7d831 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -204,7 +204,7 @@ class TransactionDeletionRecord(Document): @frappe.whitelist() def get_doctypes_to_be_ignored(): - doctypes_to_be_ignored_list = [ + doctypes_to_be_ignored = [ "Account", "Cost Center", "Warehouse", @@ -223,4 +223,7 @@ def get_doctypes_to_be_ignored(): "Customer", "Supplier", ] - return doctypes_to_be_ignored_list + + doctypes_to_be_ignored.extend(frappe.get_hooks("company_data_to_be_ignored") or []) + + return doctypes_to_be_ignored From 3b66920342430d44996b41b1334f4588a7030a85 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 16 Dec 2022 19:22:29 +0530 Subject: [PATCH 34/41] fix: Unable to import COA through importer --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 4 ++++ erpnext/setup/doctype/company/company.py | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 325a356c3c..220b74727b 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -485,6 +485,10 @@ def set_default_accounts(company): "default_payable_account": frappe.db.get_value( "Account", {"company": company.name, "account_type": "Payable", "is_group": 0} ), + "default_provisional_account": frappe.db.get_value( + "Account", + {"company": company.name, "account_type": "Service Received But Not Billed", "is_group": 0}, + ), } ) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index d6f2378094..07ee2890c4 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -70,9 +70,6 @@ class Company(NestedSet): self.abbr = self.abbr.strip() - # if self.get('__islocal') and len(self.abbr) > 5: - # frappe.throw(_("Abbreviation cannot have more than 5 characters")) - if not self.abbr.strip(): frappe.throw(_("Abbreviation is mandatory")) From 4ecce242a80a3cba7213ab7111ded8de7bb92081 Mon Sep 17 00:00:00 2001 From: Gokulnath <95605271+Gokulnath17@users.noreply.github.com> Date: Sat, 17 Dec 2022 20:06:01 +0530 Subject: [PATCH 35/41] feat: adding warehouse filter for sales order ananlysis report feat: adding warehouse filter for sales order ananlysis report --- .../report/sales_order_analysis/sales_order_analysis.js | 6 ++++++ .../report/sales_order_analysis/sales_order_analysis.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js index 91748bc7be..f3f931edfd 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js @@ -44,6 +44,12 @@ frappe.query_reports["Sales Order Analysis"] = { } } }, + { + "fieldname": "warehouse", + "label": __("Warehouse"), + "fieldtype": "Link", + "options": "Warehouse" + }, { "fieldname": "status", "label": __("Status"), diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index 720aa41982..63d339a839 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -53,6 +53,9 @@ def get_conditions(filters): if filters.get("status"): conditions += " and so.status in %(status)s" + if filters.get("warehouse"): + conditions += " and soi.warehouse = %(warehouse)s" + return conditions From b09eade3e4b41833e507410410f63b93d8d17de7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 16 Dec 2022 21:58:22 +0530 Subject: [PATCH 36/41] fix: ERR journals reported in AR/AP Exchange Rate Revaluation on Receivable/Payable will included in AR/AP report --- .../accounts_receivable.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index a195c57586..9d9684340c 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -99,6 +99,9 @@ class ReceivablePayableReport(object): # Get return entries self.get_return_entries() + # Get Exchange Rate Revaluations + self.get_exchange_rate_revaluations() + self.data = [] for ple in self.ple_entries: @@ -251,7 +254,8 @@ class ReceivablePayableReport(object): row.invoice_grand_total = row.invoiced if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and ( - abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision + (abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision) + or (row.voucher_no in self.err_journals) ): # non-zero oustanding, we must consider this row @@ -1028,3 +1032,17 @@ class ReceivablePayableReport(object): "data": {"labels": self.ageing_column_labels, "datasets": rows}, "type": "percentage", } + + def get_exchange_rate_revaluations(self): + je = qb.DocType("Journal Entry") + results = ( + qb.from_(je) + .select(je.name) + .where( + (je.company == self.filters.company) + & (je.posting_date.lte(self.filters.report_date)) + & (je.voucher_type == "Exchange Rate Revaluation") + ) + .run() + ) + self.err_journals = [x[0] for x in results] if results else [] From 2ed86760d7696b0129a0d01cc4287cfe001f3a66 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 18 Dec 2022 12:39:54 +0530 Subject: [PATCH 37/41] test: err for party should be in AR/AP report --- .../test_accounts_receivable.py | 134 +++++++++++++++--- 1 file changed, 116 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index bac8beed2e..97a9c15fc7 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -1,9 +1,10 @@ import unittest import frappe -from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_days, getdate, today +from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.utils import add_days, flt, getdate, today +from erpnext import get_default_cost_center from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute @@ -17,10 +18,37 @@ class TestAccountsReceivable(FrappeTestCase): frappe.db.sql("delete from `tabPayment Entry` where company='_Test Company 2'") frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'") frappe.db.sql("delete from `tabPayment Ledger Entry` where company='_Test Company 2'") + frappe.db.sql("delete from `tabJournal Entry` where company='_Test Company 2'") + frappe.db.sql("delete from `tabExchange Rate Revaluation` where company='_Test Company 2'") + + self.create_usd_account() def tearDown(self): frappe.db.rollback() + def create_usd_account(self): + name = "Debtors USD" + exists = frappe.db.get_list( + "Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"} + ) + if exists: + self.debtors_usd = exists[0].name + else: + debtors = frappe.get_doc( + "Account", + frappe.db.get_list( + "Account", filters={"company": "_Test Company 2", "account_name": "Debtors"} + )[0].name, + ) + + debtors_usd = frappe.new_doc("Account") + debtors_usd.company = debtors.company + debtors_usd.account_name = "Debtors USD" + debtors_usd.account_currency = "USD" + debtors_usd.parent_account = debtors.parent_account + debtors_usd.account_type = debtors.account_type + self.debtors_usd = debtors_usd.save().name + def test_accounts_receivable(self): filters = { "company": "_Test Company 2", @@ -33,7 +61,7 @@ class TestAccountsReceivable(FrappeTestCase): } # check invoice grand total and invoiced column's value for 3 payment terms - name = make_sales_invoice() + name = make_sales_invoice().name report = execute(filters) expected_data = [[100, 30], [100, 50], [100, 20]] @@ -118,8 +146,74 @@ class TestAccountsReceivable(FrappeTestCase): ], ) + @change_settings( + "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1} + ) + def test_exchange_revaluation_for_party(self): + """ + Exchange Revaluation for party on Receivable/Payable shoule be included + """ -def make_sales_invoice(): + company = "_Test Company 2" + customer = "_Test Customer 2" + + # Using Exchange Gain/Loss account for unrealized as well. + company_doc = frappe.get_doc("Company", company) + company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account + company_doc.save() + + si = make_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si.currency = "USD" + si.conversion_rate = 0.90 + si.debit_to = self.debtors_usd + si = si.save().submit() + + # Exchange Revaluation + err = frappe.new_doc("Exchange Rate Revaluation") + err.company = company + err.posting_date = today() + accounts = err.get_accounts_data() + err.extend("accounts", accounts) + err.accounts[0].new_exchange_rate = 0.95 + row = err.accounts[0] + row.new_balance_in_base_currency = flt( + row.new_exchange_rate * flt(row.balance_in_account_currency) + ) + row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency) + err.set_total_gain_loss() + err = err.save().submit() + + # Submit JV for ERR + jv = frappe.get_doc(err.make_jv_entry()) + jv = jv.save() + for x in jv.accounts: + x.cost_center = get_default_cost_center(jv.company) + jv.submit() + + filters = { + "company": company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + report = execute(filters) + + expected_data_for_err = [0, -5, 0, 5] + row = [x for x in report[1] if x.voucher_type == jv.doctype and x.voucher_no == jv.name][0] + self.assertEqual( + expected_data_for_err, + [ + row.invoiced, + row.paid, + row.credit_note, + row.outstanding, + ], + ) + + +def make_sales_invoice(no_payment_schedule=False, do_not_submit=False): frappe.set_user("Administrator") si = create_sales_invoice( @@ -134,22 +228,26 @@ def make_sales_invoice(): do_not_save=1, ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30), - ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50), - ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20), - ) + if not no_payment_schedule: + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30), + ) + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50), + ) + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20), + ) - si.submit() + si = si.save() - return si.name + if not do_not_submit: + si = si.submit() + + return si def make_payment(docname): From 2b4eae5f8405cb3ec536cf5c8740eaaa0edf5496 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 19 Dec 2022 16:24:55 +0530 Subject: [PATCH 38/41] fix: unsupported operand type(s) for +=: 'int' and 'NoneType' --- erpnext/stock/doctype/pick_list/pick_list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 5de7fedc4e..aff5e0539c 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -192,13 +192,13 @@ class PickList(Document): if item_map.get(key): item_map[key].qty += item.qty - item_map[key].stock_qty += item.stock_qty + item_map[key].stock_qty += flt(item.stock_qty, item.precision("stock_qty")) else: item_map[key] = item # maintain count of each item (useful to limit get query) self.item_count_map.setdefault(item_code, 0) - self.item_count_map[item_code] += item.stock_qty + self.item_count_map[item_code] += flt(item.stock_qty, item.precision("stock_qty")) return item_map.values() From 88ce11f03df30634ec9ee1c0e50ccb424452c82c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 19 Dec 2022 16:44:19 +0530 Subject: [PATCH 39/41] fix: incorrect type hints (#33381) [skip ci] --- erpnext/stock/doctype/serial_no/serial_no.py | 4 ++-- .../doctype/stock_reconciliation/stock_reconciliation.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index a2748d0d09..541d4d17e1 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -766,13 +766,13 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note): @frappe.whitelist() def auto_fetch_serial_number( - qty: float, + qty: int, item_code: str, warehouse: str, posting_date: Optional[str] = None, batch_nos: Optional[Union[str, List[str]]] = None, for_doctype: Optional[str] = None, - exclude_sr_nos: Optional[List[str]] = None, + exclude_sr_nos=None, ) -> List[str]: filters = frappe._dict({"item_code": item_code, "warehouse": warehouse}) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 3a0b38a0fc..398b3c98e3 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -715,8 +715,8 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None): def get_stock_balance_for( item_code: str, warehouse: str, - posting_date: str, - posting_time: str, + posting_date, + posting_time, batch_no: Optional[str] = None, with_valuation_rate: bool = True, ): From b1721b79cebc7c4c438f00a1deaebaa5b39f7e0b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 19 Dec 2022 23:24:34 +0530 Subject: [PATCH 40/41] fix: daily scheduler to identify and fix stock transfer entries having incorrect valuation --- erpnext/hooks.py | 1 + .../stock/doctype/stock_entry/stock_entry.py | 73 ++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index fd19d2585c..e84a32d46f 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -393,6 +393,7 @@ scheduler_events = { ], "daily": [ "erpnext.support.doctype.issue.issue.auto_close_tickets", + "erpnext.stock.doctype.stock_entry.stock_entry.audit_incorrect_valuation_entries", "erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity", "erpnext.controllers.accounts_controller.update_invoice_status", "erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index d401f818c6..a047a9b814 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -4,12 +4,24 @@ import json from collections import defaultdict +from typing import Dict import frappe from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum -from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate +from frappe.utils import ( + add_days, + cint, + comma_or, + cstr, + flt, + format_time, + formatdate, + getdate, + nowdate, + today, +) import erpnext from erpnext.accounts.general_ledger import process_gl_map @@ -2700,3 +2712,62 @@ def get_stock_entry_data(work_order): ) .orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx) ).run(as_dict=1) + + +def audit_incorrect_valuation_entries(): + # Audit of stock transfer entries having incorrect valuation + from erpnext.controllers.stock_controller import create_repost_item_valuation_entry + + stock_entries = get_incorrect_stock_entries() + + for stock_entry, values in stock_entries.items(): + reposting_data = frappe._dict( + { + "posting_date": values.posting_date, + "posting_time": values.posting_time, + "voucher_type": "Stock Entry", + "voucher_no": stock_entry, + "company": values.company, + } + ) + + create_repost_item_valuation_entry(reposting_data) + + +def get_incorrect_stock_entries() -> Dict: + stock_entry = frappe.qb.DocType("Stock Entry") + stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") + transfer_purposes = [ + "Material Transfer", + "Material Transfer for Manufacture", + "Send to Subcontractor", + ] + + query = ( + frappe.qb.from_(stock_entry) + .inner_join(stock_ledger_entry) + .on(stock_entry.name == stock_ledger_entry.voucher_no) + .select( + stock_entry.name, + stock_entry.company, + stock_entry.posting_date, + stock_entry.posting_time, + Sum(stock_ledger_entry.stock_value_difference).as_("stock_value"), + ) + .where( + (stock_entry.docstatus == 1) + & (stock_entry.purpose.isin(transfer_purposes)) + & (stock_ledger_entry.modified > add_days(today(), -2)) + ) + .groupby(stock_ledger_entry.voucher_detail_no) + .having(Sum(stock_ledger_entry.stock_value_difference) != 0) + ) + + data = query.run(as_dict=True) + stock_entries = {} + + for row in data: + if abs(row.stock_value) > 0.1 and row.name not in stock_entries: + stock_entries.setdefault(row.name, row) + + return stock_entries From f31612376af60e9b7ed5bce10edba49e9dc27b0d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 20 Dec 2022 00:14:41 +0530 Subject: [PATCH 41/41] test: added test case to validate audit for incorrect entries --- erpnext/hooks.py | 2 +- .../doctype/stock_entry/test_stock_entry.py | 42 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index e84a32d46f..7d72c76b81 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -393,7 +393,6 @@ scheduler_events = { ], "daily": [ "erpnext.support.doctype.issue.issue.auto_close_tickets", - "erpnext.stock.doctype.stock_entry.stock_entry.audit_incorrect_valuation_entries", "erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity", "erpnext.controllers.accounts_controller.update_invoice_status", "erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year", @@ -421,6 +420,7 @@ scheduler_events = { "erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall", "erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans", "erpnext.crm.utils.open_leads_opportunities_based_on_todays_event", + "erpnext.stock.doctype.stock_entry.stock_entry.audit_incorrect_valuation_entries", ], "monthly_long": [ "erpnext.accounts.deferred_revenue.process_deferred_accounting", diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index b574b718fe..680d209735 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,7 +5,7 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, flt, nowdate, nowtime, today +from frappe.utils import add_days, flt, now, nowdate, nowtime, today from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( @@ -17,6 +17,8 @@ from erpnext.stock.doctype.item.test_item import ( from erpnext.stock.doctype.serial_no.serial_no import * # noqa from erpnext.stock.doctype.stock_entry.stock_entry import ( FinishedGoodError, + audit_incorrect_valuation_entries, + get_incorrect_stock_entries, move_sample_to_retention_warehouse, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -1614,6 +1616,44 @@ class TestStockEntry(FrappeTestCase): self.assertRaises(BatchExpiredError, se.save) + def test_audit_incorrect_stock_entries(self): + item_code = "Test Incorrect Valuation Rate Item - 001" + create_item(item_code=item_code, is_stock_item=1) + + make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + posting_date=add_days(nowdate(), -10), + qty=2, + rate=500, + to_warehouse="_Test Warehouse - _TC", + ) + + transfer_entry = make_stock_entry( + item_code=item_code, + purpose="Material Transfer", + qty=2, + rate=500, + from_warehouse="_Test Warehouse - _TC", + to_warehouse="_Test Warehouse 1 - _TC", + ) + + sle_name = frappe.db.get_value( + "Stock Ledger Entry", {"voucher_no": transfer_entry.name, "actual_qty": (">", 0)}, "name" + ) + + frappe.db.set_value( + "Stock Ledger Entry", sle_name, {"modified": add_days(now(), -1), "stock_value_difference": 10} + ) + + stock_entries = get_incorrect_stock_entries() + self.assertTrue(transfer_entry.name in stock_entries) + + audit_incorrect_valuation_entries() + + stock_entries = get_incorrect_stock_entries() + self.assertFalse(transfer_entry.name in stock_entries) + def make_serialized_item(**args): args = frappe._dict(args)