feat: link vendor invoices in the LCV

This commit is contained in:
Rohit Waghchaure
2025-07-30 23:18:55 +05:30
parent 30b3570987
commit ee47c5eba9
8 changed files with 239 additions and 6 deletions

View File

@@ -63,6 +63,7 @@
"column_break_50",
"base_total",
"base_net_total",
"claimed_landed_cost_amount",
"column_break_28",
"total",
"net_total",
@@ -1651,6 +1652,15 @@
"label": "Select Dispatch Address ",
"options": "Address",
"print_hide": 1
},
{
"fieldname": "claimed_landed_cost_amount",
"fieldtype": "Currency",
"label": "Claimed Landed Cost Amount (Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
}
],
"grid_page_length": 50,
@@ -1658,7 +1668,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2025-04-09 16:49:22.175081",
"modified": "2025-07-30 23:16:05.722875",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
@@ -1723,4 +1733,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -104,6 +104,7 @@ class PurchaseInvoice(BuyingController):
billing_address_display: DF.TextEditor | None
buying_price_list: DF.Link | None
cash_bank_account: DF.Link | None
claimed_landed_cost_amount: DF.Currency
clearance_date: DF.Date | None
company: DF.Link | None
contact_display: DF.SmallText | None

View File

@@ -0,0 +1,44 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-07-30 19:20:35.277688",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"vendor_invoice",
"amount"
],
"fields": [
{
"fieldname": "vendor_invoice",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Vendor Invoice",
"options": "Purchase Invoice",
"search_index": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-07-30 23:15:43.737772",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Vendor Invoice",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,24 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LandedCostVendorInvoice(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
amount: DF.Currency
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
vendor_invoice: DF.Link | None
# end: auto-generated types
pass

View File

@@ -165,6 +165,15 @@ frappe.ui.form.on("Landed Cost Voucher", {
};
}
});
frm.set_query("vendor_invoice", "vendor_invoices", (doc, cdt, cdn) => {
return {
query: "erpnext.stock.doctype.landed_cost_voucher.landed_cost_voucher.get_vendor_invoices",
filters: {
company: doc.company,
},
};
});
},
});
@@ -189,3 +198,24 @@ frappe.ui.form.on("Landed Cost Purchase Receipt", {
}
},
});
frappe.ui.form.on("Landed Cost Vendor Invoice", {
vendor_invoice(frm, cdt, cdn) {
var d = locals[cdt][cdn];
if (d.vendor_invoice) {
frappe.call({
method: "get_vendor_invoice_amount",
doc: frm.doc,
args: {
vendor_invoice: d.vendor_invoice,
},
callback: function (r) {
if (r.message) {
$.extend(d, r.message);
refresh_field("vendor_invoices");
}
},
});
}
},
});

View File

