mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-15 15:38:39 +00:00
test: add Phase 0 GL characterization safety net
Golden-master snapshot harness (GLSnapshot / assert_gl_snapshot) plus 12 characterization scenarios for Sales and Purchase Invoice (basic, taxes, multi-currency, returns, round-off, discount accounting, advance, POS). Locks current GL Entry output so the upcoming GL pipeline refactor (composer / validator / sink) can be verified byte-identical. Regenerate goldens with REGEN_GL_SNAPSHOTS=1.
This commit is contained in:
110
erpnext/accounts/gl_snapshot.py
Normal file
110
erpnext/accounts/gl_snapshot.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""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}'")
|
||||
30
erpnext/accounts/gl_snapshots/pi_basic.json
Normal file
30
erpnext/accounts/gl_snapshots/pi_basic.json
Normal file
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
30
erpnext/accounts/gl_snapshots/pi_multi_currency.json
Normal file
30
erpnext/accounts/gl_snapshots/pi_multi_currency.json
Normal file
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
30
erpnext/accounts/gl_snapshots/pi_return.json
Normal file
30
erpnext/accounts/gl_snapshots/pi_return.json
Normal file
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
58
erpnext/accounts/gl_snapshots/pi_with_taxes.json
Normal file
58
erpnext/accounts/gl_snapshots/pi_with_taxes.json
Normal file
@@ -0,0 +1,58 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
30
erpnext/accounts/gl_snapshots/si_basic.json
Normal file
30
erpnext/accounts/gl_snapshots/si_basic.json
Normal file
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
30
erpnext/accounts/gl_snapshots/si_multi_currency.json
Normal file
30
erpnext/accounts/gl_snapshots/si_multi_currency.json
Normal file
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
86
erpnext/accounts/gl_snapshots/si_pos.json
Normal file
86
erpnext/accounts/gl_snapshots/si_pos.json
Normal file
@@ -0,0 +1,86 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
30
erpnext/accounts/gl_snapshots/si_return.json
Normal file
30
erpnext/accounts/gl_snapshots/si_return.json
Normal file
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
58
erpnext/accounts/gl_snapshots/si_round_off.json
Normal file
58
erpnext/accounts/gl_snapshots/si_round_off.json
Normal file
@@ -0,0 +1,58 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
30
erpnext/accounts/gl_snapshots/si_with_advance.json
Normal file
30
erpnext/accounts/gl_snapshots/si_with_advance.json
Normal file
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
44
erpnext/accounts/gl_snapshots/si_with_discount.json
Normal file
44
erpnext/accounts/gl_snapshots/si_with_discount.json
Normal file
@@ -0,0 +1,44 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
44
erpnext/accounts/gl_snapshots/si_with_taxes.json
Normal file
44
erpnext/accounts/gl_snapshots/si_with_taxes.json
Normal file
@@ -0,0 +1,44 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
189
erpnext/accounts/test_gl_characterization.py
Normal file
189
erpnext/accounts/test_gl_characterization.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""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.purchase_invoice.purchase_invoice import make_debit_note
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice 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
|
||||
|
||||
POSTING_DATE = "2024-01-15"
|
||||
COMPANY = "_Test Company"
|
||||
CUSTOMER = "_Test Customer"
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user