diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f84048dc244..18d24bcc503 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1601,6 +1601,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): def set_missing_values(source, target): target.run_method("set_missing_values") + set_purchase_references(target) def update_details(source_doc, target_doc, source_parent): target_doc.inter_company_invoice_reference = source_doc.name @@ -1682,6 +1683,90 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): return doclist +def set_purchase_references(doc): + # add internal PO or PR links if any + if doc.is_internal_transfer(): + if doc.doctype == 'Purchase Receipt': + so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference) + + if so_item_map: + pd_item_map, parent_child_map, warehouse_map = \ + get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item') + + update_pr_items(doc, so_item_map, pd_item_map, parent_child_map, warehouse_map) + + elif doc.doctype == 'Purchase Invoice': + dn_item_map, so_item_map = get_sales_invoice_details(doc.inter_company_invoice_reference) + # First check for Purchase receipt + if list(dn_item_map.values()): + pd_item_map, parent_child_map, warehouse_map = \ + get_pd_details('Purchase Receipt Item', dn_item_map, 'delivery_note_item') + + update_pi_items(doc, 'pr_detail', 'purchase_receipt', + dn_item_map, pd_item_map, parent_child_map, warehouse_map) + + if list(so_item_map.values()): + pd_item_map, parent_child_map, warehouse_map = \ + get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item') + + update_pi_items(doc, 'po_detail', 'purchase_order', + so_item_map, pd_item_map, parent_child_map, warehouse_map) + +def update_pi_items(doc, detail_field, parent_field, sales_item_map, + purchase_item_map, parent_child_map, warehouse_map): + for item in doc.get('items'): + item.set(detail_field, purchase_item_map.get(sales_item_map.get(item.sales_invoice_item))) + item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item))) + if doc.update_stock: + item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item)) + +def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map): + for item in doc.get('items'): + item.purchase_order_item = purchase_item_map.get(sales_item_map.get(item.delivery_note_item)) + item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item)) + item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item)) + +def get_delivery_note_details(internal_reference): + so_item_map = {} + + si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'], + filters={'parent': internal_reference}) + + for d in si_item_details: + so_item_map.setdefault(d.name, d.so_detail) + + return so_item_map + +def get_sales_invoice_details(internal_reference): + dn_item_map = {} + so_item_map = {} + + si_item_details = frappe.get_all('Sales Invoice Item', fields=['name', 'so_detail', + 'dn_detail'], filters={'parent': internal_reference}) + + for d in si_item_details: + if d.dn_detail: + dn_item_map.setdefault(d.name, d.dn_detail) + if d.so_detail: + so_item_map.setdefault(d.name, d.so_detail) + + return dn_item_map, so_item_map + +def get_pd_details(doctype, sd_detail_map, sd_detail_field): + pd_item_map = {} + accepted_warehouse_map = {} + parent_child_map = {} + + pd_item_details = frappe.get_all(doctype, + fields=[sd_detail_field, 'name', 'warehouse', 'parent'], filters={sd_detail_field: ('in', list(sd_detail_map.values()))}) + + for d in pd_item_details: + pd_item_map.setdefault(d.get(sd_detail_field), d.name) + parent_child_map.setdefault(d.get(sd_detail_field), d.parent) + accepted_warehouse_map.setdefault(d.get(sd_detail_field), d.warehouse) + + return pd_item_map, parent_child_map, accepted_warehouse_map + def update_taxes(doc, party=None, party_type=None, company=None, doctype=None, party_address=None, company_address=None, shipping_address_name=None, master_doctype=None): # Update Party Details diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index d267ddac62e..ee2beea67f9 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1116,14 +1116,15 @@ "fieldname": "represents_company", "fieldtype": "Link", "label": "Represents Company", - "options": "Company" + "options": "Company", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-01-10 20:50:41.545354", + "modified": "2021-01-20 22:07:23.487138", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6251f2c97fa..8d5f8a55ae0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -75,6 +75,8 @@ class AccountsController(TransactionBase): self.ensure_supplier_is_not_blocked() self.validate_date_with_fiscal_year() + self.validate_inter_company_reference() + self.set_incoming_rate() if self.meta.get_field("currency"): @@ -207,6 +209,14 @@ class AccountsController(TransactionBase): validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company, self.meta.get_label(date_field), self) + def validate_inter_company_reference(self): + if self.is_internal_transfer() and self.doctype in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'): + if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference') + or self.get('inter_company_order_reference')): + msg = _("Internal Sale or Delivery Reference missing. ") + msg += _("Please create purchase from internal sale or delivery document itself") + frappe.throw(msg, title=_("Internal Sales Reference Missing")) + def validate_due_date(self): if self.get('is_pos'): return diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2b3b6a9a4bc..bfa0ec08edf 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -399,7 +399,6 @@ class StockController(AccountsController): self.validate_in_transit_warehouses() self.validate_multi_currency() self.validate_packed_items() - self.validate_inter_company_reference() def validate_in_transit_warehouses(self): if (self.doctype == 'Sales Invoice' and self.get('update_stock')) or self.doctype == 'Delivery Note': @@ -420,13 +419,6 @@ class StockController(AccountsController): if self.doctype in ('Sales Invoice', 'Delivery Note Item') and self.get('packed_items'): frappe.throw(_("Packed Items cannot be transferred internally")) - def validate_inter_company_reference(self): - if self.doctype in ('Purchase Invoice', 'Purchase Receipt'): - if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')): - msg = _("Internal Sale or Delivery Reference missing. ") - msg += _("Please create purchase from internal sale or delivery document itself") - frappe.throw(msg, title=_("Internal Sales Reference Missing")) - def repost_future_sle_and_gle(self): args = frappe._dict({ "posting_date": self.posting_date, diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 5daa15995c8..f3954e1275c 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -235,6 +235,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }; + this.frm.trigger('set_internal_warehouses'); + return frappe.run_serially([ () => set_value('currency', currency), () => set_value('price_list_currency', currency), @@ -731,16 +733,19 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.calculate_taxes_and_totals(false); }, - update_stock: function() { + set_internal_warehouses: function() { let me = this; - if (['Delivery Note', 'Sales Invoice'].includes(me.frm.doc.doctype)) { + if ((this.frm.doc.doctype === 'Sales Invoice' && me.frm.doc.update_stock) + || this.frm.doc.doctype == 'Delivery Note') { if (this.frm.doc.is_internal_customer && this.frm.doc.company === this.frm.doc.represents_company) { frappe.db.get_value('Company', this.frm.doc.company, 'default_in_transit_warehouse', function(value) { me.frm.set_value('set_target_warehouse', value.default_in_transit_warehouse); }); } } - if (['Purchase Receipt', 'Purchase Invoice'].includes(me.frm.doc.doctype)) { + + if ((this.frm.doc.doctype === 'Purchase Invoice' && me.frm.doc.update_stock) + || this.frm.doc.doctype == 'Purchase Receipt') { if (this.frm.doc.is_internal_supplier && this.frm.doc.company === this.frm.doc.represents_company) { frappe.db.get_value('Company', this.frm.doc.company, 'default_in_transit_warehouse', function(value) { me.frm.set_value('set_from_warehouse', value.default_in_transit_warehouse); @@ -835,7 +840,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) { erpnext.utils.get_shipping_address(this.frm, function(){ set_party_account(set_pricing); - }) + }); // Get default company billing address in Purchase Invoice, Order and Receipt frappe.call({ diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 770704e595b..808dd5add05 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -276,6 +276,12 @@ erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) { erpnext.utils.get_shipping_address = function(frm, callback){ if (frm.doc.company) { + if (!(frm.doc.inter_com_order_reference || frm.doc.internal_invoice_reference || + frm.doc.internal_order_reference)) { + if (callback) { + return callback(); + } + } frappe.call({ method: "erpnext.accounts.custom.address.get_shipping_address", args: { diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 87eb33df704..0a5c6651ba3 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1105,7 +1105,8 @@ "hide_days": 1, "hide_seconds": 1, "label": "Inter Company Order Reference", - "options": "Purchase Order" + "options": "Purchase Order", + "read_only": 1 }, { "description": "Track this Sales Order against any Project", @@ -1471,14 +1472,15 @@ "fieldname": "represents_company", "fieldtype": "Link", "label": "Represents Company", - "options": "Company" + "options": "Company", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-01-10 20:49:51.118079", + "modified": "2021-01-20 23:40:39.929296", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index a3f3e997159..4c89c3311e9 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -95,13 +95,19 @@ frappe.ui.form.on("Delivery Note", { frm.page.set_inner_btn_group_as_primary(__('Create')); } - if (frm.doc.docstatus === 1 && frm.doc.is_internal_customer && !frm.doc.inter_company_reference) { - frm.add_custom_button(__('Purchase Receipt'), function() { - frappe.model.open_mapped_doc({ - method: 'erpnext.stock.doctype.delivery_note.delivery_note.make_inter_company_purchase_receipt', - frm: frm, - }) - }, __('Create')); + if (frm.doc.docstatus == 1 && !frm.doc.inter_company_reference) { + let internal = me.frm.doc.is_internal_customer; + if (internal) { + let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Receipt" : + "Inter Company Purchase Receipt"; + + me.frm.add_custom_button(button_label, function() { + frappe.model.open_mapped_doc({ + method: 'erpnext.stock.doctype.delivery_note.delivery_note.make_inter_company_purchase_receipt', + frm: frm, + }); + }, __('Create')); + } } } }); diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index e06e4955f66..fa5a7fbe719 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -665,7 +665,7 @@ def make_inter_company_purchase_receipt(source_name, target_doc=None): def make_inter_company_transaction(doctype, source_name, target_doc=None): from erpnext.accounts.doctype.sales_invoice.sales_invoice import (validate_inter_company_transaction, - get_inter_company_details, update_address, update_taxes) + get_inter_company_details, update_address, update_taxes, set_purchase_references) if doctype == 'Delivery Note': source_doc = frappe.get_doc(doctype, source_name) @@ -683,6 +683,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): def set_missing_values(source, target): target.run_method("set_missing_values") + set_purchase_references(target) if target.doctype == 'Purchase Receipt': master_doctype = 'Purchase Taxes and Charges Template' @@ -726,7 +727,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): doctype=target_doc.doctype, party_address=target_doc.customer_address, company_address=target_doc.company_address, shipping_address_name=target_doc.shipping_address_name) - doclist = get_mapped_doc(doctype, source_name, { + doclist = get_mapped_doc(doctype, source_name, { doctype: { "doctype": target_doctype, "postprocess": update_details,