Merge pull request #25126 from rohitwaghchaure/purchase-invoice-to-purchase-receipt-develop

feat: purchase receipt creation from purchase invoice
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index e61cde8..f58c8f4 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -514,6 +514,28 @@
 		}
 	},
 
+	refresh: function(frm) {
+		frm.events.add_custom_buttons(frm);
+	},
+
+	add_custom_buttons: function(frm) {
+		if (frm.doc.per_received < 100) {
+			frm.add_custom_button(__('Purchase Receipt'), () => {
+				frm.events.make_purchase_receipt(frm);
+			}, __('Create'));
+		}
+
+		if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) {
+			frm.add_custom_button(__('Purchase Receipt'), () => {
+				frappe.route_options = {
+					'purchase_invoice': frm.doc.name
+				}
+
+				frappe.set_route("List", "Purchase Receipt", "List")
+			}, __('View'));
+		}
+	},
+
 	onload: function(frm) {
 		if(frm.doc.__onload && frm.is_new()) {
 			if(frm.doc.supplier) {
@@ -539,5 +561,13 @@
 	update_stock: function(frm) {
 		hide_fields(frm.doc);
 		frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
+	},
+
+	make_purchase_receipt: function(frm) {
+		frappe.model.open_mapped_doc({
+			method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
+			frm: frm,
+			freeze_message: __("Creating Purchase Receipt ...")
+		})
 	}
 })
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 2d5760b..24e67fe 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -163,7 +163,8 @@
   "to_date",
   "column_break_114",
   "auto_repeat",
-  "update_auto_repeat_reference"
+  "update_auto_repeat_reference",
+  "per_received"
  ],
  "fields": [
   {
@@ -1364,6 +1365,15 @@
    "print_hide": 1,
    "print_width": "50px",
    "width": "50px"
+  },
+  {
+   "fieldname": "per_received",
+   "fieldtype": "Percent",
+   "hidden": 1,
+   "label": "Per Received",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 5c4e32e..83e9f75 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1207,3 +1207,41 @@
 
 def on_doctype_update():
 	frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
+
+@frappe.whitelist()
+def make_purchase_receipt(source_name, target_doc=None):
+	def update_item(obj, target, source_parent):
+		target.qty = flt(obj.qty) - flt(obj.received_qty)
+		target.received_qty = flt(obj.qty) - flt(obj.received_qty)
+		target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
+		target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
+		target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \
+			flt(obj.rate) * flt(source_parent.conversion_rate)
+
+	doc = get_mapped_doc("Purchase Invoice", source_name, {
+		"Purchase Invoice": {
+			"doctype": "Purchase Receipt",
+			"validation": {
+				"docstatus": ["=", 1],
+			}
+		},
+		"Purchase Invoice Item": {
+			"doctype": "Purchase Receipt Item",
+			"field_map": {
+				"name": "purchase_invoice_item",
+				"parent": "purchase_invoice",
+				"bom": "bom",
+				"purchase_order": "purchase_order",
+				"po_detail": "purchase_order_item",
+				"material_request": "material_request",
+				"material_request_item": "material_request_item"
+			},
+			"postprocess": update_item,
+			"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
+		},
+		"Purchase Taxes and Charges": {
+			"doctype": "Purchase Taxes and Charges"
+		}
+	}, target_doc)
+
+	return doc
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 96ad0fd..10e1c73 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -607,6 +607,7 @@
    "oldfieldname": "purchase_order",
    "oldfieldtype": "Link",
    "options": "Purchase Order",
+   "print_hide": 1,
    "read_only": 1,
    "search_index": 1
   },
@@ -853,7 +854,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-02-23 00:59:52.614805",
+ "modified": "2021-03-30 09:02:39.256602",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/report/billed_items_to_be_received/__init__.py b/erpnext/accounts/report/billed_items_to_be_received/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/__init__.py
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js
new file mode 100644
index 0000000..e1fccb6
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js
@@ -0,0 +1,29 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports['Billed Items To Be Received'] = {
+	'filters': [
+		{
+			'label': __('Company'),
+			'fieldname': 'company',
+			'fieldtype': 'Link',
+			'options': 'Company',
+			'reqd': 1,
+			'default': frappe.defaults.get_default('Company')
+		},
+		{
+			'label': __('As on Date'),
+			'fieldname': 'posting_date',
+			'fieldtype': 'Date',
+			'reqd': 1,
+			'default': get_today()
+		},
+		{
+			'label': __('Purchase Invoice'),
+			'fieldname': 'purchase_invoice',
+			'fieldtype': 'Link',
+			'options': 'Purchase Invoice'
+		}
+	]
+};
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json
new file mode 100644
index 0000000..de09b33
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json
@@ -0,0 +1,39 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-03-30 09:35:38.683028",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-03-31 08:48:30.944429",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Billed Items To Be Received",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "",
+ "ref_doctype": "Purchase Invoice",
+ "report_name": "Billed Items To Be Received",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Accounts User"
+  },
+  {
+   "role": "Purchase User"
+  },
+  {
+   "role": "Accounts Manager"
+  },
+  {
+   "role": "Auditor"
+  },
+  {
+   "role": "Stock User"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py
new file mode 100644
index 0000000..2ce5d50
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py
@@ -0,0 +1,107 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+	data = get_data(filters) or []
+	columns = get_columns()
+
+	return columns, data
+
+def get_data(report_filters):
+	filters = get_report_filters(report_filters)
+	fields = get_report_fields()
+
+	return frappe.get_all('Purchase Invoice',
+		fields= fields, filters=filters)
+
+def get_report_filters(report_filters):
+	filters = [['Purchase Invoice','company','=',report_filters.get('company')],
+		['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1],
+		['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]]
+
+	if report_filters.get('purchase_invoice'):
+		filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]])
+
+	return filters
+
+def get_report_fields():
+	fields = []
+	for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']:
+		fields.append('`tabPurchase Invoice`.`{}`'.format(p_field))
+
+	for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']:
+		fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field))
+
+	return fields
+
+def get_columns():
+	return [
+		{
+			'label': _('Purchase Invoice'),
+			'fieldname': 'name',
+			'fieldtype': 'Link',
+			'options': 'Purchase Invoice',
+			'width': 170
+		},
+		{
+			'label': _('Supplier'),
+			'fieldname': 'supplier',
+			'fieldtype': 'Link',
+			'options': 'Supplier',
+			'width': 120
+		},
+		{
+			'label': _('Posting Date'),
+			'fieldname': 'posting_date',
+			'fieldtype': 'Date',
+			'width': 100
+		},
+		{
+			'label': _('Item Code'),
+			'fieldname': 'item_code',
+			'fieldtype': 'Link',
+			'options': 'Item',
+			'width': 100
+		},
+		{
+			'label': _('Item Name'),
+			'fieldname': 'item_name',
+			'fieldtype': 'Data',
+			'width': 100
+		},
+		{
+			'label': _('UOM'),
+			'fieldname': 'uom',
+			'fieldtype': 'Link',
+			'options': 'UOM',
+			'width': 100
+		},
+		{
+			'label': _('Invoiced Qty'),
+			'fieldname': 'qty',
+			'fieldtype': 'Float',
+			'width': 100
+		},
+		{
+			'label': _('Received Qty'),
+			'fieldname': 'received_qty',
+			'fieldtype': 'Float',
+			'width': 100
+		},
+		{
+			'label': _('Rate'),
+			'fieldname': 'rate',
+			'fieldtype': 'Currency',
+			'width': 100
+		},
+		{
+			'label': _('Amount'),
+			'fieldname': 'amount',
+			'fieldtype': 'Currency',
+			'width': 100
+		}
+	]
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 3c4f908..42f4472 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -435,6 +435,35 @@
 		po.load_from_db()
 		self.assertEqual(po.get("items")[0].received_qty, 5)
 
