diff --git a/erpnext/accounts/gl_snapshot.py b/erpnext/accounts/gl_snapshot.py deleted file mode 100644 index 1bb81384f45..00000000000 --- a/erpnext/accounts/gl_snapshot.py +++ /dev/null @@ -1,110 +0,0 @@ -"""Golden-master snapshot harness for GL Entry characterization tests. - -Captures the General 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) are stripped so the -snapshot is stable across runs. - -This is the Phase 0 safety net for the accounts/controller refactor: every -later phase must keep these snapshots byte-identical. Regenerate goldens with:: - - REGEN_GL_SNAPSHOTS=1 bench run-tests --site test-site-ai \\ - --module erpnext.accounts.test_gl_characterization -""" - -import json -import os -from pathlib import Path - -import frappe -from frappe.utils import flt - -SNAPSHOT_DIR = Path(__file__).parent / "gl_snapshots" -REGEN_ENV = "REGEN_GL_SNAPSHOTS" -PRECISION = 2 - - -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 — e.g. two POS payment legs that tie on account/party/amount - # but differ only in `against`. - 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, PRECISION), - "credit": flt(row.credit, PRECISION), - "debit_in_account_currency": flt(row.debit_in_account_currency, PRECISION), - "credit_in_account_currency": flt(row.credit_in_account_currency, 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(","))) - - -def assert_gl_snapshot(test_case, name: str, voucher_type: str, voucher_no: str) -> None: - """Compare a voucher's GL entries against the golden snapshot ``name``. - - In regen mode (``REGEN_GL_SNAPSHOTS`` set) the golden file is written instead - of asserted, so the same scenarios both produce and verify the goldens. - """ - actual = GLSnapshot(voucher_type, voucher_no).capture() - 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"GL snapshot mismatch for '{name}'") diff --git a/erpnext/accounts/gl_snapshots/dn_basic.json b/erpnext/accounts/gl_snapshots/dn_basic.json deleted file mode 100644 index c810898479f..00000000000 --- a/erpnext/accounts/gl_snapshots/dn_basic.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/dn_return.json b/erpnext/accounts/gl_snapshots/dn_return.json deleted file mode 100644 index 74da64a3ef8..00000000000 --- a/erpnext/accounts/gl_snapshots/dn_return.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/je_against_si.json b/erpnext/accounts/gl_snapshots/je_against_si.json deleted file mode 100644 index f4b705e1702..00000000000 --- a/erpnext/accounts/gl_snapshots/je_against_si.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "Write Off - _TC", - "cost_center": "_Test Cost Center - _TC", - "credit": 1000.0, - "credit_in_account_currency": 1000.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Write Off - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/je_basic.json b/erpnext/accounts/gl_snapshots/je_basic.json deleted file mode 100644 index 6a9eeb9f8e9..00000000000 --- a/erpnext/accounts/gl_snapshots/je_basic.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "_Test Bank - _TC", - "account_currency": "INR", - "against": "_Test Cash - _TC", - "cost_center": "_Test Cost Center - _TC", - "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" - }, - { - "account": "_Test Cash - _TC", - "account_currency": "INR", - "against": "_Test Bank - _TC", - "cost_center": "_Test Cost Center - _TC", - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/je_multi_currency.json b/erpnext/accounts/gl_snapshots/je_multi_currency.json deleted file mode 100644 index fea7c3c3af9..00000000000 --- a/erpnext/accounts/gl_snapshots/je_multi_currency.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "_Test Bank - _TC", - "account_currency": "INR", - "against": "_Test Bank USD - _TC", - "cost_center": "_Test Cost Center - _TC", - "credit": 7500.0, - "credit_in_account_currency": 7500.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 Bank USD - _TC", - "account_currency": "USD", - "against": "_Test Bank - _TC", - "cost_center": "_Test Cost Center - _TC", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 7500.0, - "debit_in_account_currency": 100.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/pe_multi_currency.json b/erpnext/accounts/gl_snapshots/pe_multi_currency.json deleted file mode 100644 index e10a073a58a..00000000000 --- a/erpnext/accounts/gl_snapshots/pe_multi_currency.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "_Test Bank - _TC", - "account_currency": "INR", - "against": "_Test Supplier USD", - "cost_center": null, - "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" - }, - { - "account": "_Test Payable USD - _TC", - "account_currency": "USD", - "against": "_Test Bank - _TC", - "cost_center": null, - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 1000.0, - "debit_in_account_currency": 12.5, - "is_opening": "No", - "party": "_Test Supplier USD", - "party_type": "Supplier", - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/pe_pay_against_pi.json b/erpnext/accounts/gl_snapshots/pe_pay_against_pi.json deleted file mode 100644 index d3953462b50..00000000000 --- a/erpnext/accounts/gl_snapshots/pe_pay_against_pi.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "Creditors - _TC", - "account_currency": "INR", - "against": "_Test Bank - _TC", - "cost_center": null, - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 250.0, - "debit_in_account_currency": 250.0, - "is_opening": "No", - "party": "_Test Supplier", - "party_type": "Supplier", - "posting_date": "2024-01-15" - }, - { - "account": "_Test Bank - _TC", - "account_currency": "INR", - "against": "_Test Supplier", - "cost_center": null, - "credit": 250.0, - "credit_in_account_currency": 250.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/pe_receive_against_si.json b/erpnext/accounts/gl_snapshots/pe_receive_against_si.json deleted file mode 100644 index 61650a490cc..00000000000 --- a/erpnext/accounts/gl_snapshots/pe_receive_against_si.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "_Test Cash - _TC", - "cost_center": null, - "credit": 1000.0, - "credit_in_account_currency": 1000.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "_Test Cash - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": null, - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/pe_with_deductions.json b/erpnext/accounts/gl_snapshots/pe_with_deductions.json deleted file mode 100644 index df86e1e11f5..00000000000 --- a/erpnext/accounts/gl_snapshots/pe_with_deductions.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "_Test Cash - _TC", - "cost_center": null, - "credit": 1000.0, - "credit_in_account_currency": 1000.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "_Test Cash - _TC", - "cost_center": null, - "credit": 50.0, - "credit_in_account_currency": 50.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Write Off - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 50.0, - "debit_in_account_currency": 50.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "_Test Cash - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": null, - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/pe_with_taxes.json b/erpnext/accounts/gl_snapshots/pe_with_taxes.json deleted file mode 100644 index 70d167e826b..00000000000 --- a/erpnext/accounts/gl_snapshots/pe_with_taxes.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "account": "Creditors - _TC", - "account_currency": "INR", - "against": "_Test Bank - _TC", - "cost_center": null, - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 1000.0, - "debit_in_account_currency": 1000.0, - "is_opening": "No", - "party": "_Test Supplier", - "party_type": "Supplier", - "posting_date": "2024-01-15" - }, - { - "account": "_Test Account Service Tax - _TC", - "account_currency": "INR", - "against": "_Test Supplier", - "cost_center": "_Test Cost Center - _TC", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 100.0, - "debit_in_account_currency": 100.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "_Test Bank - _TC", - "account_currency": "INR", - "against": "_Test Supplier", - "cost_center": null, - "credit": 1100.0, - "credit_in_account_currency": 1100.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/pi_basic.json b/erpnext/accounts/gl_snapshots/pi_basic.json deleted file mode 100644 index 7e999295def..00000000000 --- a/erpnext/accounts/gl_snapshots/pi_basic.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "Creditors - _TC", - "account_currency": "INR", - "against": "_Test Account Cost for Goods Sold - _TC", - "cost_center": null, - "credit": 250.0, - "credit_in_account_currency": 250.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": "_Test Supplier", - "party_type": "Supplier", - "posting_date": "2024-01-15" - }, - { - "account": "_Test Account Cost for Goods Sold - _TC", - "account_currency": "INR", - "against": "_Test Supplier", - "cost_center": "_Test Cost Center - _TC", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 250.0, - "debit_in_account_currency": 250.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/pi_multi_currency.json b/erpnext/accounts/gl_snapshots/pi_multi_currency.json deleted file mode 100644 index e20e31810bb..00000000000 --- a/erpnext/accounts/gl_snapshots/pi_multi_currency.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "Creditors - _TC", - "account_currency": "INR", - "against": "_Test Account Cost for Goods Sold - _TC", - "cost_center": null, - "credit": 18750.0, - "credit_in_account_currency": 18750.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": "_Test Supplier", - "party_type": "Supplier", - "posting_date": "2024-01-15" - }, - { - "account": "_Test Account Cost for Goods Sold - _TC", - "account_currency": "INR", - "against": "_Test Supplier", - "cost_center": "_Test Cost Center - _TC", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 18750.0, - "debit_in_account_currency": 18750.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/pi_return.json b/erpnext/accounts/gl_snapshots/pi_return.json deleted file mode 100644 index ffc8afc9a03..00000000000 --- a/erpnext/accounts/gl_snapshots/pi_return.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "Creditors - _TC", - "account_currency": "INR", - "against": "_Test Account Cost for Goods Sold - _TC", - "cost_center": null, - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 250.0, - "debit_in_account_currency": 250.0, - "is_opening": "No", - "party": "_Test Supplier", - "party_type": "Supplier", - "posting_date": "2024-01-15" - }, - { - "account": "_Test Account Cost for Goods Sold - _TC", - "account_currency": "INR", - "against": "_Test Supplier", - "cost_center": "_Test Cost Center - _TC", - "credit": 250.0, - "credit_in_account_currency": 250.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/pi_with_taxes.json b/erpnext/accounts/gl_snapshots/pi_with_taxes.json deleted file mode 100644 index 5cb6ca60a3e..00000000000 --- a/erpnext/accounts/gl_snapshots/pi_with_taxes.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "account": "Creditors - _TC", - "account_currency": "INR", - "against": "_Test Account Cost for Goods Sold - _TC", - "cost_center": null, - "credit": 288.0, - "credit_in_account_currency": 288.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": "_Test Supplier", - "party_type": "Supplier", - "posting_date": "2024-01-15" - }, - { - "account": "Round Off - _TC", - "account_currency": "INR", - "against": "_Test Supplier", - "cost_center": "Main - _TC", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 0.5, - "debit_in_account_currency": 0.5, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "_Test Account Cost for Goods Sold - _TC", - "account_currency": "INR", - "against": "_Test Supplier", - "cost_center": "_Test Cost Center - _TC", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 250.0, - "debit_in_account_currency": 250.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "_Test Account VAT - _TC", - "account_currency": "INR", - "against": "_Test Supplier", - "cost_center": "_Test Cost Center - _TC", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 37.5, - "debit_in_account_currency": 37.5, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/pr_basic.json b/erpnext/accounts/gl_snapshots/pr_basic.json deleted file mode 100644 index 8a7f7d0824d..00000000000 --- a/erpnext/accounts/gl_snapshots/pr_basic.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/pr_return.json b/erpnext/accounts/gl_snapshots/pr_return.json deleted file mode 100644 index 32715c31173..00000000000 --- a/erpnext/accounts/gl_snapshots/pr_return.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/pr_with_taxes.json b/erpnext/accounts/gl_snapshots/pr_with_taxes.json deleted file mode 100644 index 38ed6a59549..00000000000 --- a/erpnext/accounts/gl_snapshots/pr_with_taxes.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/se_material_issue.json b/erpnext/accounts/gl_snapshots/se_material_issue.json deleted file mode 100644 index 705b5255ee3..00000000000 --- a/erpnext/accounts/gl_snapshots/se_material_issue.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "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": 750.0, - "debit_in_account_currency": 750.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": 750.0, - "credit_in_account_currency": 750.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/se_material_receipt.json b/erpnext/accounts/gl_snapshots/se_material_receipt.json deleted file mode 100644 index bde33cb2748..00000000000 --- a/erpnext/accounts/gl_snapshots/se_material_receipt.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/se_material_transfer.json b/erpnext/accounts/gl_snapshots/se_material_transfer.json deleted file mode 100644 index fe51488c706..00000000000 --- a/erpnext/accounts/gl_snapshots/se_material_transfer.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/erpnext/accounts/gl_snapshots/si_basic.json b/erpnext/accounts/gl_snapshots/si_basic.json deleted file mode 100644 index 48bcf835043..00000000000 --- a/erpnext/accounts/gl_snapshots/si_basic.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "Sales - _TC", - "cost_center": null, - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 1000.0, - "debit_in_account_currency": 1000.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Sales - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/si_multi_currency.json b/erpnext/accounts/gl_snapshots/si_multi_currency.json deleted file mode 100644 index 637eb8110b0..00000000000 --- a/erpnext/accounts/gl_snapshots/si_multi_currency.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "Sales - _TC", - "cost_center": null, - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 75000.0, - "debit_in_account_currency": 75000.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Sales - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "credit": 75000.0, - "credit_in_account_currency": 75000.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/si_pos.json b/erpnext/accounts/gl_snapshots/si_pos.json deleted file mode 100644 index 153c15cf334..00000000000 --- a/erpnext/accounts/gl_snapshots/si_pos.json +++ /dev/null @@ -1,86 +0,0 @@ -[ - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "Sales - _TC", - "cost_center": null, - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 1000.0, - "debit_in_account_currency": 1000.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "_Test Bank - _TC", - "cost_center": null, - "credit": 500.0, - "credit_in_account_currency": 500.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "_Test Cash - _TC", - "cost_center": null, - "credit": 500.0, - "credit_in_account_currency": 500.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Sales - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "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" - }, - { - "account": "_Test Bank - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": null, - "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": "_Test Cash - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": null, - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/si_return.json b/erpnext/accounts/gl_snapshots/si_return.json deleted file mode 100644 index 477f83d50c5..00000000000 --- a/erpnext/accounts/gl_snapshots/si_return.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "Sales - _TC", - "cost_center": null, - "credit": 1000.0, - "credit_in_account_currency": 1000.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Sales - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/si_round_off.json b/erpnext/accounts/gl_snapshots/si_round_off.json deleted file mode 100644 index c994ced1fec..00000000000 --- a/erpnext/accounts/gl_snapshots/si_round_off.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "Sales - _TC", - "cost_center": null, - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 106.0, - "debit_in_account_currency": 106.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Round Off - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "Main - _TC", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 0.5, - "debit_in_account_currency": 0.5, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Sales - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "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" - }, - { - "account": "_Test Account Service Tax - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "credit": 6.5, - "credit_in_account_currency": 6.5, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/si_with_advance.json b/erpnext/accounts/gl_snapshots/si_with_advance.json deleted file mode 100644 index 48bcf835043..00000000000 --- a/erpnext/accounts/gl_snapshots/si_with_advance.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "Sales - _TC", - "cost_center": null, - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 1000.0, - "debit_in_account_currency": 1000.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Sales - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/si_with_discount.json b/erpnext/accounts/gl_snapshots/si_with_discount.json deleted file mode 100644 index 29a2d25ac0e..00000000000 --- a/erpnext/accounts/gl_snapshots/si_with_discount.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "Sales - _TC", - "cost_center": null, - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 90.0, - "debit_in_account_currency": 90.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Discount Account - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 10.0, - "debit_in_account_currency": 10.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - }, - { - "account": "Sales - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "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" - } -] diff --git a/erpnext/accounts/gl_snapshots/si_with_taxes.json b/erpnext/accounts/gl_snapshots/si_with_taxes.json deleted file mode 100644 index 228e76cf295..00000000000 --- a/erpnext/accounts/gl_snapshots/si_with_taxes.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "account": "Debtors - _TC", - "account_currency": "INR", - "against": "Sales - _TC", - "cost_center": null, - "credit": 0.0, - "credit_in_account_currency": 0.0, - "debit": 1140.0, - "debit_in_account_currency": 1140.0, - "is_opening": "No", - "party": "_Test Customer", - "party_type": "Customer", - "posting_date": "2024-01-15" - }, - { - "account": "Sales - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "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" - }, - { - "account": "_Test Account Service Tax - _TC", - "account_currency": "INR", - "against": "_Test Customer", - "cost_center": "_Test Cost Center - _TC", - "credit": 140.0, - "credit_in_account_currency": 140.0, - "debit": 0.0, - "debit_in_account_currency": 0.0, - "is_opening": "No", - "party": null, - "party_type": null, - "posting_date": "2024-01-15" - } -] diff --git a/erpnext/accounts/gl_snapshots/sr_basic.json b/erpnext/accounts/gl_snapshots/sr_basic.json deleted file mode 100644 index ab4cf49e410..00000000000 --- a/erpnext/accounts/gl_snapshots/sr_basic.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "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" - } -] diff --git a/erpnext/accounts/test_gl_characterization.py b/erpnext/accounts/test_gl_characterization.py deleted file mode 100644 index e7eb65c0edf..00000000000 --- a/erpnext/accounts/test_gl_characterization.py +++ /dev/null @@ -1,532 +0,0 @@ -"""Phase 0 characterization tests for the accounts/controller refactor. - -These are golden-master snapshot tests: each scenario builds a representative -voucher, submits it, and compares its GL entries against a stored snapshot -(see ``erpnext/accounts/gl_snapshots``). They assert nothing about *correct* -accounting — only that GL output stays byte-identical as the GL pipeline is -refactored into composer / validator / sink services. - -Regenerate goldens after an intentional change:: - - REGEN_GL_SNAPSHOTS=1 bench run-tests --site test-erpnext-v17 \\ - --module erpnext.accounts.test_gl_characterization -""" - -import frappe -from frappe.tests import IntegrationTestCase -from frappe.tests.classes.context_managers import change_settings - -from erpnext.accounts.doctype.account.test_account import create_account -from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import ( - set_default_account_for_mode_of_payment, -) -from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry -from erpnext.accounts.doctype.purchase_invoice.mapper import make_debit_note -from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice -from erpnext.accounts.doctype.sales_invoice.mapper import make_sales_return -from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.accounts.gl_snapshot import assert_gl_snapshot -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 - -POSTING_DATE = "2024-01-15" -COMPANY = "_Test Company" -CUSTOMER = "_Test Customer" -WAREHOUSE = "_Test Warehouse - _TC" -DN_COMPANY = "_Test Company with perpetual inventory" -DN_WAREHOUSE = "Stores - TCP1" - - -def make_dated_purchase_invoice(**args): - """make_purchase_invoice ignores posting_date unless set_posting_time is on, - which would make snapshots depend on the run date. Force the backdated time.""" - pi = make_purchase_invoice(do_not_save=True, **args) - pi.set_posting_time = 1 - pi.posting_date = POSTING_DATE - return pi - - -def make_dated_payment_entry(**args): - """Standalone Payment Entry (no invoice reference) on a fixed posting date. - - Mirrors test_payment_entry.create_payment_entry without importing that test - module, whose import drags in test-record dependencies that conflict during - discovery.""" - pe = frappe.new_doc("Payment Entry") - pe.company = COMPANY - pe.payment_type = args.get("payment_type") or "Pay" - pe.party_type = args.get("party_type") or "Supplier" - pe.party = args.get("party") or "_Test Supplier" - pe.paid_from = args.get("paid_from") or "_Test Bank - _TC" - pe.paid_to = args.get("paid_to") or "Creditors - _TC" - pe.paid_amount = args.get("paid_amount") or 1000 - pe.setup_party_account_field() - pe.set_missing_values() - pe.set_exchange_rate() - pe.received_amount = pe.paid_amount / pe.target_exchange_rate - pe.reference_no = "Test001" - pe.posting_date = POSTING_DATE - pe.reference_date = POSTING_DATE - return pe - - -def make_dated_journal_entry(accounts, multi_currency=0): - """Journal Entry on a fixed posting date built from explicit account rows. - - Inlined rather than importing test_journal_entry.make_journal_entry, whose - import drags in test-record dependencies that conflict during discovery.""" - jv = frappe.new_doc("Journal Entry") - jv.posting_date = POSTING_DATE - jv.company = COMPANY - jv.remark = "test" - jv.multi_currency = multi_currency - jv.set("accounts", accounts) - return jv - - -class TestGLCharacterization(IntegrationTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - for mode, account in (("Cash", "_Test Cash - _TC"), ("Bank Draft", "_Test Bank - _TC")): - set_default_account_for_mode_of_payment(frappe.get_doc("Mode of Payment", mode), COMPANY, account) - - def test_si_basic(self): - si = create_sales_invoice(posting_date=POSTING_DATE, qty=10, rate=100) - assert_gl_snapshot(self, "si_basic", "Sales Invoice", si.name) - - def test_si_with_taxes(self): - si = create_sales_invoice(posting_date=POSTING_DATE, qty=10, rate=100, do_not_save=True) - si.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account Service Tax - _TC", - "cost_center": "_Test Cost Center - _TC", - "description": "Service Tax", - "rate": 14, - }, - ) - si.insert() - si.submit() - assert_gl_snapshot(self, "si_with_taxes", "Sales Invoice", si.name) - - def test_si_multi_currency(self): - si = create_sales_invoice( - posting_date=POSTING_DATE, qty=10, rate=100, currency="USD", conversion_rate=75 - ) - assert_gl_snapshot(self, "si_multi_currency", "Sales Invoice", si.name) - - def test_si_return(self): - original = create_sales_invoice(posting_date=POSTING_DATE, qty=10, rate=100) - credit_note = make_sales_return(original.name) - credit_note.set_posting_time = 1 - credit_note.posting_date = POSTING_DATE - credit_note.insert() - credit_note.submit() - assert_gl_snapshot(self, "si_return", "Sales Invoice", credit_note.name) - - def test_si_round_off(self): - si = create_sales_invoice(posting_date=POSTING_DATE, qty=1, rate=100, do_not_save=True) - si.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account Service Tax - _TC", - "cost_center": "_Test Cost Center - _TC", - "description": "Service Tax", - "rate": 6.5, - }, - ) - si.insert() - si.submit() - assert_gl_snapshot(self, "si_round_off", "Sales Invoice", si.name) - - def test_si_with_discount_accounting(self): - with change_settings("Selling Settings", {"enable_discount_accounting": 1}): - discount_account = create_account( - account_name="Discount Account", - parent_account="Indirect Expenses - _TC", - company=COMPANY, - ) - si = create_sales_invoice( - posting_date=POSTING_DATE, qty=1, rate=90, discount_account=discount_account - ) - assert_gl_snapshot(self, "si_with_discount", "Sales Invoice", si.name) - - def test_si_with_advance(self): - advance = frappe.get_doc( - { - "doctype": "Payment Entry", - "payment_type": "Receive", - "party_type": "Customer", - "party": CUSTOMER, - "company": COMPANY, - "posting_date": POSTING_DATE, - "paid_from": "Debtors - _TC", - "paid_to": "_Test Cash - _TC", - "paid_from_account_currency": "INR", - "paid_to_account_currency": "INR", - "source_exchange_rate": 1, - "target_exchange_rate": 1, - "reference_no": "ADV-1", - "reference_date": POSTING_DATE, - "paid_amount": 500, - "received_amount": 500, - } - ) - advance.insert() - advance.submit() - - si = create_sales_invoice(posting_date=POSTING_DATE, qty=10, rate=100, do_not_save=True) - si.allocate_advances_automatically = 1 - si.insert() - si.submit() - assert_gl_snapshot(self, "si_with_advance", "Sales Invoice", si.name) - - def test_si_pos(self): - si = create_sales_invoice(posting_date=POSTING_DATE, qty=10, rate=100, do_not_save=True) - si.is_pos = 1 - si.append("payments", {"mode_of_payment": "Cash", "amount": 500}) - si.append("payments", {"mode_of_payment": "Bank Draft", "amount": 500}) - si.insert() - si.submit() - assert_gl_snapshot(self, "si_pos", "Sales Invoice", si.name) - - def test_pi_basic(self): - pi = make_dated_purchase_invoice(qty=5, rate=50) - pi.insert() - pi.submit() - assert_gl_snapshot(self, "pi_basic", "Purchase Invoice", pi.name) - - def test_pi_with_taxes(self): - pi = make_dated_purchase_invoice(qty=5, rate=50) - pi.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account VAT - _TC", - "cost_center": "_Test Cost Center - _TC", - "description": "VAT", - "rate": 15, - }, - ) - pi.insert() - pi.submit() - assert_gl_snapshot(self, "pi_with_taxes", "Purchase Invoice", pi.name) - - def test_pi_multi_currency(self): - pi = make_dated_purchase_invoice(qty=5, rate=50, currency="USD", conversion_rate=75) - pi.insert() - pi.submit() - assert_gl_snapshot(self, "pi_multi_currency", "Purchase Invoice", pi.name) - - def test_pi_return(self): - original = make_dated_purchase_invoice(qty=5, rate=50) - original.insert() - original.submit() - debit_note = make_debit_note(original.name) - debit_note.set_posting_time = 1 - debit_note.posting_date = POSTING_DATE - debit_note.insert() - debit_note.submit() - assert_gl_snapshot(self, "pi_return", "Purchase Invoice", debit_note.name) - - def test_pe_receive_against_si(self): - si = create_sales_invoice(posting_date=POSTING_DATE, qty=10, rate=100) - pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") - pe.posting_date = POSTING_DATE - pe.reference_no = "PE-REC-1" - pe.reference_date = POSTING_DATE - pe.insert() - pe.submit() - assert_gl_snapshot(self, "pe_receive_against_si", "Payment Entry", pe.name) - - def test_pe_pay_against_pi(self): - pi = make_dated_purchase_invoice(qty=5, rate=50) - pi.insert() - pi.submit() - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") - pe.posting_date = POSTING_DATE - pe.reference_no = "PE-PAY-1" - pe.reference_date = POSTING_DATE - pe.insert() - pe.submit() - assert_gl_snapshot(self, "pe_pay_against_pi", "Payment Entry", pe.name) - - def test_pe_with_deductions(self): - si = create_sales_invoice(posting_date=POSTING_DATE, qty=10, rate=100) - pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") - pe.posting_date = POSTING_DATE - pe.reference_no = "PE-DED-1" - pe.reference_date = POSTING_DATE - pe.received_amount = pe.received_amount - 50 - pe.append( - "deductions", - { - "account": "Write Off - _TC", - "cost_center": "_Test Cost Center - _TC", - "amount": 50, - }, - ) - pe.insert() - pe.submit() - assert_gl_snapshot(self, "pe_with_deductions", "Payment Entry", pe.name) - - def test_pe_with_taxes(self): - frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 1) - pe = make_dated_payment_entry(party="_Test Supplier", paid_to="Creditors - _TC") - pe.append( - "taxes", - { - "account_head": "_Test Account Service Tax - _TC", - "charge_type": "Actual", - "tax_amount": 100, - "add_deduct_tax": "Add", - "description": "Service Tax", - "cost_center": "_Test Cost Center - _TC", - }, - ) - pe.save() - pe.submit() - assert_gl_snapshot(self, "pe_with_taxes", "Payment Entry", pe.name) - - def test_pe_multi_currency(self): - pe = make_dated_payment_entry(party="_Test Supplier USD", paid_to="_Test Payable USD - _TC") - pe.target_exchange_rate = 80 - pe.received_amount = pe.paid_amount / pe.target_exchange_rate - pe.save() - pe.submit() - assert_gl_snapshot(self, "pe_multi_currency", "Payment Entry", pe.name) - - def test_je_basic(self): - jv = make_dated_journal_entry( - [ - { - "account": "_Test Cash - _TC", - "cost_center": "_Test Cost Center - _TC", - "debit_in_account_currency": 1000, - "exchange_rate": 1, - }, - { - "account": "_Test Bank - _TC", - "cost_center": "_Test Cost Center - _TC", - "credit_in_account_currency": 1000, - "exchange_rate": 1, - }, - ] - ) - jv.insert() - jv.submit() - assert_gl_snapshot(self, "je_basic", "Journal Entry", jv.name) - - def test_je_multi_currency(self): - jv = make_dated_journal_entry( - [ - { - "account": "_Test Bank USD - _TC", - "cost_center": "_Test Cost Center - _TC", - "debit_in_account_currency": 100, - "exchange_rate": 75, - }, - { - "account": "_Test Bank - _TC", - "cost_center": "_Test Cost Center - _TC", - "credit_in_account_currency": 7500, - "exchange_rate": 1, - }, - ], - multi_currency=1, - ) - jv.insert() - jv.submit() - assert_gl_snapshot(self, "je_multi_currency", "Journal Entry", jv.name) - - def test_je_against_si(self): - si = create_sales_invoice(posting_date=POSTING_DATE, qty=10, rate=100) - jv = make_dated_journal_entry( - [ - { - "account": "Write Off - _TC", - "cost_center": "_Test Cost Center - _TC", - "debit_in_account_currency": 1000, - "exchange_rate": 1, - }, - { - "account": "Debtors - _TC", - "party_type": "Customer", - "party": CUSTOMER, - "cost_center": "_Test Cost Center - _TC", - "credit_in_account_currency": 1000, - "exchange_rate": 1, - "reference_type": "Sales Invoice", - "reference_name": si.name, - }, - ] - ) - jv.insert() - jv.submit() - assert_gl_snapshot(self, "je_against_si", "Journal Entry", jv.name) - - def test_dn_basic(self): - make_stock_entry(item_code="_Test Item", target=DN_WAREHOUSE, qty=10, basic_rate=100) - dn = _make_dated_delivery_note(qty=5, rate=150) - dn.insert() - dn.submit() - assert_gl_snapshot(self, "dn_basic", "Delivery Note", dn.name) - - def test_dn_return(self): - make_stock_entry(item_code="_Test Item", target=DN_WAREHOUSE, qty=10, basic_rate=100) - 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_gl_snapshot(self, "dn_return", "Delivery Note", ret.name) - - def test_se_material_receipt(self): - se = make_stock_entry( - item_code="_Test Item", - target=DN_WAREHOUSE, - qty=5, - basic_rate=100, - company=DN_COMPANY, - posting_date=POSTING_DATE, - do_not_submit=True, - ) - se.submit() - assert_gl_snapshot(self, "se_material_receipt", "Stock Entry", se.name) - - def test_se_material_issue(self): - make_stock_entry( - item_code="_Test Item", target=DN_WAREHOUSE, qty=10, basic_rate=100, company=DN_COMPANY - ) - se = make_stock_entry( - item_code="_Test Item", - source=DN_WAREHOUSE, - qty=5, - company=DN_COMPANY, - posting_date=POSTING_DATE, - do_not_submit=True, - ) - se.submit() - assert_gl_snapshot(self, "se_material_issue", "Stock Entry", se.name) - - def test_se_material_transfer(self): - make_stock_entry( - item_code="_Test Item", target=DN_WAREHOUSE, qty=10, basic_rate=100, company=DN_COMPANY - ) - se = make_stock_entry( - item_code="_Test Item", - source=DN_WAREHOUSE, - target="Finished Goods - TCP1", - qty=5, - company=DN_COMPANY, - posting_date=POSTING_DATE, - do_not_submit=True, - ) - se.submit() - assert_gl_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_gl_snapshot(self, "sr_basic", "Stock Reconciliation", sr.name) - - def test_pr_basic(self): - pr = make_purchase_receipt( - company=DN_COMPANY, - warehouse=DN_WAREHOUSE, - posting_date=POSTING_DATE, - qty=5, - rate=100, - ) - assert_gl_snapshot(self, "pr_basic", "Purchase Receipt", pr.name) - - def test_pr_with_taxes(self): - pr = make_purchase_receipt( - company=DN_COMPANY, - warehouse=DN_WAREHOUSE, - posting_date=POSTING_DATE, - qty=5, - rate=100, - get_taxes_and_charges=True, - ) - assert_gl_snapshot(self, "pr_with_taxes", "Purchase Receipt", pr.name) - - def test_pr_return(self): - original = make_purchase_receipt( - company=DN_COMPANY, - warehouse=DN_WAREHOUSE, - posting_date=POSTING_DATE, - qty=5, - rate=100, - ) - from erpnext.stock.doctype.purchase_receipt.mapper import make_purchase_return - - ret = make_purchase_return(original.name) - ret.posting_date = POSTING_DATE - ret.set_posting_time = 1 - ret.insert() - ret.submit() - assert_gl_snapshot(self, "pr_return", "Purchase Receipt", ret.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 = DN_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", DN_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 = DN_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", DN_COMPANY, "stock_adjustment_account") - sr.cost_center = frappe.get_cached_value("Company", DN_COMPANY, "cost_center") - sr.append( - "items", - { - "item_code": args.get("item_code", "_Test Item"), - "warehouse": args.get("warehouse", DN_WAREHOUSE), - "qty": args.get("qty", 10), - "valuation_rate": args.get("rate", 100), - }, - ) - return sr diff --git a/specs/accounts_refactor_spec.md b/specs/accounts_refactor_spec.md deleted file mode 100644 index 655af9dc87b..00000000000 --- a/specs/accounts_refactor_spec.md +++ /dev/null @@ -1,103 +0,0 @@ -# Accounts / Controller Refactor — Spec - -## Motivation -Move ERPNext away from the deep `AccountsController → SellingController/BuyingController → SalesInvoice` -inheritance chain and the monolithic `sales_invoice.py` / god-object `accounts_controller.py` -toward **composition**: per-doctype `services/` plus shared module-level `accounts/services/`. -Goal is testability, readability, and factoring shared domain logic out so Sales/Purchase -voucher logic is not duplicated. - -## Target structure -``` -erpnext -├── controllers -│ └── transaction_controller.py # thin lifecycle base, delegates to services -├── accounts -│ ├── general_ledger.py # the SINK (unchanged): post / merge / round-off / reverse -│ ├── services -│ │ ├── base_gl_composer.py # BaseGLComposer — shared GL helpers -│ │ ├── gl_validator.py # list-level validation (functions, stateless) -│ │ ├── advances.py -│ │ ├── taxes.py -│ │ └── budget.py -│ └── doctype -│ └── sales_invoice -│ ├── sales_invoice.py # thin: delegates to services -│ ├── services -│ │ ├── gl_composer.py # SalesInvoiceGLComposer(BaseGLComposer) -│ │ ├── pos.py -│ │ ├── loyalty.py -│ │ ├── status.py -│ │ ├── inter_company.py -│ │ ├── fixed_assets.py -│ │ └── timesheet_billing.py -│ ├── mapper.py -│ └── api.py -``` - -## GL layer — frozen design -Pipeline: -``` -SalesInvoiceGLComposer.compose() → gl_entries → gl_validator.validate(gl_entries) → general_ledger.make_gl_entries() -``` - -| Role | Location | Form | Responsibility | -|---|---|---|---| -| **Composer (base)** | `accounts/services/base_gl_composer.py` → `BaseGLComposer` | class (stateful, holds `self.doc`) | shared row factory + common entries | -| **Composer (doctype)** | `sales_invoice/services/gl_composer.py` → `SalesInvoiceGLComposer(BaseGLComposer)` | class | voucher-specific rows via `.compose()` | -| **Validator** | `accounts/services/gl_validator.py` | module functions (stateless) | assert the finished `gl_entries` list is legal to post | -| **Sink** | `accounts/general_ledger.py` (unchanged) | module functions | merge / round-off / post / reverse | - -### Naming decisions (frozen) -- Chose **`compose`** over `make`/`build` — the sink already owns the verb `make` (`make_gl_entries`); `compose` avoids a two-makers collision. -- `base_` prefix on the shared/abstract file; the concrete subclass carries the specific name, no prefix. -- Rejected: `gl_map` (it's a list, not a map — but it's an entrenched public param; rename to `gl_entries` later as its own deprecation pass), `gl_processor` (redundant with `general_ledger.py`), `gl_entries.py` (collides with the `gl_entry` doctype + the ubiquitous local var), `ledger_builder` (clashes with stock/payment ledger), `builder`/`maker` (generic; "maker" collides with `make_gl_entries`). - -## Bucketing `accounts_controller.py` -- **Base composer (`BaseGLComposer`):** `get_gl_dict`, `get_value_in_transaction_currency`, `make_discount_gl_entries` (+ `get_amount_and_base_amount`, `get_tax_amounts`), `make_precision_loss_gl_entry`, `make_exchange_gain_loss_journal` (+ `gain_loss_journal_already_booked`), `set_transaction_currency_and_rate_in_gl_map`. Regional hooks `update_gl_dict_with_regional_fields` / `..._app_based_fields` stay free functions called inside `get_gl_dict`. -- **Advances service:** `set_advances`, `get_advance_entries`, `clear_unallocated_advances`, `validate_advance_entries`, `set_advance_gain_or_loss`, `calculate_total_advance_from_ledger`, `set_total_advance_paid`, `set_advance_payment_status`, `delink_advance_entries`, `create_advance_and_reconcile`, `get_advance_payment_doctypes`, `_remove_advance_payment_ledger_entries`, module funcs `get_advance_journal_entries` / `get_advance_payment_entries`. -- **Validator (from `general_ledger.py`):** `validate_disabled_accounts`, `validate_accounting_period`, `validate_cwip_accounts`, `check_freezing_date`, `validate_against_pcv`, `validate_allowed_dimensions`. (Moved in Phase 1.) - - **Balance trio stays in `general_ledger.py` for now** (revised during Phase 1): `get_debit_credit_difference` / `get_debit_credit_allowance` / `raise_debit_credit_not_equal_error`. `get_debit_credit_difference` *mutates* entries (rounds debit/credit in place) and the trio is interleaved with `process_debit_credit_difference` → `make_round_off_gle` (the round-off *repair* run before and after balancing). It is not a standalone pre-post gate, so it can't move into a pure `validate(gl_entries)` without changing behavior. It travels with round-off when that moves compose-side (see below). - - **Stays in compose (do NOT move to validator):** `process_debit_credit_difference` / `make_round_off_gle` — these *repair* balance by appending a round-off entry (mutation), not validation. - - **Stays in composer (not validator):** row-level checks (right account for a row, dimension applicability) — validator only validates the finished list. -- **Leave in controller:** `validate_company_in_accounting_dimension`, `validate_company` (dimension validation, not GL). - -## Phases -Each phase is behavior-preserving, one draft PR, gated by the Phase-0 snapshot suite + `bench run-tests --site test-site-ai`. - -### Phase 0 — Safety net (first, mandatory) -Characterization tests snapshotting `gl_entries` output for representative transactions (SI/PI with taxes, multi-currency, advances, discounts, round-off, POS). Every later phase passes iff snapshots are byte-identical. - -### Phase 1 — Extract `gl_validator.py` (lowest risk) — DONE -Moved the 6 pure list-level validators to `erpnext/accounts/services/gl_validator.py`; `general_ledger.py` imports and calls them at the existing call sites (no behavior change). A consolidated `gl_validator.validate(gl_entries)` facade is deferred — the current checks run at different points (make_gl_entries / save_entries per-entry / make_reverse_gl_entries), so collapsing them into one call would alter ordering. Verified: all 12 Phase-0 snapshots byte-identical. - -### Phase 2 — Pilot composer on Sales Invoice only — DONE -Added `BaseGLComposer` (minimal: holds `self.doc`) and `SalesInvoiceGLComposer`. SI's `get_gl_entries` is a thin shim delegating to `SalesInvoiceGLComposer(self).compose()`. All 11 SI-specific row builders (make_customer/tax/item/internal_transfer/pos/loyalty/write_off/rounding GL entries, stock_delivered_but_not_billed, get_gl_entries_for_fixed_asset, get_gle_for_change_amount) moved onto the composer and operate on `self.doc`. The `super().get_gl_entries()` stock-expense call became `super(SalesInvoice, doc).get_gl_entries()` (MRO-faithful). Bucket-A shared helpers (`get_gl_dict`, `make_discount_gl_entries`, `make_precision_loss_gl_entry`, `set_transaction_currency_and_rate_in_gl_map`, `get_tax_amounts`, `get_amount_and_base_amount`) **stay on the controller** — they're still called via `self.doc` and only lift to `BaseGLComposer` once all doctypes use composers (can't move while other doctypes inherit them). Verified: 12 snapshots + 10 existing SI tests (perpetual `super()`, POS change, write-off, returns, fixed-asset disposal/regain, internal transfer, loyalty) all green. - -### Phase 3 — Second doctype: Purchase Invoice (base earns its shape) — DONE -Added `PurchaseInvoiceGLComposer` with all 13 PI GL builders migrated (make_supplier_gl_entry, add_supplier_gl_entry, make_item_gl_entries, make_stock_adjustment_entry, get_provisional_accounts, make_provisional_gl_entry, update_net_purchase_amount_for_linked_assets, make_tax_gl_entries, make_internal_transfer_gl_entries, make_gl_entries_for_tax_withholding, make_payment_gl_entries, make_write_off_gl_entry, make_gle_for_rounding_adjustment). PI.get_gl_entries is a thin shim. **Decision after comparing SI and PI: keep `BaseGLComposer` minimal** (`self.doc` + abstract `compose`). The two flows differ too much to share a template — different step order, different builders, per-doctype `make_regional_gl_entries`. Revisit base-lifting only when a 3rd+ doctype reveals a real common shape. Remaining on doc: Bucket-A helpers (`make_precision_loss_gl_entry`, `set_transaction_currency_and_rate_in_gl_map`, `get_gl_dict`, `get_tax_amounts`, `get_amount_and_base_amount`) and inherited `set_gl_entry_for_purchase_expense`. Verified: 12 snapshots + 80/81 existing PI tests green (1 pre-existing failure in `test_purchase_invoice_with_exchange_rate_difference_for_non_stock_item`, unrelated to refactoring). - -### Phase 4 — Roll out composer to remaining GL-posting doctypes -Payment Entry, Journal Entry, Delivery Note, Stock Entry, etc. Mechanical now; one PR per doctype (or small batches), each snapshot-gated. - -- **Payment Entry — DONE.** Added `payment_entry/services/gl_composer.py` → `PaymentEntryGLComposer(BaseGLComposer)`. `compose()` mirrors the old `build_gl_map` (setup party account field, set txn currency/rate, then party/bank/deductions/tax builders, then `add_regional_gl_entries`). The four row builders (`add_party_gl_entries`, `add_bank_gl_entries`, `add_tax_gl_entries`, `add_deductions_gl_entries`) moved onto the composer and operate on `self.doc`; `build_gl_map` is now a thin shim delegating to the composer. **Advance builders stay on the doc** (`make_advance_gl_entries`, `add_advance_gl_entries`, `get_dr_and_account_for_advances`, `add_advance_gl_for_reference`) — they post in a separate pass inside `make_gl_entries`, not part of `compose()`, and belong to the Phase 5 advances service. Shared helpers (`get_gl_dict`, `calculate_base_allocated_amount_for_reference`, `get_exchange_rate`, `get_party_account_for_taxes`) stay on the doc, called via `self.doc`. Extended the Phase-0 snapshot net with 5 PE scenarios (receive-vs-SI, pay-vs-PI, deductions, taxes, multi-currency). Verified: 17 snapshots byte-identical + 53 existing PE tests green. -- **Journal Entry — DONE.** Added `journal_entry/services/gl_composer.py` → `JournalEntryGLComposer(BaseGLComposer)`. A JE already carries its ledger rows in the `accounts` child table, so `compose()` is a straight projection of those rows into GL dicts via `self.doc.get_gl_dict` (resolving txn currency/rate from the first foreign-currency row, mirroring the former `build_gl_map`). `build_gl_map` is now a thin shim (kept public — JE tests call it directly). Dropped the now-unused `get_advance_payment_doctypes` import from `journal_entry.py`. Extended the snapshot net with 3 JE scenarios (basic two-line, multi-currency, against-SI with party + reference). Verified: 20 snapshots byte-identical + 18 existing JE tests green. - -### Phase 5 — Extract `advances.py` -Move the advances cluster. After composers, because advances cross-calls the exchange-gain/loss helper now on `BaseGLComposer`. - -### Phase 6 — Extract remaining domain services from `accounts_controller` -`taxes.py`, `budget.py`, etc. Shrink `accounts_controller` to a thin lifecycle base that delegates. - -### Phase 7 — Split the rest of the `sales_invoice.py` monolith -Non-GL doctype services: `pos.py`, `loyalty.py`, `status.py`, `inter_company.py`, `fixed_assets.py`, `timesheet_billing.py`. Independent of GL work; can run parallel to 5–6. - -### Phase 8 — Collapse the inheritance chain -Flatten `SellingController` / `BuyingController` layers that are now pass-through. Last, because only safe once the delegated-to services exist. - -**Dependencies:** 1→2→3 sequential; 4 and 7 can parallelize once 3 lands; 8 always last. - -## Cross-cutting rules -- Public signatures stay stable — keep the `gl_map=` param and `make_gl_entries` intact. The `gl_map → gl_entries` rename is its own deprecation pass, deferred to the end (or excluded). -- Composers are classes (stateful, per-document); sink and validator are stateless module functions. -- Every phase: behavior-preserving, snapshot + `bench run-tests --site test-site-ai` green before merge, draft PR.