Merge pull request #21273 from marination/quoted-item-report

refactor: Quoted Item Comparison Report
This commit is contained in:
rohitwaghchaure 2020-05-15 13:47:48 +05:30 committed by GitHub
commit 2ee400989b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 234 additions and 96 deletions

View File

@ -18,6 +18,10 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
refresh: function() { refresh: function() {
var me = this; var me = this;
this._super(); this._super();
if (this.frm.doc.__islocal && !this.frm.doc.valid_till) {
this.frm.set_value('valid_till', frappe.datetime.add_months(this.frm.doc.transaction_date, 1));
}
if (this.frm.doc.docstatus === 1) { if (this.frm.doc.docstatus === 1) {
cur_frm.add_custom_button(__("Purchase Order"), this.make_purchase_order, cur_frm.add_custom_button(__("Purchase Order"), this.make_purchase_order,
__('Create')); __('Create'));

View File

@ -13,9 +13,10 @@
"supplier", "supplier",
"supplier_name", "supplier_name",
"column_break1", "column_break1",
"transaction_date",
"amended_from",
"company", "company",
"transaction_date",
"valid_till",
"amended_from",
"address_section", "address_section",
"supplier_address", "supplier_address",
"contact_person", "contact_person",
@ -791,13 +792,18 @@
"options": "Opportunity", "options": "Opportunity",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "valid_till",
"fieldtype": "Date",
"label": "Valid Till"
} }
], ],
"icon": "fa fa-shopping-cart", "icon": "fa fa-shopping-cart",
"idx": 29, "idx": 29,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2019-12-30 19:17:28.208693", "modified": "2020-04-15 11:44:52.958022",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier Quotation", "name": "Supplier Quotation",

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt, nowdate, add_days from frappe.utils import flt, nowdate, add_days, getdate
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.buying_controller import BuyingController from erpnext.controllers.buying_controller import BuyingController
@ -28,6 +28,7 @@ class SupplierQuotation(BuyingController):
validate_for_items(self) validate_for_items(self)
self.validate_with_previous_doc() self.validate_with_previous_doc()
self.validate_uom_is_integer("uom", "qty") self.validate_uom_is_integer("uom", "qty")
self.validate_valid_till()
def on_submit(self): def on_submit(self):
frappe.db.set(self, "status", "Submitted") frappe.db.set(self, "status", "Submitted")
@ -52,6 +53,11 @@ class SupplierQuotation(BuyingController):
"is_child_table": True "is_child_table": True
} }
}) })
def validate_valid_till(self):
if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
frappe.throw(_("Valid till Date cannot be before Transaction Date"))
def update_rfq_supplier_status(self, include_me): def update_rfq_supplier_status(self, include_me):
rfq_list = set([]) rfq_list = set([])
for item in self.items: for item in self.items:
@ -158,3 +164,11 @@ def make_quotation(source_name, target_doc=None):
}, target_doc) }, target_doc)
return doclist return doclist
def set_expired_status():
frappe.db.sql("""
UPDATE
`tabSupplier Quotation` SET `status` = 'Expired'
WHERE
`status` not in ('Cancelled', 'Stopped') AND `valid_till` < %s
""", (nowdate()))

View File

@ -5,6 +5,8 @@ frappe.listview_settings['Supplier Quotation'] = {
return [__("Ordered"), "green", "status,=,Ordered"]; return [__("Ordered"), "green", "status,=,Ordered"];
} else if(doc.status==="Rejected") { } else if(doc.status==="Rejected") {
return [__("Lost"), "darkgrey", "status,=,Lost"]; return [__("Lost"), "darkgrey", "status,=,Lost"];
} else if(doc.status==="Expired") {
return [__("Expired"), "darkgrey", "status,=,Expired"];
} }
} }
}; };

View File

