Merge branch 'develop' into pur-inv-status-fix
This commit is contained in:
commit
2b8f307ffe
@ -53,7 +53,7 @@ frappe.query_reports["General Ledger"] = {
|
||||
"label": __("Voucher No"),
|
||||
"fieldtype": "Data",
|
||||
on_change: function() {
|
||||
frappe.query_report.set_filter_value('group_by', "");
|
||||
frappe.query_report.set_filter_value('group_by', "Group by Voucher (Consolidated)");
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -3,6 +3,14 @@
|
||||
|
||||
frappe.query_reports["Customer Acquisition and Loyalty"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "view_type",
|
||||
"label": __("View Type"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Monthly", "Territory Wise"],
|
||||
"default": "Monthly",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
@ -24,6 +32,13 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = {
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
"reqd": 1
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
'formatter': function(value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
if (data && data.bold) {
|
||||
value = value.bold();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
@ -2,65 +2,186 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import calendar
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import getdate, cint, cstr
|
||||
import calendar
|
||||
from frappe.utils import cint, cstr
|
||||
|
||||
def execute(filters=None):
|
||||
# key yyyy-mm
|
||||
new_customers_in = {}
|
||||
repeat_customers_in = {}
|
||||
customers = []
|
||||
company_condition = ""
|
||||
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)
|
||||
|
||||
if filters.get("company"):
|
||||
company_condition = ' and company=%(company)s'
|
||||
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
|
||||
|
||||
for si in frappe.db.sql("""select 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):
|
||||
customers_in = get_customer_stats(filters)
|
||||
|
||||
key = si.posting_date.strftime("%Y-%m")
|
||||
if not si.customer in customers:
|
||||
new_customers_in.setdefault(key, [0, 0.0])
|
||||
new_customers_in[key][0] += 1
|
||||
new_customers_in[key][1] += si.base_grand_total
|
||||
customers.append(si.customer)
|
||||
else:
|
||||
repeat_customers_in.setdefault(key, [0, 0.0])
|
||||
repeat_customers_in[key][0] += 1
|
||||
repeat_customers_in[key][1] += si.base_grand_total
|
||||
# time series
|
||||
from_year, from_month, temp = filters.get('from_date').split('-')
|
||||
to_year, to_month, temp = filters.get('to_date').split('-')
|
||||
|
||||
# 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)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
def get_data_by_territory(filters, common_columns):
|
||||
columns = [{
|
||||
'label': 'Territory',
|
||||
'fieldname': 'territory',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Territory',
|
||||
'width': 150
|
||||
}]
|
||||
columns += common_columns
|
||||
|
||||
new = new_customers_in.get(key, [0,0.0])
|
||||
repeat = repeat_customers_in.get(key, [0,0.0])
|
||||
customers_in = get_customer_stats(filters, tree_view=True)
|
||||
|
||||
out.append([cstr(year), calendar.month_name[month],
|
||||
new[0], repeat[0], new[0] + repeat[0],
|
||||
new[1], repeat[1], new[1] + repeat[1]])
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
return [
|
||||
_("Year") + "::100",
|
||||
_("Month") + "::100",
|
||||
_("New Customers") + ":Int:100",
|
||||
_("Repeat Customers") + ":Int:100",
|
||||
_("Total") + ":Int:100",
|
||||
_("New Customer Revenue") + ":Currency:150",
|
||||
_("Repeat Customer Revenue") + ":Currency:150",
|
||||
_("Total Revenue") + ":Currency:150"
|
||||
], out
|
||||
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 and posting_date >= %(from_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
|
||||
|
@ -20,31 +20,36 @@ def get_columns():
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory"
|
||||
"options": "Territory",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Opportunity Amount"),
|
||||
"fieldname": "opportunity_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": currency
|
||||
"options": currency,
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Quotation Amount"),
|
||||
"fieldname": "quotation_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": currency
|
||||
"options": currency,
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Order Amount"),
|
||||
"fieldname": "order_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": currency
|
||||
"options": currency,
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Billing Amount"),
|
||||
"fieldname": "billing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": currency
|
||||
"options": currency,
|
||||
"width": 150
|
||||
}
|
||||
]
|
||||
|
||||
@ -62,8 +67,7 @@ def get_data(filters=None):
|
||||
territory_opportunities = list(filter(lambda x: x.territory == territory.name, opportunities))
|
||||
t_opportunity_names = []
|
||||
if territory_opportunities:
|
||||
t_opportunity_names = [t.name for t in territory_opportunities]
|
||||
|
||||
t_opportunity_names = [t.name for t in territory_opportunities]
|
||||
territory_quotations = []
|
||||
if t_opportunity_names and quotations:
|
||||
territory_quotations = list(filter(lambda x: x.opportunity in t_opportunity_names, quotations))
|
||||
@ -76,7 +80,7 @@ def get_data(filters=None):
|
||||
list(filter(lambda x: x.quotation in t_quotation_names, sales_orders))
|
||||
t_order_names = []
|
||||
if territory_orders:
|
||||
t_order_names = [t.name for t in territory_orders]
|
||||
t_order_names = [t.name for t in territory_orders]
|
||||
|
||||
territory_invoices = list(filter(lambda x: x.sales_order in t_order_names, sales_invoices)) if t_order_names and sales_invoices else []
|
||||
|
||||
@ -96,12 +100,12 @@ def get_opportunities(filters):
|
||||
|
||||
if filters.get('transaction_date'):
|
||||
conditions = " WHERE transaction_date between {0} and {1}".format(
|
||||
frappe.db.escape(filters['transaction_date'][0]),
|
||||
frappe.db.escape(filters['transaction_date'][0]),
|
||||
frappe.db.escape(filters['transaction_date'][1]))
|
||||
|
||||
|
||||
if filters.company:
|
||||
if conditions:
|
||||
conditions += " AND"
|
||||
conditions += " AND"
|
||||
else:
|
||||
conditions += " WHERE"
|
||||
conditions += " company = %(company)s"
|
||||
@ -115,7 +119,7 @@ def get_opportunities(filters):
|
||||
def get_quotations(opportunities):
|
||||
if not opportunities:
|
||||
return []
|
||||
|
||||
|
||||
opportunity_names = [o.name for o in opportunities]
|
||||
|
||||
return frappe.db.sql("""
|
||||
@ -155,5 +159,5 @@ def _get_total(doclist, amount_field="base_grand_total"):
|
||||
total = 0
|
||||
for doc in doclist:
|
||||
total += doc.get(amount_field, 0)
|
||||
|
||||
|
||||
return total
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
|
||||
from frappe.utils import flt
|
||||
from frappe import _
|
||||
|
||||
@ -14,6 +12,7 @@ class Territory(NestedSet):
|
||||
nsm_parent_field = 'parent_territory'
|
||||
|
||||
def validate(self):
|
||||
|
||||
for d in self.get('targets') or []:
|
||||
if not flt(d.target_qty) and not flt(d.target_amount):
|
||||
frappe.throw(_("Either target qty or target amount is mandatory"))
|
||||
|
Loading…
Reference in New Issue
Block a user