Hosting-Produkte: Staffelpreise nach Laufzeit (1/3/6/12/24 Monate)
This commit is contained in:
parent
e0fd57532d
commit
5efe37ecc0
12 changed files with 145 additions and 28 deletions
|
|
@ -113,3 +113,20 @@ describe('Rechnungspositionen aus der Domain-Aufstellung', () => {
|
|||
expect((await call(app, acc, 'GET', `/invoices/${draft.id}`)).json().totalNetCents).toBe(1891);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rechnungsposition aus Produkt mit Laufzeit-Staffel', () => {
|
||||
it('Ohne Laufzeitangabe der Basispreis, mit Laufzeitangabe die Staffel; unbekannte Laufzeit abgelehnt', async () => {
|
||||
const admin = await staff('psugg-admin@example.com', 'superadmin'); const acc = await staff('psugg-acc@example.com', 'accounting');
|
||||
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: 'HOST-PSUGG', category: 'hosting', orderableByCustomer: true, requiresApproval: false, customerActions: [], status: 'active',
|
||||
version: { name: 'Hosting X', taxRateId: t19, setupCents: 0, recurringCents: 999, billingInterval: 'monthly', termMonths: 1, renewal: 'auto', renewalTermMonths: 1, noticeDays: 30, provisioning: {},
|
||||
termPrices: [{ termMonths: 12, recurringCents: 9588 }] },
|
||||
});
|
||||
const base = (await call(app, acc, 'GET', `/invoices/suggest-from-product/${prod.json().id}`)).json();
|
||||
expect(base.items).toEqual([{ description: 'Hosting X', quantity: 1, unitPriceNetCents: 999, taxBp: 1900 }]);
|
||||
const tier = (await call(app, acc, 'GET', `/invoices/suggest-from-product/${prod.json().id}?termMonths=12`)).json();
|
||||
expect(tier.items).toEqual([{ description: 'Hosting X (12 Monate)', quantity: 1, unitPriceNetCents: 9588, taxBp: 1900 }]);
|
||||
expect((await call(app, acc, 'GET', `/invoices/suggest-from-product/${prod.json().id}?termMonths=6`)).statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -266,3 +266,37 @@ describe('Familytool-Editionen (Paket), Edition und Laufzeitpflege', () => {
|
|||
const days = (new Date(tr.validUntil).getTime() - Date.now()) / 86400000; expect(days).toBeGreaterThan(13); expect(days).toBeLessThan(14.1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Staffelpreise nach Laufzeit', () => {
|
||||
it('Produkt mit mehreren Laufzeiten: Bestellung wählt eine Staffel, Preis/Laufzeit werden serverseitig aus der Staffel übernommen', async () => {
|
||||
const admin = await staff('term-admin@example.com', 'admin');
|
||||
const A = await customer(admin, 'business', 'Firma Term', 'term-a@example.com');
|
||||
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: 'HOST-TERM', category: 'hosting', orderableByCustomer: true, requiresApproval: false, customerActions: [], status: 'active',
|
||||
version: { name: 'Hosting Staffel', taxRateId: t19, setupCents: 500, recurringCents: 999, billingInterval: 'monthly', termMonths: 1, renewal: 'auto', renewalTermMonths: 1, noticeDays: 30, provisioning: {},
|
||||
termPrices: [{ termMonths: 1, recurringCents: 999 }, { termMonths: 3, recurringCents: 2699 }, { termMonths: 12, recurringCents: 9588 }, { termMonths: 24, recurringCents: 17988 }] },
|
||||
});
|
||||
expect(prod.statusCode).toBe(200);
|
||||
// doppelte Laufzeit wird abgelehnt
|
||||
expect((await call(app, admin, 'POST', '/admin/products', {
|
||||
sku: 'HOST-TERM-BAD', category: 'hosting', orderableByCustomer: true, requiresApproval: false, customerActions: [], status: 'draft',
|
||||
version: { name: 'x', taxRateId: t19, setupCents: 0, recurringCents: 0, billingInterval: 'monthly', termMonths: 1, renewal: 'none', noticeDays: 30, provisioning: {}, termPrices: [{ termMonths: 12, recurringCents: 100 }, { termMonths: 12, recurringCents: 200 }] },
|
||||
})).statusCode).toBe(400);
|
||||
|
||||
const cat = (await call(app, A.client, 'GET', `/catalog?org=${A.org}`)).json();
|
||||
const item = cat.find((c: any) => c.sku === 'HOST-TERM');
|
||||
expect(item.termPrices).toEqual([
|
||||
{ termMonths: 1, price: { net: 999, tax: 190, gross: 1189 } }, { termMonths: 3, price: { net: 2699, tax: 513, gross: 3212 } },
|
||||
{ termMonths: 12, price: { net: 9588, tax: 1822, gross: 11410 } }, { termMonths: 24, price: { net: 17988, tax: 3418, gross: 21406 } },
|
||||
]);
|
||||
|
||||
// nicht angebotene Laufzeit wird abgelehnt
|
||||
expect((await call(app, A.client, 'POST', '/orders', { orgId: A.org, items: [{ productId: prod.json().id, termMonths: 6 }] })).statusCode).toBe(400);
|
||||
|
||||
const order = (await call(app, A.client, 'POST', '/orders', { orgId: A.org, items: [{ productId: prod.json().id, termMonths: 12 }] })).json();
|
||||
const items = (await call(app, admin, 'GET', `/orders/${order.id}`)).json().items;
|
||||
expect(items[0].snapshot.recurring).toEqual({ net: 9588, tax: 1822, gross: 11410 });
|
||||
expect(items[0].snapshot.terms).toEqual({ termMonths: 12, renewal: 'auto', renewalTermMonths: 12, noticeDays: 30 });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import mysql from 'mysql2/promise';
|
|||
import '../src/core/config.js';
|
||||
|
||||
/** Vor jeder Testdatei: Datenzeilen leeren (Testdatenbank!), Stammdaten (Steuersätze, Einstellungen) und Zähler zurücksetzen. */
|
||||
const DATA = ['backup_targets', 'backup_settings', 'domain_tlds', 'domain_records', 'ticket_attachments', 'ticket_messages', 'tickets', 'invoice_items', 'invoices', 'audit_events', 'jobs', 'mail_log', 'contracts', 'order_items', 'orders', 'product_versions', 'products', 'resources', 'connector_instances', 'sessions', 'mfa_totp', 'recovery_codes', 'user_tokens', 'memberships', 'billing_profiles', 'organizations', 'users'];
|
||||
const DATA = ['backup_targets', 'backup_settings', 'domain_tlds', 'domain_records', 'ticket_attachments', 'ticket_messages', 'tickets', 'invoice_items', 'invoices', 'product_term_prices', 'audit_events', 'jobs', 'mail_log', 'contracts', 'order_items', 'orders', 'product_versions', 'products', 'resources', 'connector_instances', 'sessions', 'mfa_totp', 'recovery_codes', 'user_tokens', 'memberships', 'billing_profiles', 'organizations', 'users'];
|
||||
beforeAll(async () => {
|
||||
if (process.env.DB_NAME !== 'kundencenter_test') throw new Error('Tests dürfen nur gegen kundencenter_test laufen');
|
||||
const c = await mysql.createConnection({ host: process.env.DB_HOST ?? '127.0.0.1', user: process.env.DB_USER!, password: process.env.DB_PASSWORD!, database: 'kundencenter_test' });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue