From 6e597b9c4241ea079c25d1a6fd97fa95f68d79f4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 26 Sep 2025 19:45:17 +0530 Subject: [PATCH] feat: service expense account in the subcontracting receipt --- .../tests/test_subcontracting_controller.py | 4 + .../subcontracting_receipt.py | 70 +++++++++++++++--- .../test_subcontracting_receipt.py | 73 +++++++++++++++++++ .../subcontracting_receipt_item.json | 14 +++- .../subcontracting_receipt_item.py | 1 + 5 files changed, 151 insertions(+), 11 deletions(-) diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index 5caaba6c70b..94ec192779e 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -1308,6 +1308,7 @@ def make_subcontracted_items(): "Subcontracted Item SA7": {}, "Subcontracted Item SA8": {}, "Subcontracted Item SA9": {"stock_uom": "Litre"}, + "Subcontracted Item SA10": {}, } for item, properties in sub_contracted_items.items(): @@ -1329,6 +1330,7 @@ def make_raw_materials(): "Subcontracted SRM Item 5": {"has_serial_no": 1, "serial_no_series": "SRIID.####"}, "Subcontracted SRM Item 8": {}, "Subcontracted SRM Item 9": {"stock_uom": "Litre"}, + "Subcontracted SRM Item 10": {}, } for item, properties in raw_materials.items(): @@ -1357,6 +1359,7 @@ def make_service_items(): "Subcontracted Service Item 7": {}, "Subcontracted Service Item 8": {}, "Subcontracted Service Item 9": {}, + "Subcontracted Service Item 10": {}, } for item, properties in service_items.items(): @@ -1381,6 +1384,7 @@ def make_bom_for_subcontracted_items(): "Subcontracted Item SA6": ["Subcontracted SRM Item 3"], "Subcontracted Item SA7": ["Subcontracted SRM Item 1"], "Subcontracted Item SA8": ["Subcontracted SRM Item 8"], + "Subcontracted Item SA10": ["Subcontracted SRM Item 10"], } for item_code, raw_materials in boms.items(): diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 949d1850053..e7555194672 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -117,7 +117,13 @@ class SubcontractingReceipt(SubcontractingController): self.validate_items_qty() self.set_items_bom() self.set_items_cost_center() - self.set_items_expense_account() + + if self.company: + default_expense_account = self.get_company_default( + "default_expense_account", ignore_validation=True + ) + self.set_service_expense_account(default_expense_account) + self.set_expense_account_for_subcontracted_items(default_expense_account) def validate(self): self.reset_supplied_items() @@ -205,6 +211,39 @@ class SubcontractingReceipt(SubcontractingController): doc = frappe.get_doc("Job Card", row.job_card) doc.set_manufactured_qty() + def set_service_expense_account(self, default_expense_account): + for row in self.get("items"): + if not row.service_expense_account and row.purchase_order_item: + service_item = frappe.db.get_value( + "Purchase Order Item", row.purchase_order_item, "item_code" + ) + + if service_item: + if default := ( + get_item_defaults(service_item, self.company) + or get_item_group_defaults(service_item, self.company) + or get_brand_defaults(service_item, self.company) + ): + if service_expense_account := default.get("expense_account"): + row.service_expense_account = service_expense_account + + if not row.service_expense_account: + row.service_expense_account = default_expense_account + + def set_expense_account_for_subcontracted_items(self, default_expense_account): + for row in self.get("items"): + if not row.expense_account: + if default := ( + get_item_defaults(row.item_code, self.company) + or get_item_group_defaults(row.item_code, self.company) + or get_brand_defaults(row.item_code, self.company) + ): + if expense_account := default.get("expense_account"): + row.expense_account = expense_account + + if not row.expense_account: + row.expense_account = default_expense_account + def get_manufactured_qty(self, job_card): table = frappe.qb.DocType("Subcontracting Receipt Item") query = ( @@ -262,14 +301,6 @@ class SubcontractingReceipt(SubcontractingController): self.company, ) - def set_items_expense_account(self): - if self.company: - expense_account = self.get_company_default("default_expense_account", ignore_validation=True) - - for item in self.items: - if not item.expense_account: - item.expense_account = expense_account - def set_supplied_items_expense_account(self): for item in self.supplied_items: if not item.expense_account: @@ -625,13 +656,17 @@ class SubcontractingReceipt(SubcontractingController): project=item.project, item=item, ) + + service_cost = flt( + item.service_cost_per_qty, item.precision("service_cost_per_qty") + ) * flt(item.qty, item.precision("qty")) # Expense Account (Credit) self.add_gl_entry( gl_entries=gl_entries, account=item.expense_account, cost_center=item.cost_center, debit=0.0, - credit=stock_value_diff, + credit=flt(stock_value_diff) - service_cost, remarks=remarks, against_account=accepted_warehouse_account, account_currency=get_account_currency(item.expense_account), @@ -639,6 +674,21 @@ class SubcontractingReceipt(SubcontractingController): item=item, ) + service_account = item.service_expense_account or item.expense_account + # Expense Account (Credit) + self.add_gl_entry( + gl_entries=gl_entries, + account=service_account, + cost_center=item.cost_center, + debit=0.0, + credit=service_cost, + remarks=remarks, + against_account=accepted_warehouse_account, + account_currency=get_account_currency(service_account), + project=item.project, + item=item, + ) + if flt(item.rm_supp_cost) and supplier_warehouse_account: for rm_item in supplied_items_details.get(item.name): # Supplier Warehouse Account (Credit) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 6ef0d939663..46b11dc29fa 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -421,6 +421,79 @@ class TestSubcontractingReceipt(IntegrationTestCase): self.assertEqual(expected_values[gle.account][0], gle.debit) self.assertEqual(expected_values[gle.account][1], gle.credit) + def test_subcontracting_receipt_for_service_expense_account(self): + service_expense_account = ( + frappe.get_doc( + { + "doctype": "Account", + "account_name": "_Test Service Expense", + "account_type": "Expense Account", + "company": "_Test Company with perpetual inventory", + "is_group": 0, + "parent_account": "Indirect Expenses - TCP1", + } + ) + .insert(ignore_if_duplicate=True) + .name + ) + + service_item_doc = frappe.get_doc("Item", "Subcontracted Service Item 10") + service_item_doc.append( + "item_defaults", + { + "company": "_Test Company with perpetual inventory", + "expense_account": service_expense_account, + "default_warehouse": "Stores - TCP1", + }, + ) + + service_item_doc.save() + + service_items = [ + { + "warehouse": "Stores - TCP1", + "item_code": "Subcontracted Service Item 10", + "qty": 10, + "rate": 100, + "fg_item": "Subcontracted Item SA10", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + supplier_warehouse="Work In Progress - TCP1", + service_items=service_items, + ) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr = make_subcontracting_receipt(sco.name) + scr.submit() + + for item in scr.items: + self.assertEqual(item.service_expense_account, service_expense_account) + + gl_entries = get_gl_entries("Subcontracting Receipt", scr.name) + self.assertTrue(gl_entries) + + fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse) + expense_account = scr.items[0].expense_account + expected_values = { + fg_warehouse_ac: [2000, 1000], + expense_account: [1000, 1000], + service_expense_account: [0, 1000], + } + + for gle in gl_entries: + self.assertEqual(expected_values[gle.account][0], gle.debit) + self.assertEqual(expected_values[gle.account][1], gle.credit) + @IntegrationTestCase.change_settings("Stock Settings", {"use_serial_batch_fields": 0}) def test_subcontracting_receipt_with_zero_service_cost(self): warehouse = "Stores - TCP1" diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index 466ad8b5e9c..9c1f8e60946 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -66,6 +66,8 @@ "manufacturer_part_no", "accounting_details_section", "expense_account", + "column_break_exht", + "service_expense_account", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -597,13 +599,23 @@ "label": "Landed Cost Voucher Amount", "no_copy": 1, "read_only": 1 + }, + { + "fieldname": "column_break_exht", + "fieldtype": "Column Break" + }, + { + "fieldname": "service_expense_account", + "fieldtype": "Link", + "label": "Service Expense Account", + "options": "Account" } ], "grid_page_length": 50, "idx": 1, "istable": 1, "links": [], - "modified": "2025-06-11 08:45:18.903036", + "modified": "2025-09-26 12:00:38.877638", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py index 875a7d5477e..e916a90462f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py @@ -56,6 +56,7 @@ class SubcontractingReceiptItem(Document): serial_and_batch_bundle: DF.Link | None serial_no: DF.SmallText | None service_cost_per_qty: DF.Currency + service_expense_account: DF.Link | None stock_uom: DF.Link subcontracting_order: DF.Link | None subcontracting_order_item: DF.Data | None