+	def test_purchase_order_invoice_receipt_workflow(self):
+		from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt
+
+		po = create_purchase_order()
+		pi = make_pi_from_po(po.name)
+
+		pi.submit()
+
+		pr = make_purchase_receipt(pi.name)
+		pr.submit()
+
+		pi.load_from_db()
+
+		self.assertEquals(pi.per_received, 100.00)
+		self.assertEquals(pi.items[0].qty, pi.items[0].received_qty)
+
+		po.load_from_db()
+
+		self.assertEquals(po.per_received, 100.00)
+		self.assertEquals(po.per_billed, 100.00)
+
+		pr.cancel()
+
+		pi.load_from_db()
+		pi.cancel()
+
+		po.load_from_db()
+		po.cancel()
+
 	def test_make_purchase_invoice(self):
 		po = create_purchase_order(do_not_submit=True)
 
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 4d1a514..befdad9 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -73,6 +73,34 @@
 				})
 			}, __('Create'));
 		}
+
+		frm.events.add_custom_buttons(frm);
+	},
+
+	add_custom_buttons: function(frm) {
+		if (frm.doc.docstatus == 0) {
+			frm.add_custom_button(__('Purchase Invoice'), function () {
+				if (!frm.doc.supplier) {
+					frappe.throw({
+						title: __("Mandatory"),
+						message: __("Please Select a Supplier")
+					});
+				}
+				erpnext.utils.map_current_doc({
+					method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
+					source_doctype: "Purchase Invoice",
+					target: frm,
+					setters: {
+						supplier: frm.doc.supplier,
+					},
+					get_query_filters: {
+						docstatus: 1,
+						per_received: ["<", 100],
+						company: frm.doc.company
+					}
+				})
+			}, __("Get Items From"));
+		}
 	},
 
 	company: function(frm) {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index d8d8310..61e60f3 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -53,7 +53,20 @@
 			'target_ref_field': 'stock_qty',
 			'source_field': 'stock_qty',
 			'percent_join_field': 'material_request'
+		},
+		{
+			'source_dt': 'Purchase Receipt Item',
+			'target_dt': 'Purchase Invoice Item',
+			'join_field': 'purchase_invoice_item',
+			'target_field': 'received_qty',
+			'target_parent_dt': 'Purchase Invoice',
+			'target_parent_field': 'per_received',
+			'target_ref_field': 'qty',
+			'source_field': 'received_qty',
+			'percent_join_field': 'purchase_invoice',
+			'overflow_type': 'receipt'
 		}]
