feat: Selling Desk, Dashboard and Onboarding (#22055)

* feat: Selling Desk, Dashboard and Onboarding

* chore: Selling Onboarding and fixes in Other onboardings

* chore: Dashboard and Number card Fixtures

* fix: Escape filters and Reposition Accounts Dashboard shortcut.

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
This commit is contained in:
Marica
2020-06-19 15:33:21 +05:30
committed by GitHub
parent 22d49726d1
commit 383807f72e
36 changed files with 1079 additions and 168 deletions

View File

@@ -12,12 +12,6 @@ frappe.query_reports["Item-wise Sales History"] = {
default: frappe.defaults.get_user_default("Company"),
reqd: 1
},
{
fieldname:"item_group",
label: __("Item Group"),
fieldtype: "Link",
options: "Item Group"
},
{
fieldname:"from_date",
reqd: 1,
@@ -32,6 +26,38 @@ frappe.query_reports["Item-wise Sales History"] = {
label: __("To Date"),
fieldtype: "Date",
},
{
fieldname:"item_group",
label: __("Item Group"),
fieldtype: "Link",
options: "Item Group"
},
{
fieldname:"item_code",
label: __("Item"),
fieldtype: "Link",
options: "Item",
get_query: () => {
return {
query: "erpnext.controllers.queries.item_query"
}
}
},
{
fieldname:"customer",
label: __("Customer"),
fieldtype: "Link",
options: "Customer"
}
],
]
"formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
let format_fields = ["delivered_quantity", "billed_amount"];
if (in_list(format_fields, column.fieldname) && data && data[column.fieldname] > 0) {
value = "<span style='color:green;'>" + value + "</span>";
}
return value;
}
};

View File

