Merge pull request #47970 from rohitwaghchaure/feat-lcv-for-wo-scr

feat: LCV for Work Order and Subcontracting Receipt
This commit is contained in:
rohitwaghchaure
2025-06-16 20:35:12 +05:30
committed by GitHub
19 changed files with 581 additions and 124 deletions

View File

@@ -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:

View File

@@ -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")):

View File

@@ -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"):

View File

@@ -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": []
}
}

View File

@@ -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

View File

@@ -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": []
}
}

View File

@@ -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

View File

@@ -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": []
}
}

View File

@@ -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");
}
},
});
}
},
});

View File

@@ -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": []
}
}

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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 = "", ""

View File

@@ -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",

View File

@@ -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

View File

@@ -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)

View File

@@ -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": []
}
}

View File

@@ -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