mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-25 07:54:46 +00:00
Merge pull request #28076 from GangaManoj/gross-profit-backport
feat: Make Gross Profit Report more readable
This commit is contained in:
@@ -36,5 +36,20 @@ frappe.query_reports["Gross Profit"] = {
|
|||||||
"options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject",
|
"options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject",
|
||||||
"default": "Invoice"
|
"default": "Invoice"
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
"tree": true,
|
||||||
|
"name_field": "parent",
|
||||||
|
"parent_field": "parent_invoice",
|
||||||
|
"initial_depth": 3,
|
||||||
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
if (data && data.indent == 0.0) {
|
||||||
|
value = $(`<span>${value}</span>`);
|
||||||
|
var $value = $(value).css("font-weight", "bold");
|
||||||
|
value = $value.wrap("<p></p>").parent().html();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,34 @@ def execute(filters=None):
|
|||||||
|
|
||||||
columns = get_columns(group_wise_columns, filters)
|
columns = get_columns(group_wise_columns, filters)
|
||||||
|
|
||||||
|
if filters.group_by == 'Invoice':
|
||||||
|
get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_wise_columns, data)
|
||||||
|
|
||||||
|
else:
|
||||||
|
get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data)
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_wise_columns, data):
|
||||||
|
column_names = get_column_names()
|
||||||
|
|
||||||
|
# to display item as Item Code: Item Name
|
||||||
|
columns[0] = 'Sales Invoice:Link/Item:300'
|
||||||
|
# removing Item Code and Item Name columns
|
||||||
|
del columns[4:6]
|
||||||
|
|
||||||
|
for src in gross_profit_data.si_list:
|
||||||
|
row = frappe._dict()
|
||||||
|
row.indent = src.indent
|
||||||
|
row.parent_invoice = src.parent_invoice
|
||||||
|
row.currency = filters.currency
|
||||||
|
|
||||||
|
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||||
|
row[column_names[col]] = src.get(col)
|
||||||
|
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data):
|
||||||
for idx, src in enumerate(gross_profit_data.grouped_data):
|
for idx, src in enumerate(gross_profit_data.grouped_data):
|
||||||
row = []
|
row = []
|
||||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||||
@@ -51,8 +79,6 @@ def execute(filters=None):
|
|||||||
row[0] = frappe.bold("Total")
|
row[0] = frappe.bold("Total")
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
return columns, data
|
|
||||||
|
|
||||||
def get_columns(group_wise_columns, filters):
|
def get_columns(group_wise_columns, filters):
|
||||||
columns = []
|
columns = []
|
||||||
column_map = frappe._dict({
|
column_map = frappe._dict({
|
||||||
@@ -93,12 +119,38 @@ def get_columns(group_wise_columns, filters):
|
|||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
def get_column_names():
|
||||||
|
return frappe._dict({
|
||||||
|
'parent': 'sales_invoice',
|
||||||
|
'customer': 'customer',
|
||||||
|
'customer_group': 'customer_group',
|
||||||
|
'posting_date': 'posting_date',
|
||||||
|
'item_code': 'item_code',
|
||||||
|
'item_name': 'item_name',
|
||||||
|
'item_group': 'item_group',
|
||||||
|
'brand': 'brand',
|
||||||
|
'description': 'description',
|
||||||
|
'warehouse': 'warehouse',
|
||||||
|
'qty': 'qty',
|
||||||
|
'base_rate': 'avg._selling_rate',
|
||||||
|
'buying_rate': 'valuation_rate',
|
||||||
|
'base_amount': 'selling_amount',
|
||||||
|
'buying_amount': 'buying_amount',
|
||||||
|
'gross_profit': 'gross_profit',
|
||||||
|
'gross_profit_percent': 'gross_profit_%',
|
||||||
|
'project': 'project'
|
||||||
|
})
|
||||||
|
|
||||||
class GrossProfitGenerator(object):
|
class GrossProfitGenerator(object):
|
||||||
def __init__(self, filters=None):
|
def __init__(self, filters=None):
|
||||||
self.data = []
|
self.data = []
|
||||||
self.average_buying_rate = {}
|
self.average_buying_rate = {}
|
||||||
self.filters = frappe._dict(filters)
|
self.filters = frappe._dict(filters)
|
||||||
self.load_invoice_items()
|
self.load_invoice_items()
|
||||||
|
|
||||||
|
if filters.group_by == 'Invoice':
|
||||||
|
self.group_items_by_invoice()
|
||||||
|
|
||||||
self.load_stock_ledger_entries()
|
self.load_stock_ledger_entries()
|
||||||
self.load_product_bundle()
|
self.load_product_bundle()
|
||||||
self.load_non_stock_items()
|
self.load_non_stock_items()
|
||||||
@@ -112,7 +164,12 @@ class GrossProfitGenerator(object):
|
|||||||
self.currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
|
self.currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
|
||||||
self.float_precision = cint(frappe.db.get_default("float_precision")) or 2
|
self.float_precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||||
|
|
||||||
for row in self.si_list:
|
grouped_by_invoice = True if self.filters.get("group_by") == "Invoice" else False
|
||||||
|
|
||||||
|
if grouped_by_invoice:
|
||||||
|
buying_amount = 0
|
||||||
|
|
||||||
|
for row in reversed(self.si_list):
|
||||||
if self.skip_row(row, self.product_bundles):
|
if self.skip_row(row, self.product_bundles):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -134,12 +191,20 @@ class GrossProfitGenerator(object):
|
|||||||
row.buying_amount = flt(self.get_buying_amount(row, row.item_code),
|
row.buying_amount = flt(self.get_buying_amount(row, row.item_code),
|
||||||
self.currency_precision)
|
self.currency_precision)
|
||||||
|
|
||||||
|
if grouped_by_invoice:
|
||||||
|
if row.indent == 1.0:
|
||||||
|
buying_amount += row.buying_amount
|
||||||
|
elif row.indent == 0.0:
|
||||||
|
row.buying_amount = buying_amount
|
||||||
|
buying_amount = 0
|
||||||
|
|
||||||
# get buying rate
|
# get buying rate
|
||||||
if row.qty:
|
if row.qty:
|
||||||
row.buying_rate = flt(row.buying_amount / row.qty, self.float_precision)
|
row.buying_rate = flt(row.buying_amount / row.qty, self.float_precision)
|
||||||
row.base_rate = flt(row.base_amount / row.qty, self.float_precision)
|
row.base_rate = flt(row.base_amount / row.qty, self.float_precision)
|
||||||
else:
|
else:
|
||||||
row.buying_rate, row.base_rate = 0.0, 0.0
|
if self.is_not_invoice_row(row):
|
||||||
|
row.buying_rate, row.base_rate = 0.0, 0.0
|
||||||
|
|
||||||
# calculate gross profit
|
# calculate gross profit
|
||||||
row.gross_profit = flt(row.base_amount - row.buying_amount, self.currency_precision)
|
row.gross_profit = flt(row.base_amount - row.buying_amount, self.currency_precision)
|
||||||
@@ -185,14 +250,17 @@ class GrossProfitGenerator(object):
|
|||||||
for returned_item_row in returned_item_rows:
|
for returned_item_row in returned_item_rows:
|
||||||
row.qty += returned_item_row.qty
|
row.qty += returned_item_row.qty
|
||||||
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
||||||
row.buying_amount = flt(row.qty * row.buying_rate, self.currency_precision)
|
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
|
||||||
if row.qty or row.base_amount:
|
if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row):
|
||||||
row = self.set_average_rate(row)
|
row = self.set_average_rate(row)
|
||||||
self.grouped_data.append(row)
|
self.grouped_data.append(row)
|
||||||
self.add_to_totals(row)
|
self.add_to_totals(row)
|
||||||
self.set_average_gross_profit(self.totals)
|
self.set_average_gross_profit(self.totals)
|
||||||
self.grouped_data.append(self.totals)
|
self.grouped_data.append(self.totals)
|
||||||
|
|
||||||
|
def is_not_invoice_row(self, row):
|
||||||
|
return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice"
|
||||||
|
|
||||||
def set_average_rate(self, new_row):
|
def set_average_rate(self, new_row):
|
||||||
self.set_average_gross_profit(new_row)
|
self.set_average_gross_profit(new_row)
|
||||||
new_row.buying_rate = flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
|
new_row.buying_rate = flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
|
||||||
@@ -356,6 +424,109 @@ class GrossProfitGenerator(object):
|
|||||||
.format(conditions=conditions, sales_person_cols=sales_person_cols,
|
.format(conditions=conditions, sales_person_cols=sales_person_cols,
|
||||||
sales_team_table=sales_team_table, match_cond = get_match_cond('Sales Invoice')), self.filters, as_dict=1)
|
sales_team_table=sales_team_table, match_cond = get_match_cond('Sales Invoice')), self.filters, as_dict=1)
|
||||||
|
|
||||||
|
def group_items_by_invoice(self):
|
||||||
|
"""
|
||||||
|
Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parents = []
|
||||||
|
|
||||||
|
for row in self.si_list:
|
||||||
|
if row.parent not in parents:
|
||||||
|
parents.append(row.parent)
|
||||||
|
|
||||||
|
parents_index = 0
|
||||||
|
for index, row in enumerate(self.si_list):
|
||||||
|
if parents_index < len(parents) and row.parent == parents[parents_index]:
|
||||||
|
invoice = self.get_invoice_row(row)
|
||||||
|
self.si_list.insert(index, invoice)
|
||||||
|
parents_index += 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
# skipping the bundle items rows
|
||||||
|
if not row.indent:
|
||||||
|
row.indent = 1.0
|
||||||
|
row.parent_invoice = row.parent
|
||||||
|
row.parent = row.item_code
|
||||||
|
|
||||||
|
if frappe.db.exists('Product Bundle', row.item_code):
|
||||||
|
self.add_bundle_items(row, index)
|
||||||
|
|
||||||
|
def get_invoice_row(self, row):
|
||||||
|
return frappe._dict({
|
||||||
|
'parent_invoice': "",
|
||||||
|
'indent': 0.0,
|
||||||
|
'parent': row.parent,
|
||||||
|
'posting_date': row.posting_date,
|
||||||
|
'posting_time': row.posting_time,
|
||||||
|
'project': row.project,
|
||||||
|
'update_stock': row.update_stock,
|
||||||
|
'customer': row.customer,
|
||||||
|
'customer_group': row.customer_group,
|
||||||
|
'item_code': None,
|
||||||
|
'item_name': None,
|
||||||
|
'description': None,
|
||||||
|
'warehouse': None,
|
||||||
|
'item_group': None,
|
||||||
|
'brand': None,
|
||||||
|
'dn_detail': None,
|
||||||
|
'delivery_note': None,
|
||||||
|
'qty': None,
|
||||||
|
'item_row': None,
|
||||||
|
'is_return': row.is_return,
|
||||||
|
'cost_center': row.cost_center,
|
||||||
|
'base_net_amount': frappe.db.get_value('Sales Invoice', row.parent, 'base_net_total')
|
||||||
|
})
|
||||||
|
|
||||||
|
def add_bundle_items(self, product_bundle, index):
|
||||||
|
bundle_items = self.get_bundle_items(product_bundle)
|
||||||
|
|
||||||
|
for i, item in enumerate(bundle_items):
|
||||||
|
bundle_item = self.get_bundle_item_row(product_bundle, item)
|
||||||
|
self.si_list.insert((index+i+1), bundle_item)
|
||||||
|
|
||||||
|
def get_bundle_items(self, product_bundle):
|
||||||
|
return frappe.get_all(
|
||||||
|
'Product Bundle Item',
|
||||||
|
filters = {
|
||||||
|
'parent': product_bundle.item_code
|
||||||
|
},
|
||||||
|
fields = ['item_code', 'qty']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_bundle_item_row(self, product_bundle, item):
|
||||||
|
item_name, description, item_group, brand = self.get_bundle_item_details(item.item_code)
|
||||||
|
|
||||||
|
return frappe._dict({
|
||||||
|
'parent_invoice': product_bundle.item_code,
|
||||||
|
'indent': product_bundle.indent + 1,
|
||||||
|
'parent': item.item_code,
|
||||||
|
'posting_date': product_bundle.posting_date,
|
||||||
|
'posting_time': product_bundle.posting_time,
|
||||||
|
'project': product_bundle.project,
|
||||||
|
'customer': product_bundle.customer,
|
||||||
|
'customer_group': product_bundle.customer_group,
|
||||||
|
'item_code': item.item_code,
|
||||||
|
'item_name': item_name,
|
||||||
|
'description': description,
|
||||||
|
'warehouse': product_bundle.warehouse,
|
||||||
|
'item_group': item_group,
|
||||||
|
'brand': brand,
|
||||||
|
'dn_detail': product_bundle.dn_detail,
|
||||||
|
'delivery_note': product_bundle.delivery_note,
|
||||||
|
'qty': (flt(product_bundle.qty) * flt(item.qty)),
|
||||||
|
'item_row': None,
|
||||||
|
'is_return': product_bundle.is_return,
|
||||||
|
'cost_center': product_bundle.cost_center
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_bundle_item_details(self, item_code):
|
||||||
|
return frappe.db.get_value(
|
||||||
|
'Item',
|
||||||
|
item_code,
|
||||||
|
['item_name', 'description', 'item_group', 'brand']
|
||||||
|
)
|
||||||
|
|
||||||
def load_stock_ledger_entries(self):
|
def load_stock_ledger_entries(self):
|
||||||
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
||||||
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
||||||
|
|||||||
Reference in New Issue
Block a user