@@ -11,7 +11,10 @@ def execute(filters=None):
filters = frappe._dict(filters or {})
columns = get_columns(filters)
data = get_data(filters)
return columns, data
chart_data = get_chart_data(data)
return columns, data, None, chart_data
def get_columns(filters):
return [
@@ -181,6 +184,12 @@ def get_conditions(filters):
if filters.get('to_date'):
conditions += "AND so.transaction_date <= '%s'" %filters.to_date
if filters.get("item_code"):
conditions += "AND so_item.item_code = '%s'" %frappe.db.escape(filters.item_code)
if filters.get("customer"):
conditions += "AND so.customer = '%s'" %frappe.db.escape(filters.customer)
return conditions
def get_customer_details():
@@ -212,3 +221,34 @@ def get_sales_order_details(company_list, filters):
AND so.company in ({0})
AND so.docstatus = 1 {1}
""".format(','.join(["%s"] * len(company_list)), conditions), tuple(company_list), as_dict=1)
def get_chart_data(data):
item_wise_sales_map = {}
labels, datapoints = [], []
for row in data:
item_key = row.get("item_code")
if not item_key in item_wise_sales_map:
item_wise_sales_map[item_key] = 0
item_wise_sales_map[item_key] = flt(item_wise_sales_map[item_key]) + flt(row.get("amount"))
item_wise_sales_map = { item: value for item, value in (sorted(item_wise_sales_map.items(), key = lambda i: i[1], reverse=True))}
for key in item_wise_sales_map:
labels.append(key)
datapoints.append(item_wise_sales_map[key])
return {
"data" : {
"labels" : labels[:30], # show max of 30 items in chart
"datasets" : [
{
"name" : _(" Total Sales Amount"),
"values" : datapoints[:30]
}
]
},
"type" : "bar"
}

View File

@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from erpnext.controllers.trends import get_columns, get_data
def execute(filters=None):
@@ -11,4 +12,48 @@ def execute(filters=None):
conditions = get_columns(filters, "Quotation")
data = get_data(filters, conditions)
return conditions["columns"], data
chart_data = get_chart_data(data, conditions, filters)
return conditions["columns"], data, None, chart_data
def get_chart_data(data, conditions, filters):
if not (data and conditions):
return []
datapoints = []
start = 2 if filters.get("based_on") in ["Item", "Customer"] else 1
if filters.get("group_by"):
start += 1
# fetch only periodic columns as labels
columns = conditions.get("columns")[start:-2][1::2]
labels = [column.split(':')[0] for column in columns]
datapoints = [0] * len(labels)
for row in data:
# If group by filter, don't add first row of group (it's already summed)
if not row[start-1]:
continue
# Remove None values and compute only periodic data
row = [x if x else 0 for x in row[start:-2]]
row = row[1::2]
for i in range(len(row)):
datapoints[i] += row[i]
return {
"data" : {
"labels" : labels,
"datasets" : [
{
"name" : _("{0}").format(filters.get("period")) + _(" Quoted Amount"),
"values" : datapoints
}
]
},
"type" : "line",
"lineOptions": {
"regionFill": 1
}
}

View File

@@ -0,0 +1,85 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Sales Order Analysis"] = {
"filters": [
{
"fieldname": "company",
"label": __("Company"),
"fieldtype": "Link",
"width": "80",
"options": "Company",
"reqd": 1,
"default": frappe.defaults.get_default("company")
},
{
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"width": "80",
"reqd": 1,
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
"width": "80",
"reqd": 1,
"default": frappe.datetime.get_today()
},
{
"fieldname": "sales_order",
"label": __("Sales Order"),
"fieldtype": "MultiSelectList",
"width": "80",
"options": "Sales Order",
"get_data": function(txt) {
return frappe.db.get_link_options("Sales Order", txt);
},
"get_query": () =>{
return {
filters: { "docstatus": 1 }
}
}
},
{
"fieldname": "status",
"label": __("Status"),
"fieldtype": "MultiSelectList",
"width": "80",
get_data: function(txt) {
let status = ["To Bill", "To Deliver", "To Deliver and Bill", "Completed"]
let options = []
for (let option of status){
options.push({
"value": option,
"description": ""
})
}
return options
}
},
{
"fieldname": "group_by_so",
"label": __("Group by Sales Order"),
"fieldtype": "Check",
"default": 0
}
],
"formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
let format_fields = ["delivered_qty", "billed_amount"];
if (in_list(format_fields, column.fieldname) && data && data[column.fieldname] > 0) {
value = "<span style='color:green;'>" + value + "</span>";
}
if (column.fieldname == "delay" && data && data[column.fieldname] > 0) {
value = "<span style='color:red;'>" + value + "</span>";
}
return value;
}
};

View File

@@ -0,0 +1,36 @@
{
"add_total_row": 1,
"creation": "2020-05-29 14:54:53.591445",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2020-05-29 14:54:53.591445",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Analysis",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Sales Order",
"report_name": "Sales Order Analysis",
"report_type": "Script Report",
"roles": [
{
"role": "Sales User"
},
{
"role": "Sales Manager"
},
{
"role": "Maintenance User"
},
{
"role": "Accounts User"
},
{
"role": "Stock User"
}
]
}

View File

@@ -0,0 +1,279 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import copy
from frappe import _
from frappe.utils import flt, date_diff, getdate
def execute(filters=None):
if not filters:
return [], [], None, []
validate_filters(filters)
columns = get_columns(filters)
conditions = get_conditions(filters)
data = get_data(conditions, filters)
if not data:
return [], [], None, []
data, chart_data = prepare_data(data, filters)
return columns, data, None, chart_data
def validate_filters(filters):
from_date, to_date = filters.get("from_date"), filters.get("to_date")
if not from_date and to_date:
frappe.throw(_("From and To Dates are required."))
elif date_diff(to_date, from_date) < 0:
frappe.throw(_("To Date cannot be before From Date."))
def get_conditions(filters):
conditions = ""
if filters.get("from_date") and filters.get("to_date"):
conditions += " and so.transaction_date between %(from_date)s and %(to_date)s"
if filters.get("company"):
conditions += " and so.company = %(company)s"
if filters.get("sales_order"):
conditions += " and so.name in %(sales_order)s"
if filters.get("status"):
conditions += " and so.status in %(status)s"
return conditions
def get_data(conditions, filters):
data = frappe.db.sql("""
SELECT
so.transaction_date as date,
soi.delivery_date as delivery_date,
so.name as sales_order,
so.status, so.customer, soi.item_code,
DATEDIFF(CURDATE(), soi.delivery_date) as delay_days,
IF(so.status in ('Completed','To Bill'), 0, (SELECT delay_days)) as delay,
soi.qty, soi.delivered_qty,
(soi.qty - soi.delivered_qty) AS pending_qty,
IFNULL(sii.qty, 0) as billed_qty,
soi.base_amount as amount,
(soi.delivered_qty * soi.base_rate) as delivered_qty_amount,
(soi.billed_amt * IFNULL(so.conversion_rate, 1)) as billed_amount,
(soi.base_amount - (soi.billed_amt * IFNULL(so.conversion_rate, 1))) as pending_amount,
soi.warehouse as warehouse,
so.company, soi.name
FROM
`tabSales Order` so,
`tabSales Order Item` soi
LEFT JOIN `tabSales Invoice Item` sii
ON sii.so_detail = soi.name
WHERE
soi.parent = so.name
and so.status not in ('Stopped', 'Closed', 'On Hold')
and so.docstatus = 1
{conditions}
GROUP BY soi.name
ORDER BY so.transaction_date ASC
""".format(conditions=conditions), filters, as_dict=1)
return data
def prepare_data(data, filters):
completed, pending = 0, 0
if filters.get("group_by_so"):
sales_order_map = {}
for row in data:
# sum data for chart
completed += row["billed_amount"]
pending += row["pending_amount"]
# prepare data for report view
row["qty_to_bill"] = flt(row["qty"]) - flt(row["billed_qty"])
row["delay"] = 0 if row["delay"] < 0 else row["delay"]
if filters.get("group_by_so"):
so_name = row["sales_order"]
if not so_name in sales_order_map:
# create an entry
row_copy = copy.deepcopy(row)
sales_order_map[so_name] = row_copy
else:
# update existing entry
so_row = sales_order_map[so_name]
so_row["required_date"] = max(getdate(so_row["delivery_date"]), getdate(row["delivery_date"]))
so_row["delay"] = min(so_row["delay"], row["delay"])
# sum numeric columns
fields = ["qty", "delivered_qty", "pending_qty", "billed_qty", "qty_to_bill", "amount",
"delivered_qty_amount", "billed_amount", "pending_amount"]
for field in fields:
so_row[field] = flt(row[field]) + flt(so_row[field])
chart_data = prepare_chart_data(pending, completed)
if filters.get("group_by_so"):
data = []
for so in sales_order_map:
data.append(sales_order_map[so])
return data, chart_data
return data, chart_data
def prepare_chart_data(pending, completed):
labels = ["Amount to Bill", "Billed Amount"]
return {
"data" : {
"labels": labels,
"datasets": [
{"values": [pending, completed]}
]
},
"type": 'donut',
"height": 300
}
def get_columns(filters):
columns = [
{
"label":_("Date"),
"fieldname": "date",
"fieldtype": "Date",
"width": 90
},
{
"label": _("Sales Order"),
"fieldname": "sales_order",
"fieldtype": "Link",
"options": "Sales Order",
"width": 160
},
{
"label":_("Status"),
"fieldname": "status",
"fieldtype": "Data",
"width": 130
},
{
"label": _("Customer"),
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
"width": 130
}]
if not filters.get("group_by_so"):
columns.append({
"label":_("Item Code"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
"width": 100
})
columns.extend([
{
"label": _("Qty"),
"fieldname": "qty",
"fieldtype": "Float",
"width": 120,
"convertible": "qty"
},
{
"label": _("Delivered Qty"),
"fieldname": "delivered_qty",
"fieldtype": "Float",
"width": 120,
"convertible": "qty"
},
{
"label": _("Qty to Deliver"),
"fieldname": "pending_qty",
"fieldtype": "Float",
"width": 120,
"convertible": "qty"
},
{
"label": _("Billed Qty"),
"fieldname": "billed_qty",
"fieldtype": "Float",
"width": 80,
"convertible": "qty"
},
{
"label": _("Qty to Bill"),
"fieldname": "qty_to_bill",
"fieldtype": "Float",
"width": 80,
"convertible": "qty"
},
{
"label": _("Amount"),
"fieldname": "amount",
"fieldtype": "Currency",
"width": 110,
"options": "Company:company:default_currency",
"convertible": "rate"
},
{
"label": _("Billed Amount"),
"fieldname": "billed_amount",
"fieldtype": "Currency",
"width": 110,
"options": "Company:company:default_currency",
"convertible": "rate"
},
{
"label": _("Pending Amount"),
"fieldname": "pending_amount",
"fieldtype": "Currency",
"width": 130,
"options": "Company:company:default_currency",
"convertible": "rate"
},
{
"label": _("Amount Delivered"),
"fieldname": "delivered_qty_amount",
"fieldtype": "Currency",
"width": 100,
"options": "Company:company:default_currency",
"convertible": "rate"
},
{
"label":_("Delivery Date"),
"fieldname": "delivery_date",
"fieldtype": "Date",
"width": 120
},
{
"label": _("Delay (in Days)"),
"fieldname": "delay",
"fieldtype": "Data",
"width": 100
}
])
if not filters.get("group_by_so"):
columns.append({
"label": _("Warehouse"),
"fieldname": "warehouse",
"fieldtype": "Link",
"options": "Warehouse",
"width": 100
})
columns.append({
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"width": 100
})
return columns

View File

@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from erpnext.controllers.trends import get_columns,get_data
def execute(filters=None):
@@ -10,4 +11,48 @@ def execute(filters=None):
data = []
conditions = get_columns(filters, "Sales Order")
data = get_data(filters, conditions)
return conditions["columns"], data
chart_data = get_chart_data(data, conditions, filters)
return conditions["columns"], data, None, chart_data
def get_chart_data(data, conditions, filters):
if not (data and conditions):
return []
datapoints = []
start = 2 if filters.get("based_on") in ["Item", "Customer"] else 1
if filters.get("group_by"):
start += 1
# fetch only periodic columns as labels
columns = conditions.get("columns")[start:-2][1::2]
labels = [column.split(':')[0] for column in columns]
datapoints = [0] * len(labels)
for row in data:
# If group by filter, don't add first row of group (it's already summed)
if not row[start-1]:
continue
# Remove None values and compute only periodic data
row = [x if x else 0 for x in row[start:-2]]
row = row[1::2]
for i in range(len(row)):
datapoints[i] += row[i]
return {
"data" : {
"labels" : labels,
"datasets" : [
{
"name" : _("{0}").format(filters.get("period")) + _(" Sales Value"),
"values" : datapoints
}
]
},
"type" : "line",
"lineOptions": {
"regionFill": 1
}
}