diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 79f8deea59b5..8fdbaa1c906b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -129,6 +129,26 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. ); } + if (doc.docstatus === 0) { + const cur_doc = this.frm.doc; + const invoice_items = cur_doc.items; + + this.frm.add_custom_button(__("Link to Purchase Order"), function () { + let unmapped_items = get_unmapped_items(invoice_items); + + frappe.call({ + method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.link_purchase_order", + args: { + pi_item_code: unmapped_items, + supplier: cur_doc.supplier, + }, + callback: function (r) { + assign_purchase_order(invoice_items, r.message, cur_doc); + }, + }); + }); + } + if (doc.docstatus === 0) { this.frm.add_custom_button( __("Purchase Order"), @@ -446,6 +466,51 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. } }; +function get_unmapped_items(invoice_items) { + return invoice_items.map((item) => { + item.purchase_order = ""; + return item.item_code; + }); +} + +function assign_purchase_order(invoice_items, items_to_link, cur_doc) { + if (!Object.keys(items_to_link).length) { + frappe.msgprint(__("No Purchase Orders matched")); + return; + } + + let total_idx = invoice_items.length; + + invoice_items.forEach((item) => { + const matched_item = items_to_link[item.item_code]; + if (!matched_item) return; + + if (item.qty > matched_item.qty) { + const new_row = frappe.model.add_child(cur_doc, item.doctype, "items"); + Object.assign(new_row, item); + new_row.qty = item.qty - matched_item.qty; + new_row.idx = ++total_idx; + + frappe.msgprint("Splitting " + new_row.qty + " units of " + new_row.item_code); + item.qty = matched_item.qty; + } + + frappe.msgprint( + "Assigning " + matched_item.purchase_order + " to " + item.item_code + " (row " + item.idx + ")" + ); + item.purchase_order = matched_item.purchase_order; + + if (items_to_link[item.item_code]) { + items_to_link[item.item_code] -= item.qty; + + if (items_to_link[item.item_code] <= 0) { + delete items_to_link[item.item_code]; + } + } + }); + refresh_field("items"); +} + cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice); // Hide Fields diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 4b767be42e11..0a8dc5a64db5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - +import json import frappe from frappe import _, qb, throw -from frappe.model.mapper import get_mapped_doc +from frappe.model import child_table_fields, default_fields, table_fields +from frappe.model.mapper import get_mapped_doc, map_fetch_fields, map_fields from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate @@ -1934,6 +1935,31 @@ def make_stock_entry(source_name, target_doc=None): return doc +@frappe.whitelist() +def link_purchase_order(pi_item_code, supplier): + pi_item_code = json.loads(pi_item_code) + + purchase_orders = frappe.get_all( + "Purchase Order", + filters={"supplier": supplier, "docstatus": 1}, + pluck="name", + order_by="creation asc", + ) + purchase_order_items = frappe.get_all( + "Purchase Order Item", + filters={"parent": ["in", purchase_orders]}, + fields=["parent", "item_code", "qty"], + ) + + item_to_po_map = { + item.item_code: {"purchase_order": item.parent, "qty": item.qty} + for item in purchase_order_items + if item.item_code in pi_item_code + } + + return item_to_po_map + + @frappe.whitelist() def change_release_date(name, release_date=None): if frappe.db.exists("Purchase Invoice", name):