+
 		if cint(self.is_return):
 			self.status_updater.extend([
 				{
@@ -514,7 +527,9 @@
 	def update_billing_status(self, update_modified=True):
 		updated_pr = [self.name]
 		for d in self.get("items"):
-			if d.purchase_order_item:
+			if d.purchase_invoice and d.purchase_invoice_item:
+				d.db_set('billed_amt', d.amount, update_modified=update_modified)
+			elif d.purchase_order_item:
 				updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified)
 
 		for pr in set(updated_pr):
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index efe3642..82cc98e 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -72,16 +72,18 @@
   "warehouse",
   "rejected_warehouse",
   "from_warehouse",
-  "purchase_order",
   "material_request",
+  "purchase_order",
+  "purchase_invoice",
   "column_break_40",
   "is_fixed_asset",
   "asset_location",
   "asset_category",
   "schedule_date",
   "quality_inspection",
-  "purchase_order_item",
   "material_request_item",
+  "purchase_order_item",
+  "purchase_invoice_item",
   "purchase_receipt_item",
   "delivery_note_item",
   "putaway_rule",
@@ -937,7 +939,21 @@
    "fieldname": "base_rate_with_margin",
    "fieldtype": "Currency",
    "label": "Rate With Margin (Company Currency)",
-   "options": "Company:company:default_currency",
+   "options": "Company:company:default_currency"
+  },
+  {
+   "fieldname": "purchase_invoice",
+   "fieldtype": "Link",
+   "label": "Purchase Invoice",
+   "options": "Purchase Invoice",
+   "read_only": 1
+  },
+  {
+   "fieldname": "purchase_invoice_item",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Purchase Invoice Item",
+   "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
   }
@@ -945,7 +961,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-02-23 00:59:14.360847",
+ "modified": "2021-03-29 04:17:00.336298",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",