Merge branch 'develop' of https://github.com/frappe/erpnext into accounts_dashboard

This commit is contained in:
Deepesh Garg 2020-05-14 22:57:27 +05:30
commit 33f654ebfe
9 changed files with 210 additions and 86 deletions

View File

@ -1,6 +1,5 @@
dist: trusty
language: python language: python
dist: trusty
git: git:
depth: 1 depth: 1
@ -14,21 +13,10 @@ addons:
jobs: jobs:
include: include:
- name: "Python 2.7 Server Side Test"
python: 2.7
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 3.6 Server Side Test" - name: "Python 3.6 Server Side Test"
python: 3.6 python: 3.6
script: bench --site test_site run-tests --app erpnext --coverage script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 2.7 Patch Test"
python: 2.7
before_script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
script: bench --site test_site migrate
- name: "Python 3.6 Patch Test" - name: "Python 3.6 Patch Test"
python: 3.6 python: 3.6
before_script: before_script:
@ -40,8 +28,7 @@ install:
- cd ~ - cd ~
- nvm install 10 - nvm install 10
- git clone https://github.com/frappe/bench --depth 1 - pip install frappe-bench
- pip install -e ./bench
- git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1 - git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1
- bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench - bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench

View File

@ -14,7 +14,7 @@ from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist() @frappe.whitelist()
@cache_source @cache_source
def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None, def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
to_date = None, timespan = None, time_interval = None): to_date = None, timespan = None, time_interval = None, heatmap_year = None):
if chart_name: if chart_name:
chart = frappe.get_doc('Dashboard Chart', chart_name) chart = frappe.get_doc('Dashboard Chart', chart_name)
else: else:

View File

