Merge pull request #2442 from ankitjavalkarwork/gross-profit
Gross profit
This commit is contained in:
commit
4f7c51d68e
@ -22,5 +22,12 @@ frappe.query_reports["Gross Profit"] = {
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date")
|
||||
},
|
||||
{
|
||||
"fieldname":"group_by",
|
||||
"label": __("Group By"),
|
||||
"fieldtype": "Select",
|
||||
"options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject",
|
||||
"default": "Invoice"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -3,130 +3,249 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from erpnext.stock.utils import get_buying_amount, get_sales_bom_buying_amount
|
||||
from frappe import _, scrub
|
||||
from frappe.utils import flt, cstr, cint
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters: filters = {}
|
||||
gross_profit_data = GrossProfitGenerator(filters)
|
||||
|
||||
stock_ledger_entries = get_stock_ledger_entries(filters)
|
||||
source = get_source_data(filters)
|
||||
item_sales_bom = get_item_sales_bom()
|
||||
|
||||
total_gross_profit = 0.0
|
||||
total_selling_amount = 0.0
|
||||
total_buying_amount = 0.0
|
||||
|
||||
columns = [_("Delivery Note/Sales Invoice") + "::120", _("Link") + "::30", _("Posting Date") + ":Date", _("Posting Time"),
|
||||
_("Item Code") + ":Link/Item", _("Item Name"), _("Description"), _("Warehouse") + ":Link/Warehouse",
|
||||
_("Qty") + ":Float", _("Selling Rate") + ":Currency", _("Avg. Buying Rate") + ":Currency",
|
||||
_("Selling Amount") + ":Currency", _("Buying Amount") + ":Currency",
|
||||
_("Gross Profit") + ":Currency", _("Gross Profit %") + ":Percent", _("Project") + ":Link/Project"]
|
||||
data = []
|
||||
for row in source:
|
||||
selling_amount = flt(row.base_amount)
|
||||
total_selling_amount += flt(row.base_amount)
|
||||
source = gross_profit_data.grouped_data if filters.get("group_by") != "Invoice" else gross_profit_data.data
|
||||
|
||||
item_sales_bom_map = item_sales_bom.get(row.parenttype, {}).get(row.name, frappe._dict())
|
||||
group_wise_columns = frappe._dict({
|
||||
"invoice": ["name", "posting_date", "posting_time", "item_code", "item_name", "brand", "description", \
|
||||
"warehouse", "qty", "base_rate", "buying_rate", "base_amount",
|
||||
"buying_amount", "gross_profit", "gross_profit_percent", "project"],
|
||||
"item_code": ["item_code", "item_name", "brand", "description", "warehouse", "qty", "base_rate",
|
||||
"buying_rate", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent"],
|
||||
"warehouse": ["warehouse", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
|
||||
"gross_profit", "gross_profit_percent"],
|
||||
"territory": ["territory", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
|
||||
"gross_profit", "gross_profit_percent"],
|
||||
"brand": ["brand", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
|
||||
"gross_profit", "gross_profit_percent"],
|
||||
"item_group": ["item_group", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
|
||||
"gross_profit", "gross_profit_percent"],
|
||||
"customer": ["customer", "customer_group", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
|
||||
"gross_profit", "gross_profit_percent"],
|
||||
"customer_group": ["customer_group", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
|
||||
"gross_profit", "gross_profit_percent"],
|
||||
"sales_person": ["sales_person", "allocated_amount", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount",
|
||||
"gross_profit", "gross_profit_percent"],
|
||||
"project": ["project", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent"],
|
||||
"territory": ["territory", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent"]
|
||||
})
|
||||
|
||||
if item_sales_bom_map.get(row.item_code):
|
||||
buying_amount = get_sales_bom_buying_amount(row.item_code, row.warehouse,
|
||||
row.parenttype, row.name, row.item_row, stock_ledger_entries, item_sales_bom_map)
|
||||
else:
|
||||
buying_amount = get_buying_amount(row.item_code, row.qty, row.parenttype, row.name, row.item_row,
|
||||
stock_ledger_entries.get((row.item_code, row.warehouse), []))
|
||||
columns = get_columns(group_wise_columns, filters)
|
||||
|
||||
buying_amount = buying_amount > 0 and buying_amount or 0
|
||||
total_buying_amount += buying_amount
|
||||
|
||||
gross_profit = selling_amount - buying_amount
|
||||
total_gross_profit += gross_profit
|
||||
|
||||
if selling_amount:
|
||||
gross_profit_percent = (gross_profit / selling_amount) * 100.0
|
||||
else:
|
||||
gross_profit_percent = 0.0
|
||||
|
||||
icon = """<a href="%s"><i class="icon icon-share" style="cursor: pointer;"></i></a>""" \
|
||||
% ("/".join(["#Form", row.parenttype, row.name]),)
|
||||
data.append([row.name, icon, row.posting_date, row.posting_time, row.item_code, row.item_name,
|
||||
row.description, row.warehouse, row.qty, row.base_rate,
|
||||
row.qty and (buying_amount / row.qty) or 0, row.base_amount, buying_amount,
|
||||
gross_profit, gross_profit_percent, row.project])
|
||||
|
||||
if total_selling_amount:
|
||||
total_gross_profit_percent = (total_gross_profit / total_selling_amount) * 100.0
|
||||
else:
|
||||
total_gross_profit_percent = 0.0
|
||||
|
||||
data.append(["Total", None, None, None, None, None, None, None, None, None, None, total_selling_amount,
|
||||
total_buying_amount, total_gross_profit, total_gross_profit_percent, None])
|
||||
for src in source:
|
||||
row = []
|
||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||
row.append(src.get(col))
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_stock_ledger_entries(filters):
|
||||
query = """select item_code, voucher_type, voucher_no,
|
||||
voucher_detail_no, posting_date, posting_time, stock_value,
|
||||
warehouse, actual_qty as qty
|
||||
from `tabStock Ledger Entry`"""
|
||||
def get_columns(group_wise_columns, filters):
|
||||
columns = []
|
||||
column_map = frappe._dict({
|
||||
"name": _("Sales Invoice") + "::120",
|
||||
"posting_date": _("Posting Date") + ":Date",
|
||||
"posting_time": _("Posting Time"),
|
||||
"item_code": _("Item Code") + ":Link/Item",
|
||||
"item_name": _("Item Name"),
|
||||
"item_group": _("Item Group") + ":Link/Item",
|
||||
"brand": _("Brand"),
|
||||
"description": _("Description"),
|
||||
"warehouse": _("Warehouse") + ":Link/Warehouse",
|
||||
"qty": _("Qty") + ":Float",
|
||||
"base_rate": _("Avg. Selling Rate") + ":Currency",
|
||||
"buying_rate": _("Avg. Buying Rate") + ":Currency",
|
||||
"base_amount": _("Selling Amount") + ":Currency",
|
||||
"buying_amount": _("Buying Amount") + ":Currency",
|
||||
"gross_profit": _("Gross Profit") + ":Currency",
|
||||
"gross_profit_percent": _("Gross Profit %") + ":Percent",
|
||||
"project": _("Project") + ":Link/Project",
|
||||
"sales_person": _("Sales person"),
|
||||
"allocated_amount": _("Allocated Amount") + ":Currency",
|
||||
"customer": _("Customer") + ":Link/Customer",
|
||||
"customer_group": _("Customer Group") + ":Link/Customer Group",
|
||||
"territory": _("Territory") + ":Link/Territory"
|
||||
})
|
||||
|
||||
if filters.get("company"):
|
||||
query += """ where company=%(company)s"""
|
||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||
columns.append(column_map.get(col))
|
||||
|
||||
query += " order by item_code desc, warehouse desc, posting_date desc, posting_time desc, name desc"
|
||||
return columns
|
||||
|
||||
res = frappe.db.sql(query, filters, as_dict=True)
|
||||
class GrossProfitGenerator(object):
|
||||
def __init__(self, filters=None):
|
||||
self.data = []
|
||||
self.filters = frappe._dict(filters)
|
||||
self.load_invoice_items()
|
||||
self.load_stock_ledger_entries()
|
||||
self.load_sales_bom()
|
||||
self.load_non_stock_items()
|
||||
self.process()
|
||||
|
||||
out = {}
|
||||
for r in res:
|
||||
if (r.item_code, r.warehouse) not in out:
|
||||
out[(r.item_code, r.warehouse)] = []
|
||||
def process(self):
|
||||
self.grouped = {}
|
||||
for row in self.si_list:
|
||||
if self.skip_row(row, self.sales_boms):
|
||||
continue
|
||||
|
||||
out[(r.item_code, r.warehouse)].append(r)
|
||||
row.selling_amount = flt(row.base_amount)
|
||||
|
||||
return out
|
||||
sales_boms = self.sales_boms.get(row.parenttype, {}).get(row.name, frappe._dict())
|
||||
|
||||
def get_item_sales_bom():
|
||||
item_sales_bom = {}
|
||||
# get buying amount
|
||||
if row.item_code in sales_boms:
|
||||
row.buying_amount = self.get_buying_amount_from_sales_bom(row, sales_boms[row.item_code])
|
||||
else:
|
||||
row.buying_amount = self.get_buying_amount(row, row.item_code)
|
||||
|
||||
for d in frappe.db.sql("""select parenttype, parent, parent_item,
|
||||
item_code, warehouse, -1*qty as total_qty, parent_detail_docname
|
||||
from `tabPacked Item` where docstatus=1""", as_dict=True):
|
||||
item_sales_bom.setdefault(d.parenttype, frappe._dict()).setdefault(d.parent,
|
||||
frappe._dict()).setdefault(d.parent_item, []).append(d)
|
||||
# get buying rate
|
||||
if row.qty:
|
||||
row.buying_rate = (row.buying_amount / row.qty) * 100.0
|
||||
else:
|
||||
row.buying_rate = 0.0
|
||||
|
||||
return item_sales_bom
|
||||
# calculate gross profit
|
||||
row.gross_profit = row.selling_amount - row.buying_amount
|
||||
if row.selling_amount:
|
||||
row.gross_profit_percent = (row.gross_profit / row.selling_amount) * 100.0
|
||||
else:
|
||||
row.gross_profit_percent = 0.0
|
||||
|
||||
def get_source_data(filters):
|
||||
conditions = ""
|
||||
if filters.get("company"):
|
||||
conditions += " and company=%(company)s"
|
||||
if filters.get("from_date"):
|
||||
conditions += " and posting_date>=%(from_date)s"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and posting_date<=%(to_date)s"
|
||||
# add to grouped
|
||||
if self.filters.group_by != "Invoice":
|
||||
self.grouped.setdefault(row.get(scrub(self.filters.group_by)), []).append(row)
|
||||
|
||||
delivery_note_items = frappe.db.sql("""select item.parenttype, dn.name,
|
||||
dn.posting_date, dn.posting_time, dn.project_name,
|
||||
item.item_code, item.item_name, item.description, item.warehouse,
|
||||
item.qty, item.base_rate, item.base_amount, item.name as "item_row",
|
||||
timestamp(dn.posting_date, dn.posting_time) as posting_datetime
|
||||
from `tabDelivery Note` dn, `tabDelivery Note Item` item
|
||||
where item.parent = dn.name and dn.docstatus = 1 %s
|
||||
order by dn.posting_date desc, dn.posting_time desc""" % (conditions,), filters, as_dict=1)
|
||||
self.data.append(row)
|
||||
|
||||
sales_invoice_items = frappe.db.sql("""select item.parenttype, si.name,
|
||||
si.posting_date, si.posting_time, si.project_name,
|
||||
item.item_code, item.item_name, item.description, item.warehouse,
|
||||
item.qty, item.base_rate, item.base_amount, item.name as "item_row",
|
||||
timestamp(si.posting_date, si.posting_time) as posting_datetime
|
||||
from `tabSales Invoice` si, `tabSales Invoice Item` item
|
||||
where item.parent = si.name and si.docstatus = 1 %s
|
||||
order by si.posting_date desc, si.posting_time desc""" % (conditions,), filters, as_dict=1)
|
||||
if self.grouped:
|
||||
self.collapse_group()
|
||||
else:
|
||||
self.grouped_data = []
|
||||
|
||||
source = delivery_note_items + sales_invoice_items
|
||||
if len(source) > len(delivery_note_items):
|
||||
source.sort(key=lambda d: d.posting_datetime, reverse=True)
|
||||
def collapse_group(self):
|
||||
# sum buying / selling totals for group
|
||||
self.grouped_data = []
|
||||
for key in self.grouped.keys():
|
||||
for i, row in enumerate(self.grouped[key]):
|
||||
if i==0:
|
||||
new_row = row
|
||||
else:
|
||||
new_row.qty += row.qty
|
||||
new_row.buying_amount += row.buying_amount
|
||||
new_row.selling_amount += row.selling_amount
|
||||
# new_row.allocated_amount += (row.allocated_amount or 0) if new_row.allocated_amount else 0
|
||||
|
||||
return source
|
||||
new_row.gross_profit = new_row.selling_amount - new_row.buying_amount
|
||||
new_row.gross_profit_percent = ((new_row.gross_profit / new_row.selling_amount) * 100.0) \
|
||||
if new_row.selling_amount else 0
|
||||
new_row.buying_rate = ((new_row.buying_amount / new_row.qty) * 100.0) \
|
||||
if new_row.qty else 0
|
||||
|
||||
self.grouped_data.append(new_row)
|
||||
|
||||
def skip_row(self, row, sales_boms):
|
||||
if cint(row.update_stock) == 0 and not row.dn_detail:
|
||||
if row.item_code not in self.non_stock_items:
|
||||
return True
|
||||
elif row.item_code in sales_boms:
|
||||
for child_item in sales_boms[row.item_code]:
|
||||
if child_item not in self.non_stock_items:
|
||||
return True
|
||||
elif self.filters.get("group_by") != "Invoice" and not row.get(scrub(self.filters.get("group_by"))):
|
||||
return True
|
||||
|
||||
def get_buying_amount_from_sales_bom(self, row, sales_bom):
|
||||
buying_amount = 0.0
|
||||
for bom_item in sales_bom[row.item_code]:
|
||||
if bom_item.get("parent_detail_docname")==row.name:
|
||||
buying_amount += self.get_buying_amount(row, bom_item.item_code)
|
||||
|
||||
return buying_amount
|
||||
|
||||
def get_buying_amount(self, row, item_code):
|
||||
# IMP NOTE
|
||||
# stock_ledger_entries should already be filtered by item_code and warehouse and
|
||||
# sorted by posting_date desc, posting_time desc
|
||||
if item_code in self.non_stock_items:
|
||||
# average purchasing rate for non-stock items
|
||||
item_rate = frappe.db.sql("""select sum(base_amount) / sum(qty)
|
||||
from `tabPurchase Invoice Item`
|
||||
where item_code = %s and docstatus=1""", item_code)
|
||||
|
||||
return flt(row.qty) * (flt(item_rate[0][0]) if item_rate else 0)
|
||||
|
||||
else:
|
||||
if row.dn_detail:
|
||||
row.parenttype = "Delivery Note"
|
||||
row.parent = row.delivery_note
|
||||
row.name = row.dn_detail
|
||||
my_sle = self.sle.get((item_code, row.warehouse))
|
||||
for i, sle in enumerate(my_sle):
|
||||
# find the stock valution rate from stock ledger entry
|
||||
if sle.voucher_type == row.parenttype and row.parent == sle.voucher_no and \
|
||||
sle.voucher_detail_no == row.name:
|
||||
previous_stock_value = len(my_sle) > i+1 and \
|
||||
flt(my_sle[i+1].stock_value) or 0.0
|
||||
return previous_stock_value - flt(sle.stock_value)
|
||||
|
||||
return 0.0
|
||||
|
||||
def load_invoice_items(self):
|
||||
conditions = ""
|
||||
if self.filters.company:
|
||||
conditions += " and company = %(company)s"
|
||||
if self.filters.from_date:
|
||||
conditions += " and posting_date >= %(from_date)s"
|
||||
if self.filters.to_date:
|
||||
conditions += " and posting_date <= %(to_date)s"
|
||||
|
||||
self.si_list = frappe.db.sql("""select item.parenttype, si.name,
|
||||
si.posting_date, si.posting_time, si.project_name, si.update_stock,
|
||||
si.customer, si.customer_group, si.territory,
|
||||
item.item_code, item.item_name, item.description, item.warehouse,
|
||||
item.item_group, item.brand, item.dn_detail, item.delivery_note,
|
||||
item.qty, item.base_rate, item.base_amount, item.name as "item_row",
|
||||
sales.sales_person, sales.sales_designation, sales.allocated_amount,
|
||||
sales.incentives
|
||||
from `tabSales Invoice` si
|
||||
inner join `tabSales Invoice Item` item on item.parent = si.name
|
||||
left join `tabSales Team` sales on sales.parent = si.name
|
||||
where
|
||||
si.docstatus = 1 %s
|
||||
order by
|
||||
si.posting_date desc, si.posting_time desc""" % (conditions,), self.filters, as_dict=1)
|
||||
|
||||
def load_stock_ledger_entries(self):
|
||||
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
||||
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
||||
from `tabStock Ledger Entry`
|
||||
where company=%(company)s
|
||||
order by
|
||||
item_code desc, warehouse desc, posting_date desc,
|
||||
posting_time desc, name desc""", self.filters, as_dict=True)
|
||||
self.sle = {}
|
||||
for r in res:
|
||||
if (r.item_code, r.warehouse) not in self.sle:
|
||||
self.sle[(r.item_code, r.warehouse)] = []
|
||||
|
||||
self.sle[(r.item_code, r.warehouse)].append(r)
|
||||
|
||||
def load_sales_bom(self):
|
||||
self.sales_boms = {}
|
||||
|
||||
for d in frappe.db.sql("""select parenttype, parent, parent_item,
|
||||
item_code, warehouse, -1*qty as total_qty, parent_detail_docname
|
||||
from `tabPacked Item` where docstatus=1""", as_dict=True):
|
||||
self.sales_boms.setdefault(d.parenttype, frappe._dict()).setdefault(d.parent,
|
||||
frappe._dict()).setdefault(d.parent_item, []).append(d)
|
||||
|
||||
def load_non_stock_items(self):
|
||||
self.non_stock_items = frappe.db.sql_list("""select name from tabItem
|
||||
where ifnull(is_stock_item, 'No')='No'""")
|
||||
|
@ -166,37 +166,3 @@ def validate_warehouse_company(warehouse, company):
|
||||
frappe.throw(_("Warehouse {0} does not belong to company {1}").format(warehouse, company),
|
||||
InvalidWarehouseCompany)
|
||||
|
||||
def get_sales_bom_buying_amount(item_code, warehouse, voucher_type, voucher_no, voucher_detail_no,
|
||||
stock_ledger_entries, item_sales_bom):
|
||||
# sales bom item
|
||||
buying_amount = 0.0
|
||||
for bom_item in item_sales_bom[item_code]:
|
||||
if bom_item.get("parent_detail_docname")==voucher_detail_no:
|
||||
buying_amount += get_buying_amount(voucher_type, voucher_no, voucher_detail_no,
|
||||
stock_ledger_entries.get((bom_item.item_code, warehouse), []))
|
||||
|
||||
return buying_amount
|
||||
|
||||
def get_buying_amount(item_code, item_qty, voucher_type, voucher_no, item_row, stock_ledger_entries):
|
||||
# IMP NOTE
|
||||
# stock_ledger_entries should already be filtered by item_code and warehouse and
|
||||
# sorted by posting_date desc, posting_time desc
|
||||
if frappe.db.get_value("Item", item_code, "is_stock_item") == "Yes":
|
||||
for i, sle in enumerate(stock_ledger_entries):
|
||||
if sle.voucher_type == voucher_type and sle.voucher_no == voucher_no and \
|
||||
sle.voucher_detail_no == item_row:
|
||||
previous_stock_value = len(stock_ledger_entries) > i+1 and \
|
||||
flt(stock_ledger_entries[i+1].stock_value) or 0.0
|
||||
buying_amount = previous_stock_value - flt(sle.stock_value)
|
||||
|
||||
return buying_amount
|
||||
else:
|
||||
item_rate = frappe.db.sql("""select sum(base_amount) / sum(qty)
|
||||
from `tabPurchase Invoice Item`
|
||||
where item_code = %s and docstatus=1""" % ('%s'), item_code)
|
||||
buying_amount = flt(item_qty) * flt(item_rate[0][0]) if item_rate else 0
|
||||
|
||||
return buying_amount
|
||||
|
||||
return 0.0
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user