@ -5,20 +5,18 @@ frappe.query_reports["Quoted Item Comparison"] = {
filters: [ filters: [
{ {
fieldtype: "Link", fieldtype: "Link",
label: __("Supplier Quotation"), label: __("Company"),
options: "Supplier Quotation", options: "Company",
fieldname: "supplier_quotation", fieldname: "company",
default: "", default: frappe.defaults.get_user_default("Company"),
get_query: () => { "reqd": 1
return { filters: { "docstatus": ["<", 2] } }
}
}, },
{ {
reqd: 1, reqd: 1,
default: "", default: "",
options: "Item", options: "Item",
label: __("Item"), label: __("Item"),
fieldname: "item", fieldname: "item_code",
fieldtype: "Link", fieldtype: "Link",
get_query: () => { get_query: () => {
let quote = frappe.query_report.get_filter_value('supplier_quotation'); let quote = frappe.query_report.get_filter_value('supplier_quotation');
@ -37,8 +35,37 @@ frappe.query_reports["Quoted Item Comparison"] = {
} }
} }
} }
},
{
fieldname: "supplier",
label: __("Supplier"),
fieldtype: "MultiSelectList",
get_data: function(txt) {
return frappe.db.get_link_options('Supplier', txt);
}
},
{
fieldtype: "Link",
label: __("Supplier Quotation"),
options: "Supplier Quotation",
fieldname: "supplier_quotation",
default: "",
get_query: () => {
return { filters: { "docstatus": ["<", 2] } }
}
},
{
fieldtype: "Link",
label: __("Request for Quotation"),
options: "Request for Quotation",
fieldname: "request_for_quotation",
default: "",
get_query: () => {
return { filters: { "docstatus": ["<", 2] } }
}
} }
], ],
onload: (report) => { onload: (report) => {
// Create a button for setting the default supplier // Create a button for setting the default supplier
report.page.add_inner_button(__("Select Default Supplier"), () => { report.page.add_inner_button(__("Select Default Supplier"), () => {
@ -102,6 +129,4 @@ frappe.query_reports["Quoted Item Comparison"] = {
}); });
dialog.show(); dialog.show();
} }
} }

View File

