mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 00:14:50 +00:00
feat: link vendor invoices in the LCV
This commit is contained in:
@@ -63,6 +63,7 @@
|
|||||||
"column_break_50",
|
"column_break_50",
|
||||||
"base_total",
|
"base_total",
|
||||||
"base_net_total",
|
"base_net_total",
|
||||||
|
"claimed_landed_cost_amount",
|
||||||
"column_break_28",
|
"column_break_28",
|
||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
@@ -1651,6 +1652,15 @@
|
|||||||
"label": "Select Dispatch Address ",
|
"label": "Select Dispatch Address ",
|
||||||
"options": "Address",
|
"options": "Address",
|
||||||
"print_hide": 1
|
"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,
|
"grid_page_length": 50,
|
||||||
@@ -1658,7 +1668,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-04-09 16:49:22.175081",
|
"modified": "2025-07-30 23:16:05.722875",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
billing_address_display: DF.TextEditor | None
|
billing_address_display: DF.TextEditor | None
|
||||||
buying_price_list: DF.Link | None
|
buying_price_list: DF.Link | None
|
||||||
cash_bank_account: DF.Link | None
|
cash_bank_account: DF.Link | None
|
||||||
|
claimed_landed_cost_amount: DF.Currency
|
||||||
clearance_date: DF.Date | None
|
clearance_date: DF.Date | None
|
||||||
company: DF.Link | None
|
company: DF.Link | None
|
||||||
contact_display: DF.SmallText | None
|
contact_display: DF.SmallText | None
|
||||||
|
|||||||
@@ -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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
"get_items_from_purchase_receipts",
|
"get_items_from_purchase_receipts",
|
||||||
"items",
|
"items",
|
||||||
"sec_break1",
|
"sec_break1",
|
||||||
|
"vendor_invoices",
|
||||||
"taxes",
|
"taxes",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
|
"total_vendor_invoices_cost",
|
||||||
"total_taxes_and_charges",
|
"total_taxes_and_charges",
|
||||||
"col_break1",
|
"col_break1",
|
||||||
"distribute_charges_based_on",
|
"distribute_charges_based_on",
|
||||||
@@ -79,7 +81,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "taxes",
|
"fieldname": "taxes",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Taxes and Charges",
|
"label": "Landed Cost",
|
||||||
"options": "Landed Cost Taxes and Charges",
|
"options": "Landed Cost Taxes and Charges",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
@@ -90,7 +92,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "total_taxes_and_charges",
|
"fieldname": "total_taxes_and_charges",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Total Taxes and Charges (Company Currency)",
|
"label": "Total Landed Cost (Company Currency)",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
@@ -139,6 +141,20 @@
|
|||||||
"fieldname": "section_break_5",
|
"fieldname": "section_break_5",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hide_border": 1
|
"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,
|
"grid_page_length": 50,
|
||||||
@@ -146,7 +162,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-06-09 10:08:39.574009",
|
"modified": "2025-07-30 19:25:04.899698",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Landed Cost Voucher",
|
"name": "Landed Cost Voucher",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _, bold
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.query_builder.custom import ConstantColumn
|
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 (
|
from erpnext.stock.doctype.landed_cost_taxes_and_charges.landed_cost_taxes_and_charges import (
|
||||||
LandedCostTaxesandCharges,
|
LandedCostTaxesandCharges,
|
||||||
)
|
)
|
||||||
|
from erpnext.stock.doctype.landed_cost_vendor_invoice.landed_cost_vendor_invoice import (
|
||||||
|
LandedCostVendorInvoice,
|
||||||
|
)
|
||||||
|
|
||||||
amended_from: DF.Link | None
|
amended_from: DF.Link | None
|
||||||
company: DF.Link
|
company: DF.Link
|
||||||
@@ -40,6 +43,8 @@ class LandedCostVoucher(Document):
|
|||||||
purchase_receipts: DF.Table[LandedCostPurchaseReceipt]
|
purchase_receipts: DF.Table[LandedCostPurchaseReceipt]
|
||||||
taxes: DF.Table[LandedCostTaxesandCharges]
|
taxes: DF.Table[LandedCostTaxesandCharges]
|
||||||
total_taxes_and_charges: DF.Currency
|
total_taxes_and_charges: DF.Currency
|
||||||
|
total_vendor_invoices_cost: DF.Currency
|
||||||
|
vendor_invoices: DF.Table[LandedCostVendorInvoice]
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -76,6 +81,28 @@ class LandedCostVoucher(Document):
|
|||||||
self.get_items_from_purchase_receipts()
|
self.get_items_from_purchase_receipts()
|
||||||
|
|
||||||
self.set_applicable_charges_on_item()
|
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):
|
def validate_line_items(self):
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
@@ -234,9 +261,20 @@ class LandedCostVoucher(Document):
|
|||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.validate_applicable_charges_for_item()
|
self.validate_applicable_charges_for_item()
|
||||||
self.update_landed_cost()
|
self.update_landed_cost()
|
||||||
|
self.update_claimed_landed_cost()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.update_landed_cost()
|
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):
|
def update_landed_cost(self):
|
||||||
for d in self.get("purchase_receipts"):
|
for d in self.get("purchase_receipts"):
|
||||||
@@ -333,6 +371,24 @@ class LandedCostVoucher(Document):
|
|||||||
tuple([item.valuation_rate, *serial_nos]),
|
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):
|
def get_pr_items(purchase_receipt):
|
||||||
item = frappe.qb.DocType("Item")
|
item = frappe.qb.DocType("Item")
|
||||||
@@ -383,3 +439,55 @@ def get_pr_items(purchase_receipt):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return query.run(as_dict=True)
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user