diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index d212e58e834..76a124df3aa 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -40,7 +40,6 @@ from erpnext.controllers.accounts_controller import validate_account_head from erpnext.controllers.buying_controller import BuyingController from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( - get_item_account_wise_additional_cost, update_billed_amount_based_on_po, ) @@ -940,7 +939,7 @@ class PurchaseInvoice(BuyingController): if self.update_stock and self.auto_accounting_for_stock: warehouse_account = get_warehouse_account_map(self.company) - landed_cost_entries = get_item_account_wise_additional_cost(self.name) + landed_cost_entries = self.get_item_account_wise_lcv_entries() voucher_wise_stock_value = {} if self.update_stock: diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 4e7e27f30ac..e17e9678812 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -241,18 +241,6 @@ class BuyingController(SubcontractingController): return [d.item_code for d in self.items if d.is_fixed_asset] - def set_landed_cost_voucher_amount(self): - for d in self.get("items"): - lc_voucher_data = frappe.db.sql( - """select sum(applicable_charges), cost_center - from `tabLanded Cost Item` - where docstatus = 1 and purchase_receipt_item = %s and receipt_document = %s""", - (d.name, self.name), - ) - d.landed_cost_voucher_amount = lc_voucher_data[0][0] if lc_voucher_data else 0.0 - if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]: - d.db_set("cost_center", lc_voucher_data[0][1]) - def validate_from_warehouse(self): for item in self.get("items"): if item.get("from_warehouse") and (item.get("from_warehouse") == item.get("warehouse")): diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index ed8936ef639..08ff9845fa2 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -6,6 +6,7 @@ from collections import defaultdict import frappe from frappe import _, bold +from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt, get_link_to_form, getdate import erpnext @@ -884,6 +885,91 @@ class StockController(AccountsController): return sl_dict + def set_landed_cost_voucher_amount(self): + for d in self.get("items"): + lcv_item = frappe.qb.DocType("Landed Cost Item") + query = ( + frappe.qb.from_(lcv_item) + .select(Sum(lcv_item.applicable_charges), lcv_item.cost_center) + .where((lcv_item.docstatus == 1) & (lcv_item.receipt_document == self.name)) + ) + + if self.doctype == "Stock Entry": + query = query.where(lcv_item.stock_entry_item == d.name) + else: + query = query.where(lcv_item.purchase_receipt_item == d.name) + + lc_voucher_data = query.run(as_list=True) + + d.landed_cost_voucher_amount = lc_voucher_data[0][0] if lc_voucher_data else 0.0 + if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]: + d.db_set("cost_center", lc_voucher_data[0][1]) + + def has_landed_cost_amount(self): + for row in self.items: + if row.get("landed_cost_voucher_amount"): + return True + + return False + + def get_item_account_wise_lcv_entries(self): + if not self.has_landed_cost_amount(): + return + + landed_cost_vouchers = frappe.get_all( + "Landed Cost Purchase Receipt", + fields=["parent"], + filters={"receipt_document": self.name, "docstatus": 1}, + ) + + if not landed_cost_vouchers: + return + + item_account_wise_cost = {} + + row_fieldname = "purchase_receipt_item" + if self.doctype == "Stock Entry": + row_fieldname = "stock_entry_item" + + for lcv in landed_cost_vouchers: + landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent) + + based_on_field = "applicable_charges" + # Use amount field for total item cost for manually cost distributed LCVs + if landed_cost_voucher_doc.distribute_charges_based_on != "Distribute Manually": + based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on) + + total_item_cost = 0 + + if based_on_field: + for item in landed_cost_voucher_doc.items: + total_item_cost += item.get(based_on_field) + + for item in landed_cost_voucher_doc.items: + if item.receipt_document == self.name: + for account in landed_cost_voucher_doc.taxes: + exchange_rate = account.exchange_rate or 1 + item_account_wise_cost.setdefault((item.item_code, item.get(row_fieldname)), {}) + item_account_wise_cost[(item.item_code, item.get(row_fieldname))].setdefault( + account.expense_account, {"amount": 0.0, "base_amount": 0.0} + ) + + item_row = item_account_wise_cost[(item.item_code, item.get(row_fieldname))][ + account.expense_account + ] + + if total_item_cost > 0: + item_row["amount"] += account.amount * item.get(based_on_field) / total_item_cost + + item_row["base_amount"] += ( + account.base_amount * item.get(based_on_field) / total_item_cost + ) + else: + item_row["amount"] += item.applicable_charges / exchange_rate + item_row["base_amount"] += item.applicable_charges + + return item_account_wise_cost + def update_inventory_dimensions(self, row, sl_dict) -> None: # To handle delivery note and sales invoice if row.get("item_row"): diff --git a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json index 86d65fddc00..a4340caa08f 100644 --- a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json +++ b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json @@ -17,6 +17,7 @@ "is_fixed_asset", "applicable_charges", "purchase_receipt_item", + "stock_entry_item", "accounting_dimensions_section", "cost_center", "dimension_col_break" @@ -49,7 +50,7 @@ "fieldtype": "Select", "label": "Receipt Document Type", "no_copy": 1, - "options": "Purchase Invoice\nPurchase Receipt", + "options": "Purchase Invoice\nPurchase Receipt\nStock Entry\nSubcontracting Receipt", "print_hide": 1, "read_only": 1 }, @@ -131,18 +132,27 @@ "hidden": 1, "label": "Is Fixed Asset", "read_only": 1 + }, + { + "fieldname": "stock_entry_item", + "fieldtype": "Data", + "label": "Stock Entry Item", + "no_copy": 1, + "read_only": 1 } ], + "grid_page_length": 50, "idx": 1, "istable": 1, "links": [], - "modified": "2024-03-27 13:09:59.220459", + "modified": "2025-06-11 08:53:38.096761", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Item", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py index 67f695ac17d..d132357bdc2 100644 --- a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py +++ b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py @@ -27,7 +27,10 @@ class LandedCostItem(Document): qty: DF.Float rate: DF.Currency receipt_document: DF.DynamicLink | None - receipt_document_type: DF.Literal["Purchase Invoice", "Purchase Receipt"] + receipt_document_type: DF.Literal[ + "Purchase Invoice", "Purchase Receipt", "Stock Entry", "Subcontracting Receipt" + ] + stock_entry_item: DF.Data | None # end: auto-generated types pass diff --git a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json index 72515340fe1..21645516b78 100644 --- a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json +++ b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json @@ -19,7 +19,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Receipt Document Type", - "options": "\nPurchase Invoice\nPurchase Receipt", + "options": "\nPurchase Invoice\nPurchase Receipt\nStock Entry\nSubcontracting Receipt", "reqd": 1 }, { @@ -62,16 +62,18 @@ "read_only": 1 } ], + "grid_page_length": 50, "idx": 1, "istable": 1, "links": [], - "modified": "2024-03-27 13:09:59.363367", + "modified": "2025-06-11 08:53:11.869853", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Purchase Receipt", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "ASC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py index 9f0ffb8956c..c22dbe83f3d 100644 --- a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py +++ b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py @@ -20,7 +20,9 @@ class LandedCostPurchaseReceipt(Document): parenttype: DF.Data posting_date: DF.Date | None receipt_document: DF.DynamicLink - receipt_document_type: DF.Literal["", "Purchase Invoice", "Purchase Receipt"] + receipt_document_type: DF.Literal[ + "", "Purchase Invoice", "Purchase Receipt", "Stock Entry", "Subcontracting Receipt" + ] supplier: DF.Link | None # end: auto-generated types diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json index 57328772e13..dac161a46ff 100644 --- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json +++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json @@ -72,16 +72,18 @@ "read_only": 1 } ], + "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-01-20 12:22:03.455762", + "modified": "2025-06-09 10:22:20.286641", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Taxes and Charges", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index 0ecb9f2600e..fb3b66486e9 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -28,10 +28,6 @@ erpnext.stock.LandedCostVoucher = class LandedCostVoucher extends erpnext.stock. filters: filters, }; }; - - this.frm.add_fetch("receipt_document", "supplier", "supplier"); - this.frm.add_fetch("receipt_document", "posting_date", "posting_date"); - this.frm.add_fetch("receipt_document", "base_grand_total", "grand_total"); } refresh() { @@ -150,3 +146,46 @@ frappe.ui.form.on("Landed Cost Taxes and Charges", { frm.events.set_base_amount(frm, cdt, cdn); }, }); + +frappe.ui.form.on("Landed Cost Voucher", { + setup(frm) { + frm.trigger("setup_queries"); + }, + + setup_queries(frm) { + frm.set_query("receipt_document", "purchase_receipts", (doc, cdt, cdn) => { + var d = locals[cdt][cdn]; + if (d.receipt_document_type === "Stock Entry") { + return { + filters: { + docstatus: 1, + company: frm.doc.company, + purpose: ["in", ["Manufacture", "Repack"]], + }, + }; + } + }); + }, +}); + +frappe.ui.form.on("Landed Cost Purchase Receipt", { + receipt_document(frm, cdt, cdn) { + var d = locals[cdt][cdn]; + if (d.receipt_document) { + frappe.call({ + method: "get_receipt_document_details", + doc: frm.doc, + args: { + receipt_document: d.receipt_document, + receipt_document_type: d.receipt_document_type, + }, + callback: function (r) { + if (r.message) { + $.extend(d, r.message); + refresh_field("purchase_receipts"); + } + }, + }); + } + }, +}); diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json index 10fc6c90109..8ef6ff4a115 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json @@ -49,7 +49,7 @@ { "fieldname": "purchase_receipts", "fieldtype": "Table", - "label": "Purchase Receipts", + "label": "Receipts", "options": "Landed Cost Purchase Receipt", "reqd": 1 }, @@ -61,12 +61,12 @@ { "fieldname": "get_items_from_purchase_receipts", "fieldtype": "Button", - "label": "Get Items From Purchase Receipts" + "label": "Get Items From Receipts" }, { "fieldname": "items", "fieldtype": "Table", - "label": "Purchase Receipt Items", + "label": "Receipt Items", "no_copy": 1, "options": "Landed Cost Item", "reqd": 1 @@ -141,14 +141,16 @@ "hide_border": 1 } ], + "grid_page_length": 50, "icon": "icon-usd", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-03-27 13:09:59.624249", + "modified": "2025-06-09 10:08:39.574009", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Voucher", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -165,8 +167,9 @@ "write": 1 } ], + "row_format": "Dynamic", "show_name_in_global_search": 1, "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index c80bcc8123b..16bb2c2bcb7 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -54,14 +54,18 @@ class LandedCostVoucher(Document): item.item_code = d.item_code item.description = d.description item.qty = d.qty - item.rate = d.base_rate + item.rate = d.get("base_rate") or d.get("rate") item.cost_center = d.cost_center or erpnext.get_default_cost_center(self.company) item.amount = d.base_amount item.receipt_document_type = pr.receipt_document_type item.receipt_document = pr.receipt_document - item.purchase_receipt_item = d.name item.is_fixed_asset = d.is_fixed_asset + if pr.receipt_document_type == "Stock Entry": + item.stock_entry_item = d.name + else: + item.purchase_receipt_item = d.name + def validate(self): self.check_mandatory() self.validate_receipt_documents() @@ -171,13 +175,6 @@ class LandedCostVoucher(Document): self.get("items")[item_count - 1].applicable_charges += diff def validate_applicable_charges_for_item(self): - if self.distribute_charges_based_on == "Distribute Manually" and len(self.taxes) > 1: - frappe.throw( - _( - "Please keep one Applicable Charges, when 'Distribute Charges Based On' is 'Distribute Manually'. For more charges, please create another Landed Cost Voucher." - ) - ) - based_on = self.distribute_charges_based_on.lower() if based_on != "distribute manually": @@ -212,6 +209,28 @@ class LandedCostVoucher(Document): ) ) + @frappe.whitelist() + def get_receipt_document_details(self, receipt_document_type, receipt_document): + if receipt_document_type in [ + "Purchase Invoice", + "Purchase Receipt", + "Subcontracting Receipt", + ]: + fields = ["supplier", "posting_date"] + if receipt_document_type == "Subcontracting Receipt": + fields.append("total as grand_total") + else: + fields.append("base_grand_total as grand_total") + elif receipt_document_type == "Stock Entry": + fields = ["total_incoming_value as grand_total"] + + return frappe.db.get_value( + receipt_document_type, + receipt_document, + fields, + as_dict=True, + ) + def on_submit(self): self.validate_applicable_charges_for_item() self.update_landed_cost() @@ -229,8 +248,11 @@ class LandedCostVoucher(Document): # set landed cost voucher amount in pr item doc.set_landed_cost_voucher_amount() - # set valuation amount in pr item - doc.update_valuation_rate(reset_outgoing_rate=False) + if d.receipt_document_type == "Subcontracting Receipt": + doc.calculate_items_qty_and_amount() + else: + # set valuation amount in pr item + doc.update_valuation_rate(reset_outgoing_rate=False) # db_update will update and save landed_cost_voucher_amount and voucher_amount in PR for item in doc.get("items"): @@ -238,6 +260,9 @@ class LandedCostVoucher(Document): # asset rate will be updated while creating asset gl entries from PI or PY + if d.receipt_document_type in ["Stock Entry", "Subcontracting Receipt"]: + continue + # update latest valuation rate in serial no self.update_rate_in_serial_no_for_non_asset_items(doc) @@ -311,8 +336,13 @@ class LandedCostVoucher(Document): def get_pr_items(purchase_receipt): item = frappe.qb.DocType("Item") - pr_item = frappe.qb.DocType(purchase_receipt.receipt_document_type + " Item") - return ( + + if purchase_receipt.receipt_document_type == "Stock Entry": + pr_item = frappe.qb.DocType("Stock Entry Detail") + else: + pr_item = frappe.qb.DocType(purchase_receipt.receipt_document_type + " Item") + + query = ( frappe.qb.from_(pr_item) .inner_join(item) .on(item.name == pr_item.item_code) @@ -320,11 +350,8 @@ def get_pr_items(purchase_receipt): pr_item.item_code, pr_item.description, pr_item.qty, - pr_item.base_rate, - pr_item.base_amount, pr_item.name, pr_item.cost_center, - pr_item.is_fixed_asset, ConstantColumn(purchase_receipt.receipt_document_type).as_("receipt_document_type"), ConstantColumn(purchase_receipt.receipt_document).as_("receipt_document"), ) @@ -332,5 +359,26 @@ def get_pr_items(purchase_receipt): (pr_item.parent == purchase_receipt.receipt_document) & ((item.is_stock_item == 1) | (item.is_fixed_asset == 1)) ) - .run(as_dict=True) ) + + if purchase_receipt.receipt_document_type == "Subcontracting Receipt": + query = query.select( + pr_item.rate.as_("base_rate"), + pr_item.amount.as_("base_amount"), + ) + + elif purchase_receipt.receipt_document_type == "Stock Entry": + query = query.select( + pr_item.basic_rate.as_("base_rate"), + pr_item.basic_amount.as_("base_amount"), + ) + + query = query.where(pr_item.is_finished_item == 1) + else: + query = query.select( + pr_item.base_rate, + pr_item.base_amount, + pr_item.is_fixed_asset, + ) + + return query.run(as_dict=True) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 8b6268e1b6e..bf3b74f04d7 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -2,6 +2,8 @@ # License: GNU General Public License v3. See license.txt +import copy + import frappe from frappe.tests import IntegrationTestCase from frappe.utils import add_days, add_to_date, flt, now, nowtime, today @@ -1067,6 +1069,189 @@ class TestLandedCostVoucher(IntegrationTestCase): frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"), ) + def test_lcv_for_work_order_scr(self): + from erpnext.controllers.tests.test_subcontracting_controller import ( + get_rm_items, + get_subcontracting_order, + make_stock_in_entry, + make_stock_transfer_entry, + ) + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record + from erpnext.manufacturing.doctype.work_order.work_order import ( + make_stock_entry as make_stock_entry_for_wo, + ) + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry + from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( + make_subcontracting_receipt, + ) + + wo_fg_item = "Test Item FG LCV WO 1" + wo_rm_items = ["Test Item RM LCV WO 1", "Test Item RM LCV WO 2"] + + scr_fg_item = "Test Item FG LCV SCR 1" + scr_rm_items = ["Test Item RM LCV SCR 1", "Test Item RM LCV SCR 2"] + company = "_Test Company with perpetual inventory" + warehouse = frappe.get_value("Warehouse", {"company": company}, "name") + wip_warehouse = frappe.get_value( + "Warehouse", + {"company": company, "name": ("!=", warehouse)}, + "name", + ) + + service_item = "Test Service Item LCV" + + for item in scr_rm_items + wo_rm_items + [wo_fg_item, scr_fg_item, service_item]: + make_item( + item, + { + "is_stock_item": 1 if item != service_item else 0, + "stock_uom": "Nos", + "company": company, + "is_sub_contracted_item": 1 if item == scr_fg_item else 0, + }, + ) + + for item in scr_rm_items + wo_rm_items: + make_stock_entry( + item_code=item, + company=company, + to_warehouse=warehouse, + qty=10, + rate=100, + ) + + make_bom(item=wo_fg_item, company=company, raw_materials=wo_rm_items) + + make_bom(item=scr_fg_item, company=company, raw_materials=scr_rm_items) + + service_items = [ + { + "warehouse": warehouse, + "item_code": service_item, + "qty": 10, + "rate": 100, + "fg_item": scr_fg_item, + "fg_item_qty": 10, + "company": company, + "supplier_warehouse": wip_warehouse, + }, + ] + sco = get_subcontracting_order( + service_items=service_items, + company=company, + warehouse=warehouse, + supplier_warehouse=wip_warehouse, + ) + + 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.save() + scr.submit() + + wo_order = make_wo_order_test_record( + production_item=wo_fg_item, + planned_start_date=now(), + qty=10, + source_warehouse=warehouse, + wip_warehouse=wip_warehouse, + fg_warehouse=warehouse, + company=company, + ) + + se = frappe.get_doc(make_stock_entry_for_wo(wo_order.name, "Material Transfer for Manufacture", 10)) + se.submit() + + se = frappe.get_doc(make_stock_entry_for_wo(wo_order.name, "Manufacture", 10)) + se.submit() + + lcv = make_landed_cost_voucher( + company=scr.company, + receipt_document_type="Subcontracting Receipt", + receipt_document=scr.name, + distribute_charges_based_on="Distribute Manually", + do_not_save=True, + ) + + lcv.append( + "purchase_receipts", + { + "receipt_document_type": "Stock Entry", + "receipt_document": se.name, + }, + ) + + lcv.get_items_from_purchase_receipts() + + accounts = [ + "Electricity Charges - TCP1", + "Rent Charges - TCP1", + ] + + for account in accounts: + if not frappe.db.exists("Account", account): + create_account( + account_name=account.split(" - ")[0], + account_type="Expense Account", + parent_account="Direct Expenses - TCP1", + company=company, + ) + + for account in accounts: + lcv.append( + "taxes", + { + "description": f"{account} Charges", + "expense_account": account, + "amount": 100, + }, + ) + + for row in lcv.items: + row.applicable_charges = 100.00 + + lcv.save() + lcv.submit() + + for d in lcv.purchase_receipts: + gl_entries = frappe.get_all( + "GL Entry", + filters={ + "voucher_type": d.receipt_document_type, + "voucher_no": d.receipt_document, + "is_cancelled": 0, + "account": ("in", accounts), + }, + fields=["account", "credit"], + ) + + for gl in gl_entries: + self.assertEqual(gl.credit, 50.0) + + lcv.cancel() + + for d in lcv.purchase_receipts: + gl_entries = frappe.get_all( + "GL Entry", + filters={ + "voucher_type": d.receipt_document_type, + "voucher_no": d.receipt_document, + "is_cancelled": 0, + "account": ("in", accounts), + }, + fields=["account", "credit"], + ) + + self.assertFalse(gl_entries) + def make_landed_cost_voucher(**args): args = frappe._dict(args) @@ -1082,23 +1267,24 @@ def make_landed_cost_voucher(**args): { "receipt_document_type": args.receipt_document_type, "receipt_document": args.receipt_document, - "supplier": ref_doc.supplier, - "posting_date": ref_doc.posting_date, - "grand_total": ref_doc.grand_total, + "supplier": ref_doc.get("supplier"), + "posting_date": ref_doc.get("posting_date"), + "grand_total": ref_doc.get("grand_total"), } ], ) - lcv.set( - "taxes", - [ - { - "description": "Shipping Charges", - "expense_account": args.expense_account or "Expenses Included In Valuation - TCP1", - "amount": args.charges, - } - ], - ) + if args.charges: + lcv.set( + "taxes", + [ + { + "description": "Shipping Charges", + "expense_account": args.expense_account or "Expenses Included In Valuation - TCP1", + "amount": args.charges, + } + ], + ) if not args.do_not_save: lcv.insert() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 62aa0460b9c..7ff4fde4fa5 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -738,7 +738,7 @@ class PurchaseReceipt(BuyingController): if d.is_fixed_asset else self.get_company_default("stock_received_but_not_billed") ) - landed_cost_entries = get_item_account_wise_additional_cost(self.name) + landed_cost_entries = self.get_item_account_wise_lcv_entries() if d.is_fixed_asset: stock_asset_account_name = d.expense_account stock_value_diff = ( @@ -1502,58 +1502,6 @@ def make_inter_company_delivery_note(source_name, target_doc=None): return make_inter_company_transaction("Purchase Receipt", source_name, target_doc) -def get_item_account_wise_additional_cost(purchase_document): - landed_cost_vouchers = frappe.get_all( - "Landed Cost Purchase Receipt", - fields=["parent"], - filters={"receipt_document": purchase_document, "docstatus": 1}, - ) - - if not landed_cost_vouchers: - return - - item_account_wise_cost = {} - - for lcv in landed_cost_vouchers: - landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent) - - based_on_field = None - # Use amount field for total item cost for manually cost distributed LCVs - if landed_cost_voucher_doc.distribute_charges_based_on != "Distribute Manually": - based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on) - - total_item_cost = 0 - - if based_on_field: - for item in landed_cost_voucher_doc.items: - total_item_cost += item.get(based_on_field) - - for item in landed_cost_voucher_doc.items: - if item.receipt_document == purchase_document: - for account in landed_cost_voucher_doc.taxes: - exchange_rate = account.exchange_rate or 1 - item_account_wise_cost.setdefault((item.item_code, item.purchase_receipt_item), {}) - item_account_wise_cost[(item.item_code, item.purchase_receipt_item)].setdefault( - account.expense_account, {"amount": 0.0, "base_amount": 0.0} - ) - - item_row = item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][ - account.expense_account - ] - - if total_item_cost > 0: - item_row["amount"] += account.amount * item.get(based_on_field) / total_item_cost - - item_row["base_amount"] += ( - account.base_amount * item.get(based_on_field) / total_item_cost - ) - else: - item_row["amount"] += item.applicable_charges / exchange_rate - item_row["base_amount"] += item.applicable_charges - - return item_account_wise_cost - - @erpnext.allow_regional def update_regional_gl_entries(gl_list, doc): return diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index fa823294d0b..7a2566edf3e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -23,6 +23,7 @@ from frappe.utils import ( import erpnext from erpnext.accounts.general_ledger import process_gl_map +from erpnext.accounts.utils import get_account_currency from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals from erpnext.manufacturing.doctype.bom.bom import ( @@ -1014,12 +1015,20 @@ class StockEntry(StockController): continue d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs - def update_valuation_rate(self): + def update_valuation_rate(self, reset_outgoing_rate=True): for d in self.get("items"): + if not reset_outgoing_rate and d.s_warehouse: + continue + if d.transfer_qty: - d.amount = flt(flt(d.basic_amount) + flt(d.additional_cost), d.precision("amount")) + d.amount = flt( + flt(d.basic_amount) + flt(d.additional_cost) + flt(d.landed_cost_voucher_amount), + d.precision("amount"), + ) # Do not round off valuation rate to avoid precision loss - d.valuation_rate = flt(d.basic_rate) + (flt(d.additional_cost) / flt(d.transfer_qty)) + d.valuation_rate = flt(d.basic_rate) + ( + flt(d.additional_cost) + flt(d.landed_cost_voucher_amount) / flt(d.transfer_qty) + ) def set_total_incoming_outgoing_value(self): self.total_incoming_value = self.total_outgoing_value = 0.0 @@ -1390,7 +1399,7 @@ class StockEntry(StockController): ) ) - def update_stock_ledger(self, allow_negative_stock=False): + def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False): sl_entries = [] finished_item_row = self.get_finished_item_row() @@ -1404,7 +1413,11 @@ class StockEntry(StockController): if self.docstatus == 2: sl_entries.reverse() - self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) + self.make_sl_entries( + sl_entries, + allow_negative_stock=allow_negative_stock, + via_landed_cost_voucher=via_landed_cost_voucher, + ) def get_finished_item_row(self): finished_item_row = None @@ -1607,8 +1620,63 @@ class StockEntry(StockController): ) ) + self.set_gl_entries_for_landed_cost_voucher(gl_entries, warehouse_account) + return process_gl_map(gl_entries) + def set_gl_entries_for_landed_cost_voucher(self, gl_entries, warehouse_account): + landed_cost_entries = self.get_item_account_wise_lcv_entries() + if not landed_cost_entries: + return + + for item in self.get("items"): + if item.s_warehouse: + continue + + if (item.item_code, item.name) in landed_cost_entries: + for account, amount in landed_cost_entries[(item.item_code, item.name)].items(): + account_currency = get_account_currency(account) + credit_amount = ( + flt(amount["base_amount"]) + if (amount["base_amount"] or account_currency != self.company_currency) + else flt(amount["amount"]) + ) + + gl_entries.append( + self.get_gl_dict( + { + "account": account, + "against": warehouse_account.get(item.t_warehouse)["account"], + "cost_center": item.cost_center, + "debit": 0.0, + "credit": credit_amount, + "remarks": _("Accounting Entry for LCV in Stock Entry {0}").format(self.name), + "credit_in_account_currency": flt(amount["amount"]), + "account_currency": account_currency, + "project": item.project, + }, + item=item, + ) + ) + + account_currency = get_account_currency(item.expense_account) + gl_entries.append( + self.get_gl_dict( + { + "account": item.expense_account, + "against": account, + "cost_center": item.cost_center, + "debit": credit_amount, + "credit": 0.0, + "remarks": _("Accounting Entry for LCV in Stock Entry {0}").format(self.name), + "debit_in_account_currency": flt(amount["amount"]), + "account_currency": account_currency, + "project": item.project, + }, + item=item, + ) + ) + def update_work_order(self): def _validate_work_order(pro_doc): msg, title = "", "" diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index ea497f7ab7a..198585dbb53 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -39,6 +39,7 @@ "rates_section", "basic_rate", "additional_cost", + "landed_cost_voucher_amount", "valuation_rate", "allow_zero_valuation_rate", "col_break3", @@ -606,6 +607,13 @@ { "fieldname": "column_break_prps", "fieldtype": "Column Break" + }, + { + "fieldname": "landed_cost_voucher_amount", + "fieldtype": "Currency", + "label": "Landed Cost Voucher Amount", + "no_copy": 1, + "read_only": 1 } ], "grid_page_length": 50, @@ -613,7 +621,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-03-26 21:00:58.544797", + "modified": "2025-06-09 10:24:34.717676", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py index bd3dda1b98f..b6c1dcca180 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py @@ -37,6 +37,7 @@ class StockEntryDetail(Document): item_group: DF.Data | None item_name: DF.Data | None job_card_item: DF.Data | None + landed_cost_voucher_amount: DF.Currency material_request: DF.Link | None material_request_item: DF.Link | None original_item: DF.Link | None diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 40955684a39..20c15904795 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -432,10 +432,15 @@ class SubcontractingReceipt(SubcontractingController): else: item.scrap_cost_per_qty = 0 + lcv_cost_per_qty = 0.0 + if item.landed_cost_voucher_amount: + lcv_cost_per_qty = item.landed_cost_voucher_amount / item.qty + item.rate = ( flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty) + + flt(lcv_cost_per_qty) - flt(item.scrap_cost_per_qty) ) @@ -567,6 +572,7 @@ class SubcontractingReceipt(SubcontractingController): gl_entries = [] self.make_item_gl_entries(gl_entries, warehouse_account) + self.make_item_gl_entries_for_lcv(gl_entries, warehouse_account) return process_gl_map(gl_entries) @@ -738,6 +744,53 @@ class SubcontractingReceipt(SubcontractingController): + "\n".join(warehouse_with_no_account) ) + def make_item_gl_entries_for_lcv(self, gl_entries, warehouse_account): + landed_cost_entries = self.get_item_account_wise_lcv_entries() + + if not landed_cost_entries: + return + + for item in self.items: + if item.landed_cost_voucher_amount and landed_cost_entries: + remarks = _("Accounting Entry for Landed Cost Voucher for SCR {0}").format(self.name) + if (item.item_code, item.name) in landed_cost_entries: + for account, amount in landed_cost_entries[(item.item_code, item.name)].items(): + account_currency = get_account_currency(account) + credit_amount = ( + flt(amount["base_amount"]) + if (amount["base_amount"] or account_currency != self.company_currency) + else flt(amount["amount"]) + ) + + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=item.cost_center, + debit=0.0, + credit=credit_amount, + remarks=remarks, + against_account=warehouse_account.get(item.warehouse)["account"], + credit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, + project=item.project, + item=item, + ) + + account_currency = get_account_currency(item.expense_account) + self.add_gl_entry( + gl_entries=gl_entries, + account=item.expense_account, + cost_center=item.cost_center, + debit=credit_amount, + credit=0.0, + remarks=remarks, + against_account=warehouse_account.get(item.warehouse)["account"], + debit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, + project=item.project, + item=item, + ) + def auto_create_purchase_receipt(self): if frappe.db.get_single_value("Buying Settings", "auto_create_purchase_receipt"): make_purchase_receipt(self, save=True, notify=True) 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 999af7b4421..466ad8b5e9c 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -28,6 +28,7 @@ "rate_and_amount", "rate", "amount", + "landed_cost_voucher_amount", "column_break_19", "rm_cost_per_qty", "service_cost_per_qty", @@ -589,12 +590,20 @@ "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 } ], + "grid_page_length": 50, "idx": 1, "istable": 1, "links": [], - "modified": "2024-12-06 15:23:58.680169", + "modified": "2025-06-11 08:45:18.903036", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", @@ -602,7 +611,8 @@ "owner": "Administrator", "permissions": [], "quick_entry": 1, + "row_format": "Dynamic", "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} 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..875a7d5477e 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py @@ -29,6 +29,7 @@ class SubcontractingReceiptItem(Document): item_code: DF.Link item_name: DF.Data | None job_card: DF.Link | None + landed_cost_voucher_amount: DF.Currency manufacturer: DF.Link | None manufacturer_part_no: DF.Data | None page_break: DF.Check