Sales Goal by Company (#9723)

* [sales goal] in company; dashboard, graph, notifs, wiz

* [test] target notifications

* cache past year monthly sales of every company daily, patch

* [minor] query fixes

* update sales goal docs
This commit is contained in:
Prateeksha Singh 2017-07-18 10:35:12 +05:30 committed by Makarand Bauskar
parent e2d0d0a0c1
commit e012e24423
18 changed files with 2199 additions and 1861 deletions

View File

@ -98,6 +98,26 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.set_default_print_format();
},
on_submit: function(doc, dt, dn) {
var me = this;
$.each(doc["items"], function(i, row) {
if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note)
})
if(this.frm.doc.is_pos) {
this.frm.msgbox = frappe.msgprint(
`<a class="btn btn-primary" onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">
${__('Print')}</a>
<a class="btn btn-default" href="javascript:frappe.new_doc(cur_frm.doctype);">
${__('New')}</a>`
);
} else if(cint(frappe.boot.notification_settings.sales_invoice)) {
this.frm.email_doc(frappe.boot.notification_settings.sales_invoice_message);
}
},
set_default_print_format: function() {
// set default print format to POS type
if(cur_frm.doc.is_pos) {
@ -306,7 +326,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.frm.refresh_fields();
},
company_address: function() {
var me = this;
if(this.frm.doc.company_address) {
@ -445,24 +465,6 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn) {
erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "cost_center");
}
cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
$.each(doc["items"], function(i, row) {
if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note)
})
if(cur_frm.doc.is_pos) {
cur_frm.msgbox = frappe.msgprint(
`<a class="btn btn-primary" onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">
${__('Print')}</a>
<a class="btn btn-default" href="javascript:frappe.new_doc(cur_frm.doctype);">
${__('New')}</a>`
);
} else if(cint(frappe.boot.notification_settings.sales_invoice)) {
cur_frm.email_doc(frappe.boot.notification_settings.sales_invoice_message);
}
}
cur_frm.set_query("debit_to", function(doc) {
// filter on Account
if (doc.customer) {

View File

@ -83,10 +83,10 @@ class SalesInvoice(SellingController):
if not self.is_opening:
self.is_opening = 'No'
if self._action != 'submit' and self.update_stock and not self.is_return:
set_batch_nos(self, 'warehouse', True)
self.set_against_income_account()
self.validate_c_form()
@ -98,7 +98,7 @@ class SalesInvoice(SellingController):
self.set_billing_hours_and_amount()
self.update_timesheet_billing_for_project()
self.set_status()
def before_save(self):
set_account_for_mode_of_payment(self)
@ -139,6 +139,8 @@ class SalesInvoice(SellingController):
self.update_time_sheet(self.name)
frappe.enqueue('erpnext.setup.doctype.company.company.update_company_current_month_sales', company=self.company)
def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos:
frappe.throw(_("At least one mode of payment is required for POS invoice."))
@ -816,7 +818,7 @@ class SalesInvoice(SellingController):
self.validate_serial_against_sales_invoice()
def validate_serial_against_delivery_note(self):
"""
"""
validate if the serial numbers in Sales Invoice Items are same as in
Delivery Note Item
"""
@ -918,7 +920,6 @@ def make_delivery_note(source_name, target_doc=None):
return doclist
@frappe.whitelist()
def make_sales_return(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 MiB

View File

@ -15,5 +15,6 @@ third-party-backups
workflows
bar-code
company-setup
setting-company-sales-goal
calculate-incentive-for-sales-team
articles

View File

@ -0,0 +1,15 @@
# Setting Company Sales Goal
Monthly sales targets can be set for a company via the Company master. By default, the Company master dashboard features past sales stats.
<img class="screenshot" alt="Sales Graph" src="{{docs_base_url}}/assets/img/sales_goal/sales_history_graph.png">
You can set the **Sales Target** field to track progress to track progress with respect to it.
<img class="screenshot" alt="Setting Sales Goal" src="{{docs_base_url}}/assets/img/sales_goal/setting_sales_goal.gif">
The target progress is also shown in notifications:
<img class="screenshot" alt="Sales Notification" src="{{docs_base_url}}/assets/img/sales_goal/sales_goal_notification.png">
{next}

View File

@ -183,7 +183,8 @@ scheduler_events = {
"erpnext.projects.doctype.task.task.set_tasks_as_overdue",
"erpnext.accounts.doctype.asset.depreciation.post_depreciation_entries",
"erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary",
"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status"
"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status",
"erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history"
]
}

View File

@ -418,4 +418,5 @@ erpnext.patches.v8_1.allow_invoice_copy_to_edit_after_submit
erpnext.patches.v8_1.add_hsn_sac_codes
erpnext.patches.v8_1.update_gst_state #17-07-2017
erpnext.patches.v8_1.removed_report_support_hours
erpnext.patches.v8_1.add_indexes_in_transaction_doctypes
erpnext.patches.v8_1.add_indexes_in_transaction_doctypes
erpnext.patches.v8_3.update_company_total_sales

View File

View File

@ -0,0 +1,15 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from erpnext.setup.doctype.company.company import update_company_current_month_sales, update_company_monthly_sales
def execute():
'''Update company monthly sales history based on sales invoices'''
frappe.reload_doctype("Company")
companies = [d['name'] for d in frappe.get_list("Company")]
for company in companies:
update_company_current_month_sales(company)
update_company_monthly_sales(company)

View File

@ -216,6 +216,17 @@ var erpnext_slides = [
]
},
{
// Sales Target
name: 'Goals',
domains: ['manufacturing', 'services', 'retail', 'distribution'],
title: __("Set your Target"),
help: __("Set a sales target you'd like to achieve."),
fields: [
{fieldtype:"Currency", fieldname:"sales_target", label:__("Monthly Sales Target")},
]
},
{
// Taxes
name: 'taxes',

File diff suppressed because it is too large Load Diff

View File

@ -147,7 +147,7 @@ class Company(Document):
def validate_perpetual_inventory(self):
if not self.get("__islocal"):
if cint(self.enable_perpetual_inventory) == 1 and not self.default_inventory_account:
frappe.msgprint(_("Set default inventory account for perpetual inventory"),
frappe.msgprint(_("Set default inventory account for perpetual inventory"),
alert=True, indicator='orange')
def set_default_accounts(self):
@ -310,3 +310,45 @@ def get_name_with_abbr(name, company):
parts.append(company_abbr)
return " - ".join(parts)
def update_company_current_month_sales(company):
from frappe.utils import today, formatdate
current_month_year = formatdate(today(), "MM-yyyy")
results = frappe.db.sql(('''
select
sum(grand_total) as total, date_format(posting_date, '%m-%Y') as month_year
from
`tabSales Invoice`
where
date_format(posting_date, '%m-%Y')="{0}" and
company = "{1}"
group by
month_year;
''').format(current_month_year, frappe.db.escape(company)), as_dict = True)
monthly_total = results[0]['total'] if len(results) > 0 else 0
frappe.db.sql(('''
update tabCompany set total_monthly_sales = %s where name=%s
'''), (monthly_total, frappe.db.escape(company)))
frappe.db.commit()
def update_company_monthly_sales(company):
'''Cache past year monthly sales of every company based on sales invoices'''
from frappe.utils.goal import get_monthly_results
import json
filter_str = 'company = "'+ company +'" and status != "Draft"'
month_to_value_dict = get_monthly_results("Sales Invoice", "grand_total", "posting_date", filter_str, "sum")
frappe.db.sql(('''
update tabCompany set sales_monthly_history = %s where name=%s
'''), (json.dumps(month_to_value_dict), frappe.db.escape(company)))
frappe.db.commit()
def cache_companies_monthly_sales_history():
companies = [d['name'] for d in frappe.get_list("Company")]
for company in companies:
update_company_monthly_sales(company)
frappe.db.commit()

View File

@ -0,0 +1,42 @@
from frappe import _
def get_data():
return {
'heatmap': True,
'heatmap_message': _('This is based on transactions against this Company. See timeline below for details'),
'graph': True,
'graph_method': "frappe.utils.goal.get_monthly_goal_graph_data",
'graph_method_args': {
'title': 'Sales',
'goal_value_field': 'sales_target',
'goal_total_field': 'total_monthly_sales',
'goal_history_field': 'sales_monthly_history',
'goal_doctype': 'Sales Invoice',
'goal_doctype_link': 'company',
'goal_field': 'grand_total',
'date_field': 'posting_date',
'filter_str': 'status != "Draft"',
'aggregation': 'sum'
},
'fieldname': 'company',
'transactions': [
{
'label': _('Pre Sales'),
'items': ['Quotation']
},
{
'label': _('Orders'),
'items': ['Sales Order', 'Delivery Note', 'Sales Invoice']
},
{
'label': _('Support'),
'items': ['Issue']
},
{
'label': _('Projects'),
'items': ['Project']
}
]
}

View File

@ -91,7 +91,8 @@ def create_fiscal_year_and_company(args):
'country': args.get('country'),
'create_chart_of_accounts_based_on': 'Standard Template',
'chart_of_accounts': args.get('chart_of_accounts'),
'domain': args.get('domain')
'domain': args.get('domain'),
'sales_target': args.get('sales_target')
}).insert()
#Enable shopping cart

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
def get_notification_config():
notification_for_doctype = { "for_doctype":
notifications = { "for_doctype":
{
"Issue": {"status": "Open"},
"Warranty Claim": {"status": "Open"},
@ -56,12 +56,20 @@ def get_notification_config():
"Production Order": { "status": ("in", ("Draft", "Not Started", "In Process")) },
"BOM": {"docstatus": 0},
"Timesheet": {"status": "Draft"}
},
"targets": {
"Company": {
"filters" : { "sales_target": ( ">", 0 ) },
"target_field" : "sales_target",
"value_field" : "total_monthly_sales"
}
}
}
doctype = [d for d in notification_for_doctype.get('for_doctype')]
doctype = [d for d in notifications.get('for_doctype')]
for doc in frappe.get_all('DocType',
fields= ["name"], filters = {"name": ("not in", doctype), 'is_submittable': 1}):
notification_for_doctype["for_doctype"][doc.name] = {"docstatus": 0}
notifications["for_doctype"][doc.name] = {"docstatus": 0}
return notification_for_doctype
return notifications

View File

@ -0,0 +1,46 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import unittest
from frappe.desk import notifications
from frappe.test_runner import make_test_objects
class TestNotifications(unittest.TestCase):
def setUp(self):
test_records = [
{
"abbr": "_TC6",
"company_name": "_Test Company 6",
"country": "India",
"default_currency": "INR",
"doctype": "Company",
"domain": "Manufacturing",
"sales_target": 2000,
"chart_of_accounts": "Standard"
},
{
"abbr": "_TC7",
"company_name": "_Test Company 7",
"country": "United States",
"default_currency": "USD",
"doctype": "Company",
"domain": "Retail",
"sales_target": 10000,
"total_monthly_sales": 1000,
"chart_of_accounts": "Standard"
},
]
make_test_objects('Company', test_records=test_records, reset=True)
def test_get_notifications_for_targets(self):
'''
Test notification config entries for targets as percentages
'''
config = notifications.get_notification_config()
doc_target_percents = notifications.get_notifications_for_targets(config, {})
self.assertEquals(doc_target_percents['Company']['_Test Company 7'], 10)
self.assertEquals(doc_target_percents['Company']['_Test Company 6'], 0)