@ -2,103 +2,180 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
from erpnext.setup.utils import get_exchange_rate
from frappe.utils import flt, cint
import frappe import frappe
from frappe.utils import flt, cint
from frappe import _
from collections import defaultdict
from erpnext.setup.utils import get_exchange_rate
def execute(filters=None): def execute(filters=None):
qty_list = get_quantity_list(filters.item) if not filters:
data = get_quote_list(filters.item, qty_list) return [], []
columns = get_columns(qty_list)
return columns, data conditions = get_conditions(filters)
supplier_quotation_data = get_data(filters, conditions)
def get_quote_list(item, qty_list): columns = get_columns()
out = []
if not item: data, chart_data = prepare_data(supplier_quotation_data)
return columns, data, None, chart_data
def get_conditions(filters):
conditions = ""
if filters.get("supplier_quotation"):
conditions += " AND sqi.parent = %(supplier_quotation)s"
if filters.get("request_for_quotation"):
conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s"
if filters.get("supplier"):
conditions += " AND sq.supplier in %(supplier)s"
return conditions
def get_data(filters, conditions):
if not filters.get("item_code"):
return [] return []
suppliers = [] supplier_quotation_data = frappe.db.sql("""SELECT
price_data = [] sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
company_currency = frappe.db.get_default("currency") sq.supplier
float_precision = cint(frappe.db.get_default("float_precision")) or 2 FROM
# Get the list of suppliers `tabSupplier Quotation Item` sqi,
for root in frappe.db.sql("""select parent, qty, rate from `tabSupplier Quotation Item` `tabSupplier Quotation` sq
where item_code=%s and docstatus < 2""", item, as_dict=1): WHERE
for splr in frappe.db.sql("""select supplier from `tabSupplier Quotation` sqi.item_code = %(item_code)s
where name =%s and docstatus < 2""", root.parent, as_dict=1): AND sqi.parent = sq.name
ip = frappe._dict({ AND sqi.docstatus < 2
"supplier": splr.supplier, AND sq.company = %(company)s
"qty": root.qty, AND sq.status != 'Expired'
"parent": root.parent, {0}""".format(conditions), filters, as_dict=1)
"rate": root.rate
}) return supplier_quotation_data
price_data.append(ip)
suppliers.append(splr.supplier) def prepare_data(supplier_quotation_data):
out, suppliers, qty_list = [], [], []
supplier_wise_map = defaultdict(list)
supplier_qty_price_map = {}
company_currency = frappe.db.get_default("currency")
float_precision = cint(frappe.db.get_default("float_precision")) or 2
for data in supplier_quotation_data:
supplier = data.get("supplier")
supplier_currency = frappe.db.get_value("Supplier", data.get("supplier"), "default_currency")
#Add a row for each supplier
for root in set(suppliers):
supplier_currency = frappe.db.get_value("Supplier", root, "default_currency")
if supplier_currency: if supplier_currency:
exchange_rate = get_exchange_rate(supplier_currency, company_currency) exchange_rate = get_exchange_rate(supplier_currency, company_currency)
else: else:
exchange_rate = 1 exchange_rate = 1
row = frappe._dict({ row = {
"supplier_name": root "quotation": data.get("parent"),
}) "qty": data.get("qty"),
for col in qty_list: "price": flt(data.get("rate") * exchange_rate, float_precision),
# Get the quantity for this row "uom": data.get("uom"),
for item_price in price_data: "request_for_quotation": data.get("request_for_quotation"),
if str(item_price.qty) == col.key and item_price.supplier == root: }
row[col.key] = flt(item_price.rate * exchange_rate, float_precision)
row[col.key + "QUOTE"] = item_price.parent
break
else:
row[col.key] = ""
row[col.key + "QUOTE"] = ""
out.append(row)
return out
def get_quantity_list(item):
out = []
if item:
qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item`
where ifnull(item_code,'')=%s and docstatus < 2 order by qty""", item, as_dict=1)
for qt in qty_list: # map for report view of form {'supplier1':[{},{},...]}
col = frappe._dict({ supplier_wise_map[supplier].append(row)
"key": str(qt.qty),
"label": "Qty: " + str(int(qt.qty))
})
out.append(col)
return out # map for chart preparation of the form {'supplier1': {'qty': 'price'}}
if not supplier in supplier_qty_price_map:
def get_columns(qty_list): supplier_qty_price_map[supplier] = {}
supplier_qty_price_map[supplier][row["qty"]] = row["price"]
suppliers.append(supplier)
qty_list.append(data.get("qty"))
suppliers = list(set(suppliers))
qty_list = list(set(qty_list))
# final data format for report view
for supplier in suppliers:
supplier_wise_map[supplier][0].update({"supplier_name": supplier})
for entry in supplier_wise_map[supplier]:
out.append(entry)
chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
return out, chart_data
def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
data_points_map = {}
qty_list.sort()
# create qty wise values map of the form {'qty1':[value1, value2]}
for supplier in suppliers:
entry = supplier_qty_price_map[supplier]
for qty in qty_list:
if not qty in data_points_map:
data_points_map[qty] = []
if qty in entry:
data_points_map[qty].append(entry[qty])
else:
data_points_map[qty].append(None)
dataset = []
for qty in qty_list:
datapoints = {
"name": _("Price for Qty ") + str(qty),
"values": data_points_map[qty]
}
dataset.append(datapoints)
chart_data = {
"data": {
"labels": suppliers,
"datasets": dataset
},
"type": "bar"
}
return chart_data
def get_columns():
columns = [{ columns = [{
"fieldname": "supplier_name", "fieldname": "supplier_name",
"label": "Supplier", "label": _("Supplier"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Supplier", "options": "Supplier",
"width": 200 "width": 200
}] },
{
for qty in qty_list: "fieldname": "quotation",
columns.append({ "label": _("Supplier Quotation"),
"fieldname": qty.key, "fieldtype": "Link",
"label": qty.label, "options": "Supplier Quotation",
"fieldtype": "Currency", "width": 200
"options": "currency", },
"width": 80 {
}) "fieldname": "qty",
columns.append({ "label": _("Quantity"),
"fieldname": qty.key + "QUOTE", "fieldtype": "Float",
"label": "Quotation", "width": 80
"fieldtype": "Link", },
"options": "Supplier Quotation", {
"width": 90 "fieldname": "price",
}) "label": _("Price"),
"fieldtype": "Currency",
"options": "Company:company:default_currency",
"width": 110
},
{
"fieldname": "uom",
"label": _("UOM"),
"fieldtype": "Link",
"options": "UOM",
"width": 90
},
{
"fieldname": "request_for_quotation",
"label": _("Request for Quotation"),
"fieldtype": "Link",
"options": "Request for Quotation",
"width": 200
}
]
return columns return columns

View File

@ -308,7 +308,8 @@ scheduler_events = {
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts", "erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status", "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
"erpnext.selling.doctype.quotation.quotation.set_expired_status", "erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status" "erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status"
], ],
"daily_long": [ "daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.setup.doctype.email_digest.email_digest.send",

View File

@ -683,3 +683,4 @@ erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashme
execute:frappe.delete_doc_if_exists("Page", "appointment-analytic") execute:frappe.delete_doc_if_exists("Page", "appointment-analytic")
execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True)
erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price
erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation

View File

@ -0,0 +1,8 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("buying", "doctype", "supplier_quotation")
frappe.db.sql("""UPDATE `tabSupplier Quotation`
SET valid_till = DATE_ADD(transaction_date , INTERVAL 1 MONTH)
WHERE docstatus < 2""")