Merge branch 'develop' into refactor/buying/reports
diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
index d70ac46..71019e8 100644
--- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
@@ -127,32 +127,27 @@
 	return columns
 
 
-def get_conditions(filters):
-	conditions = ""
-
+def apply_filters_on_query(filters, parent, child, query):
 	if filters.get("company"):
-		conditions += " AND parent.company=%s" % frappe.db.escape(filters.get("company"))
+		query = query.where(parent.company == filters.get("company"))
 
 	if filters.get("cost_center") or filters.get("project"):
-		conditions += """
-			AND (child.`cost_center`=%s OR child.`project`=%s)
-			""" % (
-			frappe.db.escape(filters.get("cost_center")),
-			frappe.db.escape(filters.get("project")),
+		query = query.where(
+			(child.cost_center == filters.get("cost_center")) | (child.project == filters.get("project"))
 		)
 
 	if filters.get("from_date"):
-		conditions += " AND parent.transaction_date>='%s'" % filters.get("from_date")
+		query = query.where(parent.transaction_date >= filters.get("from_date"))
 
 	if filters.get("to_date"):
-		conditions += " AND parent.transaction_date<='%s'" % filters.get("to_date")
-	return conditions
+		query = query.where(parent.transaction_date <= filters.get("to_date"))
+
+	return query
 
 
 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)
+	purchase_order_entry = get_po_entries(filters)
+	mr_records, procurement_record_against_mr = get_mapped_mr_details(filters)
 	pr_records = get_mapped_pr_records()
 	pi_records = get_mapped_pi_records()
 
@@ -187,11 +182,15 @@
 	return procurement_record
 
 
-def get_mapped_mr_details(conditions):
+def get_mapped_mr_details(filters):
 	mr_records = {}
-	mr_details = frappe.db.sql(
-		"""
-		SELECT
+	parent = frappe.qb.DocType("Material Request")
+	child = frappe.qb.DocType("Material Request Item")
+
+	query = (
+		frappe.qb.from_(parent)
+		.from_(child)
+		.select(
 			parent.transaction_date,
 			parent.per_ordered,
 			parent.owner,
@@ -203,18 +202,13 @@
 			child.uom,
 			parent.status,
 			child.project,
-			child.cost_center
-		FROM `tabMaterial Request` parent, `tabMaterial Request Item` child
-		WHERE
-			parent.per_ordered>=0
-			AND parent.name=child.parent
-			AND parent.docstatus=1
-			{conditions}
-		""".format(
-			conditions=conditions
-		),
-		as_dict=1,
-	)  # nosec
+			child.cost_center,
+		)
+		.where((parent.per_ordered >= 0) & (parent.name == child.parent) & (parent.docstatus == 1))
+	)
+	query = apply_filters_on_query(filters, parent, child, query)
+
+	mr_details = query.run(as_dict=True)
 
 	procurement_record_against_mr = []
 	for record in mr_details:
@@ -241,46 +235,49 @@
 
 
 def get_mapped_pi_records():
