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:
Nabin Hait
2026-05-27 00:49:48 +05:30
parent dfbd8db9d3
commit 064340cafb
14 changed files with 799 additions and 0 deletions

View 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}'")

View 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"
}
]

View 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"
}
]

View 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"
}
]

View 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"
}
]

View 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"
}
]

View 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"
}
]

View 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"
}
]

View 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"
}
]

View 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"
}
]

View 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"
}
]

View 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"
}
]

View 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"
}
]

View 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)