diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index 3b11a0f8029..dc80a23198a 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 23a1ab9ebf4..f5dd1c4f16c 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -116,7 +116,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() @@ -196,6 +202,39 @@ class SubcontractingReceipt(SubcontractingController): if item.subcontracting_order: check_on_hold_or_closed_status("Subcontracting Order", item.subcontracting_order) + 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 validate_items_qty(self): for item in self.items: if not (item.qty or item.rejected_qty): @@ -242,14 +281,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: @@ -599,13 +630,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), @@ -613,6 +648,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 7bdfb229d46..d5e30bd9368 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -422,6 +422,79 @@ class TestSubcontractingReceipt(FrappeTestCase): 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) + @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 23a7e69669d..5a02ab91bf0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -64,6 +64,8 @@ "manufacturer_part_no", "accounting_details_section", "expense_account", + "column_break_exht", + "service_expense_account", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -580,12 +582,22 @@ "fieldname": "add_serial_batch_for_rejected_qty", "fieldtype": "Button", "label": "Add Serial / Batch No (Rejected Qty)" + }, + { + "fieldname": "column_break_exht", + "fieldtype": "Column Break" + }, + { + "fieldname": "service_expense_account", + "fieldtype": "Link", + "label": "Service Expense Account", + "options": "Account" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2024-12-06 15:23:58.680169", + "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 69f7ae73e7a..8d5265f3a0f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py @@ -55,6 +55,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