-	return frappe._dict(
-		frappe.db.sql(
-			"""
-		SELECT
-			pi_item.po_detail,
-			pi_item.base_amount
-		FROM `tabPurchase Invoice Item` as pi_item
-		INNER JOIN `tabPurchase Order` as po
-		ON pi_item.`purchase_order` = po.`name`
-		WHERE
-			pi_item.docstatus = 1
-			AND po.status not in ('Closed','Completed','Cancelled')
-			AND pi_item.po_detail IS NOT NULL
-		"""
+	po = frappe.qb.DocType("Purchase Order")
+	pi_item = frappe.qb.DocType("Purchase Invoice Item")
+	pi_records = (
+		frappe.qb.from_(pi_item)
+		.inner_join(po)
+		.on(pi_item.purchase_order == po.name)
+		.select(pi_item.po_detail, pi_item.base_amount)
+		.where(
+			(pi_item.docstatus == 1)
+			& (po.status.notin(("Closed", "Completed", "Cancelled")))
+			& (pi_item.po_detail.isnotnull())
 		)
-	)
+	).run()
+
+	return frappe._dict(pi_records)
 
 
 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
-			AND pr.status not in  ('Closed','Completed','Cancelled')
-		"""
+	pr = frappe.qb.DocType("Purchase Receipt")
+	pr_item = frappe.qb.DocType("Purchase Receipt Item")
+	pr_records = (
+		frappe.qb.from_(pr)
+		.from_(pr_item)
+		.select(pr_item.purchase_order_item, pr.posting_date)
+		.where(
+			(pr.docstatus == 1)
+			& (pr.name == pr_item.parent)
+			& (pr_item.purchase_order_item.isnotnull())
+			& (pr.status.notin(("Closed", "Completed", "Cancelled")))
 		)
-	)
+	).run()
+
+	return frappe._dict(pr_records)
 
 
-def get_po_entries(conditions):
-	return frappe.db.sql(
-		"""
-		SELECT
+def get_po_entries(filters):
+	parent = frappe.qb.DocType("Purchase Order")
+	child = frappe.qb.DocType("Purchase Order Item")
+
+	query = (
+		frappe.qb.from_(parent)
+		.from_(child)
+		.select(
 			child.name,
 			child.parent,
 			child.cost_center,
@@ -297,17 +294,15 @@
 			parent.transaction_date,
 			parent.supplier,
 			parent.status,
-			parent.owner
-		FROM `tabPurchase Order` parent, `tabPurchase Order Item` child
-		WHERE
-			parent.docstatus = 1
-			AND parent.name = child.parent
-			AND parent.status not in  ('Closed','Completed','Cancelled')
-			{conditions}
-		GROUP BY
-			parent.name, child.item_code
-		""".format(
-			conditions=conditions
-		),
-		as_dict=1,
-	)  # nosec
+			parent.owner,
+		)
+		.where(
+			(parent.docstatus == 1)
+			& (parent.name == child.parent)
+			& (parent.status.notin(("Closed", "Completed", "Cancelled")))
+		)
+		.groupby(parent.name, child.item_code)
+	)
+	query = apply_filters_on_query(filters, parent, child, query)
+
+	return query.run(as_dict=True)
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
index a5c4649..e10c0e2 100644
--- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
@@ -6,6 +6,7 @@
 
 import frappe
 from frappe import _
+from frappe.query_builder.functions import IfNull
 from frappe.utils import date_diff, flt, getdate
 
 
@@ -16,9 +17,7 @@
 	validate_filters(filters)
 
 	columns = get_columns(filters)
-	conditions = get_conditions(filters)
-
-	data = get_data(conditions, filters)
+	data = get_data(filters)
 
 	if not data:
 		return [], [], None, []
@@ -37,60 +36,61 @@
 		frappe.throw(_("To Date cannot be before From Date."))
 
 
-def get_conditions(filters):
-	conditions = ""
-	if filters.get("from_date") and filters.get("to_date"):
-		conditions += " and po.transaction_date between %(from_date)s and %(to_date)s"
+def get_data(filters):
+	po = frappe.qb.DocType("Purchase Order")
+	po_item = frappe.qb.DocType("Purchase Order Item")
+	pi_item = frappe.qb.DocType("Purchase Invoice Item")
 
-	for field in ["company", "name"]:
+	query = (
+		frappe.qb.from_(po)
+		.from_(po_item)
+		.left_join(pi_item)
+		.on(pi_item.po_detail == po_item.name)
+		.select(
+			po.transaction_date.as_("date"),
+			po_item.schedule_date.as_("required_date"),
+			po_item.project,
+			po.name.as_("purchase_order"),
+			po.status,
+			po.supplier,
+			po_item.item_code,
+			po_item.qty,
+			po_item.received_qty,
+			(po_item.qty - po_item.received_qty).as_("pending_qty"),
+			IfNull(pi_item.qty, 0).as_("billed_qty"),
+			po_item.base_amount.as_("amount"),
+			(po_item.received_qty * po_item.base_rate).as_("received_qty_amount"),
+			(po_item.billed_amt * IfNull(po.conversion_rate, 1)).as_("billed_amount"),
+			(po_item.base_amount - (po_item.billed_amt * IfNull(po.conversion_rate, 1))).as_(
+				"pending_amount"
+			),
+			po.set_warehouse.as_("warehouse"),
+			po.company,
+			po_item.name,
+		)
+		.where(
+			(po_item.parent == po.name) & (po.status.notin(("Stopped", "Closed"))) & (po.docstatus == 1)
+		)
+		.groupby(po_item.name)
+		.orderby(po.transaction_date)
+	)
+
+	for field in ("company", "name"):
 		if filters.get(field):
-			conditions += f" and po.{field} = %({field})s"
+			query = query.where(po[field] == filters.get(field))
+
+	if filters.get("from_date") and filters.get("to_date"):
+		query = query.where(
+			po.transaction_date.between(filters.get("from_date"), filters.get("to_date"))
+		)
 
 	if filters.get("status"):
-		conditions += " and po.status in %(status)s"
+		query = query.where(po.status.isin(filters.get("status")))
 
 	if filters.get("project"):
-		conditions += " and poi.project = %(project)s"
+		query = query.where(po_item.project == filters.get("project"))
 
-	return conditions
-
-
-def get_data(conditions, filters):
-	data = frappe.db.sql(
-		"""
-		SELECT
-			po.transaction_date as date,
-			poi.schedule_date as required_date,
-			poi.project,
-			po.name as purchase_order,
-			po.status, po.supplier, poi.item_code,
-			poi.qty, poi.received_qty,
-			(poi.qty - poi.received_qty) AS pending_qty,
-			IFNULL(pii.qty, 0) as billed_qty,
-			poi.base_amount as amount,
-			(poi.received_qty * poi.base_rate) as received_qty_amount,
-			(poi.billed_amt * IFNULL(po.conversion_rate, 1)) as billed_amount,
-			(poi.base_amount - (poi.billed_amt * IFNULL(po.conversion_rate, 1))) as pending_amount,
-			po.set_warehouse as warehouse,
-			po.company, poi.name
-		FROM
-			`tabPurchase Order` po,
-			`tabPurchase Order Item` poi
-		LEFT JOIN `tabPurchase Invoice Item` pii
-			ON pii.po_detail = poi.name
-		WHERE
-			poi.parent = po.name
-			and po.status not in ('Stopped', 'Closed')
-			and po.docstatus = 1
-			{0}
-		GROUP BY poi.name
-		ORDER BY po.transaction_date ASC
-	""".format(
-			conditions
-		),
-		filters,
-		as_dict=1,
-	)
+	data = query.run(as_dict=True)
 
 	return data
 
diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
index 3013b6d..a728290 100644
--- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
+++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
@@ -16,8 +16,7 @@
 		return [], []
 
 	columns = get_columns(filters)
-	conditions = get_conditions(filters)
-	supplier_quotation_data = get_data(filters, conditions)
+	supplier_quotation_data = get_data(filters)
 
 	data, chart_data = prepare_data(supplier_quotation_data, filters)
 	message = get_message()
@@ -25,50 +24,51 @@
 	return columns, data, message, chart_data
 
 
-def get_conditions(filters):
-	conditions = ""
+def get_data(filters):
+	sq = frappe.qb.DocType("Supplier Quotation")
+	sq_item = frappe.qb.DocType("Supplier Quotation Item")
+
+	query = (
+		frappe.qb.from_(sq_item)
+		.from_(sq)
+		.select(
+			sq_item.parent,
+			sq_item.item_code,
+			sq_item.qty,
+			sq_item.stock_qty,
+			sq_item.amount,
+			sq_item.uom,
+			sq_item.stock_uom,
+			sq_item.request_for_quotation,
+			sq_item.lead_time_days,
+			sq.supplier.as_("supplier_name"),
+			sq.valid_till,
+		)
+		.where(
+			(sq_item.parent == sq.name)
+			& (sq_item.docstatus < 2)
+			& (sq.company == filters.get("company"))
+			& (sq.transaction_date.between(filters.get("from_date"), filters.get("to_date")))
+		)
+		.orderby(sq.transaction_date, sq_item.item_code)
+	)
+
 	if filters.get("item_code"):
-		conditions += " AND sqi.item_code = %(item_code)s"
+		query = query.where(sq_item.item_code == filters.get("item_code"))
 
 	if filters.get("supplier_quotation"):
-		conditions += " AND sqi.parent in %(supplier_quotation)s"
+		query = query.where(sq_item.parent.isin(filters.get("supplier_quotation")))
 
 	if filters.get("request_for_quotation"):
-		conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s"
+		query = query.where(sq_item.request_for_quotation == filters.get("request_for_quotation"))
 
 	if filters.get("supplier"):
-		conditions += " AND sq.supplier in %(supplier)s"
+		query = query.where(sq.supplier.isin(filters.get("supplier")))
 
 	if not filters.get("include_expired"):
-		conditions += " AND sq.status != 'Expired'"
+		query = query.where(sq.status != "Expired")
 
-	return conditions
-
-
-def get_data(filters, conditions):
-	supplier_quotation_data = frappe.db.sql(
-		"""
-		SELECT
-			sqi.parent, sqi.item_code,
-			sqi.qty, sqi.stock_qty, sqi.amount,
-			sqi.uom, sqi.stock_uom,
-			sqi.request_for_quotation,
-			sqi.lead_time_days, sq.supplier as supplier_name, sq.valid_till
-		FROM
-			`tabSupplier Quotation Item` sqi,
-			`tabSupplier Quotation` sq
-		WHERE
-			sqi.parent = sq.name
-			AND sqi.docstatus < 2
-			AND sq.company = %(company)s
-			AND sq.transaction_date between %(from_date)s and %(to_date)s
-			{0}
-			order by sq.transaction_date, sqi.item_code""".format(
-			conditions
-		),
-		filters,
-		as_dict=1,
-	)
+	supplier_quotation_data = query.run(as_dict=True)
 
 	return supplier_quotation_data