From 9bb71e5ec406be64bc0d2df1b24e2d67e8858030 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 5 Jun 2026 14:45:09 +0530 Subject: [PATCH] chore(stock): remove ledger characterization scaffolding Phase 0 golden-master safety net for the stock_controller refactor. It served its purpose (every extraction verified byte-identical GL + Stock Ledger output) and is removed before shipping, mirroring the earlier GL characterization cleanup. --- erpnext/stock/ledger_snapshot.py | 174 ------------ erpnext/stock/ledger_snapshots/dn_basic.json | 47 ---- erpnext/stock/ledger_snapshots/dn_return.json | 47 ---- erpnext/stock/ledger_snapshots/pr_basic.json | 47 ---- .../stock/ledger_snapshots/pr_batch_item.json | 47 ---- erpnext/stock/ledger_snapshots/pr_return.json | 47 ---- .../ledger_snapshots/pr_serial_item.json | 47 ---- .../stock/ledger_snapshots/pr_with_taxes.json | 75 ------ .../ledger_snapshots/se_material_issue.json | 47 ---- .../ledger_snapshots/se_material_receipt.json | 47 ---- .../se_material_transfer.json | 31 --- erpnext/stock/ledger_snapshots/sr_basic.json | 47 ---- erpnext/stock/test_ledger_characterization.py | 248 ------------------ 13 files changed, 951 deletions(-) delete mode 100644 erpnext/stock/ledger_snapshot.py delete mode 100644 erpnext/stock/ledger_snapshots/dn_basic.json delete mode 100644 erpnext/stock/ledger_snapshots/dn_return.json delete mode 100644 erpnext/stock/ledger_snapshots/pr_basic.json delete mode 100644 erpnext/stock/ledger_snapshots/pr_batch_item.json delete mode 100644 erpnext/stock/ledger_snapshots/pr_return.json delete mode 100644 erpnext/stock/ledger_snapshots/pr_serial_item.json delete mode 100644 erpnext/stock/ledger_snapshots/pr_with_taxes.json delete mode 100644 erpnext/stock/ledger_snapshots/se_material_issue.json delete mode 100644 erpnext/stock/ledger_snapshots/se_material_receipt.json delete mode 100644 erpnext/stock/ledger_snapshots/se_material_transfer.json delete mode 100644 erpnext/stock/ledger_snapshots/sr_basic.json delete mode 100644 erpnext/stock/test_ledger_characterization.py diff --git a/erpnext/stock/ledger_snapshot.py b/erpnext/stock/ledger_snapshot.py deleted file mode 100644 index a50d245670d..00000000000 --- a/erpnext/stock/ledger_snapshot.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Golden-master snapshot harness for ledger characterization tests. - -Captures the General Ledger *and* Stock Ledger entries produced by a submitted -voucher in a normalized, deterministic form and compares them against a stored -golden snapshot. Volatile fields (name, creation, voucher number, serial/batch -bundle id) are stripped so the snapshot is stable across runs. - -This is the Phase 0 safety net for the stock_controller refactor: every later -phase must keep these snapshots byte-identical. Regenerate goldens with:: - - REGEN_LEDGER_SNAPSHOTS=1 bench run-tests --site test-erpnext-v17 \\ - --module erpnext.stock.test_ledger_characterization -""" - -import json -import os -from pathlib import Path - -import frappe -from frappe.utils import flt - -SNAPSHOT_DIR = Path(__file__).parent / "ledger_snapshots" -REGEN_ENV = "REGEN_LEDGER_SNAPSHOTS" -GL_PRECISION = 2 -QTY_PRECISION = 6 -RATE_PRECISION = 4 - - -class GLSnapshot: - """Normalized, order-stable view of a voucher's GL entries.""" - - def __init__(self, voucher_type: str, voucher_no: str) -> None: - self.voucher_type = voucher_type - self.voucher_no = voucher_no - - def capture(self) -> list[dict]: - rows = [self._normalize(row) for row in self._fetch_rows()] - # Sort on the full normalized row so ordering never depends on the DB's - # return order. - return sorted(rows, key=lambda row: json.dumps(row, sort_keys=True)) - - def _fetch_rows(self) -> list[dict]: - gl = frappe.qb.DocType("GL Entry") - query = ( - frappe.qb.from_(gl) - .select( - gl.account, - gl.party_type, - gl.party, - gl.debit, - gl.credit, - gl.debit_in_account_currency, - gl.credit_in_account_currency, - gl.account_currency, - gl.against, - gl.cost_center, - gl.is_opening, - gl.posting_date, - ) - .where( - (gl.voucher_type == self.voucher_type) - & (gl.voucher_no == self.voucher_no) - & (gl.is_cancelled == 0) - ) - .orderby(gl.account, gl.party, gl.debit, gl.credit) - ) - return query.run(as_dict=True) - - def _normalize(self, row: dict) -> dict: - return { - "account": row.account, - "party_type": row.party_type or None, - "party": row.party or None, - "debit": flt(row.debit, GL_PRECISION), - "credit": flt(row.credit, GL_PRECISION), - "debit_in_account_currency": flt(row.debit_in_account_currency, GL_PRECISION), - "credit_in_account_currency": flt(row.credit_in_account_currency, GL_PRECISION), - "account_currency": row.account_currency, - "against": self._normalize_against(row.against), - "cost_center": row.cost_center, - "is_opening": row.is_opening, - "posting_date": str(row.posting_date), - } - - def _normalize_against(self, against: str | None) -> str | None: - """`against` is a comma-joined account list whose order is not stable.""" - if not against: - return None - return ", ".join(sorted(part.strip() for part in against.split(","))) - - -class SLSnapshot: - """Normalized, order-stable view of a voucher's Stock Ledger entries.""" - - def __init__(self, voucher_type: str, voucher_no: str) -> None: - self.voucher_type = voucher_type - self.voucher_no = voucher_no - - def capture(self) -> list[dict]: - rows = [self._normalize(row) for row in self._fetch_rows()] - return sorted(rows, key=lambda row: json.dumps(row, sort_keys=True)) - - def _fetch_rows(self) -> list[dict]: - sle = frappe.qb.DocType("Stock Ledger Entry") - query = ( - frappe.qb.from_(sle) - .select( - sle.item_code, - sle.warehouse, - sle.stock_uom, - sle.actual_qty, - sle.qty_after_transaction, - sle.incoming_rate, - sle.valuation_rate, - sle.stock_value, - sle.stock_value_difference, - sle.serial_and_batch_bundle, - sle.posting_date, - ) - .where( - (sle.voucher_type == self.voucher_type) - & (sle.voucher_no == self.voucher_no) - & (sle.is_cancelled == 0) - ) - .orderby(sle.item_code, sle.warehouse, sle.actual_qty) - ) - return query.run(as_dict=True) - - def _normalize(self, row: dict) -> dict: - return { - "item_code": row.item_code, - "warehouse": row.warehouse, - "stock_uom": row.stock_uom, - "actual_qty": flt(row.actual_qty, QTY_PRECISION), - "qty_after_transaction": flt(row.qty_after_transaction, QTY_PRECISION), - "incoming_rate": flt(row.incoming_rate, RATE_PRECISION), - "valuation_rate": flt(row.valuation_rate, RATE_PRECISION), - "stock_value": flt(row.stock_value, RATE_PRECISION), - "stock_value_difference": flt(row.stock_value_difference, RATE_PRECISION), - # Linkage presence, not the volatile bundle docname — catches a dropped - # serial/batch bundle link without coupling the golden to generated names. - "has_serial_and_batch_bundle": bool(row.serial_and_batch_bundle), - "posting_date": str(row.posting_date), - } - - -def capture_ledger_snapshot(voucher_type: str, voucher_no: str) -> dict: - """Combined GL + SLE snapshot for a single voucher.""" - return { - "gl": GLSnapshot(voucher_type, voucher_no).capture(), - "sle": SLSnapshot(voucher_type, voucher_no).capture(), - } - - -def assert_ledger_snapshot(test_case, name: str, voucher_type: str, voucher_no: str) -> None: - """Compare a voucher's GL + SLE entries against the golden snapshot ``name``. - - In regen mode (``REGEN_LEDGER_SNAPSHOTS`` set) the golden file is written - instead of asserted, so the same scenarios both produce and verify the goldens. - """ - actual = capture_ledger_snapshot(voucher_type, voucher_no) - path = SNAPSHOT_DIR / f"{name}.json" - - if os.environ.get(REGEN_ENV): - SNAPSHOT_DIR.mkdir(exist_ok=True) - path.write_text(json.dumps(actual, indent="\t", sort_keys=True) + "\n") - return - - test_case.assertTrue( - path.exists(), - f"Golden snapshot {path} missing. Run with {REGEN_ENV}=1 to create it.", - ) - expected = json.loads(path.read_text()) - test_case.assertEqual(expected, actual, f"Ledger snapshot mismatch for '{name}'") diff --git a/erpnext/stock/ledger_snapshots/dn_basic.json b/erpnext/stock/ledger_snapshots/dn_basic.json deleted file mode 100644 index 0c405a4772d..00000000000 --- a/erpnext/stock/ledger_snapshots/dn_basic.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "gl": [ - { - "account": "Stock Delivered But Not Billed - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 500.0, - "debit_in_account_currency": 500.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Stock In Hand - TCP1", - "account_currency": "INR", - "against": "Stock Delivered But Not Billed - TCP1", - "cost_center": "Main - TCP1", - "credit": 500.0, - "credit_in_account_currency": 500.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } - ], - "sle": [ - { - "actual_qty": -5.0, - "has_serial_and_batch_bundle": false, - "incoming_rate": 0.0, - "item_code": "_Test Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 5.0, - "stock_uom": "_Test UOM", - "stock_value": 500.0, - "stock_value_difference": -500.0, - "valuation_rate": 100.0, - "warehouse": "Stores - TCP1" - } - ] -} diff --git a/erpnext/stock/ledger_snapshots/dn_return.json b/erpnext/stock/ledger_snapshots/dn_return.json deleted file mode 100644 index 0144a698310..00000000000 --- a/erpnext/stock/ledger_snapshots/dn_return.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "gl": [ - { - "account": "Stock Delivered But Not Billed - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 500.0, - "credit_in_account_currency": 500.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Stock In Hand - TCP1", - "account_currency": "INR", - "against": "Stock Delivered But Not Billed - TCP1", - "cost_center": "Main - TCP1", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 500.0, - "debit_in_account_currency": 500.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } - ], - "sle": [ - { - "actual_qty": 5.0, - "has_serial_and_batch_bundle": false, - "incoming_rate": 100.0, - "item_code": "_Test Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 10.0, - "stock_uom": "_Test UOM", - "stock_value": 1000.0, - "stock_value_difference": 500.0, - "valuation_rate": 100.0, - "warehouse": "Stores - TCP1" - } - ] -} diff --git a/erpnext/stock/ledger_snapshots/pr_basic.json b/erpnext/stock/ledger_snapshots/pr_basic.json deleted file mode 100644 index 62e422fb272..00000000000 --- a/erpnext/stock/ledger_snapshots/pr_basic.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "gl": [ - { - "account": "Stock In Hand - TCP1", - "account_currency": "INR", - "against": "Stock Received But Not Billed - TCP1", - "cost_center": "Main - TCP1", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 500.0, - "debit_in_account_currency": 500.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Stock Received But Not Billed - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 500.0, - "credit_in_account_currency": 500.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } - ], - "sle": [ - { - "actual_qty": 5.0, - "has_serial_and_batch_bundle": false, - "incoming_rate": 100.0, - "item_code": "_Test Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 5.0, - "stock_uom": "_Test UOM", - "stock_value": 500.0, - "stock_value_difference": 500.0, - "valuation_rate": 100.0, - "warehouse": "Stores - TCP1" - } - ] -} diff --git a/erpnext/stock/ledger_snapshots/pr_batch_item.json b/erpnext/stock/ledger_snapshots/pr_batch_item.json deleted file mode 100644 index 76c4bf81517..00000000000 --- a/erpnext/stock/ledger_snapshots/pr_batch_item.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "gl": [ - { - "account": "Stock In Hand - TCP1", - "account_currency": "INR", - "against": "Stock Received But Not Billed - TCP1", - "cost_center": "Main - TCP1", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 1000.0, - "debit_in_account_currency": 1000.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Stock Received But Not Billed - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 1000.0, - "credit_in_account_currency": 1000.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } - ], - "sle": [ - { - "actual_qty": 10.0, - "has_serial_and_batch_bundle": true, - "incoming_rate": 100.0, - "item_code": "_Test Characterization Batch Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 10.0, - "stock_uom": "Nos", - "stock_value": 1000.0, - "stock_value_difference": 1000.0, - "valuation_rate": 100.0, - "warehouse": "Stores - TCP1" - } - ] -} diff --git a/erpnext/stock/ledger_snapshots/pr_return.json b/erpnext/stock/ledger_snapshots/pr_return.json deleted file mode 100644 index 9dc339da1a3..00000000000 --- a/erpnext/stock/ledger_snapshots/pr_return.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "gl": [ - { - "account": "Stock In Hand - TCP1", - "account_currency": "INR", - "against": "Stock Received But Not Billed - TCP1", - "cost_center": "Main - TCP1", - "credit": 500.0, - "credit_in_account_currency": 500.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Stock Received But Not Billed - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 500.0, - "debit_in_account_currency": 500.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } - ], - "sle": [ - { - "actual_qty": -5.0, - "has_serial_and_batch_bundle": false, - "incoming_rate": 0.0, - "item_code": "_Test Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 0.0, - "stock_uom": "_Test UOM", - "stock_value": 0.0, - "stock_value_difference": -500.0, - "valuation_rate": 100.0, - "warehouse": "Stores - TCP1" - } - ] -} diff --git a/erpnext/stock/ledger_snapshots/pr_serial_item.json b/erpnext/stock/ledger_snapshots/pr_serial_item.json deleted file mode 100644 index b581bff8d00..00000000000 --- a/erpnext/stock/ledger_snapshots/pr_serial_item.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "gl": [ - { - "account": "Stock In Hand - TCP1", - "account_currency": "INR", - "against": "Stock Received But Not Billed - TCP1", - "cost_center": "Main - TCP1", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 500.0, - "debit_in_account_currency": 500.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Stock Received But Not Billed - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 500.0, - "credit_in_account_currency": 500.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } - ], - "sle": [ - { - "actual_qty": 5.0, - "has_serial_and_batch_bundle": true, - "incoming_rate": 100.0, - "item_code": "_Test Characterization Serial Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 5.0, - "stock_uom": "Nos", - "stock_value": 500.0, - "stock_value_difference": 500.0, - "valuation_rate": 100.0, - "warehouse": "Stores - TCP1" - } - ] -} diff --git a/erpnext/stock/ledger_snapshots/pr_with_taxes.json b/erpnext/stock/ledger_snapshots/pr_with_taxes.json deleted file mode 100644 index dc6a71b6ed7..00000000000 --- a/erpnext/stock/ledger_snapshots/pr_with_taxes.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "gl": [ - { - "account": "Stock In Hand - TCP1", - "account_currency": "INR", - "against": "Stock Received But Not Billed - TCP1", - "cost_center": "Main - TCP1", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 750.0, - "debit_in_account_currency": 750.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Stock Received But Not Billed - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 500.0, - "credit_in_account_currency": 500.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "_Test Account Customs Duty - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 150.0, - "credit_in_account_currency": 150.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "_Test Account Shipping Charges - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 100.0, - "credit_in_account_currency": 100.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } - ], - "sle": [ - { - "actual_qty": 5.0, - "has_serial_and_batch_bundle": false, - "incoming_rate": 150.0, - "item_code": "_Test Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 5.0, - "stock_uom": "_Test UOM", - "stock_value": 750.0, - "stock_value_difference": 750.0, - "valuation_rate": 150.0, - "warehouse": "Stores - TCP1" - } - ] -} diff --git a/erpnext/stock/ledger_snapshots/se_material_issue.json b/erpnext/stock/ledger_snapshots/se_material_issue.json deleted file mode 100644 index 87f1be7efff..00000000000 --- a/erpnext/stock/ledger_snapshots/se_material_issue.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "gl": [ - { - "account": "Stock Adjustment - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 500.0, - "debit_in_account_currency": 500.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Stock In Hand - TCP1", - "account_currency": "INR", - "against": "Stock Adjustment - TCP1", - "cost_center": "Main - TCP1", - "credit": 500.0, - "credit_in_account_currency": 500.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } - ], - "sle": [ - { - "actual_qty": -5.0, - "has_serial_and_batch_bundle": false, - "incoming_rate": 0.0, - "item_code": "_Test Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 5.0, - "stock_uom": "_Test UOM", - "stock_value": 500.0, - "stock_value_difference": -500.0, - "valuation_rate": 100.0, - "warehouse": "Stores - TCP1" - } - ] -} diff --git a/erpnext/stock/ledger_snapshots/se_material_receipt.json b/erpnext/stock/ledger_snapshots/se_material_receipt.json deleted file mode 100644 index 47c8aa744ce..00000000000 --- a/erpnext/stock/ledger_snapshots/se_material_receipt.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "gl": [ - { - "account": "Stock Adjustment - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 500.0, - "credit_in_account_currency": 500.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Stock In Hand - TCP1", - "account_currency": "INR", - "against": "Stock Adjustment - TCP1", - "cost_center": "Main - TCP1", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 500.0, - "debit_in_account_currency": 500.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } - ], - "sle": [ - { - "actual_qty": 5.0, - "has_serial_and_batch_bundle": false, - "incoming_rate": 100.0, - "item_code": "_Test Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 5.0, - "stock_uom": "_Test UOM", - "stock_value": 500.0, - "stock_value_difference": 500.0, - "valuation_rate": 100.0, - "warehouse": "Stores - TCP1" - } - ] -} diff --git a/erpnext/stock/ledger_snapshots/se_material_transfer.json b/erpnext/stock/ledger_snapshots/se_material_transfer.json deleted file mode 100644 index d9d76458d3f..00000000000 --- a/erpnext/stock/ledger_snapshots/se_material_transfer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "gl": [], - "sle": [ - { - "actual_qty": -5.0, - "has_serial_and_batch_bundle": false, - "incoming_rate": 0.0, - "item_code": "_Test Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 5.0, - "stock_uom": "_Test UOM", - "stock_value": 500.0, - "stock_value_difference": -500.0, - "valuation_rate": 100.0, - "warehouse": "Stores - TCP1" - }, - { - "actual_qty": 5.0, - "has_serial_and_batch_bundle": false, - "incoming_rate": 100.0, - "item_code": "_Test Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 5.0, - "stock_uom": "_Test UOM", - "stock_value": 500.0, - "stock_value_difference": 500.0, - "valuation_rate": 100.0, - "warehouse": "Finished Goods - TCP1" - } - ] -} diff --git a/erpnext/stock/ledger_snapshots/sr_basic.json b/erpnext/stock/ledger_snapshots/sr_basic.json deleted file mode 100644 index 1203530416d..00000000000 --- a/erpnext/stock/ledger_snapshots/sr_basic.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "gl": [ - { - "account": "Stock Adjustment - TCP1", - "account_currency": "INR", - "against": "Stock In Hand - TCP1", - "cost_center": "Main - TCP1", - "credit": 1500.0, - "credit_in_account_currency": 1500.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Stock In Hand - TCP1", - "account_currency": "INR", - "against": "Stock Adjustment - TCP1", - "cost_center": "Main - TCP1", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 1500.0, - "debit_in_account_currency": 1500.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } - ], - "sle": [ - { - "actual_qty": 0.0, - "has_serial_and_batch_bundle": false, - "incoming_rate": 0.0, - "item_code": "_Test Item", - "posting_date": "2024-01-15", - "qty_after_transaction": 10.0, - "stock_uom": "_Test UOM", - "stock_value": 1500.0, - "stock_value_difference": 1500.0, - "valuation_rate": 150.0, - "warehouse": "Stores - TCP1" - } - ] -} diff --git a/erpnext/stock/test_ledger_characterization.py b/erpnext/stock/test_ledger_characterization.py deleted file mode 100644 index 9a3cbd69b20..00000000000 --- a/erpnext/stock/test_ledger_characterization.py +++ /dev/null @@ -1,248 +0,0 @@ -"""Phase 0 characterization tests for the stock_controller refactor. - -These are golden-master snapshot tests: each scenario builds a representative -stock voucher, submits it, and compares its GL *and* Stock Ledger entries against -a stored snapshot (see ``erpnext/stock/ledger_snapshots``). They assert nothing -about *correct* accounting or valuation — only that ledger output stays -byte-identical as ``stock_controller`` is split into services. - -Determinism: each test is wrapped in a savepoint that is rolled back in tearDown, -so the cumulative Stock Ledger fields (qty_after_transaction, stock_value, -valuation_rate) do not depend on test execution order or on state left by other -tests. Prerequisite stock is posted on PREREQUISITE_DATE (before POSTING_DATE) so -balances are positive and independent of the wall-clock date. Run the module in -isolation (``--module ...``) as below. - -Regenerate goldens after an intentional change:: - - REGEN_LEDGER_SNAPSHOTS=1 bench run-tests --site test-erpnext-v17 \\ - --module erpnext.stock.test_ledger_characterization -""" - -import frappe -from frappe.tests import IntegrationTestCase - -from erpnext.stock.doctype.item.test_item import make_item -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt -from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry -from erpnext.stock.ledger_snapshot import assert_ledger_snapshot - -POSTING_DATE = "2024-01-15" -PREREQUISITE_DATE = "2024-01-10" -CUSTOMER = "_Test Customer" -COMPANY = "_Test Company with perpetual inventory" -WAREHOUSE = "Stores - TCP1" - - -class TestLedgerCharacterization(IntegrationTestCase): - def setUp(self): - frappe.db.savepoint("ledger_characterization") - - def tearDown(self): - frappe.db.rollback(save_point="ledger_characterization") - - def test_dn_basic(self): - make_stock_entry( - item_code="_Test Item", target=WAREHOUSE, qty=10, basic_rate=100, posting_date=PREREQUISITE_DATE - ) - dn = _make_dated_delivery_note(qty=5, rate=150) - dn.insert() - dn.submit() - assert_ledger_snapshot(self, "dn_basic", "Delivery Note", dn.name) - - def test_dn_return(self): - make_stock_entry( - item_code="_Test Item", target=WAREHOUSE, qty=10, basic_rate=100, posting_date=PREREQUISITE_DATE - ) - original = _make_dated_delivery_note(qty=5, rate=150) - original.insert() - original.submit() - - ret = frappe.copy_doc(original) - ret.is_return = 1 - ret.return_against = original.name - for item in ret.items: - item.qty = -item.qty - ret.set_posting_time = 1 - ret.posting_date = POSTING_DATE - ret.insert() - ret.submit() - assert_ledger_snapshot(self, "dn_return", "Delivery Note", ret.name) - - def test_se_material_receipt(self): - se = make_stock_entry( - item_code="_Test Item", - target=WAREHOUSE, - qty=5, - basic_rate=100, - company=COMPANY, - posting_date=POSTING_DATE, - do_not_submit=True, - ) - se.submit() - assert_ledger_snapshot(self, "se_material_receipt", "Stock Entry", se.name) - - def test_se_material_issue(self): - make_stock_entry( - item_code="_Test Item", - target=WAREHOUSE, - qty=10, - basic_rate=100, - company=COMPANY, - posting_date=PREREQUISITE_DATE, - ) - se = make_stock_entry( - item_code="_Test Item", - source=WAREHOUSE, - qty=5, - company=COMPANY, - posting_date=POSTING_DATE, - do_not_submit=True, - ) - se.submit() - assert_ledger_snapshot(self, "se_material_issue", "Stock Entry", se.name) - - def test_se_material_transfer(self): - make_stock_entry( - item_code="_Test Item", - target=WAREHOUSE, - qty=10, - basic_rate=100, - company=COMPANY, - posting_date=PREREQUISITE_DATE, - ) - se = make_stock_entry( - item_code="_Test Item", - source=WAREHOUSE, - target="Finished Goods - TCP1", - qty=5, - company=COMPANY, - posting_date=POSTING_DATE, - do_not_submit=True, - ) - se.submit() - assert_ledger_snapshot(self, "se_material_transfer", "Stock Entry", se.name) - - def test_sr_basic(self): - sr = _make_dated_stock_reconciliation(qty=10, rate=150) - sr.insert() - sr.submit() - assert_ledger_snapshot(self, "sr_basic", "Stock Reconciliation", sr.name) - - def test_pr_basic(self): - pr = make_purchase_receipt( - company=COMPANY, warehouse=WAREHOUSE, posting_date=POSTING_DATE, qty=5, rate=100 - ) - assert_ledger_snapshot(self, "pr_basic", "Purchase Receipt", pr.name) - - def test_pr_with_taxes(self): - pr = make_purchase_receipt( - company=COMPANY, - warehouse=WAREHOUSE, - posting_date=POSTING_DATE, - qty=5, - rate=100, - get_taxes_and_charges=True, - ) - assert_ledger_snapshot(self, "pr_with_taxes", "Purchase Receipt", pr.name) - - def test_pr_return(self): - from erpnext.stock.doctype.purchase_receipt.mapper import make_purchase_return - - original = make_purchase_receipt( - company=COMPANY, warehouse=WAREHOUSE, posting_date=POSTING_DATE, qty=5, rate=100 - ) - ret = make_purchase_return(original.name) - ret.posting_date = POSTING_DATE - ret.set_posting_time = 1 - ret.insert() - ret.submit() - assert_ledger_snapshot(self, "pr_return", "Purchase Receipt", ret.name) - - def test_pr_batch_item(self): - """Exercises SerialBatchBundleService bundle creation + SLE bundle linkage.""" - item_code = make_item( - "_Test Characterization Batch Item", - { - "has_batch_no": 1, - "create_new_batch": 1, - "batch_number_series": "CHAR-BATCH-.#####", - "is_stock_item": 1, - }, - ).name - pr = make_purchase_receipt( - item_code=item_code, - company=COMPANY, - warehouse=WAREHOUSE, - posting_date=POSTING_DATE, - qty=10, - rate=100, - ) - assert_ledger_snapshot(self, "pr_batch_item", "Purchase Receipt", pr.name) - - def test_pr_serial_item(self): - """Exercises SerialBatchBundleService for serialized items + SLE bundle linkage.""" - item_code = make_item( - "_Test Characterization Serial Item", - {"has_serial_no": 1, "serial_no_series": "CHAR-SER-.#####", "is_stock_item": 1}, - ).name - pr = make_purchase_receipt( - item_code=item_code, - company=COMPANY, - warehouse=WAREHOUSE, - posting_date=POSTING_DATE, - qty=5, - rate=100, - ) - assert_ledger_snapshot(self, "pr_serial_item", "Purchase Receipt", pr.name) - - -def _make_dated_delivery_note(**args) -> frappe.Document: - """Minimal Delivery Note on a fixed posting date using the perpetual-inventory - test company. - - Inlined to avoid importing test_delivery_note which drags in conflicting - test-record dependencies at discovery time.""" - dn = frappe.new_doc("Delivery Note") - dn.company = COMPANY - dn.customer = CUSTOMER - dn.posting_date = POSTING_DATE - dn.set_posting_time = 1 - dn.append( - "items", - { - "item_code": args.get("item_code", "_Test Item"), - "warehouse": args.get("warehouse", WAREHOUSE), - "qty": args.get("qty", 1), - "rate": args.get("rate", 100), - "expense_account": "Cost of Goods Sold - TCP1", - "cost_center": "Main - TCP1", - }, - ) - return dn - - -def _make_dated_stock_reconciliation(**args) -> frappe.Document: - """Minimal Stock Reconciliation on a fixed posting date using the perpetual-inventory - test company. - - Inlined to avoid importing test_stock_reconciliation which drags in conflicting - test-record dependencies at discovery time.""" - sr = frappe.new_doc("Stock Reconciliation") - sr.company = COMPANY - sr.purpose = args.get("purpose", "Stock Reconciliation") - sr.posting_date = POSTING_DATE - sr.posting_time = "00:00:00" - sr.set_posting_time = 1 - sr.expense_account = frappe.get_cached_value("Company", COMPANY, "stock_adjustment_account") - sr.cost_center = frappe.get_cached_value("Company", COMPANY, "cost_center") - sr.append( - "items", - { - "item_code": args.get("item_code", "_Test Item"), - "warehouse": args.get("warehouse", WAREHOUSE), - "qty": args.get("qty", 10), - "valuation_rate": args.get("rate", 100), - }, - ) - return sr