From ba9b63af4940644745af186ca98187b8459675d9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 26 Sep 2025 19:45:17 +0530 Subject: [PATCH 1/4] feat: service expense account in the subcontracting receipt (cherry picked from commit 6e597b9c4241ea079c25d1a6fd97fa95f68d79f4) # Conflicts: # erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py # erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py # erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json --- .../tests/test_subcontracting_controller.py | 4 + .../subcontracting_receipt.py | 90 ++++++++++++++++--- .../test_subcontracting_receipt.py | 77 ++++++++++++++++ .../subcontracting_receipt_item.json | 34 +++++++ .../subcontracting_receipt_item.py | 1 + 5 files changed, 196 insertions(+), 10 deletions(-) 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..8a4bed9e8ba 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,59 @@ class SubcontractingReceipt(SubcontractingController): if item.subcontracting_order: check_on_hold_or_closed_status("Subcontracting Order", item.subcontracting_order) +<<<<<<< HEAD +======= + def update_job_card(self): + for row in self.get("items"): + if row.job_card: + 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 = ( + frappe.qb.from_(table) + .select(Sum(table.qty)) + .where((table.job_card == job_card) & (table.docstatus == 1)) + ) + + qty = query.run()[0][0] or 0.0 + return flt(qty) + +>>>>>>> 6e597b9c42 (feat: service expense account in the subcontracting receipt) def validate_items_qty(self): for item in self.items: if not (item.qty or item.rejected_qty): @@ -242,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: @@ -599,13 +650,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 +668,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..8545e164156 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -422,7 +422,84 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertEqual(expected_values[gle.account][0], gle.debit) self.assertEqual(expected_values[gle.account][1], gle.credit) +<<<<<<< HEAD @change_settings("Stock Settings", {"use_serial_batch_fields": 0}) +======= + 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}) +>>>>>>> 6e597b9c42 (feat: service expense account in the subcontracting receipt) def test_subcontracting_receipt_with_zero_service_cost(self): warehouse = "Stores - TCP1" service_items = [ 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..0da669da9c8 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,44 @@ "fieldname": "add_serial_batch_for_rejected_qty", "fieldtype": "Button", "label": "Add Serial / Batch No (Rejected Qty)" +<<<<<<< HEAD +======= + }, + { + "fieldname": "job_card", + "fieldtype": "Link", + "label": "Job Card", + "options": "Job Card", + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "landed_cost_voucher_amount", + "fieldtype": "Currency", + "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" +>>>>>>> 6e597b9c42 (feat: service expense account in the subcontracting receipt) } ], "idx": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2024-12-06 15:23:58.680169", +======= + "modified": "2025-09-26 12:00:38.877638", +>>>>>>> 6e597b9c42 (feat: service expense account in the subcontracting receipt) "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 From 15f0c28cdabf5d35ea605adca34a882b60dc482f Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 27 Sep 2025 10:01:53 +0530 Subject: [PATCH 2/4] chore: fix conflicts --- .../subcontracting_receipt.py | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 8a4bed9e8ba..f5dd1c4f16c 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -202,14 +202,6 @@ class SubcontractingReceipt(SubcontractingController): if item.subcontracting_order: check_on_hold_or_closed_status("Subcontracting Order", item.subcontracting_order) -<<<<<<< HEAD -======= - def update_job_card(self): - for row in self.get("items"): - if row.job_card: - 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: @@ -243,18 +235,6 @@ class SubcontractingReceipt(SubcontractingController): 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 = ( - frappe.qb.from_(table) - .select(Sum(table.qty)) - .where((table.job_card == job_card) & (table.docstatus == 1)) - ) - - qty = query.run()[0][0] or 0.0 - return flt(qty) - ->>>>>>> 6e597b9c42 (feat: service expense account in the subcontracting receipt) def validate_items_qty(self): for item in self.items: if not (item.qty or item.rejected_qty): From c26ed80d6724642cdf3c2b2cb8f978971c348860 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 27 Sep 2025 10:02:40 +0530 Subject: [PATCH 3/4] chore: fix conflicts --- .../subcontracting_receipt/test_subcontracting_receipt.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 8545e164156..d5e30bd9368 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -422,9 +422,6 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertEqual(expected_values[gle.account][0], gle.debit) self.assertEqual(expected_values[gle.account][1], gle.credit) -<<<<<<< HEAD - @change_settings("Stock Settings", {"use_serial_batch_fields": 0}) -======= def test_subcontracting_receipt_for_service_expense_account(self): service_expense_account = ( frappe.get_doc( @@ -498,8 +495,7 @@ class TestSubcontractingReceipt(FrappeTestCase): 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}) ->>>>>>> 6e597b9c42 (feat: service expense account in the subcontracting receipt) + @change_settings("Stock Settings", {"use_serial_batch_fields": 0}) def test_subcontracting_receipt_with_zero_service_cost(self): warehouse = "Stores - TCP1" service_items = [ From cecd17728678089b845e699248340b2ad6096542 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 27 Sep 2025 10:03:27 +0530 Subject: [PATCH 4/4] chore: fix conflicts Removed fields related to job card and landed cost voucher amount from subcontracting receipt item. --- .../subcontracting_receipt_item.json | 22 ------------------- 1 file changed, 22 deletions(-) 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 0da669da9c8..5a02ab91bf0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -582,23 +582,6 @@ "fieldname": "add_serial_batch_for_rejected_qty", "fieldtype": "Button", "label": "Add Serial / Batch No (Rejected Qty)" -<<<<<<< HEAD -======= - }, - { - "fieldname": "job_card", - "fieldtype": "Link", - "label": "Job Card", - "options": "Job Card", - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "landed_cost_voucher_amount", - "fieldtype": "Currency", - "label": "Landed Cost Voucher Amount", - "no_copy": 1, - "read_only": 1 }, { "fieldname": "column_break_exht", @@ -609,17 +592,12 @@ "fieldtype": "Link", "label": "Service Expense Account", "options": "Account" ->>>>>>> 6e597b9c42 (feat: service expense account in the subcontracting receipt) } ], "idx": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2024-12-06 15:23:58.680169", -======= "modified": "2025-09-26 12:00:38.877638", ->>>>>>> 6e597b9c42 (feat: service expense account in the subcontracting receipt) "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item",