feat(crm): 3-reminder dunning + flat/percent Mahngebühr on 2nd & 3rd + AGB notice
- Late fee can now be a FLAT amount OR a PERCENTAGE of the invoice gross (crm_invoices_late_fee_type/_percent, migration 143; defaults preserve the current flat behaviour). - Fee is charged from the 2nd reminder onward and accumulates per fee-bearing reminder (2nd = 1×, 3rd = 2×), computed from the level so re-applying a level never stacks. New resolvePerReminderFeeMinor() shared by applyReminder + the payment-check fee preview. - Reminder ladder extended to 3 levels (caps raised in sendReminder + recordPaymentCheckAction); the built-in dunning flow now loops 3× (seed v3, re-seeds the disabled built-in on boot). - Settings UI: flat/percent toggle + percent field, and a prominent AGB callout — a late fee is only enforceable if the concrete amount is stated in the terms (Mara's wording), 'verify with your Treuhänder'. en + native de. The fee math is examples-only / Treuhänder-verify; issued invoices stay immutable (the fee is tracked in late_fee_amount_minor, not folded into the original total). Tests 17/17, tsc 0, build green.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Migration 143: late-fee (Mahngebühr) type — flat amount OR percentage.
|
||||
*
|
||||
* Extends the existing flat `crm_invoices_late_fee_minor` with a type switch so
|
||||
* the dunning fee can be a percentage of the invoice gross instead of a fixed
|
||||
* amount. The fee is charged from the 2nd reminder onwards (the 1st is
|
||||
* fee-free), accumulating per fee-bearing reminder (2nd = 1×, 3rd = 2×).
|
||||
*
|
||||
* Seeds conservative defaults that PRESERVE current behaviour: type='flat'
|
||||
* (so the existing flat fee keeps applying) and percent=0. Idempotent —
|
||||
* only inserts keys that don't already exist, never clobbers an admin value.
|
||||
*
|
||||
* ⚠️ A late fee is only legally enforceable if the concrete amount is stated in
|
||||
* the AGB (Liechtenstein/Swiss law) — the admin UI surfaces this; verify with a
|
||||
* Treuhänder. See docs/crm-disclaimers / [[feedback_legal_financial_examples_only]].
|
||||
*/
|
||||
exports.up = async function (knex) {
|
||||
if (!(await knex.schema.hasTable('app_settings'))) return;
|
||||
const seeds = [
|
||||
{ setting_key: 'crm_invoices_late_fee_type', setting_value: JSON.stringify('flat'), setting_type: 'crm' },
|
||||
{ setting_key: 'crm_invoices_late_fee_percent', setting_value: JSON.stringify(0), setting_type: 'crm' },
|
||||
];
|
||||
for (const s of seeds) {
|
||||
const exists = await knex('app_settings').where({ setting_key: s.setting_key }).first();
|
||||
if (!exists) await knex('app_settings').insert({ ...s, updated_at: new Date() });
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = async function (knex) {
|
||||
if (!(await knex.schema.hasTable('app_settings'))) return;
|
||||
await knex('app_settings')
|
||||
.whereIn('setting_key', ['crm_invoices_late_fee_type', 'crm_invoices_late_fee_percent'])
|
||||
.del();
|
||||
};
|
||||
Reference in New Issue
Block a user