From 3ce64170dba0ffd0ecbba5c6b2e8161da0ad82de Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Apr 2022 14:35:22 +0530 Subject: [PATCH 1/2] feat: Item-wise provisional accounting for service items --- .../purchase_invoice/purchase_invoice.py | 6 +++-- erpnext/setup/doctype/company/company.js | 3 ++- erpnext/stock/doctype/item/item.js | 11 ++++++++++ .../doctype/item_default/item_default.json | 10 ++++++++- .../purchase_receipt/purchase_receipt.json | 17 +------------- .../purchase_receipt/purchase_receipt.py | 22 +++++++++++-------- .../purchase_receipt_item.json | 22 +++++++++++++++++-- erpnext/stock/get_item_details.py | 5 +++++ 8 files changed, 65 insertions(+), 31 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index e6a46d0676..a5f9e24e15 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -811,7 +811,9 @@ class PurchaseInvoice(BuyingController): if provisional_accounting_for_non_stock_items: if item.purchase_receipt: - provisional_account = self.get_company_default("default_provisional_account") + provisional_account = frappe.db.get_value( + "Purchase Receipt Item", item.pr_detail, "provisional_expense_account" + ) or self.get_company_default("default_provisional_account") purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt) if not purchase_receipt_doc: @@ -834,7 +836,7 @@ class PurchaseInvoice(BuyingController): if expense_booked_in_pr: # Intentionally passing purchase invoice item to handle partial billing purchase_receipt_doc.add_provisional_gl_entry( - item, gl_entries, self.posting_date, reverse=1 + item, gl_entries, self.posting_date, provisional_account, reverse=1 ) if not self.is_internal_transfer(): diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index dd185fc663..0de5b2d5a3 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -233,7 +233,8 @@ erpnext.company.setup_queries = function(frm) { ["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}], ["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}], ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}], - ["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}] + ["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}], + ["default_provisional_account", {"root_type": ["in", ["Liability", "Asset"]]}] ], function(i, v) { erpnext.company.set_custom_query(frm, v); }); diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 23301a6a78..3abb609302 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -377,6 +377,17 @@ $.extend(erpnext.item, { } } + frm.set_query('default_provisional_account', 'item_defaults', (doc, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + "company": row.company, + "root_type": ["in", ["Liability", "Asset"]], + "is_group": 0 + } + } + }) + }, make_dashboard: function(frm) { diff --git a/erpnext/stock/doctype/item_default/item_default.json b/erpnext/stock/doctype/item_default/item_default.json index bc171604f4..042d398256 100644 --- a/erpnext/stock/doctype/item_default/item_default.json +++ b/erpnext/stock/doctype/item_default/item_default.json @@ -15,6 +15,7 @@ "default_supplier", "column_break_8", "expense_account", + "default_provisional_account", "selling_defaults", "selling_cost_center", "column_break_12", @@ -101,11 +102,17 @@ "fieldtype": "Link", "label": "Default Discount Account", "options": "Account" + }, + { + "fieldname": "default_provisional_account", + "fieldtype": "Link", + "label": "Default Provisional Account", + "options": "Account" } ], "istable": 1, "links": [], - "modified": "2021-07-13 01:26:03.860065", + "modified": "2022-04-10 20:18:54.148195", "modified_by": "Administrator", "module": "Stock", "name": "Item Default", @@ -114,5 +121,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 6e5f6f5b52..19c490d37f 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -106,8 +106,6 @@ "terms", "bill_no", "bill_date", - "accounting_details_section", - "provisional_expense_account", "more_info", "project", "status", @@ -1145,26 +1143,13 @@ "label": "Represents Company", "options": "Company", "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "accounting_details_section", - "fieldtype": "Section Break", - "label": "Accounting Details" - }, - { - "fieldname": "provisional_expense_account", - "fieldtype": "Link", - "hidden": 1, - "label": "Provisional Expense Account", - "options": "Account" } ], "icon": "fa fa-truck", "idx": 261, "is_submittable": 1, "links": [], - "modified": "2022-03-10 11:40:52.690984", + "modified": "2022-04-10 22:50:37.761362", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 1e1c0b9f7c..d050624b23 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -145,10 +145,13 @@ class PurchaseReceipt(BuyingController): ) ) - if provisional_accounting_for_non_stock_items: - default_provisional_account = self.get_company_default("default_provisional_account") - if not self.provisional_expense_account: - self.provisional_expense_account = default_provisional_account + if not provisional_accounting_for_non_stock_items: + return + + default_provisional_account = self.get_company_default("default_provisional_account") + for item in self.get("items"): + if not item.get("provisional_expense_account"): + item.provisional_expense_account = default_provisional_account def validate_with_previous_doc(self): super(PurchaseReceipt, self).validate_with_previous_doc( @@ -518,9 +521,10 @@ class PurchaseReceipt(BuyingController): + "\n".join(warehouse_with_no_account) ) - def add_provisional_gl_entry(self, item, gl_entries, posting_date, reverse=0): - provisional_expense_account = self.get("provisional_expense_account") - credit_currency = get_account_currency(provisional_expense_account) + def add_provisional_gl_entry( + self, item, gl_entries, posting_date, provisional_account, reverse=0 + ): + credit_currency = get_account_currency(provisional_account) debit_currency = get_account_currency(item.expense_account) expense_account = item.expense_account remarks = self.get("remarks") or _("Accounting Entry for Service") @@ -534,7 +538,7 @@ class PurchaseReceipt(BuyingController): self.add_gl_entry( gl_entries=gl_entries, - account=provisional_expense_account, + account=provisional_account, cost_center=item.cost_center, debit=0.0, credit=multiplication_factor * item.amount, @@ -554,7 +558,7 @@ class PurchaseReceipt(BuyingController): debit=multiplication_factor * item.amount, credit=0.0, remarks=remarks, - against_account=provisional_expense_account, + against_account=provisional_account, account_currency=debit_currency, project=item.project, voucher_detail_no=item.name, diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 03a4201ce5..1c65ac86c9 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -96,7 +96,6 @@ "include_exploded_items", "batch_no", "rejected_serial_no", - "expense_account", "item_tax_rate", "item_weight_details", "weight_per_unit", @@ -107,6 +106,10 @@ "manufacturer", "column_break_16", "manufacturer_part_no", + "accounting_details_section", + "expense_account", + "column_break_102", + "provisional_expense_account", "accounting_dimensions_section", "project", "dimension_col_break", @@ -971,12 +974,27 @@ "label": "Product Bundle", "options": "Product Bundle", "read_only": 1 + }, + { + "fieldname": "provisional_expense_account", + "fieldtype": "Link", + "label": "Provisional Expense Account", + "options": "Account" + }, + { + "fieldname": "accounting_details_section", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "column_break_102", + "fieldtype": "Column Break" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2022-02-01 11:32:27.980524", + "modified": "2022-04-11 13:07:32.061402", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index d3a230e3d8..324ff4f409 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -345,6 +345,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): "expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults), "discount_account": get_default_discount_account(args, item_defaults), + "provisional_expense_account": get_provisional_account(args, item_defaults), "cost_center": get_default_cost_center( args, item_defaults, item_group_defaults, brand_defaults ), @@ -699,6 +700,10 @@ def get_default_expense_account(args, item, item_group, brand): ) +def get_provisional_account(args, item): + return item.get("default_provisional_account") or args.default_provisional_account + + def get_default_discount_account(args, item): return item.get("default_discount_account") or args.discount_account From ad171c6225fc50305e565ce2a67bc60fc1e85722 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 14 Apr 2022 12:03:12 +0530 Subject: [PATCH 2/2] test: Update test case --- .../doctype/purchase_invoice/test_purchase_invoice.py | 5 ++++- erpnext/stock/doctype/item/item.js | 4 ++-- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 73390dd6f4..59bd637e41 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1482,7 +1482,8 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(payment_entry.taxes[0].allocated_amount, 0) def test_provisional_accounting_entry(self): - item = create_item("_Test Non Stock Item", is_stock_item=0) + create_item("_Test Non Stock Item", is_stock_item=0) + provisional_account = create_account( account_name="Provision Account", parent_account="Current Liabilities - _TC", @@ -1505,6 +1506,8 @@ class TestPurchaseInvoice(unittest.TestCase): pi.save() pi.submit() + self.assertEquals(pr.items[0].provisional_expense_account, "Provision Account - _TC") + # Check GLE for Purchase Invoice expected_gle = [ ["Cost of Goods Sold - _TC", 250, 0, add_days(pr.posting_date, -1)], diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 3abb609302..ae8b488479 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -385,8 +385,8 @@ $.extend(erpnext.item, { "root_type": ["in", ["Liability", "Asset"]], "is_group": 0 } - } - }) + }; + }); }, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d050624b23..ec0e809aa6 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -512,7 +512,9 @@ class PurchaseReceipt(BuyingController): and flt(d.qty) and provisional_accounting_for_non_stock_items ): - self.add_provisional_gl_entry(d, gl_entries, self.posting_date) + self.add_provisional_gl_entry( + d, gl_entries, self.posting_date, d.get("provisional_expense_account") + ) if warehouse_with_no_account: frappe.msgprint(