feat(report): add temporary source-document rows to agricultural stock report

Append detail rows listing the Stock Ledger entries (with a clickable link
to each source voucher) and sale prices behind opening/purchased/COGS/
closing, for tracing. The summary stays the first row. Also decide stock
direction by stock_value_difference so opening-stock reconciliations
(actual_qty = 0) are counted correctly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-06-25 09:31:18 +00:00
parent beec3f4416
commit bd61196c84
1 changed files with 77 additions and 5 deletions

View File

@ -71,6 +71,20 @@ def get_columns(filters=None):
"fieldname": "trade_markup_vat", "fieldname": "trade_markup_vat",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 200 "width": 200
},
# TEMPORARY: source documents behind each figure (for tracing).
{
"label": _("Source type"),
"fieldname": "source_type",
"fieldtype": "Data",
"width": 150
},
{
"label": _("Source document"),
"fieldname": "source_document",
"fieldtype": "Dynamic Link",
"options": "source_type",
"width": 200
} }
] ]
@ -129,10 +143,12 @@ def _movements(company, from_date, to_date):
conditions += " AND sle.posting_date <= %(to_date)s" conditions += " AND sle.posting_date <= %(to_date)s"
params["to_date"] = to_date params["to_date"] = to_date
# Direction is decided by the value change, not actual_qty: opening-stock
# reconciliations post with actual_qty = 0 but a non-zero value.
result = frappe.db.sql(f""" result = frappe.db.sql(f"""
SELECT SELECT
COALESCE(SUM(CASE WHEN sle.actual_qty > 0 THEN sle.stock_value_difference ELSE 0 END), 0) AS purchased, COALESCE(SUM(CASE WHEN sle.stock_value_difference > 0 THEN sle.stock_value_difference ELSE 0 END), 0) AS purchased,
COALESCE(SUM(CASE WHEN sle.actual_qty < 0 THEN -sle.stock_value_difference ELSE 0 END), 0) AS cogs COALESCE(SUM(CASE WHEN sle.stock_value_difference < 0 THEN -sle.stock_value_difference ELSE 0 END), 0) AS cogs
FROM `tabStock Ledger Entry` sle FROM `tabStock Ledger Entry` sle
INNER JOIN `tabItem` it ON it.name = sle.item_code INNER JOIN `tabItem` it ON it.name = sle.item_code
WHERE {conditions} WHERE {conditions}
@ -166,6 +182,58 @@ def _sold_sale_price(company, from_date, to_date):
return flt(result[0][0]) if result else 0.0 return flt(result[0][0]) if result else 0.0
def _detail_rows(company, from_date, to_date):
"""TEMPORARY: one row per source document so figures can be traced.
Lists the Stock Ledger entries of agricultural items up to the period end
(entries before the period are marked "(opening)") with a clickable link
to the source voucher, and the sale price for outgoing sales.
"""
params = {}
conditions = "sle.is_cancelled = 0 AND it.agricultural_goods = 1"
conditions += _company_cond(company, params, "sle")
if to_date:
conditions += " AND sle.posting_date <= %(to_date)s"
params["to_date"] = to_date
sle_rows = frappe.db.sql(f"""
SELECT
sle.posting_date, sle.voucher_type, sle.voucher_no, sle.item_code,
sle.actual_qty, sle.stock_value_difference, sle.stock_value
FROM `tabStock Ledger Entry` sle
INNER JOIN `tabItem` it ON it.name = sle.item_code
WHERE {conditions}
ORDER BY sle.posting_date, sle.posting_time, sle.creation
""", params, as_dict=1)
# Sale price per (Sales Invoice, item) for outgoing sale rows.
si_names = [r.voucher_no for r in sle_rows if r.voucher_type == "Sales Invoice"]
sale_map = {}
if si_names:
for d in frappe.db.sql("""
SELECT parent, item_code, SUM(net_amount) AS amt
FROM `tabSales Invoice Item`
WHERE parent IN %(names)s
GROUP BY parent, item_code
""", {"names": tuple(si_names)}, as_dict=1):
sale_map[(d.parent, d.item_code)] = flt(d.amt)
rows = []
for r in sle_rows:
diff = flt(r.stock_value_difference)
before = from_date and getdate(r.posting_date) < from_date
rows.append({
"period": f"{r.posting_date}" + (" (opening)" if before else ""),
"purchased": diff if diff > 0 else None,
"sold_purchase_price": -diff if diff < 0 else None,
"sold": sale_map.get((r.voucher_no, r.item_code)) if diff < 0 else None,
"closing_balance": flt(r.stock_value),
"source_type": r.voucher_type,
"source_document": r.voucher_no,
})
return rows
def get_data(filters=None): def get_data(filters=None):
"""Single row for the selected period, sourced from the Stock Ledger. """Single row for the selected period, sourced from the Stock Ledger.
@ -200,7 +268,7 @@ def get_data(filters=None):
else: else:
period = _("All time") period = _("All time")
return [{ summary = {
"period": period, "period": period,
"opening_balance": opening_balance, "opening_balance": opening_balance,
"purchased": purchased, "purchased": purchased,
@ -208,8 +276,12 @@ def get_data(filters=None):
"sold_purchase_price": cogs, "sold_purchase_price": cogs,
"closing_balance": closing_balance, "closing_balance": closing_balance,
"trade_markup": trade_markup, "trade_markup": trade_markup,
"trade_markup_vat": trade_markup_vat "trade_markup_vat": trade_markup_vat,
}] "source_type": "TOTAL",
}
# TEMPORARY: append source-document rows for tracing.
return [summary] + _detail_rows(company, from_date, to_date)
def _day_before(d): def _day_before(d):