From 69909c0f51a7d8759de85d00247ea804329481f0 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 29 Mar 2019 20:44:23 +0530 Subject: [PATCH 01/13] feat: create procurement tracker report --- .../report/procurement_tracker/__init__.py | 0 .../procurement_tracker.js | 9 + .../procurement_tracker.json | 20 +++ .../procurement_tracker.py | 167 ++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 erpnext/buying/report/procurement_tracker/__init__.py create mode 100644 erpnext/buying/report/procurement_tracker/procurement_tracker.js create mode 100644 erpnext/buying/report/procurement_tracker/procurement_tracker.json create mode 100644 erpnext/buying/report/procurement_tracker/procurement_tracker.py diff --git a/erpnext/buying/report/procurement_tracker/__init__.py b/erpnext/buying/report/procurement_tracker/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.js b/erpnext/buying/report/procurement_tracker/procurement_tracker.js new file mode 100644 index 00000000000..7096b9f9f8c --- /dev/null +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.js @@ -0,0 +1,9 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Procurement Tracker"] = { + "filters": [ + + ] +} diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.json b/erpnext/buying/report/procurement_tracker/procurement_tracker.json new file mode 100644 index 00000000000..028736c7c41 --- /dev/null +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.json @@ -0,0 +1,20 @@ +{ + "add_total_row": 1, + "creation": "2019-03-29 17:05:45.196949", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2019-03-29 17:18:06.678728", + "modified_by": "Administrator", + "module": "Buying", + "name": "Procurement Tracker", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Purchase Order", + "report_name": "Procurement Tracker", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py new file mode 100644 index 00000000000..ee6fd2d0fbf --- /dev/null +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -0,0 +1,167 @@ +# 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 _ +from frappe.utils import cint,cstr + +def execute(filters=None): + columns = get_columns() + data = get_data() + return columns, data + +def get_columns(): + columns = [ + { + "label": _("Date Requisition Received in Procurement "), + "fieldname": "date_requisition_received_in_procurement", + "fieldtype": "Date", + "width": 140 + }, + { + "label": _("Date Requisition was Raised"), + "fieldname": "date_requisition_was_raised", + "fieldtype": "Date", + "width": 140 + }, + { + "label": _("Sector/Project"), + "options": "Cost Center", + "fieldname": "sector/project", + "fieldtype": "Link", + "width": 140 + }, + { + "label": _("Requesting Site"), + "options": "Warehouse", + "fieldname": "requesting_site", + "fieldtype": "Link", + "width": 140 + }, + { + "label": _("Requestor"), + "options": "Employee", + "fieldname": "requestor", + "fieldtype": "Link", + "width": 140 + }, + { + "label": _("Budget Code"), + "options": "Budget", + "fieldname": "budget_code", + "fieldtype": "Link", + "width": 140 + }, + { + "label": _("Requisition Line"), + "options": "Item", + "fieldname": "requisition_line", + "fieldtype": "Link", + "width": 140 + }, + { + "label": _("Description"), + "fieldname": "description", + "fieldtype": "Data", + "width": 200 + }, + { + "label": _("Quantity"), + "fieldname": "quantity", + "fieldtype": "Int", + "width": 140 + }, + { + "label": _("Unit of Measure"), + "options": "UOM", + "fieldname": "unit_of_measurement", + "fieldtype": "Link", + "width": 140 + }, + { + "label": _("Status"), + "fieldname": "status", + "fieldtype": "data", + "width": 140 + }, + { + "label": _("Purchase Order Date"), + "fieldname": "purchase_order_date", + "fieldtype": "Date", + "width": 140 + }, + { + "label": _("Purchase Order"), + "options": "Purchase Order", + "fieldname": "purchase_order", + "fieldtype": "Link", + "width": 140 + }, + { + "label": _("Supplier"), + "options": "Supplier", + "fieldname": "supplier", + "fieldtype": "Link", + "width": 140 + }, + { + "label": _("Estimated Cost"), + "fieldname": "estimated_cost", + "fieldtype": "Float", + "width": 140 + }, + { + "label": _("Actual Cost"), + "fieldname": "actual_cost", + "fieldtype": "Float", + "width": 140 + }, + { + "label": _("Purchase Order Amount"), + "fieldname": "purchase_order_amount", + "fieldtype": "Float", + "width": 140 + }, + { + "label": _("Purchase Order Amount(USD)"), + "fieldname": "purchase_order_amount_usd", + "fieldtype": "Float", + "width": 140 + }, + { + "label": _("Expected Delivery Date"), + "fieldname": "expected_delivery_date", + "fieldtype": "Date", + "width": 140 + }, + { + "label": _("Actual Delivery Date"), + "fieldname": "actual_delivery_date", + "fieldtype": "Date", + "width": 140 + }, + ] + return columns + +def get_data(): + purchase_order_entry = frappe.db.sql(""" + SELECT + po_item.item_code, + po_item.item_name, + po_item.description, + po.name, + po.transaction_date, + po.customer, + po.territory, + sum(po_item.qty) as net_qty, + po.company + FROM `tabPurchase Order` po, `tabPurchase Order Item` po_item + WHERE + po.docstatus = 1 + and po.name = po_item.parent + and po.status not in ("Closed","Completed","Cancelled") + GROUP BY + po.name,po_item.item_code + """, as_dict = 1) + return data \ No newline at end of file From 5de08f71c5b0ca0764cb94b4d7a589c62d0eca33 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 1 Apr 2019 21:14:05 +0530 Subject: [PATCH 02/13] feat: fetch all required fields from po --- .../procurement_tracker.py | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index ee6fd2d0fbf..0b6799c4891 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -149,13 +149,22 @@ def get_data(): SELECT po_item.item_code, po_item.item_name, + po_item.cost_center, + po_item.project, + po_item.warehouse, + po_item.material_request, po_item.description, + po_item.stock_uom, + po_item.qty, + po_item.net_amount, + po_item.base_amount, + po_item.schedule_date, + po_item.expected_delivery_date, po.name, po.transaction_date, - po.customer, - po.territory, - sum(po_item.qty) as net_qty, - po.company + po.supplier, + po.status, + po.owner FROM `tabPurchase Order` po, `tabPurchase Order Item` po_item WHERE po.docstatus = 1 @@ -164,4 +173,14 @@ def get_data(): GROUP BY po.name,po_item.item_code """, as_dict = 1) - return data \ No newline at end of file + + mr_records = frappe._dict(frappe.db.sql(""" + SELECT + name, + transaction_date + FROM `tabMaterial Request` + WHERE + per_ordered = 100 + and docstatus = 1 + """)) + return purchase_order_entry \ No newline at end of file From 46b9204e9d4aa682e1bef345251bcf9d658fa5d7 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 3 Apr 2019 19:45:08 +0530 Subject: [PATCH 03/13] feat: fetch data from budget records for cost estimation --- .../procurement_tracker.py | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index 0b6799c4891..901f19604fe 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -168,8 +168,8 @@ def get_data(): FROM `tabPurchase Order` po, `tabPurchase Order Item` po_item WHERE po.docstatus = 1 - and po.name = po_item.parent - and po.status not in ("Closed","Completed","Cancelled") + AND po.name = po_item.parent + AND po.status not in ("Closed","Completed","Cancelled") GROUP BY po.name,po_item.item_code """, as_dict = 1) @@ -181,6 +181,32 @@ def get_data(): FROM `tabMaterial Request` WHERE per_ordered = 100 - and docstatus = 1 + AND docstatus = 1 """)) + + supplier_quotation_records = frappe._dict(frappe.db.sql(""" + SELECT + name, + base_amount + FROM `tabSupplier Quotation Item` + WHERE + per_ordered = 100 + AND docstatus = 1 + """)) + + budget_records = frappe.db.sql(""" + SELECT + budget.name, + budget.project, + budget.cost_center, + budget_account.account, + budget_account.budget_amount + FROM `tabBudget` budget, `tabBudget Account` budget_account + WHERE + budget.project IS NOT NULL + AND budget.name = budget_account.parent + AND budget.cost_center IS NOT NULL + AND budget.docstatus = 1 + """, as_dict = 1) + return purchase_order_entry \ No newline at end of file From fcd0edf4153a63e217384577820ce8a7675a6328 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 19 Apr 2019 17:22:54 +0530 Subject: [PATCH 04/13] test: compare expected data to generated data --- .../test_procurement_tracker.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 erpnext/buying/report/procurement_tracker/test_procurement_tracker.py diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py new file mode 100644 index 00000000000..1da601d4b61 --- /dev/null +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -0,0 +1,44 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import unittest +from frappe.utils import nowdate, add_months +from apps.erpnext.erpnext.buying.report.procurement_tracker.procurement_tracker import execute +from erpnext.stock.doctype.material_request.test_material_request import make_material_request +from erpnext.stock.doctype.material_request.material_request import make_purchase_order +from erpnext.accounts.doctype.budget.test_budget import make_budget + +class TestProcurementTracker(unittest.TestCase): + def test_result_for_procurement_tracker(self): + mr = make_material_request() + + po = make_purchase_order(mr.name) + po.get("Items")[0].cost_center = "_Test Cost Center - _TC" + po.get("Items")[0].amount = 1000 + po.submit() + + report = execute() + expected_data = { + "material_request_date": nowdate(), + "cost_center": "_Test Cost Center - _TC", + "project": '', + "requesting_site": "_Test Warehouse - _TC", + "requestor": "Administrator", + "material_request_no": mr.name, + "description": '', + "quantity": 10, + "unit_of_measurement": "_Test UOM", + "status": "To Receive and Bill", + "purchase_order_date": nowdate(), + "purchase_order": po.name, + "supplier": '', + "estimated_cost": '', + "actual_cost": '', + "purchase_order_amt": 1000, + "purchase_order_amt_usd": 1000, + "expected_delivery_date": nowdate(), + "actual_delivery_date": '' + } + length = len(report[1]) + self.assertEqual(expected_data, report[length]) \ No newline at end of file From 438fa620b0e553d4774ace72606f0c67a2958051 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Sun, 21 Apr 2019 23:44:21 +0530 Subject: [PATCH 05/13] feat: fetch purchase invoice and purchase receipt records for actual cost calculation --- .../procurement_tracker.py | 132 +++++++++++------- .../test_procurement_tracker.py | 3 +- 2 files changed, 82 insertions(+), 53 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index 901f19604fe..3bfae4d5089 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import cint,cstr def execute(filters=None): columns = get_columns() @@ -14,21 +13,22 @@ def execute(filters=None): def get_columns(): columns = [ { - "label": _("Date Requisition Received in Procurement "), - "fieldname": "date_requisition_received_in_procurement", + "label": _("Material Request Date"), + "fieldname": "material_request_date", "fieldtype": "Date", "width": 140 }, { - "label": _("Date Requisition was Raised"), - "fieldname": "date_requisition_was_raised", - "fieldtype": "Date", - "width": 140 - }, - { - "label": _("Sector/Project"), + "label": _("Cost Center"), "options": "Cost Center", - "fieldname": "sector/project", + "fieldname": "cost_center", + "fieldtype": "Link", + "width": 140 + }, + { + "label": _("Project"), + "options": "Project", + "fieldname": "project", "fieldtype": "Link", "width": 140 }, @@ -47,16 +47,16 @@ def get_columns(): "width": 140 }, { - "label": _("Budget Code"), - "options": "Budget", - "fieldname": "budget_code", + "label": _("Material Request No"), + "options": "Material Request", + "fieldname": "material_request_no", "fieldtype": "Link", "width": 140 }, { - "label": _("Requisition Line"), - "options": "Item", - "fieldname": "requisition_line", + "label": _("Budget Code"), + "options": "Budget", + "fieldname": "budget_code", "fieldtype": "Link", "width": 140 }, @@ -119,13 +119,13 @@ def get_columns(): }, { "label": _("Purchase Order Amount"), - "fieldname": "purchase_order_amount", + "fieldname": "purchase_order_amt", "fieldtype": "Float", "width": 140 }, { - "label": _("Purchase Order Amount(USD)"), - "fieldname": "purchase_order_amount_usd", + "label": _("Purchase Order Amount(Company Currency)"), + "fieldname": "purchase_order_amt_usd", "fieldtype": "Float", "width": 140 }, @@ -147,20 +147,19 @@ def get_columns(): def get_data(): purchase_order_entry = frappe.db.sql(""" SELECT - po_item.item_code, - po_item.item_name, + po_item.name, + po_item.parent, po_item.cost_center, po_item.project, po_item.warehouse, po_item.material_request, + po_item.material_request_item, po_item.description, po_item.stock_uom, po_item.qty, - po_item.net_amount, + po_item.amount, po_item.base_amount, po_item.schedule_date, - po_item.expected_delivery_date, - po.name, po.transaction_date, po.supplier, po.status, @@ -172,41 +171,72 @@ def get_data(): AND po.status not in ("Closed","Completed","Cancelled") GROUP BY po.name,po_item.item_code - """, as_dict = 1) + """, as_dict=1) - mr_records = frappe._dict(frappe.db.sql(""" + mr_details = frappe.db.sql(""" SELECT - name, - transaction_date - FROM `tabMaterial Request` + mr.transaction_date, + mr_item.name, + mr_item.parent, + mr_item.amount + FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item WHERE - per_ordered = 100 - AND docstatus = 1 - """)) + per_ordered > 0 + AND mr.name = mr_item.parent + AND mr.docstatus = 1 + """, as_dict=1) - supplier_quotation_records = frappe._dict(frappe.db.sql(""" + pi_records = frappe._dict(frappe.db.sql(""" SELECT - name, + po_detail, base_amount - FROM `tabSupplier Quotation Item` + FROM `tabPurchase Invoice Item` WHERE - per_ordered = 100 - AND docstatus = 1 + docstatus=1 + AND po_detail IS NOT NULL """)) - budget_records = frappe.db.sql(""" + pr_records = frappe._dict(frappe.db.sql(""" SELECT - budget.name, - budget.project, - budget.cost_center, - budget_account.account, - budget_account.budget_amount - FROM `tabBudget` budget, `tabBudget Account` budget_account + pr_item.purchase_order_item, + pr.posting_date + FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item WHERE - budget.project IS NOT NULL - AND budget.name = budget_account.parent - AND budget.cost_center IS NOT NULL - AND budget.docstatus = 1 - """, as_dict = 1) + pr.docstatus=1 + AND pr.name=pr_item.parent + AND pr_item.purchase_order_item IS NOT NULL + """)) - return purchase_order_entry \ No newline at end of file + + mr_records = {} + for record in mr_details: + mr_records.setdefault(record.name, []).append(frappe._dict(record)) + + procurement_record=[] + for po in purchase_order_entry: + # fetch material records linked to the purchase order item + mr_record = mr_records.get(po.material_request_item, [{}])[0] + + procurement_detail = { + "material_request_date": mr_record.get('transaction_date', ''), + "cost_center": po.cost_center, + "project": po.project, + "requesting_site": po.warehouse, + "requestor": po.owner, + "material_request_no": po.material_request, + "description": po.description, + "quantity": po.qty, + "unit_of_measurement": po.stock_uom, + "status": po.status, + "purchase_order_date": po.transaction_date, + "purchase_order": po.parent, + "supplier": po.supplier, + "estimated_cost": mr_record.get('amount'), + "actual_cost": pi_records.get(po.name, ''), + "purchase_order_amt": po.amount, + "purchase_order_amt_in_company_currency": po.base_amount, + "expected_delivery_date": po.schedule_date, + "actual_delivery_date": pr_records.get(po.name, {}) + } + procurement_record.append(procurement_detail) + return procurement_record \ No newline at end of file diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index 1da601d4b61..7faf48deb0c 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -4,10 +4,9 @@ from __future__ import unicode_literals import unittest from frappe.utils import nowdate, add_months -from apps.erpnext.erpnext.buying.report.procurement_tracker.procurement_tracker import execute +from erpnext.buying.report.procurement_tracker.procurement_tracker import execute from erpnext.stock.doctype.material_request.test_material_request import make_material_request from erpnext.stock.doctype.material_request.material_request import make_purchase_order -from erpnext.accounts.doctype.budget.test_budget import make_budget class TestProcurementTracker(unittest.TestCase): def test_result_for_procurement_tracker(self): From 869544c0bc8af7fa4e2fa92b29a175d0da9db919 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 26 Apr 2019 01:29:36 +0530 Subject: [PATCH 06/13] fix: add mapped records as functions --- .../procurement_tracker.py | 169 ++++++++++-------- 1 file changed, 95 insertions(+), 74 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index 3bfae4d5089..cf3ac7d0d75 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -18,6 +18,13 @@ def get_columns(): "fieldtype": "Date", "width": 140 }, + { + "label": _("Material Request No"), + "options": "Material Request", + "fieldname": "material_request_no", + "fieldtype": "Link", + "width": 140 + }, { "label": _("Cost Center"), "options": "Cost Center", @@ -46,13 +53,6 @@ def get_columns(): "fieldtype": "Link", "width": 140 }, - { - "label": _("Material Request No"), - "options": "Material Request", - "fieldname": "material_request_no", - "fieldtype": "Link", - "width": 140 - }, { "label": _("Budget Code"), "options": "Budget", @@ -145,74 +145,14 @@ def get_columns(): return columns def get_data(): - purchase_order_entry = frappe.db.sql(""" - SELECT - po_item.name, - po_item.parent, - po_item.cost_center, - po_item.project, - po_item.warehouse, - po_item.material_request, - po_item.material_request_item, - po_item.description, - po_item.stock_uom, - po_item.qty, - po_item.amount, - po_item.base_amount, - po_item.schedule_date, - po.transaction_date, - po.supplier, - po.status, - po.owner - FROM `tabPurchase Order` po, `tabPurchase Order Item` po_item - WHERE - po.docstatus = 1 - AND po.name = po_item.parent - AND po.status not in ("Closed","Completed","Cancelled") - GROUP BY - po.name,po_item.item_code - """, as_dict=1) - - mr_details = frappe.db.sql(""" - SELECT - mr.transaction_date, - mr_item.name, - mr_item.parent, - mr_item.amount - FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item - WHERE - per_ordered > 0 - AND mr.name = mr_item.parent - AND mr.docstatus = 1 - """, as_dict=1) - - pi_records = frappe._dict(frappe.db.sql(""" - SELECT - po_detail, - base_amount - FROM `tabPurchase Invoice Item` - WHERE - docstatus=1 - AND po_detail IS NOT NULL - """)) - - pr_records = frappe._dict(frappe.db.sql(""" - SELECT - pr_item.purchase_order_item, - pr.posting_date - FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item - WHERE - pr.docstatus=1 - AND pr.name=pr_item.parent - AND pr_item.purchase_order_item IS NOT NULL - """)) - - - mr_records = {} - for record in mr_details: - mr_records.setdefault(record.name, []).append(frappe._dict(record)) + purchase_order_entry = get_po_entries() + mr_records, procurement_record_against_mr = get_mapped_mr_details() + pr_records = get_mapped_pr_records() + pi_records = get_mapped_pi_records() procurement_record=[] + if procurement_record_against_mr: + procurement_record += procurement_record_against_mr for po in purchase_order_entry: # fetch material records linked to the purchase order item mr_record = mr_records.get(po.material_request_item, [{}])[0] @@ -239,4 +179,85 @@ def get_data(): "actual_delivery_date": pr_records.get(po.name, {}) } procurement_record.append(procurement_detail) - return procurement_record \ No newline at end of file + return procurement_record + +def get_mapped_mr_details(): + mr_records = {} + mr_details = frappe.db.sql(""" + SELECT + mr.transaction_date, + mr.per_ordered, + mr_item.name, + mr_item.parent, + mr_item.amount + FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item + WHERE + mr.per_ordered>=0 + AND mr.name=mr_item.parent + AND mr.docstatus=1 + """, as_dict=1) + + procurement_record_against_mr = [] + for record in mr_details: + if record.per_ordered: + mr_records.setdefault(record.name, []).append(frappe._dict(record)) + else: + procurement_record_details = dict( + material_request_date=record.transaction_date, + material_request_no=record.parent, + estimated_cost=record.amount + ) + procurement_record_against_mr.append(procurement_record_details) + return mr_records, procurement_record_against_mr + +def get_mapped_pi_records(): + return frappe._dict(frappe.db.sql(""" + SELECT + po_detail, + base_amount + FROM `tabPurchase Invoice Item` + WHERE + docstatus=1 + AND po_detail IS NOT NULL + """)) + +def get_mapped_pr_records(): + return frappe._dict(frappe.db.sql(""" + SELECT + pr_item.purchase_order_item, + pr.posting_date + FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item + WHERE + pr.docstatus=1 + AND pr.name=pr_item.parent + AND pr_item.purchase_order_item IS NOT NULL + """)) + +def get_po_entries(): + return frappe.db.sql(""" + SELECT + po_item.name, + po_item.parent, + po_item.cost_center, + po_item.project, + po_item.warehouse, + po_item.material_request, + po_item.material_request_item, + po_item.description, + po_item.stock_uom, + po_item.qty, + po_item.amount, + po_item.base_amount, + po_item.schedule_date, + po.transaction_date, + po.supplier, + po.status, + po.owner + FROM `tabPurchase Order` po, `tabPurchase Order Item` po_item + WHERE + po.docstatus = 1 + AND po.name = po_item.parent + AND po.status not in ("Closed","Completed","Cancelled") + GROUP BY + po.name,po_item.item_code + """, as_dict=1) \ No newline at end of file From da7b1673c30217ed2d424cc2b7761f5cf16d4425 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 26 Apr 2019 01:31:05 +0530 Subject: [PATCH 07/13] test: track pi and pr changes --- .../test_procurement_tracker.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index 7faf48deb0c..32975e8910f 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -7,15 +7,22 @@ from frappe.utils import nowdate, add_months from erpnext.buying.report.procurement_tracker.procurement_tracker import execute from erpnext.stock.doctype.material_request.test_material_request import make_material_request from erpnext.stock.doctype.material_request.material_request import make_purchase_order +from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt, make_purchase_invoice class TestProcurementTracker(unittest.TestCase): def test_result_for_procurement_tracker(self): mr = make_material_request() - + mr.submit() po = make_purchase_order(mr.name) - po.get("Items")[0].cost_center = "_Test Cost Center - _TC" - po.get("Items")[0].amount = 1000 + po.supplier = "_Test Supplier" + po.get("items")[0].cost_center = "_Test Cost Center - _TC" + po.get("items")[0].amount = 1000 + po.get("items")[0].base_amount = 1000 po.submit() + pr = make_purchase_receipt(po.name) + pr.submit() + pi = make_purchase_invoice(po.name) + pi.submit() report = execute() expected_data = { @@ -25,19 +32,19 @@ class TestProcurementTracker(unittest.TestCase): "requesting_site": "_Test Warehouse - _TC", "requestor": "Administrator", "material_request_no": mr.name, - "description": '', + "description": '_Test Item 1', "quantity": 10, "unit_of_measurement": "_Test UOM", "status": "To Receive and Bill", "purchase_order_date": nowdate(), "purchase_order": po.name, - "supplier": '', - "estimated_cost": '', - "actual_cost": '', + "supplier": "_Test Supplier", + "estimated_cost": 0.0, + "actual_cost": 1000, "purchase_order_amt": 1000, "purchase_order_amt_usd": 1000, "expected_delivery_date": nowdate(), - "actual_delivery_date": '' + "actual_delivery_date": nowdate() } length = len(report[1]) - self.assertEqual(expected_data, report[length]) \ No newline at end of file + self.assertEqual(expected_data, report[1][length-1]) \ No newline at end of file From 9cd9671bb5952d2d6aacb1d923c9ad36ae8dd4b9 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 26 Apr 2019 16:02:25 +0530 Subject: [PATCH 08/13] fix: minor changes --- .../procurement_tracker.py | 7 ++--- .../test_procurement_tracker.py | 31 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index cf3ac7d0d75..18a66605341 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -156,7 +156,6 @@ def get_data(): for po in purchase_order_entry: # fetch material records linked to the purchase order item mr_record = mr_records.get(po.material_request_item, [{}])[0] - procurement_detail = { "material_request_date": mr_record.get('transaction_date', ''), "cost_center": po.cost_center, @@ -172,11 +171,11 @@ def get_data(): "purchase_order": po.parent, "supplier": po.supplier, "estimated_cost": mr_record.get('amount'), - "actual_cost": pi_records.get(po.name, ''), + "actual_cost": pi_records.get(po.name), "purchase_order_amt": po.amount, "purchase_order_amt_in_company_currency": po.base_amount, "expected_delivery_date": po.schedule_date, - "actual_delivery_date": pr_records.get(po.name, {}) + "actual_delivery_date": pr_records.get(po.name) } procurement_record.append(procurement_detail) return procurement_record @@ -207,7 +206,7 @@ def get_mapped_mr_details(): material_request_no=record.parent, estimated_cost=record.amount ) - procurement_record_against_mr.append(procurement_record_details) + procurement_record_against_mr.append(procurement_record_details) return mr_records, procurement_record_against_mr def get_mapped_pi_records(): diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index 32975e8910f..7d8e750c4bc 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -3,48 +3,47 @@ from __future__ import unicode_literals import unittest -from frappe.utils import nowdate, add_months +from datetime import datetime +import frappe from erpnext.buying.report.procurement_tracker.procurement_tracker import execute from erpnext.stock.doctype.material_request.test_material_request import make_material_request from erpnext.stock.doctype.material_request.material_request import make_purchase_order from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt, make_purchase_invoice class TestProcurementTracker(unittest.TestCase): + maxDiff = None def test_result_for_procurement_tracker(self): mr = make_material_request() - mr.submit() po = make_purchase_order(mr.name) po.supplier = "_Test Supplier" po.get("items")[0].cost_center = "_Test Cost Center - _TC" - po.get("items")[0].amount = 1000 - po.get("items")[0].base_amount = 1000 po.submit() pr = make_purchase_receipt(po.name) pr.submit() - pi = make_purchase_invoice(po.name) - pi.submit() + frappe.db.commit() + date_obj = datetime.date(datetime.now()) report = execute() expected_data = { - "material_request_date": nowdate(), + "material_request_date": date_obj, "cost_center": "_Test Cost Center - _TC", - "project": '', + "project": None, "requesting_site": "_Test Warehouse - _TC", "requestor": "Administrator", "material_request_no": mr.name, "description": '_Test Item 1', - "quantity": 10, + "quantity": 10.0, "unit_of_measurement": "_Test UOM", - "status": "To Receive and Bill", - "purchase_order_date": nowdate(), + "status": "To Bill", + "purchase_order_date": date_obj, "purchase_order": po.name, "supplier": "_Test Supplier", "estimated_cost": 0.0, - "actual_cost": 1000, - "purchase_order_amt": 1000, - "purchase_order_amt_usd": 1000, - "expected_delivery_date": nowdate(), - "actual_delivery_date": nowdate() + "actual_cost": None, + "purchase_order_amt": 0.0, + "purchase_order_amt_in_company_currency": 0.0, + "expected_delivery_date": date_obj, + "actual_delivery_date": date_obj } length = len(report[1]) self.assertEqual(expected_data, report[1][length-1]) \ No newline at end of file From 96661ba847fd457e972e0d5eb298793cfb324ee5 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Sun, 28 Apr 2019 17:11:02 +0530 Subject: [PATCH 09/13] feat: add filters for multiple company operation --- .../procurement_tracker.js | 23 +++++++++++- .../procurement_tracker.py | 37 +++++++++++++------ .../test_procurement_tracker.py | 31 +++++++++++++--- .../material_request/test_material_request.py | 3 +- 4 files changed, 75 insertions(+), 19 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.js b/erpnext/buying/report/procurement_tracker/procurement_tracker.js index 7096b9f9f8c..0013666ce89 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.js +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.js @@ -4,6 +4,27 @@ frappe.query_reports["Procurement Tracker"] = { "filters": [ - + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + fieldname: "cost_center", + label: __("Cost Center"), + fieldtype: "Link", + options: "Cost Center", + reqd: 1 + }, + { + fieldname: "project", + label: __("Project"), + fieldtype: "Link", + options: "Project", + reqd: 1 + } ] } diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index 18a66605341..e3361a4c3f5 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -6,11 +6,11 @@ import frappe from frappe import _ def execute(filters=None): - columns = get_columns() - data = get_data() + columns = get_columns(filters) + data = get_data(filters) return columns, data -def get_columns(): +def get_columns(filters): columns = [ { "label": _("Material Request Date"), @@ -144,9 +144,22 @@ def get_columns(): ] return columns -def get_data(): - purchase_order_entry = get_po_entries() - mr_records, procurement_record_against_mr = get_mapped_mr_details() +def get_conditions(filters): + conditions = "" + + if filters.get("company"): + conditions += " AND company='%s'"% filters.get('company') + if filters.get("cost_center") or filters.get("project"): + conditions += """ + AND (cost_center='%s' + OR project='%s') + """% (filters.get('cost_center'), filters.get('project')) + return conditions + +def get_data(filters): + conditions = get_conditions(filters) + purchase_order_entry = get_po_entries(conditions) + mr_records, procurement_record_against_mr = get_mapped_mr_details(conditions) pr_records = get_mapped_pr_records() pi_records = get_mapped_pi_records() @@ -157,7 +170,7 @@ def get_data(): # fetch material records linked to the purchase order item mr_record = mr_records.get(po.material_request_item, [{}])[0] procurement_detail = { - "material_request_date": mr_record.get('transaction_date', ''), + "material_request_date": mr_record.get('transaction_date'), "cost_center": po.cost_center, "project": po.project, "requesting_site": po.warehouse, @@ -180,7 +193,7 @@ def get_data(): procurement_record.append(procurement_detail) return procurement_record -def get_mapped_mr_details(): +def get_mapped_mr_details(conditions): mr_records = {} mr_details = frappe.db.sql(""" SELECT @@ -194,7 +207,8 @@ def get_mapped_mr_details(): mr.per_ordered>=0 AND mr.name=mr_item.parent AND mr.docstatus=1 - """, as_dict=1) + {conditions} + """.format(conditions=conditions), as_dict=1) #nosec procurement_record_against_mr = [] for record in mr_details: @@ -232,7 +246,7 @@ def get_mapped_pr_records(): AND pr_item.purchase_order_item IS NOT NULL """)) -def get_po_entries(): +def get_po_entries(conditions): return frappe.db.sql(""" SELECT po_item.name, @@ -257,6 +271,7 @@ def get_po_entries(): po.docstatus = 1 AND po.name = po_item.parent AND po.status not in ("Closed","Completed","Cancelled") + {conditions} GROUP BY po.name,po_item.item_code - """, as_dict=1) \ No newline at end of file + """.format(conditions=conditions), as_dict=1) #nosec \ No newline at end of file diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index 7d8e750c4bc..418396fb92f 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -8,12 +8,33 @@ import frappe from erpnext.buying.report.procurement_tracker.procurement_tracker import execute from erpnext.stock.doctype.material_request.test_material_request import make_material_request from erpnext.stock.doctype.material_request.material_request import make_purchase_order -from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt, make_purchase_invoice +from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt +from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse class TestProcurementTracker(unittest.TestCase): maxDiff = None def test_result_for_procurement_tracker(self): - mr = make_material_request() + filters = { + 'company': '_Test Procurement Company', + 'cost_center': '_Test Cost Center - _TC' + } + expected_data = self.generate_expected_data() + report = execute(filters) + + length = len(report[1]) + self.assertEqual(expected_data, report[1][length-1]) + + def generate_expected_data(self): + if not frappe.db.exists("Company", "_Test Procurement Company"): + frappe.get_doc(dict( + doctype="Company", + company_name="_Test Procurement Company", + abbr="_TPC", + default_currency="INR", + country="India" + )).insert() + warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company") + mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse) po = make_purchase_order(mr.name) po.supplier = "_Test Supplier" po.get("items")[0].cost_center = "_Test Cost Center - _TC" @@ -23,12 +44,11 @@ class TestProcurementTracker(unittest.TestCase): frappe.db.commit() date_obj = datetime.date(datetime.now()) - report = execute() expected_data = { "material_request_date": date_obj, "cost_center": "_Test Cost Center - _TC", "project": None, - "requesting_site": "_Test Warehouse - _TC", + "requesting_site": "_Test Procurement Warehouse - _TPC", "requestor": "Administrator", "material_request_no": mr.name, "description": '_Test Item 1', @@ -45,5 +65,4 @@ class TestProcurementTracker(unittest.TestCase): "expected_delivery_date": date_obj, "actual_delivery_date": date_obj } - length = len(report[1]) - self.assertEqual(expected_data, report[1][length-1]) \ No newline at end of file + return expected_data \ No newline at end of file diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 7dc54d0e332..79cdc1ad18a 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -629,7 +629,8 @@ def make_material_request(**args): "item_code": args.item_code or "_Test Item", "qty": args.qty or 10, "schedule_date": args.schedule_date or today(), - "warehouse": args.warehouse or "_Test Warehouse - _TC" + "warehouse": args.warehouse or "_Test Warehouse - _TC", + "cost_center": args.cost_center or "_Test Cost Center - _TC" }) mr.insert() if not args.do_not_submit: From cc8e2b6aca38719fafa9e84d0af6dc49052e6990 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 1 May 2019 10:03:57 +0530 Subject: [PATCH 10/13] test: pass filters to execute --- .../report/procurement_tracker/procurement_tracker.py | 7 ------- .../report/procurement_tracker/test_procurement_tracker.py | 5 ++--- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index e3361a4c3f5..064bf6fb731 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -53,13 +53,6 @@ def get_columns(filters): "fieldtype": "Link", "width": 140 }, - { - "label": _("Budget Code"), - "options": "Budget", - "fieldname": "budget_code", - "fieldtype": "Link", - "width": 140 - }, { "label": _("Description"), "fieldname": "description", diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index 418396fb92f..4a13f385bb4 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -12,7 +12,6 @@ from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_r from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse class TestProcurementTracker(unittest.TestCase): - maxDiff = None def test_result_for_procurement_tracker(self): filters = { 'company': '_Test Procurement Company', @@ -60,8 +59,8 @@ class TestProcurementTracker(unittest.TestCase): "supplier": "_Test Supplier", "estimated_cost": 0.0, "actual_cost": None, - "purchase_order_amt": 0.0, - "purchase_order_amt_in_company_currency": 0.0, + "purchase_order_amt": 5000.0, + "purchase_order_amt_in_company_currency": 300000.0, "expected_delivery_date": date_obj, "actual_delivery_date": date_obj } From 13d393247a59b01e94060c807b541d64cbd2c611 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 3 May 2019 16:00:02 +0530 Subject: [PATCH 11/13] feat: add date range filters --- .../procurement_tracker/procurement_tracker.js | 17 +++++++++++++---- .../procurement_tracker/procurement_tracker.py | 8 ++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.js b/erpnext/buying/report/procurement_tracker/procurement_tracker.js index 0013666ce89..283d56c9469 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.js +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.js @@ -10,21 +10,30 @@ frappe.query_reports["Procurement Tracker"] = { fieldtype: "Link", options: "Company", default: frappe.defaults.get_user_default("Company"), - reqd: 1 }, { fieldname: "cost_center", label: __("Cost Center"), fieldtype: "Link", options: "Cost Center", - reqd: 1 }, { fieldname: "project", label: __("Project"), fieldtype: "Link", options: "Project", - reqd: 1 - } + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + default: frappe.defaults.get_user_default("year_start_date"), + }, + { + fieldname:"to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.defaults.get_user_default("year_end_date"), + }, ] } diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index 064bf6fb731..7889d95a190 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -142,11 +142,19 @@ def get_conditions(filters): if filters.get("company"): conditions += " AND company='%s'"% filters.get('company') + if filters.get("cost_center") or filters.get("project"): conditions += """ AND (cost_center='%s' OR project='%s') """% (filters.get('cost_center'), filters.get('project')) + + if filters.get("from_date"): + conditions.append("transaction_date>=%s", filters.get('from_date')) + + if filters.get("to_date"): + conditions.append("transaction_date<=%s", filters.get('to_date') + return conditions def get_data(filters): From e003cc881293219affef084ad72f76f7f8e4d26e Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 3 May 2019 16:41:04 +0530 Subject: [PATCH 12/13] fix: filter out completed purchase receipt --- .../procurement_tracker/procurement_tracker.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index 7889d95a190..2654cd49650 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -150,11 +150,10 @@ def get_conditions(filters): """% (filters.get('cost_center'), filters.get('project')) if filters.get("from_date"): - conditions.append("transaction_date>=%s", filters.get('from_date')) + conditions.append("AND transaction_date>=%s", filters.get('from_date')) if filters.get("to_date"): - conditions.append("transaction_date<=%s", filters.get('to_date') - + conditions.append("AND transaction_date<=%s", filters.get('to_date') return conditions def get_data(filters): @@ -227,12 +226,12 @@ def get_mapped_mr_details(conditions): def get_mapped_pi_records(): return frappe._dict(frappe.db.sql(""" SELECT - po_detail, - base_amount - FROM `tabPurchase Invoice Item` + pi_item.po_detail, + pi_item.base_amount + FROM `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pi_item WHERE - docstatus=1 - AND po_detail IS NOT NULL + pi.docstatus=1 + AND pi_item.po_detail IS NOT NULL """)) def get_mapped_pr_records(): @@ -245,6 +244,7 @@ def get_mapped_pr_records(): pr.docstatus=1 AND pr.name=pr_item.parent AND pr_item.purchase_order_item IS NOT NULL + AND pr.status not in ("Closed","Completed","Cancelled") """)) def get_po_entries(conditions): From 09d4933edbe6427ddab608d6307de4fbbb7f21a6 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 6 May 2019 12:53:52 +0530 Subject: [PATCH 13/13] fix: filter pi item based on po completeion --- .../procurement_tracker/procurement_tracker.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index 2654cd49650..d3ee4475450 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -150,10 +150,10 @@ def get_conditions(filters): """% (filters.get('cost_center'), filters.get('project')) if filters.get("from_date"): - conditions.append("AND transaction_date>=%s", filters.get('from_date')) + conditions += "AND transaction_date>=%s"% filters.get('from_date') if filters.get("to_date"): - conditions.append("AND transaction_date<=%s", filters.get('to_date') + conditions += "AND transaction_date<=%s"% filters.get('to_date') return conditions def get_data(filters): @@ -162,6 +162,7 @@ def get_data(filters): mr_records, procurement_record_against_mr = get_mapped_mr_details(conditions) pr_records = get_mapped_pr_records() pi_records = get_mapped_pi_records() + print(pi_records) procurement_record=[] if procurement_record_against_mr: @@ -228,9 +229,12 @@ def get_mapped_pi_records(): SELECT pi_item.po_detail, pi_item.base_amount - FROM `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pi_item + FROM `tabPurchase Invoice Item` as pi_item + INNER JOIN `tabPurchase Order` as po + ON pi_item.`purchase_order` = po.`name` WHERE - pi.docstatus=1 + pi_item.docstatus = 1 + AND po.status not in ("Closed","Completed","Cancelled") AND pi_item.po_detail IS NOT NULL """))