|
|
@ -8,179 +8,180 @@ from frappe import _ |
|
|
|
from frappe.utils import cint, cstr |
|
|
|
|
|
|
|
def execute(filters=None): |
|
|
|
common_columns = [ |
|
|
|
{ |
|
|
|
'label': _('New Customers'), |
|
|
|
'fieldname': 'new_customers', |
|
|
|
'fieldtype': 'Int', |
|
|
|
'default': 0, |
|
|
|
'width': 125 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('Repeat Customers'), |
|
|
|
'fieldname': 'repeat_customers', |
|
|
|
'fieldtype': 'Int', |
|
|
|
'default': 0, |
|
|
|
'width': 125 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('Total'), |
|
|
|
'fieldname': 'total', |
|
|
|
'fieldtype': 'Int', |
|
|
|
'default': 0, |
|
|
|
'width': 100 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('New Customer Revenue'), |
|
|
|
'fieldname': 'new_customer_revenue', |
|
|
|
'fieldtype': 'Currency', |
|
|
|
'default': 0.0, |
|
|
|
'width': 175 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('Repeat Customer Revenue'), |
|
|
|
'fieldname': 'repeat_customer_revenue', |
|
|
|
'fieldtype': 'Currency', |
|
|
|
'default': 0.0, |
|
|
|
'width': 175 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('Total Revenue'), |
|
|
|
'fieldname': 'total_revenue', |
|
|
|
'fieldtype': 'Currency', |
|
|
|
'default': 0.0, |
|
|
|
'width': 175 |
|
|
|
} |
|
|
|
] |
|
|
|
if filters.get('view_type') == 'Monthly': |
|
|
|
return get_data_by_time(filters, common_columns) |
|
|
|
else: |
|
|
|
return get_data_by_territory(filters, common_columns) |
|
|
|
common_columns = [ |
|
|
|
{ |
|
|
|
'label': _('New Customers'), |
|
|
|
'fieldname': 'new_customers', |
|
|
|
'fieldtype': 'Int', |
|
|
|
'default': 0, |
|
|
|
'width': 125 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('Repeat Customers'), |
|
|
|
'fieldname': 'repeat_customers', |
|
|
|
'fieldtype': 'Int', |
|
|
|
'default': 0, |
|
|
|
'width': 125 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('Total'), |
|
|
|
'fieldname': 'total', |
|
|
|
'fieldtype': 'Int', |
|
|
|
'default': 0, |
|
|
|
'width': 100 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('New Customer Revenue'), |
|
|
|
'fieldname': 'new_customer_revenue', |
|
|
|
'fieldtype': 'Currency', |
|
|
|
'default': 0.0, |
|
|
|
'width': 175 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('Repeat Customer Revenue'), |
|
|
|
'fieldname': 'repeat_customer_revenue', |
|
|
|
'fieldtype': 'Currency', |
|
|
|
'default': 0.0, |
|
|
|
'width': 175 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('Total Revenue'), |
|
|
|
'fieldname': 'total_revenue', |
|
|
|
'fieldtype': 'Currency', |
|
|
|
'default': 0.0, |
|
|
|
'width': 175 |
|
|
|
} |
|
|
|
] |
|
|
|
if filters.get('view_type') == 'Monthly': |
|
|
|
return get_data_by_time(filters, common_columns) |
|
|
|
else: |
|
|
|
return get_data_by_territory(filters, common_columns) |
|
|
|
|
|
|
|
def get_data_by_time(filters, common_columns): |
|
|
|
# key yyyy-mm |
|
|
|
columns = [ |
|
|
|
{ |
|
|
|
'label': _('Year'), |
|
|
|
'fieldname': 'year', |
|
|
|
'fieldtype': 'Data', |
|
|
|
'width': 100 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('Month'), |
|
|
|
'fieldname': 'month', |
|
|
|
'fieldtype': 'Data', |
|
|
|
'width': 100 |
|
|
|
}, |
|
|
|
] |
|
|
|
columns += common_columns |
|
|
|
|
|
|
|
customers_in = get_customer_stats(filters) |
|
|
|
|
|
|
|
# time series |
|
|
|
from_year, from_month, temp = filters.get('from_date').split('-') |
|
|
|
to_year, to_month, temp = filters.get('to_date').split('-') |
|
|
|
|
|
|
|
from_year, from_month, to_year, to_month = \ |
|
|
|
cint(from_year), cint(from_month), cint(to_year), cint(to_month) |
|
|
|
|
|
|
|
out = [] |
|
|
|
for year in range(from_year, to_year+1): |
|
|
|
for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13): |
|
|
|
key = '{year}-{month:02d}'.format(year=year, month=month) |
|
|
|
data = customers_in.get(key) |
|
|
|
new = data['new'] if data else [0, 0.0] |
|
|
|
repeat = data['repeat'] if data else [0, 0.0] |
|
|
|
out.append({ |
|
|
|
'year': cstr(year), |
|
|
|
'month': calendar.month_name[month], |
|
|
|
'new_customers': new[0], |
|
|
|
'repeat_customers': repeat[0], |
|
|
|
'total': new[0] + repeat[0], |
|
|
|
'new_customer_revenue': new[1], |
|
|
|
'repeat_customer_revenue': repeat[1], |
|
|
|
'total_revenue': new[1] + repeat[1] |
|
|
|
}) |
|
|
|
return columns, out |
|
|
|
# key yyyy-mm |
|
|
|
columns = [ |
|
|
|
{ |
|
|
|
'label': _('Year'), |
|
|
|
'fieldname': 'year', |
|
|
|
'fieldtype': 'Data', |
|
|
|
'width': 100 |
|
|
|
}, |
|
|
|
{ |
|
|
|
'label': _('Month'), |
|
|
|
'fieldname': 'month', |
|
|
|
'fieldtype': 'Data', |
|
|
|
'width': 100 |
|
|
|
}, |
|
|
|
] |
|
|
|
columns += common_columns |
|
|
|
|
|
|
|
customers_in = get_customer_stats(filters) |
|
|
|
|
|
|
|
# time series |
|
|
|
from_year, from_month, temp = filters.get('from_date').split('-') |
|
|
|
to_year, to_month, temp = filters.get('to_date').split('-') |
|
|
|
|
|
|
|
from_year, from_month, to_year, to_month = \ |
|
|
|
cint(from_year), cint(from_month), cint(to_year), cint(to_month) |
|
|
|
|
|
|
|
out = [] |
|
|
|
for year in range(from_year, to_year+1): |
|
|
|
for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13): |
|
|
|
key = '{year}-{month:02d}'.format(year=year, month=month) |
|
|
|
data = customers_in.get(key) |
|
|
|
new = data['new'] if data else [0, 0.0] |
|
|
|
repeat = data['repeat'] if data else [0, 0.0] |
|
|
|
out.append({ |
|
|
|
'year': cstr(year), |
|
|
|
'month': calendar.month_name[month], |
|
|
|
'new_customers': new[0], |
|
|
|
'repeat_customers': repeat[0], |
|
|
|
'total': new[0] + repeat[0], |
|
|
|
'new_customer_revenue': new[1], |
|
|
|
'repeat_customer_revenue': repeat[1], |
|
|
|
'total_revenue': new[1] + repeat[1] |
|
|
|
}) |
|
|
|
return columns, out |
|
|
|
|
|
|
|
def get_data_by_territory(filters, common_columns): |
|
|
|
columns = [{ |
|
|
|
'label': 'Territory', |
|
|
|
'fieldname': 'territory', |
|
|
|
'fieldtype': 'Link', |
|
|
|
'options': 'Territory', |
|
|
|
'width': 150 |
|
|
|
}] |
|
|
|
columns += common_columns |
|
|
|
|
|
|
|
customers_in = get_customer_stats(filters, tree_view=True) |
|
|
|
|
|
|
|
territory_dict = {} |
|
|
|
for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1): |
|
|
|
territory_dict.update({ |
|
|
|
t.name: { |
|
|
|
'parent': t.parent_territory, |
|
|
|
'is_group': t.is_group |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
depth_map = frappe._dict() |
|
|
|
for name, info in territory_dict.items(): |
|
|
|
default = depth_map.get(info['parent']) + 1 if info['parent'] else 0 |
|
|
|
depth_map.setdefault(name, default) |
|
|
|
|
|
|
|
data = [] |
|
|
|
for name, indent in depth_map.items(): |
|
|
|
condition = customers_in.get(name) |
|
|
|
new = customers_in[name]['new'] if condition else [0, 0.0] |
|
|
|
repeat = customers_in[name]['repeat'] if condition else [0, 0.0] |
|
|
|
temp = { |
|
|
|
'territory': name, |
|
|
|
'parent_territory': territory_dict[name]['parent'], |
|
|
|
'indent': indent, |
|
|
|
'new_customers': new[0], |
|
|
|
'repeat_customers': repeat[0], |
|
|
|
'total': new[0] + repeat[0], |
|
|
|
'new_customer_revenue': new[1], |
|
|
|
'repeat_customer_revenue': repeat[1], |
|
|
|
'total_revenue': new[1] + repeat[1], |
|
|
|
'bold': 0 if indent else 1 |
|
|
|
} |
|
|
|
data.append(temp) |
|
|
|
|
|
|
|
loop_data = sorted(data, key=lambda k: k['indent'], reverse=True) |
|
|
|
|
|
|
|
for ld in loop_data: |
|
|
|
if ld['parent_territory']: |
|
|
|
parent_data = [x for x in data if x['territory'] == ld['parent_territory']][0] |
|
|
|
for key in parent_data.keys(): |
|
|
|
if key not in ['indent', 'territory', 'parent_territory', 'bold']: |
|
|
|
parent_data[key] += ld[key] |
|
|
|
|
|
|
|
return columns, data, None, None, None, 1 |
|
|
|
columns = [{ |
|
|
|
'label': 'Territory', |
|
|
|
'fieldname': 'territory', |
|
|
|
'fieldtype': 'Link', |
|
|
|
'options': 'Territory', |
|
|
|
'width': 150 |
|
|
|
}] |
|
|
|
columns += common_columns |
|
|
|
|
|
|
|
customers_in = get_customer_stats(filters, tree_view=True) |
|
|
|
|
|
|
|
territory_dict = {} |
|
|
|
for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1): |
|
|
|
territory_dict.update({ |
|
|
|
t.name: { |
|
|
|
'parent': t.parent_territory, |
|
|
|
'is_group': t.is_group |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
depth_map = frappe._dict() |
|
|
|
for name, info in territory_dict.items(): |
|
|
|
default = depth_map.get(info['parent']) + 1 if info['parent'] else 0 |
|
|
|
depth_map.setdefault(name, default) |
|
|
|
|
|
|
|
data = [] |
|
|
|
for name, indent in depth_map.items(): |
|
|
|
condition = customers_in.get(name) |
|
|
|
new = customers_in[name]['new'] if condition else [0, 0.0] |
|
|
|
repeat = customers_in[name]['repeat'] if condition else [0, 0.0] |
|
|
|
temp = { |
|
|
|
'territory': name, |
|
|
|
'parent_territory': territory_dict[name]['parent'], |
|
|
|
'indent': indent, |
|
|
|
'new_customers': new[0], |
|
|
|
'repeat_customers': repeat[0], |
|
|
|
'total': new[0] + repeat[0], |
|
|
|
'new_customer_revenue': new[1], |
|
|
|
'repeat_customer_revenue': repeat[1], |
|
|
|
'total_revenue': new[1] + repeat[1], |
|
|
|
'bold': 0 if indent else 1 |
|
|
|
} |
|
|
|
data.append(temp) |
|
|
|
|
|
|
|
loop_data = sorted(data, key=lambda k: k['indent'], reverse=True) |
|
|
|
|
|
|
|
for ld in loop_data: |
|
|
|
if ld['parent_territory']: |
|
|
|
parent_data = [x for x in data if x['territory'] == ld['parent_territory']][0] |
|
|
|
for key in parent_data.keys(): |
|
|
|
if key not in ['indent', 'territory', 'parent_territory', 'bold']: |
|
|
|
parent_data[key] += ld[key] |
|
|
|
|
|
|
|
return columns, data, None, None, None, 1 |
|
|
|
|
|
|
|
def get_customer_stats(filters, tree_view=False): |
|
|
|
""" Calculates number of new and repeated customers. """ |
|
|
|
company_condition = '' |
|
|
|
if filters.get('company'): |
|
|
|
company_condition = ' and company=%(company)s' |
|
|
|
|
|
|
|
customers = [] |
|
|
|
customers_in = {} |
|
|
|
|
|
|
|
for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice` |
|
|
|
where docstatus=1 and posting_date <= %(to_date)s {company_condition} order by posting_date'''.format(company_condition=company_condition), |
|
|
|
filters, as_dict=1): |
|
|
|
|
|
|
|
key = si.territory if tree_view else si.posting_date.strftime('%Y-%m') |
|
|
|
customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]}) |
|
|
|
|
|
|
|
if not si.customer in customers: |
|
|
|
customers_in[key]['new'][0] += 1 |
|
|
|
customers_in[key]['new'][1] += si.base_grand_total |
|
|
|
customers.append(si.customer) |
|
|
|
else: |
|
|
|
customers_in[key]['repeat'][0] += 1 |
|
|
|
customers_in[key]['repeat'][1] += si.base_grand_total |
|
|
|
|
|
|
|
return customers_in |
|
|
|
""" Calculates number of new and repeated customers. """ |
|
|
|
company_condition = '' |
|
|
|
if filters.get('company'): |
|
|
|
company_condition = ' and company=%(company)s' |
|
|
|
|
|
|
|
customers = [] |
|
|
|
customers_in = {} |
|
|
|
|
|
|
|
for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice` |
|
|
|
where docstatus=1 and posting_date <= %(to_date)s |
|
|
|
{company_condition} order by posting_date'''.format(company_condition=company_condition), |
|
|
|
filters, as_dict=1): |
|
|
|
|
|
|
|
key = si.territory if tree_view else si.posting_date.strftime('%Y-%m') |
|
|
|
customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]}) |
|
|
|
|
|
|
|
if not si.customer in customers: |
|
|
|
customers_in[key]['new'][0] += 1 |
|
|
|
customers_in[key]['new'][1] += si.base_grand_total |
|
|
|
customers.append(si.customer) |
|
|
|
else: |
|
|
|
customers_in[key]['repeat'][0] += 1 |
|
|
|
customers_in[key]['repeat'][1] += si.base_grand_total |
|
|
|
|
|
|
|
return customers_in |
|
|
|