Merge pull request #47970 from rohitwaghchaure/feat-lcv-for-wo-scr
feat: LCV for Work Order and Subcontracting Receipt
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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")):
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = "", ""
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user