Let superadmins adjust contract price and terms
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>
This commit is contained in:
parent
d00f1a4cec
commit
466bd83b7e
4 changed files with 104 additions and 10 deletions
|
|
@ -203,6 +203,32 @@ describe('Bruttopreise', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Vertrag nachträglich anpassen (Superadmin)', () => {
|
||||
it('berechnet den Preis serverseitig neu, ändert Konditionen und protokolliert; nur Superadmin', async () => {
|
||||
const admin = await staff('adj-adm@shop.test', 'admin'); const sa = await staff('adj-sa@shop.test', 'superadmin');
|
||||
const P = await customer(admin, 'private', 'Anna Anpass', 'adj-p@shop.test');
|
||||
const t19 = (await call(app, admin, 'GET', '/admin/tax-rates')).json().find((t: any) => t.rateBp === 1900).id;
|
||||
const prod = (await call(app, admin, 'POST', '/admin/products', { sku: 'ADJ-1', category: 'service', status: 'active', orderableByCustomer: true, requiresApproval: false,
|
||||
version: { name: 'Altvertrag', taxRateId: t19, setupCents: 0, recurringCents: 0, billingInterval: 'yearly', termMonths: 12, renewal: 'auto', renewalTermMonths: 12, noticeDays: 30, provisioning: {} } })).json();
|
||||
await call(app, P.client, 'POST', '/orders', { orgId: P.org, items: [{ productId: prod.id }] });
|
||||
await drain();
|
||||
const k = (await call(app, P.client, 'GET', '/contracts')).json()[0];
|
||||
const body = { recurringCents: 2499, basis: 'gross', reason: 'Unkostenpreis vereinbart', termEnd: '2027-04-24', noticeDays: 14 };
|
||||
expect((await call(app, admin, 'PATCH', `/admin/contracts/${k.id}`, body)).statusCode).toBe(403);
|
||||
expect((await call(app, P.client, 'PATCH', `/admin/contracts/${k.id}`, body)).statusCode).toBe(403);
|
||||
expect((await call(app, sa, 'PATCH', `/admin/contracts/${k.id}`, { ...body, reason: '' })).statusCode).toBe(400);
|
||||
expect((await call(app, sa, 'PATCH', `/admin/contracts/${k.id}`, { renewal: 'auto', renewalTermMonths: 0, reason: 'ungültig' })).statusCode).toBe(400);
|
||||
expect((await call(app, admin, 'GET', `/contracts/${k.id}`)).json().canEdit).toBe(false);
|
||||
expect((await call(app, sa, 'GET', `/contracts/${k.id}`)).json().canEdit).toBe(true);
|
||||
expect((await call(app, sa, 'PATCH', `/admin/contracts/${k.id}`, body)).statusCode).toBe(200);
|
||||
const d = (await call(app, P.client, 'GET', `/contracts/${k.id}`)).json();
|
||||
expect(d.price.recurring).toEqual({ net: 2100, tax: 399, gross: 2499 }); expect(d.price.basis).toBe('gross'); expect(d.price.name).toBe('Altvertrag');
|
||||
expect(d.noticeDays).toBe(14); expect(new Date(d.termEnd).getFullYear()).toBe(2027); expect(d.canEdit).toBe(false);
|
||||
const log = await one("SELECT after_json FROM audit_events WHERE action = 'contract.adjust' AND resource_id = ?", [k.id]);
|
||||
expect(JSON.stringify(log?.after_json)).toContain('Unkostenpreis vereinbart');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Familytool-Editionen (Paket), Edition und Laufzeitpflege', () => {
|
||||
it('importiert das Paket als Entwürfe, sperrt Aktivierung ohne Anbieter-Erweiterung, liefert Editionen und verlängert die Lizenz mit dem Vertrag', async () => {
|
||||
resetMock();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue