mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-08 15:42:52 +00:00
feat: significant enhancements to sales analytics report to support charts
This commit is contained in:
@@ -21,9 +21,17 @@ frappe.query_reports["Sales Analytics"] = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "doc_type",
|
fieldname: "doc_type",
|
||||||
label: __("based_on"),
|
label: __("Based On"),
|
||||||
fieldtype: "Select",
|
fieldtype: "Select",
|
||||||
options: ["Sales Order", "Delivery Note", "Sales Invoice"],
|
options: [
|
||||||
|
"All",
|
||||||
|
"Quotation",
|
||||||
|
"Sales Order",
|
||||||
|
"Delivery Note",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Sales Invoice (due)",
|
||||||
|
"Payment Entry",
|
||||||
|
],
|
||||||
default: "Sales Invoice",
|
default: "Sales Invoice",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
},
|
},
|
||||||
@@ -42,14 +50,18 @@ frappe.query_reports["Sales Analytics"] = {
|
|||||||
fieldname: "from_date",
|
fieldname: "from_date",
|
||||||
label: __("From Date"),
|
label: __("From Date"),
|
||||||
fieldtype: "Date",
|
fieldtype: "Date",
|
||||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
default:
|
||||||
|
frappe.defaults.get_user_default("sales_start_date") ||
|
||||||
|
erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "to_date",
|
fieldname: "to_date",
|
||||||
label: __("To Date"),
|
label: __("To Date"),
|
||||||
fieldtype: "Date",
|
fieldtype: "Date",
|
||||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
default:
|
||||||
|
frappe.defaults.get_user_default("sales_end_date") ||
|
||||||
|
erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -73,6 +85,19 @@ frappe.query_reports["Sales Analytics"] = {
|
|||||||
default: "Monthly",
|
default: "Monthly",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldname: "curves",
|
||||||
|
label: __("Curves"),
|
||||||
|
fieldtype: "Select",
|
||||||
|
options: [
|
||||||
|
{ value: "select", label: __("Select") },
|
||||||
|
{ value: "all", label: __("All") },
|
||||||
|
{ value: "non-zeros", label: __("Non-Zeros") },
|
||||||
|
{ value: "total", label: __("Total Only") },
|
||||||
|
],
|
||||||
|
default: "select",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
get_datatable_options(options) {
|
get_datatable_options(options) {
|
||||||
return Object.assign(options, {
|
return Object.assign(options, {
|
||||||
|
|||||||
@@ -10,17 +10,58 @@ from erpnext.accounts.utils import get_fiscal_year
|
|||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
|
# Special report showing all doctype totals on a single chart; overrides some filters
|
||||||
|
if filters.doc_type == "All":
|
||||||
|
filters.tree_type = "Customer"
|
||||||
|
filters.value_quantity = "Value"
|
||||||
|
filters.curves = "total"
|
||||||
|
output = None
|
||||||
|
for dt in [
|
||||||
|
"Quotation",
|
||||||
|
"Sales Order",
|
||||||
|
"Delivery Note",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Sales Invoice (due)",
|
||||||
|
"Payment Entry",
|
||||||
|
]:
|
||||||
|
filters.doc_type = dt
|
||||||
|
output = append_report(dt, output, Analytics(filters).run())
|
||||||
|
return output
|
||||||
|
else:
|
||||||
return Analytics(filters).run()
|
return Analytics(filters).run()
|
||||||
|
|
||||||
|
|
||||||
|
def append_report(dt, org, new):
|
||||||
|
# idx 1 is data, 3 is chart
|
||||||
|
new[1].insert(0, {"entity": dt}) # heading
|
||||||
|
new[1].append({}) # empty row
|
||||||
|
# datasets can be an empty list if no dates are supplied by the Dashboard Chart
|
||||||
|
if not new[3]["data"]["datasets"]:
|
||||||
|
new[3]["data"]["datasets"].append({"name": None, "values": []})
|
||||||
|
new[3]["data"]["datasets"][0]["name"] = dt # override curve name
|
||||||
|
if org:
|
||||||
|
org[1].extend(new[1])
|
||||||
|
org[3]["data"]["datasets"].extend(new[3]["data"]["datasets"])
|
||||||
|
return org
|
||||||
|
else:
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
class Analytics:
|
class Analytics:
|
||||||
def __init__(self, filters=None):
|
def __init__(self, filters=None):
|
||||||
self.filters = frappe._dict(filters or {})
|
self.filters = frappe._dict(filters or {})
|
||||||
|
if self.filters.doc_type == "Payment Entry" and self.filters.value_quantity == "Quantity":
|
||||||
|
frappe.throw(_("Only Value available for Payment Entry"))
|
||||||
self.date_field = (
|
self.date_field = (
|
||||||
"transaction_date"
|
"transaction_date"
|
||||||
if self.filters.doc_type in ["Sales Order", "Purchase Order"]
|
if self.filters.doc_type in ["Quotation", "Sales Order", "Purchase Order"]
|
||||||
|
else "due_date"
|
||||||
|
if self.filters.doc_type == "Sales Invoice (due)"
|
||||||
else "posting_date"
|
else "posting_date"
|
||||||
)
|
)
|
||||||
|
if self.filters.doc_type.startswith("Sales Invoice"):
|
||||||
|
self.filters.doc_type = "Sales Invoice"
|
||||||
self.months = [
|
self.months = [
|
||||||
"Jan",
|
"Jan",
|
||||||
"Feb",
|
"Feb",
|
||||||
@@ -95,25 +136,37 @@ class Analytics:
|
|||||||
self.get_rows()
|
self.get_rows()
|
||||||
|
|
||||||
elif self.filters.tree_type == "Item":
|
elif self.filters.tree_type == "Item":
|
||||||
|
if self.filters.doc_type == "Payment Entry":
|
||||||
|
self.data = []
|
||||||
|
return
|
||||||
self.get_sales_transactions_based_on_items()
|
self.get_sales_transactions_based_on_items()
|
||||||
self.get_rows()
|
self.get_rows()
|
||||||
|
|
||||||
elif self.filters.tree_type in ["Customer Group", "Supplier Group", "Territory"]:
|
elif self.filters.tree_type in ["Customer Group", "Supplier Group", "Territory"]:
|
||||||
|
if self.filters.doc_type == "Payment Entry":
|
||||||
|
self.data = []
|
||||||
|
return
|
||||||
self.get_sales_transactions_based_on_customer_or_territory_group()
|
self.get_sales_transactions_based_on_customer_or_territory_group()
|
||||||
self.get_rows_by_group()
|
self.get_rows_by_group()
|
||||||
|
|
||||||
elif self.filters.tree_type == "Item Group":
|
elif self.filters.tree_type == "Item Group":
|
||||||
|
if self.filters.doc_type == "Payment Entry":
|
||||||
|
self.data = []
|
||||||
|
return
|
||||||
self.get_sales_transactions_based_on_item_group()
|
self.get_sales_transactions_based_on_item_group()
|
||||||
self.get_rows_by_group()
|
self.get_rows_by_group()
|
||||||
|
|
||||||
elif self.filters.tree_type == "Order Type":
|
elif self.filters.tree_type == "Order Type":
|
||||||
if self.filters.doc_type != "Sales Order":
|
if self.filters.doc_type not in ["Quotation", "Sales Order"]:
|
||||||
self.data = []
|
self.data = []
|
||||||
return
|
return
|
||||||
self.get_sales_transactions_based_on_order_type()
|
self.get_sales_transactions_based_on_order_type()
|
||||||
self.get_rows_by_group()
|
self.get_rows_by_group()
|
||||||
|
|
||||||
elif self.filters.tree_type == "Project":
|
elif self.filters.tree_type == "Project":
|
||||||
|
if self.filters.doc_type == "Quotation":
|
||||||
|
self.data = []
|
||||||
|
return
|
||||||
self.get_sales_transactions_based_on_project()
|
self.get_sales_transactions_based_on_project()
|
||||||
self.get_rows()
|
self.get_rows()
|
||||||
|
|
||||||
@@ -141,11 +194,22 @@ class Analytics:
|
|||||||
value_field = "total_qty as value_field"
|
value_field = "total_qty as value_field"
|
||||||
|
|
||||||
if self.filters.tree_type == "Customer":
|
if self.filters.tree_type == "Customer":
|
||||||
entity = "customer as entity"
|
|
||||||
entity_name = "customer_name as entity_name"
|
entity_name = "customer_name as entity_name"
|
||||||
|
if self.filters.doc_type == "Quotation":
|
||||||
|
entity = "party_name as entity"
|
||||||
|
elif self.filters.doc_type == "Payment Entry":
|
||||||
|
entity = "party as entity"
|
||||||
|
entity_name = "party_name as entity_name"
|
||||||
|
value_field = "base_received_amount as value_field"
|
||||||
|
else:
|
||||||
|
entity = "customer as entity"
|
||||||
else:
|
else:
|
||||||
entity = "supplier as entity"
|
entity = "supplier as entity"
|
||||||
entity_name = "supplier_name as entity_name"
|
entity_name = "supplier_name as entity_name"
|
||||||
|
if self.filters.doc_type == "Payment Entry":
|
||||||
|
entity = "party as entity"
|
||||||
|
entity_name = "party_name as entity_name"
|
||||||
|
value_field = "base_paid_amount as value_field"
|
||||||
|
|
||||||
self.entries = frappe.get_all(
|
self.entries = frappe.get_all(
|
||||||
self.filters.doc_type,
|
self.filters.doc_type,
|
||||||
@@ -232,6 +296,9 @@ class Analytics:
|
|||||||
else:
|
else:
|
||||||
value_field = "total_qty as value_field"
|
value_field = "total_qty as value_field"
|
||||||
|
|
||||||
|
if self.filters.doc_type == "Payment Entry":
|
||||||
|
value_field = "base_received_amount as value_field"
|
||||||
|
|
||||||
entity = "project as entity"
|
entity = "project as entity"
|
||||||
|
|
||||||
self.entries = frappe.get_all(
|
self.entries = frappe.get_all(
|
||||||
@@ -401,7 +468,33 @@ class Analytics:
|
|||||||
labels = [d.get("label") for d in self.columns[3 : length - 1]]
|
labels = [d.get("label") for d in self.columns[3 : length - 1]]
|
||||||
else:
|
else:
|
||||||
labels = [d.get("label") for d in self.columns[1 : length - 1]]
|
labels = [d.get("label") for d in self.columns[1 : length - 1]]
|
||||||
self.chart = {"data": {"labels": labels, "datasets": []}, "type": "line"}
|
|
||||||
|
datasets = []
|
||||||
|
if self.filters.curves != "select":
|
||||||
|
for curve in self.data:
|
||||||
|
data = {
|
||||||
|
"name": curve.get("entity_name", curve["entity"]),
|
||||||
|
"values": [curve[scrub(label)] for label in labels],
|
||||||
|
}
|
||||||
|
if self.filters.curves == "non-zeros" and not sum(data["values"]):
|
||||||
|
continue
|
||||||
|
elif self.filters.curves == "total" and "indent" in curve:
|
||||||
|
if curve["indent"] == 0:
|
||||||
|
datasets.append(data)
|
||||||
|
elif self.filters.curves == "total":
|
||||||
|
if datasets:
|
||||||
|
a = [
|
||||||
|
data["values"][idx] + datasets[0]["values"][idx]
|
||||||
|
for idx in range(len(data["values"]))
|
||||||
|
]
|
||||||
|
datasets[0]["values"] = a
|
||||||
|
else:
|
||||||
|
datasets.append(data)
|
||||||
|
datasets[0]["name"] = _("Total")
|
||||||
|
else:
|
||||||
|
datasets.append(data)
|
||||||
|
|
||||||
|
self.chart = {"data": {"labels": labels, "datasets": datasets}, "type": "line"}
|
||||||
|
|
||||||
if self.filters["value_quantity"] == "Value":
|
if self.filters["value_quantity"] == "Value":
|
||||||
self.chart["fieldtype"] = "Currency"
|
self.chart["fieldtype"] = "Currency"
|
||||||
|
|||||||
Reference in New Issue
Block a user