Files
ns_erpnext_app/ns_app/setup.py
Norman King c20dd18287 feat(statements): untaxed late-fee series, skip-fee option, audit trail
- Bill late-fee invoices under a dedicated naming series (LPF-.YYYY.-),
  registered on Sales Invoice via after_migrate, so they are easy to spot
  and filter.
- Late fees are never taxed: a zero 'Actual' tax line keeps the taxes
  table non-empty so ERPNext skips auto-applying company/item tax
  templates (posts nothing to the ledger). Fee total == computed fee.
- generate_statements(skip_late_fee=...) generates a statement without
  billing a fee.
- Record every generation on the customer's timeline (add_comment) as an
  audit trail, noting Total Due and the fee invoice raised / skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 19:52:03 -04:00

47 lines
1.5 KiB
Python

"""App setup: custom fields created/synced on migrate."""
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from ns_app.api.statements import LATE_FEE_NAMING_SERIES
# Fee schedule/amounts live on ERPNext's Dunning Type; this adds the one thing
# it lacks — the Item used to bill a late fee as a Sales Invoice.
CUSTOM_FIELDS = {
"Dunning Type": [
{
"fieldname": "custom_late_fee_item",
"label": "Late Fee Item",
"fieldtype": "Link",
"options": "Item",
"insert_after": "income_account",
"description": (
"Item used to bill a late-payment fee as a Sales Invoice when "
"customer statements are generated."
),
}
]
}
def _register_late_fee_naming_series():
"""Add the late-fee series to Sales Invoice's naming_series options."""
field = frappe.get_meta("Sales Invoice").get_field("naming_series")
options = [o for o in (field.options or "").split("\n")] if field else []
if LATE_FEE_NAMING_SERIES not in options:
options.append(LATE_FEE_NAMING_SERIES)
make_property_setter(
"Sales Invoice",
"naming_series",
"options",
"\n".join(options),
"Text",
validate_fields_for_doctype=False,
)
def after_migrate():
create_custom_fields(CUSTOM_FIELDS)
_register_late_fee_naming_series()