New PATCH /admin/contracts/:id (permission contracts.edit, superadmin
only) recalculates the price snapshot server-side and can change term
end, renewal and notice period. A reason is required and before/after
values go to the audit log. Changing the term end re-arms the renewal
reminder. The contract page gets a matching edit form.
Also fixes the domain test after replacing the KCS order number with
the order date.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sends a one-time email ~30 days before any contract's term_end
(whether renewal='auto' or 'none'), with "Behalten" and "Kündigen"
action links. "Behalten" extends the contract by one renewal term;
"Kündigen" sets the same cancellation effective-date logic as the
customer-facing cancel flow and opens a staff ticket so the external
deregistration (e.g. with a domain registrar) actually gets done.
Uses a dedicated single-use token table (contract_renewal_tokens)
since contracts, unlike user_tokens, are not scoped to a single user.
The public confirmation page lives at /vertrag-entscheidung.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>