- 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>
47 lines
1.5 KiB
Python
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()
|