@ -53,7 +53,7 @@ frappe.query_reports["General Ledger"] = {
"label": __("Voucher No"), "label": __("Voucher No"),
"fieldtype": "Data", "fieldtype": "Data",
on_change: function() { on_change: function() {
frappe.query_report.set_filter_value('group_by', ""); frappe.query_report.set_filter_value('group_by', "Group by Voucher (Consolidated)");
} }
}, },
{ {

View File

@ -38,7 +38,7 @@ class LoanSecurityPledge(Document):
for pledge in self.securities: for pledge in self.securities:
if not pledge.qty and not pledge.amount: if not pledge.qty and not pledge.amount:
frappe.throw(_("Qty or Amount is mandatroy for loan security")) frappe.throw(_("Qty or Amount is mandatory for loan security!"))
if not (self.loan_application and pledge.loan_security_price): if not (self.loan_application and pledge.loan_security_price):
pledge.loan_security_price = get_loan_security_price(pledge.loan_security) pledge.loan_security_price = get_loan_security_price(pledge.loan_security)

View File

@ -3,6 +3,14 @@
frappe.query_reports["Customer Acquisition and Loyalty"] = { frappe.query_reports["Customer Acquisition and Loyalty"] = {
"filters": [ "filters": [
{
"fieldname": "view_type",
"label": __("View Type"),
"fieldtype": "Select",
"options": ["Monthly", "Territory Wise"],
"default": "Monthly",
"reqd": 1
},
{ {
"fieldname":"company", "fieldname":"company",
"label": __("Company"), "label": __("Company"),
@ -24,6 +32,13 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = {
"fieldtype": "Date", "fieldtype": "Date",
"default": frappe.defaults.get_user_default("year_end_date"), "default": frappe.defaults.get_user_default("year_end_date"),
"reqd": 1 "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;
}
} }

View File

@ -2,65 +2,186 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import calendar
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import getdate, cint, cstr from frappe.utils import cint, cstr
import calendar
def execute(filters=None): def execute(filters=None):
# key yyyy-mm common_columns = [
new_customers_in = {} {
repeat_customers_in = {} 'label': _('New Customers'),
customers = [] 'fieldname': 'new_customers',
company_condition = "" '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"): def get_data_by_time(filters, common_columns):
company_condition = ' and company=%(company)s' # 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` customers_in = get_customer_stats(filters)
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.posting_date.strftime("%Y-%m") # time series
if not si.customer in customers: from_year, from_month, temp = filters.get('from_date').split('-')
new_customers_in.setdefault(key, [0, 0.0]) to_year, to_month, temp = filters.get('to_date').split('-')
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, to_year, to_month = \
from_year, from_month, temp = filters.get("from_date").split("-") cint(from_year), cint(from_month), cint(to_year), cint(to_month)
to_year, to_month, temp = filters.get("to_date").split("-")
from_year, from_month, to_year, to_month = \ out = []
cint(from_year), cint(from_month), cint(to_year), cint(to_month) 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 = [] def get_data_by_territory(filters, common_columns):
for year in range(from_year, to_year+1): columns = [{
for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13): 'label': 'Territory',
key = "{year}-{month:02d}".format(year=year, month=month) 'fieldname': 'territory',
'fieldtype': 'Link',
'options': 'Territory',
'width': 150
}]
columns += common_columns
new = new_customers_in.get(key, [0,0.0]) customers_in = get_customer_stats(filters, tree_view=True)
repeat = repeat_customers_in.get(key, [0,0.0])
out.append([cstr(year), calendar.month_name[month], territory_dict = {}
new[0], repeat[0], new[0] + repeat[0], for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1):
new[1], repeat[1], new[1] + repeat[1]]) territory_dict.update({
t.name: {
'parent': t.parent_territory,
'is_group': t.is_group
}
})
return [ depth_map = frappe._dict()
_("Year") + "::100", for name, info in territory_dict.items():
_("Month") + "::100", default = depth_map.get(info['parent']) + 1 if info['parent'] else 0
_("New Customers") + ":Int:100", depth_map.setdefault(name, default)
_("Repeat Customers") + ":Int:100",
_("Total") + ":Int:100",
_("New Customer Revenue") + ":Currency:150",
_("Repeat Customer Revenue") + ":Currency:150",
_("Total Revenue") + ":Currency:150"
], out
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

View File

@ -20,31 +20,36 @@ def get_columns():
"label": _("Territory"), "label": _("Territory"),
"fieldname": "territory", "fieldname": "territory",
"fieldtype": "Link", "fieldtype": "Link",
"options": "Territory" "options": "Territory",
"width": 150
}, },
{ {
"label": _("Opportunity Amount"), "label": _("Opportunity Amount"),
"fieldname": "opportunity_amount", "fieldname": "opportunity_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"options": currency "options": currency,
"width": 150
}, },
{ {
"label": _("Quotation Amount"), "label": _("Quotation Amount"),
"fieldname": "quotation_amount", "fieldname": "quotation_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"options": currency "options": currency,
"width": 150
}, },
{ {
"label": _("Order Amount"), "label": _("Order Amount"),
"fieldname": "order_amount", "fieldname": "order_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"options": currency "options": currency,
"width": 150
}, },
{ {
"label": _("Billing Amount"), "label": _("Billing Amount"),
"fieldname": "billing_amount", "fieldname": "billing_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"options": currency "options": currency,
"width": 150
} }
] ]
@ -63,7 +68,6 @@ def get_data(filters=None):
t_opportunity_names = [] t_opportunity_names = []
if territory_opportunities: 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 = [] territory_quotations = []
if t_opportunity_names and quotations: if t_opportunity_names and quotations:
territory_quotations = list(filter(lambda x: x.opportunity in t_opportunity_names, quotations)) territory_quotations = list(filter(lambda x: x.opportunity in t_opportunity_names, quotations))

View File

@ -47,9 +47,7 @@ class TestCompany(unittest.TestCase):
frappe.delete_doc("Company", "COA from Existing Company") frappe.delete_doc("Company", "COA from Existing Company")
def test_coa_based_on_country_template(self): def test_coa_based_on_country_template(self):
countries = ["India", "Brazil", "United Arab Emirates", "Canada", "Germany", "France", countries = ["Canada", "Germany", "France"]
"Guatemala", "Indonesia", "Italy", "Mexico", "Nicaragua", "Netherlands", "Singapore",
"Brazil", "Argentina", "Hungary", "Taiwan"]
for country in countries: for country in countries:
templates = get_charts_for_country(country) templates = get_charts_for_country(country)

View File

@ -3,8 +3,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import flt from frappe.utils import flt
from frappe import _ from frappe import _
@ -14,6 +12,7 @@ class Territory(NestedSet):
nsm_parent_field = 'parent_territory' nsm_parent_field = 'parent_territory'
def validate(self): def validate(self):
for d in self.get('targets') or []: for d in self.get('targets') or []:
if not flt(d.target_qty) and not flt(d.target_amount): if not flt(d.target_qty) and not flt(d.target_amount):
frappe.throw(_("Either target qty or target amount is mandatory")) frappe.throw(_("Either target qty or target amount is mandatory"))