2018-11-12 10:45:54 +00:00
|
|
|
# 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 _, scrub
|
2019-01-04 18:04:54 +00:00
|
|
|
from frappe.utils import getdate, flt, add_to_date, add_days
|
2018-11-12 10:45:54 +00:00
|
|
|
from six import iteritems
|
|
|
|
from erpnext.accounts.utils import get_fiscal_year
|
|
|
|
|
|
|
|
def execute(filters=None):
|
|
|
|
return Analytics(filters).run()
|
|
|
|
|
|
|
|
class Analytics(object):
|
|
|
|
def __init__(self, filters=None):
|
|
|
|
self.filters = frappe._dict(filters or {})
|
|
|
|
self.date_field = 'transaction_date' \
|
|
|
|
if self.filters.doc_type in ['Sales Order', 'Purchase Order'] else 'posting_date'
|
|
|
|
self.months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
|
|
|
self.get_period_date_ranges()
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
self.get_columns()
|
|
|
|
self.get_data()
|
|
|
|
self.get_chart_data()
|
2019-06-24 11:29:14 +00:00
|
|
|
return self.columns, self.data, None, self.chart
|
2018-11-12 10:45:54 +00:00
|
|
|
|
|
|
|
def get_columns(self):
|
2019-06-24 11:29:14 +00:00
|
|
|
self.columns = [{
|
2018-11-12 10:45:54 +00:00
|
|
|
"label": _(self.filters.tree_type + " ID"),
|
2019-06-24 11:29:14 +00:00
|
|
|
"options": self.filters.tree_type if self.filters.tree_type != "Order Type" else "",
|
2018-11-12 10:45:54 +00:00
|
|
|
"fieldname": "entity",
|
2019-06-24 11:29:14 +00:00
|
|
|
"fieldtype": "Link" if self.filters.tree_type != "Order Type" else "Data",
|
|
|
|
"width": 140 if self.filters.tree_type != "Order Type" else 200
|
2018-11-12 10:45:54 +00:00
|
|
|
}]
|
|
|
|
if self.filters.tree_type in ["Customer", "Supplier", "Item"]:
|
|
|
|
self.columns.append({
|
|
|
|
"label": _(self.filters.tree_type + " Name"),
|
|
|
|
"fieldname": "entity_name",
|
|
|
|
"fieldtype": "Data",
|
|
|
|
"width": 140
|
|
|
|
})
|
2019-01-04 07:47:31 +00:00
|
|
|
for end_date in self.periodic_daterange:
|
2018-11-12 10:45:54 +00:00
|
|
|
period = self.get_period(end_date)
|
|
|
|
self.columns.append({
|
|
|
|
"label": _(period),
|
|
|
|
"fieldname": scrub(period),
|
|
|
|
"fieldtype": "Float",
|
|
|
|
"width": 120
|
|
|
|
})
|
|
|
|
|
|
|
|
self.columns.append({
|
|
|
|
"label": _("Total"),
|
|
|
|
"fieldname": "total",
|
|
|
|
"fieldtype": "Float",
|
|
|
|
"width": 120
|
|
|
|
})
|
|
|
|
|
|
|
|
def get_data(self):
|
|
|
|
if self.filters.tree_type in ["Customer", "Supplier"]:
|
|
|
|
self.get_sales_transactions_based_on_customers_or_suppliers()
|
|
|
|
self.get_rows()
|
|
|
|
|
|
|
|
elif self.filters.tree_type == 'Item':
|
|
|
|
self.get_sales_transactions_based_on_items()
|
|
|
|
self.get_rows()
|
|
|
|
|
|
|
|
elif self.filters.tree_type in ["Customer Group", "Supplier Group", "Territory"]:
|
|
|
|
self.get_sales_transactions_based_on_customer_or_territory_group()
|
|
|
|
self.get_rows_by_group()
|
|
|
|
|
|
|
|
elif self.filters.tree_type == 'Item Group':
|
|
|
|
self.get_sales_transactions_based_on_item_group()
|
|
|
|
self.get_rows_by_group()
|
|
|
|
|
2019-06-24 11:29:14 +00:00
|
|
|
elif self.filters.tree_type == "Order Type":
|
|
|
|
if self.filters.doc_type != "Sales Order":
|
|
|
|
self.data = []
|
|
|
|
return
|
|
|
|
self.get_sales_transactions_based_on_order_type()
|
|
|
|
self.get_rows_by_group()
|
|
|
|
|
|
|
|
def get_sales_transactions_based_on_order_type(self):
|
|
|
|
if self.filters["value_quantity"] == 'Value':
|
|
|
|
value_field = "base_net_total"
|
|
|
|
else:
|
|
|
|
value_field = "total_qty"
|
|
|
|
|
|
|
|
self.entries = frappe.db.sql(""" select s.order_type as entity, s.{value_field} as value_field, s.{date_field}
|
|
|
|
from `tab{doctype}` s where s.docstatus = 1 and s.company = %s and s.{date_field} between %s and %s
|
|
|
|
and ifnull(s.order_type, '') != '' order by s.order_type
|
|
|
|
"""
|
|
|
|
.format(date_field=self.date_field, value_field=value_field, doctype=self.filters.doc_type),
|
|
|
|
(self.filters.company, self.filters.from_date, self.filters.to_date), as_dict=1)
|
|
|
|
|
|
|
|
self.get_teams()
|
|
|
|
|
2018-11-12 10:45:54 +00:00
|
|
|
def get_sales_transactions_based_on_customers_or_suppliers(self):
|
|
|
|
if self.filters["value_quantity"] == 'Value':
|
|
|
|
value_field = "base_net_total as value_field"
|
|
|
|
else:
|
|
|
|
value_field = "total_qty as value_field"
|
|
|
|
|
|
|
|
if self.filters.tree_type == 'Customer':
|
|
|
|
entity = "customer as entity"
|
|
|
|
entity_name = "customer_name as entity_name"
|
|
|
|
else:
|
|
|
|
entity = "supplier as entity"
|
|
|
|
entity_name = "supplier_name as entity_name"
|
|
|
|
|
|
|
|
self.entries = frappe.get_all(self.filters.doc_type,
|
|
|
|
fields=[entity, entity_name, value_field, self.date_field],
|
2019-06-24 11:29:14 +00:00
|
|
|
filters={
|
2018-11-12 10:45:54 +00:00
|
|
|
"docstatus": 1,
|
|
|
|
"company": self.filters.company,
|
|
|
|
self.date_field: ('between', [self.filters.from_date, self.filters.to_date])
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
self.entity_names = {}
|
|
|
|
for d in self.entries:
|
|
|
|
self.entity_names.setdefault(d.entity, d.entity_name)
|
|
|
|
|
|
|
|
def get_sales_transactions_based_on_items(self):
|
|
|
|
|
|
|
|
if self.filters["value_quantity"] == 'Value':
|
|
|
|
value_field = 'base_amount'
|
|
|
|
else:
|
|
|
|
value_field = 'qty'
|
|
|
|
|
|
|
|
self.entries = frappe.db.sql("""
|
|
|
|
select i.item_code as entity, i.item_name as entity_name, i.{value_field} as value_field, s.{date_field}
|
|
|
|
from `tab{doctype} Item` i , `tab{doctype}` s
|
|
|
|
where s.name = i.parent and i.docstatus = 1 and s.company = %s
|
|
|
|
and s.{date_field} between %s and %s
|
|
|
|
"""
|
2019-06-24 11:29:14 +00:00
|
|
|
.format(date_field=self.date_field, value_field=value_field, doctype=self.filters.doc_type),
|
2018-11-12 10:45:54 +00:00
|
|
|
(self.filters.company, self.filters.from_date, self.filters.to_date), as_dict=1)
|
|
|
|
|
|
|
|
self.entity_names = {}
|
|
|
|
for d in self.entries:
|
|
|
|
self.entity_names.setdefault(d.entity, d.entity_name)
|
|
|
|
|
|
|
|
def get_sales_transactions_based_on_customer_or_territory_group(self):
|
|
|
|
if self.filters["value_quantity"] == 'Value':
|
|
|
|
value_field = "base_net_total as value_field"
|
|
|
|
else:
|
|
|
|
value_field = "total_qty as value_field"
|
|
|
|
|
|
|
|
if self.filters.tree_type == 'Customer Group':
|
|
|
|
entity_field = 'customer_group as entity'
|
|
|
|
elif self.filters.tree_type == 'Supplier Group':
|
|
|
|
entity_field = "supplier as entity"
|
|
|
|
self.get_supplier_parent_child_map()
|
|
|
|
else:
|
|
|
|
entity_field = "territory as entity"
|
|
|
|
|
|
|
|
self.entries = frappe.get_all(self.filters.doc_type,
|
|
|
|
fields=[entity_field, value_field, self.date_field],
|
2019-06-24 11:29:14 +00:00
|
|
|
filters={
|
2018-11-12 10:45:54 +00:00
|
|
|
"docstatus": 1,
|
|
|
|
"company": self.filters.company,
|
|
|
|
self.date_field: ('between', [self.filters.from_date, self.filters.to_date])
|
|
|
|
}
|
|
|
|
)
|
|
|
|
self.get_groups()
|
|
|
|
|
|
|
|
def get_sales_transactions_based_on_item_group(self):
|
|
|
|
if self.filters["value_quantity"] == 'Value':
|
|
|
|
value_field = "base_amount"
|
|
|
|
else:
|
|
|
|
value_field = "qty"
|
|
|
|
|
|
|
|
self.entries = frappe.db.sql("""
|
|
|
|
select i.item_group as entity, i.{value_field} as value_field, s.{date_field}
|
|
|
|
from `tab{doctype} Item` i , `tab{doctype}` s
|
|
|
|
where s.name = i.parent and i.docstatus = 1 and s.company = %s
|
|
|
|
and s.{date_field} between %s and %s
|
2019-06-24 11:29:14 +00:00
|
|
|
""".format(date_field=self.date_field, value_field=value_field, doctype=self.filters.doc_type),
|
2018-11-12 10:45:54 +00:00
|
|
|
(self.filters.company, self.filters.from_date, self.filters.to_date), as_dict=1)
|
|
|
|
|
|
|
|
self.get_groups()
|
|
|
|
|
|
|
|
def get_rows(self):
|
2019-06-24 11:29:14 +00:00
|
|
|
self.data = []
|
2018-11-12 10:45:54 +00:00
|
|
|
self.get_periodic_data()
|
|
|
|
|
|
|
|
for entity, period_data in iteritems(self.entity_periodic_data):
|
|
|
|
row = {
|
|
|
|
"entity": entity,
|
2018-11-26 11:22:15 +00:00
|
|
|
"entity_name": self.entity_names.get(entity)
|
2018-11-12 10:45:54 +00:00
|
|
|
}
|
|
|
|
total = 0
|
2019-01-04 07:47:31 +00:00
|
|
|
for end_date in self.periodic_daterange:
|
2018-11-12 10:45:54 +00:00
|
|
|
period = self.get_period(end_date)
|
|
|
|
amount = flt(period_data.get(period, 0.0))
|
|
|
|
row[scrub(period)] = amount
|
|
|
|
total += amount
|
|
|
|
|
|
|
|
row["total"] = total
|
|
|
|
self.data.append(row)
|
2018-11-26 11:22:15 +00:00
|
|
|
|
2018-11-12 10:45:54 +00:00
|
|
|
def get_rows_by_group(self):
|
|
|
|
self.get_periodic_data()
|
|
|
|
out = []
|
|
|
|
|
|
|
|
for d in reversed(self.group_entries):
|
|
|
|
row = {
|
|
|
|
"entity": d.name,
|
2018-11-26 11:22:15 +00:00
|
|
|
"indent": self.depth_map.get(d.name)
|
2018-11-12 10:45:54 +00:00
|
|
|
}
|
|
|
|
total = 0
|
2019-01-04 07:47:31 +00:00
|
|
|
for end_date in self.periodic_daterange:
|
2018-11-12 10:45:54 +00:00
|
|
|
period = self.get_period(end_date)
|
|
|
|
amount = flt(self.entity_periodic_data.get(d.name, {}).get(period, 0.0))
|
|
|
|
row[scrub(period)] = amount
|
2019-06-24 11:29:14 +00:00
|
|
|
if d.parent and (self.filters.tree_type != "Order Type" or d.parent == "Order Types"):
|
2018-11-12 10:45:54 +00:00
|
|
|
self.entity_periodic_data.setdefault(d.parent, frappe._dict()).setdefault(period, 0.0)
|
|
|
|
self.entity_periodic_data[d.parent][period] += amount
|
|
|
|
total += amount
|
|
|
|
row["total"] = total
|
|
|
|
out = [row] + out
|
|
|
|
self.data = out
|
|
|
|
|
|
|
|
def get_periodic_data(self):
|
|
|
|
self.entity_periodic_data = frappe._dict()
|
|
|
|
|
|
|
|
for d in self.entries:
|
|
|
|
if self.filters.tree_type == "Supplier Group":
|
|
|
|
d.entity = self.parent_child_map.get(d.entity)
|
|
|
|
period = self.get_period(d.get(self.date_field))
|
|
|
|
self.entity_periodic_data.setdefault(d.entity, frappe._dict()).setdefault(period, 0.0)
|
|
|
|
self.entity_periodic_data[d.entity][period] += flt(d.value_field)
|
|
|
|
|
|
|
|
def get_period(self, posting_date):
|
|
|
|
if self.filters.range == 'Weekly':
|
2018-11-29 03:04:47 +00:00
|
|
|
period = "Week " + str(posting_date.isocalendar()[1]) + " " + str(posting_date.year)
|
2018-11-12 10:45:54 +00:00
|
|
|
elif self.filters.range == 'Monthly':
|
2018-11-29 03:04:47 +00:00
|
|
|
period = str(self.months[posting_date.month - 1]) + " " + str(posting_date.year)
|
2018-11-12 10:45:54 +00:00
|
|
|
elif self.filters.range == 'Quarterly':
|
2019-06-24 11:29:14 +00:00
|
|
|
period = "Quarter " + str(((posting_date.month - 1) // 3) + 1) + " " + str(posting_date.year)
|
2018-11-12 10:45:54 +00:00
|
|
|
else:
|
|
|
|
year = get_fiscal_year(posting_date, company=self.filters.company)
|
2019-01-04 18:04:54 +00:00
|
|
|
period = str(year[0])
|
2018-11-12 10:45:54 +00:00
|
|
|
return period
|
|
|
|
|
|
|
|
def get_period_date_ranges(self):
|
2019-01-04 18:04:54 +00:00
|
|
|
from dateutil.relativedelta import relativedelta, MO
|
2018-11-12 10:45:54 +00:00
|
|
|
from_date, to_date = getdate(self.filters.from_date), getdate(self.filters.to_date)
|
|
|
|
|
|
|
|
increment = {
|
|
|
|
"Monthly": 1,
|
|
|
|
"Quarterly": 3,
|
|
|
|
"Half-Yearly": 6,
|
|
|
|
"Yearly": 12
|
|
|
|
}.get(self.filters.range, 1)
|
|
|
|
|
2019-01-04 18:04:54 +00:00
|
|
|
if self.filters.range in ['Monthly', 'Quarterly']:
|
2019-06-24 11:29:14 +00:00
|
|
|
from_date = from_date.replace(day=1)
|
2019-01-04 18:04:54 +00:00
|
|
|
elif self.filters.range == "Yearly":
|
|
|
|
from_date = get_fiscal_year(from_date)[1]
|
|
|
|
else:
|
|
|
|
from_date = from_date + relativedelta(from_date, weekday=MO(-1))
|
|
|
|
|
2018-11-12 10:45:54 +00:00
|
|
|
self.periodic_daterange = []
|
2019-01-04 06:37:17 +00:00
|
|
|
for dummy in range(1, 53):
|
2018-11-12 10:45:54 +00:00
|
|
|
if self.filters.range == "Weekly":
|
2019-01-04 18:04:54 +00:00
|
|
|
period_end_date = add_days(from_date, 6)
|
2018-11-12 10:45:54 +00:00
|
|
|
else:
|
2019-01-04 18:04:54 +00:00
|
|
|
period_end_date = add_to_date(from_date, months=increment, days=-1)
|
2018-11-12 10:45:54 +00:00
|
|
|
|
|
|
|
if period_end_date > to_date:
|
|
|
|
period_end_date = to_date
|
|
|
|
|
2019-01-04 07:47:31 +00:00
|
|
|
self.periodic_daterange.append(period_end_date)
|
2019-01-04 18:04:54 +00:00
|
|
|
|
|
|
|
from_date = add_days(period_end_date, 1)
|
2018-11-12 10:45:54 +00:00
|
|
|
if period_end_date == to_date:
|
|
|
|
break
|
|
|
|
|
|
|
|
def get_groups(self):
|
|
|
|
if self.filters.tree_type == "Territory":
|
|
|
|
parent = 'parent_territory'
|
|
|
|
if self.filters.tree_type == "Customer Group":
|
|
|
|
parent = 'parent_customer_group'
|
|
|
|
if self.filters.tree_type == "Item Group":
|
|
|
|
parent = 'parent_item_group'
|
|
|
|
if self.filters.tree_type == "Supplier Group":
|
|
|
|
parent = 'parent_supplier_group'
|
|
|
|
|
|
|
|
self.depth_map = frappe._dict()
|
|
|
|
|
|
|
|
self.group_entries = frappe.db.sql("""select name, lft, rgt , {parent} as parent
|
|
|
|
from `tab{tree}` order by lft"""
|
2019-06-24 11:29:14 +00:00
|
|
|
.format(tree=self.filters.tree_type, parent=parent), as_dict=1)
|
|
|
|
|
|
|
|
for d in self.group_entries:
|
|
|
|
if d.parent:
|
|
|
|
self.depth_map.setdefault(d.name, self.depth_map.get(d.parent) + 1)
|
|
|
|
else:
|
|
|
|
self.depth_map.setdefault(d.name, 0)
|
|
|
|
|
|
|
|
def get_teams(self):
|
|
|
|
self.depth_map = frappe._dict()
|
|
|
|
|
|
|
|
self.group_entries = frappe.db.sql(""" select * from (select "Order Types" as name, 0 as lft,
|
|
|
|
2 as rgt, '' as parent union select distinct order_type as name, 1 as lft, 1 as rgt, "Order Types" as parent
|
|
|
|
from `tab{doctype}` where ifnull(order_type, '') != '') as b order by lft, name
|
|
|
|
"""
|
|
|
|
.format(doctype=self.filters.doc_type), as_dict=1)
|
2018-11-12 10:45:54 +00:00
|
|
|
|
|
|
|
for d in self.group_entries:
|
|
|
|
if d.parent:
|
|
|
|
self.depth_map.setdefault(d.name, self.depth_map.get(d.parent) + 1)
|
|
|
|
else:
|
|
|
|
self.depth_map.setdefault(d.name, 0)
|
|
|
|
|
|
|
|
def get_supplier_parent_child_map(self):
|
|
|
|
self.parent_child_map = frappe._dict(frappe.db.sql(""" select name, supplier_group from `tabSupplier`"""))
|
|
|
|
|
|
|
|
def get_chart_data(self):
|
2018-11-26 11:22:15 +00:00
|
|
|
length = len(self.columns)
|
2018-12-15 10:06:09 +00:00
|
|
|
|
|
|
|
if self.filters.tree_type in ["Customer", "Supplier", "Item"]:
|
2019-06-24 11:29:14 +00:00
|
|
|
labels = [d.get("label") for d in self.columns[2:length - 1]]
|
2018-12-15 10:06:09 +00:00
|
|
|
else:
|
2019-06-24 11:29:14 +00:00
|
|
|
labels = [d.get("label") for d in self.columns[1:length - 1]]
|
2018-11-12 10:45:54 +00:00
|
|
|
self.chart = {
|
|
|
|
"data": {
|
|
|
|
'labels': labels,
|
2019-06-24 11:29:14 +00:00
|
|
|
'datasets': []
|
2018-11-12 10:45:54 +00:00
|
|
|
},
|
|
|
|
"type": "line"
|
2019-06-24 11:29:14 +00:00
|
|
|
}
|