Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Stock Ledger Report): Group By, Party Column #17669

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions erpnext/stock/report/stock_balance/stock_balance.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.utils import flt, cint, getdate, now
from erpnext.stock.utils import update_included_uom_in_report
from erpnext.stock.utils import update_included_uom_in_list_report
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition

from six import iteritems
Expand Down Expand Up @@ -65,7 +65,7 @@ def execute(filters=None):
if filters.get('show_variant_attributes', 0) == 1:
columns += ["{}:Data:100".format(i) for i in get_variants_attributes()]

update_included_uom_in_report(columns, data, include_uom, conversion_factors)
update_included_uom_in_list_report(columns, data, include_uom, conversion_factors)
return columns, data

def get_columns():
Expand Down
9 changes: 8 additions & 1 deletion erpnext/stock/report/stock_ledger/stock_ledger.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ frappe.query_reports["Stock Ledger"] = {
"label": __("Include UOM"),
"fieldtype": "Link",
"options": "UOM"
},
{
"fieldname":"group_by",
"label": __("Group By"),
"fieldtype": "Select",
"options": "Ungrouped\nGroup by Item-Warehouse\nGroup by Item\nGroup by Warehouse\nGroup by Item Group\nGroup by Brand\nGroup by Party\nGroup by Voucher",
"default": "Ungrouped"
}
]
}
Expand All @@ -84,4 +91,4 @@ frappe.query_reports["Stock Ledger"] = {
// $(wrapper).bind("show", function() {
// frappe.query_report.load();
// });
// });
// });
153 changes: 124 additions & 29 deletions erpnext/stock/report/stock_ledger/stock_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@

from __future__ import unicode_literals
import frappe
from frappe import _
from erpnext.stock.utils import update_included_uom_in_report
from frappe import _, scrub
from erpnext.stock.utils import update_included_uom_in_dict_report
from frappe.desk.query_report import group_report_data
from six import iteritems

def execute(filters=None):
include_uom = filters.get("include_uom")
columns = get_columns()
items = get_items(filters)
sl_entries = get_stock_ledger_entries(filters, items)
item_details = get_item_details(items, sl_entries, include_uom)
opening_row = get_opening_balance(filters, columns)
voucher_party_details = get_voucher_party_details(sl_entries)
opening_row = get_opening_balance(filters.item_code, filters.warehouse, filters.from_date)

data = []
conversion_factors = []
Expand All @@ -21,40 +24,67 @@ def execute(filters=None):

for sle in sl_entries:
item_detail = item_details[sle.item_code]
party_detail = voucher_party_details.get(sle.voucher_no, frappe._dict())

data.append([sle.date, sle.item_code, item_detail.item_name, item_detail.item_group,
item_detail.brand, item_detail.description, sle.warehouse,
item_detail.stock_uom, sle.actual_qty, sle.qty_after_transaction,
(sle.incoming_rate if sle.actual_qty > 0 else 0.0),
sle.valuation_rate, sle.stock_value, sle.voucher_type, sle.voucher_no,
sle.batch_no, sle.serial_no, sle.project, sle.company])
row = frappe._dict({
"date": sle.date,
"item_code": sle.item_code,
"item_name": item_detail.item_name,
"item_group": item_detail.item_group,
"brand": item_detail.brand,
"description": item_detail.description,
"warehouse": sle.warehouse,
"party_type": party_detail.party_type,
"party": party_detail.party,
"stock_uom": item_detail.stock_uom,
"actual_qty": sle.actual_qty,
"qty_after_transaction": sle.qty_after_transaction,
"valuation_rate": sle.valuation_rate,
"stock_value": sle.stock_value,
"voucher_type": sle.voucher_type,
"voucher_no": sle.voucher_no,
"batch_no": sle.batch_no,
"serial_no": sle.serial_no,
"project": sle.project,
"company": sle.company
})
if sle.actual_qty:
if sle.actual_qty > 0:
row['transaction_rate'] = sle.incoming_rate
else:
row['transaction_rate'] = sle.stock_value_difference / sle.actual_qty
data.append(row)

if include_uom:
conversion_factors.append(item_detail.conversion_factor)

update_included_uom_in_report(columns, data, include_uom, conversion_factors)
update_included_uom_in_dict_report(columns, data, include_uom, conversion_factors)

data = get_grouped_data(filters, data)
return columns, data

def get_columns():
columns = [
{"label": _("Date"), "fieldname": "date", "fieldtype": "Datetime", "width": 95},
{"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 130},
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110},
{"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100},
{"label": _("Party Type"), "fieldname": "party_type", "fieldtype": "Data", "width": 80},
{"label": _("Party"), "fieldname": "party", "fieldtype": "Dynamic Link", "options": "party_type", "width": 100},
{"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 150},
{"label": _("Item Name"), "fieldname": "item_name", "width": 100},
{"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
{"label": _("Brand"), "fieldname": "brand", "fieldtype": "Link", "options": "Brand", "width": 100},
{"label": _("Description"), "fieldname": "description", "width": 200},
{"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100},
{"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 100},
{"label": _("Qty"), "fieldname": "actual_qty", "fieldtype": "Float", "width": 50, "convertible": "qty"},
{"label": _("Balance Qty"), "fieldname": "qty_after_transaction", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Incoming Rate"), "fieldname": "incoming_rate", "fieldtype": "Currency", "width": 110,
{"label": _("UOM"), "fieldname": "uom", "fieldtype": "Link", "options": "UOM", "width": 50},
{"label": _("Qty"), "fieldname": "actual_qty", "fieldtype": "Float", "width": 60, "convertible": "qty"},
{"label": _("Balance Qty"), "fieldname": "qty_after_transaction", "fieldtype": "Float", "width": 90, "convertible": "qty"},
{"label": _("Transaction Rate"), "fieldname": "transaction_rate", "fieldtype": "Currency", "width": 110,
"options": "Company:company:default_currency", "convertible": "rate"},
{"label": _("Valuation Rate"), "fieldname": "valuation_rate", "fieldtype": "Currency", "width": 110,
"options": "Company:company:default_currency", "convertible": "rate"},
{"label": _("Balance Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": 110,
"options": "Company:company:default_currency"},
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110},
{"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100},
{"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100},
{"label": _("Serial #"), "fieldname": "serial_no", "fieldtype": "Link", "options": "Serial No", "width": 100},
{"label": _("Project"), "fieldname": "project", "fieldtype": "Link", "options": "Project", "width": 100},
Expand All @@ -71,7 +101,7 @@ def get_stock_ledger_entries(filters, items):

return frappe.db.sql("""select concat_ws(" ", posting_date, posting_time) as date,
item_code, warehouse, actual_qty, qty_after_transaction, incoming_rate, valuation_rate,
stock_value, voucher_type, voucher_no, batch_no, serial_no, company, project
stock_value, voucher_type, voucher_no, batch_no, serial_no, company, project, stock_value_difference
from `tabStock Ledger Entry` sle
where company = %(company)s and
posting_date between %(from_date)s and %(to_date)s
Expand Down Expand Up @@ -128,6 +158,35 @@ def get_item_details(items, sl_entries, include_uom):

return item_details

def get_voucher_party_details(sl_entries):
voucher_party_type = {
"Delivery Note": "Customer",
"Sales Invoice": "Customer",
"Purchase Receipt": "Supplier",
"Purchase Invoice": "Supplier"
}
voucher_map = {}
for d in sl_entries:
if d.voucher_type in voucher_party_type:
voucher_map.setdefault(d.voucher_type, set()).add(d.voucher_no)

voucher_party_details = frappe._dict()
for voucher_type, vouchers in iteritems(voucher_map):
data = frappe.db.sql("""
select name, {field}
from `tab{dt}`
where name in ({dns})
""".format( # nosec
field=scrub(voucher_party_type[voucher_type]),
dt=voucher_type,
dns=", ".join(["%s"] * len(vouchers))
), list(vouchers))

for d in data:
voucher_party_details[d[0]] = frappe._dict({"party_type": voucher_party_type[voucher_type], "party": d[1]})

return voucher_party_details

def get_sle_conditions(filters):
conditions = []
if filters.get("warehouse"):
Expand All @@ -143,24 +202,60 @@ def get_sle_conditions(filters):

return "and {}".format(" and ".join(conditions)) if conditions else ""

def get_opening_balance(filters, columns):
if not (filters.item_code and filters.warehouse and filters.from_date):
return
def get_opening_balance(item_code, warehouse, from_date, from_time="00:00:00"):
if not (item_code and warehouse and from_date):
return frappe._dict()

from erpnext.stock.stock_ledger import get_previous_sle
last_entry = get_previous_sle({
"item_code": filters.item_code,
"warehouse_condition": get_warehouse_condition(filters.warehouse),
"posting_date": filters.from_date,
"posting_time": "00:00:00"
"item_code": item_code,
"warehouse_condition": get_warehouse_condition(warehouse),
"posting_date": from_date,
"posting_time": from_time
})
row = {}
row["item_code"] = _("'Opening'")
for dummy, v in ((9, 'qty_after_transaction'), (11, 'valuation_rate'), (12, 'stock_value')):
row[v] = last_entry.get(v, 0)
row = frappe._dict()
row["voucher_type"] = _("Opening")
for f in ('qty_after_transaction', 'valuation_rate', 'stock_value'):
row[f] = last_entry.get(f, 0)

return row

def get_grouped_data(filters, data):
if not filters.get("group_by") or filters.get("group_by") == "Ungrouped":
return data

group_by = []
group_by_label = filters.group_by.replace("Group by ", "")
if group_by_label == "Item-Warehouse":
group_by += ['item_code', 'warehouse']
elif group_by_label == "Item":
group_by.append('item_code')
elif group_by_label == "Party":
group_by += ['party', 'party_type']
elif group_by_label == "Voucher":
group_by.append(('voucher_no', 'voucher_type'))
else:
group_by.append(scrub(group_by_label))

def postprocess_group(group_object, grouped_by):
if group_by_label in ["Item-Warehouse", "Party"] and len(grouped_by) < 2:
return

group_header = frappe._dict({})
if 'item_code' in grouped_by and 'warehouse' in grouped_by and filters.from_date:
opening_dt = frappe.utils.get_datetime(group_object.rows[0].date)
opening_dt -= opening_dt.resolution
group_header = get_opening_balance(group_object.item_code, group_object.warehouse, opening_dt.date(), opening_dt.time())

for f, g in iteritems(grouped_by):
group_header[f] = g

group_header._bold = True
group_header._isGroupTotal = True
group_object.rows.insert(0, group_header)

return group_report_data(data, group_by, postprocess_group=postprocess_group)

def get_warehouse_condition(warehouse):
warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1)
if warehouse_details:
Expand Down
45 changes: 29 additions & 16 deletions erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import frappe
from frappe import _
from frappe.utils import flt, today
from erpnext.stock.utils import update_included_uom_in_report
from erpnext.stock.utils import update_included_uom_in_list_report


def execute(filters=None):
filters = frappe._dict(filters or {})
Expand All @@ -30,7 +31,7 @@ def execute(filters=None):

if filters.brand and filters.brand != item.brand:
continue

elif filters.item_group and filters.item_group != item.item_group:
continue

Expand All @@ -54,46 +55,57 @@ def execute(filters=None):
if include_uom:
conversion_factors.append(item.conversion_factor)

update_included_uom_in_report(columns, data, include_uom, conversion_factors)
update_included_uom_in_list_report(columns, data, include_uom, conversion_factors)
return columns, data


def get_columns():
return [
{"label": _("Item Code"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 140},
{"label": _("Item Name"), "fieldname": "item_name", "width": 100},
{"label": _("Description"), "fieldname": "description", "width": 200},
{"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
{"label": _(
"Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
{"label": _("Brand"), "fieldname": "brand", "fieldtype": "Link", "options": "Brand", "width": 100},
{"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 120},
{"label": _("UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 100},
{"label": _("Actual Qty"), "fieldname": "actual_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Planned Qty"), "fieldname": "planned_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Requested Qty"), "fieldname": "indented_qty", "fieldtype": "Float", "width": 110, "convertible": "qty"},
{"label": _("Ordered Qty"), "fieldname": "ordered_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Reserved Qty"), "fieldname": "reserved_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _(
"Planned Qty"), "fieldname": "planned_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _(
"Requested Qty"), "fieldname": "indented_qty", "fieldtype": "Float", "width": 110, "convertible": "qty"},
{"label": _(
"Ordered Qty"), "fieldname": "ordered_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _(
"Reserved Qty"), "fieldname": "reserved_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Reserved Qty for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float",
"width": 100, "convertible": "qty"},
{"label": _("Reserved for sub contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float",
"width": 100, "convertible": "qty"},
{"label": _("Projected Qty"), "fieldname": "projected_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Reorder Level"), "fieldname": "re_order_level", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Reorder Qty"), "fieldname": "re_order_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Shortage Qty"), "fieldname": "shortage_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}
{"label": _(
"Projected Qty"), "fieldname": "projected_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _(
"Reorder Level"), "fieldname": "re_order_level", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _(
"Reorder Qty"), "fieldname": "re_order_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _(
"Shortage Qty"), "fieldname": "shortage_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}
]


def get_bin_list(filters):
conditions = []

if filters.item_code:
conditions.append("item_code = '%s' "%filters.item_code)
conditions.append("item_code = '%s' " % filters.item_code)

if filters.warehouse:
warehouse_details = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"], as_dict=1)

if warehouse_details:
conditions.append(" exists (select name from `tabWarehouse` wh \
where wh.lft >= %s and wh.rgt <= %s and bin.warehouse = wh.name)"%(warehouse_details.lft,
warehouse_details.rgt))
where wh.lft >= %s and wh.rgt <= %s and bin.warehouse = wh.name)" % (warehouse_details.lft,
warehouse_details.rgt))

bin_list = frappe.db.sql("""select item_code, warehouse, actual_qty, planned_qty, indented_qty,
ordered_qty, reserved_qty, reserved_qty_for_production, reserved_qty_for_sub_contract, projected_qty
Expand All @@ -102,6 +114,7 @@ def get_bin_list(filters):

return bin_list


def get_item_map(item_code, include_uom):
"""Optimization: get only the item doc and re_order_levels table"""

Expand All @@ -122,7 +135,7 @@ def get_item_map(item_code, include_uom):
and item.disabled=0
{condition}
and (item.end_of_life > %(today)s or item.end_of_life is null or item.end_of_life='0000-00-00')
and exists (select name from `tabBin` bin where bin.item_code=item.name)"""\
and exists (select name from `tabBin` bin where bin.item_code=item.name)""" \
.format(cf_field=cf_field, cf_join=cf_join, condition=condition),
{"today": today(), "include_uom": include_uom}, as_dict=True)

Expand Down
Loading