@@ -16,8 +16,10 @@
"get_items_from_purchase_receipts",
"items",
"sec_break1",
"vendor_invoices",
"taxes",
"section_break_9",
"total_vendor_invoices_cost",
"total_taxes_and_charges",
"col_break1",
"distribute_charges_based_on",
@@ -79,7 +81,7 @@
{
"fieldname": "taxes",
"fieldtype": "Table",
"label": "Taxes and Charges",
"label": "Landed Cost",
"options": "Landed Cost Taxes and Charges",
"reqd": 1
},
@@ -90,7 +92,7 @@
{
"fieldname": "total_taxes_and_charges",
"fieldtype": "Currency",
"label": "Total Taxes and Charges (Company Currency)",
"label": "Total Landed Cost (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1,
"reqd": 1
@@ -139,6 +141,20 @@
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hide_border": 1
},
{
"fieldname": "vendor_invoices",
"fieldtype": "Table",
"label": "Vendor Invoices",
"options": "Landed Cost Vendor Invoice"
},
{
"fieldname": "total_vendor_invoices_cost",
"fieldtype": "Currency",
"label": "Total Vendor Invoices Cost (Company Currency)",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"grid_page_length": 50,
@@ -146,7 +162,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-06-09 10:08:39.574009",
"modified": "2025-07-30 19:25:04.899698",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Voucher",

View File

@@ -3,7 +3,7 @@
import frappe
from frappe import _
from frappe import _, bold
from frappe.model.document import Document
from frappe.model.meta import get_field_precision
from frappe.query_builder.custom import ConstantColumn
@@ -30,6 +30,9 @@ class LandedCostVoucher(Document):
from erpnext.stock.doctype.landed_cost_taxes_and_charges.landed_cost_taxes_and_charges import (
LandedCostTaxesandCharges,
)
from erpnext.stock.doctype.landed_cost_vendor_invoice.landed_cost_vendor_invoice import (
LandedCostVendorInvoice,
)
amended_from: DF.Link | None
company: DF.Link
@@ -40,6 +43,8 @@ class LandedCostVoucher(Document):
purchase_receipts: DF.Table[LandedCostPurchaseReceipt]
taxes: DF.Table[LandedCostTaxesandCharges]
total_taxes_and_charges: DF.Currency
total_vendor_invoices_cost: DF.Currency
vendor_invoices: DF.Table[LandedCostVendorInvoice]
# end: auto-generated types
@frappe.whitelist()
@@ -76,6 +81,28 @@ class LandedCostVoucher(Document):
self.get_items_from_purchase_receipts()
self.set_applicable_charges_on_item()
self.set_total_vendor_invoices_cost()
self.validate_vendor_invoices_cost_with_landed_cost()
def set_total_vendor_invoices_cost(self):
self.total_vendor_invoices_cost = 0.0
for row in self.vendor_invoices:
self.total_vendor_invoices_cost += flt(row.amount)
def validate_vendor_invoices_cost_with_landed_cost(self):
if not self.total_vendor_invoices_cost:
return
precision = frappe.get_precision("Landed Cost Voucher", "total_vendor_invoices_cost")
if flt(self.total_vendor_invoices_cost, precision) != flt(self.total_taxes_and_charges, precision):
frappe.throw(
_("Total Vendor Invoices Cost ({0}) must be equal to the Total Landed Cost ({1}).").format(
bold(self.total_vendor_invoices_cost),
bold(self.total_taxes_and_charges),
),
title=_("Incorrect Landed Cost"),
)
def validate_line_items(self):
for d in self.get("items"):
@@ -234,9 +261,20 @@ class LandedCostVoucher(Document):
def on_submit(self):
self.validate_applicable_charges_for_item()
self.update_landed_cost()
self.update_claimed_landed_cost()
def on_cancel(self):
self.update_landed_cost()
self.update_claimed_landed_cost()
def update_claimed_landed_cost(self):
for row in self.vendor_invoices:
frappe.db.set_value(
"Purchase Invoice",
row.vendor_invoice,
"claimed_landed_cost_amount",
flt(row.amount, row.precision("amount")) if self.docstatus == 1 else 0.0,
)
def update_landed_cost(self):
for d in self.get("purchase_receipts"):
@@ -333,6 +371,24 @@ class LandedCostVoucher(Document):
tuple([item.valuation_rate, *serial_nos]),
)
@frappe.whitelist()
def get_vendor_invoice_amount(self, vendor_invoice):
filters = frappe._dict(
{
"name": vendor_invoice,
"company": self.company,
}
)
query = get_vendor_invoice_query(filters)
result = query.run(as_dict=True)
amount = result[0].unclaimed_amount if result else 0.0
return {
"amount": amount,
}
def get_pr_items(purchase_receipt):
item = frappe.qb.DocType("Item")
@@ -383,3 +439,55 @@ def get_pr_items(purchase_receipt):
)
return query.run(as_dict=True)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_vendor_invoices(doctype, txt, searchfield, start, page_len, filters):
if not frappe.has_permission("Purchase Invoice", "read"):
return []
if txt and txt.lower().startswith(("select", "delete", "update")):
frappe.throw(_("Invalid search query"), title=_("Invalid Query"))
query = get_vendor_invoice_query(filters)
if txt:
query = query.where(doctype.name.like(f"%{txt}%"))
if start:
query = query.limit(page_len).offset(start)
return query.run(as_list=True)
def get_vendor_invoice_query(filters):
doctype = frappe.qb.DocType("Purchase Invoice")
child_doctype = frappe.qb.DocType("Purchase Invoice Item")
item = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(doctype)
.inner_join(child_doctype)
.on(child_doctype.parent == doctype.name)
.inner_join(item)
.on(item.name == child_doctype.item_code)
.select(
doctype.name,
(doctype.base_total - doctype.claimed_landed_cost_amount).as_("unclaimed_amount"),
)
.where(
(doctype.docstatus == 1)
& (doctype.is_subcontracted == 0)
& (doctype.is_return == 0)
& (doctype.update_stock == 0)
& (doctype.company == filters.get("company"))
& (item.is_stock_item == 0)
)
.having(frappe.qb.Field("unclaimed_amount") > 0)
)
if filters.get("name"):
query = query.where(doctype.name == filters.get("name"))
return query