The raw DELETE on Service Subscription was blowing up with
LinkExistsError because Service Contract.service_subscription still
referenced the sub. Worse: silently unlinking a live contract would
cost the business the break fee (résidentiel = avantages résiduels,
commercial = mensualités restantes).
Now when the user clicks 🗑 on a sub:
1. loadServiceContracts pulls `service_subscription` so the client
can spot the link without a round-trip.
2. If a non-terminal contract is linked, the dialog upgrades to:
• header: Contract name + type
• term bar: start → end, months elapsed / months remaining
(pulled live from /contract/calculate-termination)
• penalty breakdown box: total fee, split into benefits to
refund + remaining months, plus a warning that a termination
invoice will be created
• radio: "Désactiver seulement (conserver le contrat)" vs
"Résilier + facturer X$ + supprimer"
Suspend-only route goes through toggleSubStatus (no fee).
Terminate route hits /contract/terminate (status→Résilié +
invoice), then unlinks + deletes the sub, and drops an audit
line referencing the generated invoice.
3. If the linked contract is already Résilié/Complété we just scrub
the stale link inline in the plain confirm path so the
dispatcher isn't forced into the termination UI.
- InlineField on monthly row price (dblclick) + annual row monthly base
price. Saves via Service Subscription.monthly_price → mirrored back
into the UI row's actual_price; drops an audit line on the customer
timeline.
- Delete button (confirm dialog, v-if=can('delete_records')) on both
monthly + annual rows. Uses deleteDoc + local splice + invalidates
location + section caches.
- display_order custom Int field on Service Subscription, persisted in
10-step increments on drag reorder (so manual inserts have room to
squeeze between without a full re-number pass). loadSubscriptions
sorts by display_order first so the dispatcher-controlled order
survives a page reload and can drive invoice print ordering later.
- Rebate rows nested visually: 32px indent + arrow glyph + lighter
red background + smaller type + inherited red color on the inline
price input. Matches the invoice PDF grouping dispatchers expect.
Adding a forfait from the client detail dialog failed with `Update
failed: 417` because the code path manipulated ERPNext's stock
Subscription doctype — a parent/child (Subscription Plan rows) model
with tight validation ("Subscription End Date is mandatory to follow
calendar months"), and whose `plan` field expects an `SP-<hash>` doc
name rather than a free-form string.
Meanwhile all new subscription work — contract signing, chain
activation, prorated invoicing — already writes to our flat custom
`Service Subscription` doctype. The two systems were not talking to
each other: the Service Subscription created for CTR-00008 was
invisible in the client UI (which only read stock Subscription), and
the stock Subscription created by "Ajouter un service" was invisible
to the contract/chain system.
This commit makes Service Subscription the canonical doctype for
everything the ops UI does:
- useClientData.loadSubscriptions: read Service Subscription directly
(flat doc → UI row) instead of reading stock Subscription + joining
its Subscription Plan child rows to Items. Legacy stock Subscription
rows (~39k from the 2026-03-29 migration) stay as audit records
but are no longer surfaced.
- ClientDetailPage.createService: POST a Service Subscription doc
(category inferred from item_group). No parent/child logic, no
calendar-month coupling, no SP-<hash> plan reference. Manual
description + price entry now works without a catalog pick.
- useSubscriptionActions.updateSub: drop the bogus `ASUB-*` name-based
doctype detection (ASUB is not a real prefix — both stock and
Service subs are named SUB-<hex|digits>) and always target Service
Subscription. Also surface ERPNext's exception one-liner instead of
raw HTML when an update fails.
- searchPlans: empty/short query now returns top-50 of the Subscription
Plan catalog so dispatchers can browse instead of being forced to
guess a name prefix.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>