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
|
|
@ -59,7 +59,7 @@ export const ordersModule: KcModule = {
|
|||
// ---- Bestellung anlegen ------------------------------------------------
|
||||
app.post('/orders', async (req) => {
|
||||
const a = requireAuth(req);
|
||||
const b = z.object({ orgId: z.string().uuid(), note: z.string().trim().max(500).optional(), items: z.array(z.object({ productId: z.string().uuid(), quantity: z.number().int().min(1).max(100).default(1), discountBp: z.number().int().min(0).max(10000).default(0) })).min(1).max(20) }).parse(req.body);
|
||||
const b = z.object({ orgId: z.string().uuid(), note: z.string().trim().max(500).optional(), items: z.array(z.object({ productId: z.string().uuid(), quantity: z.number().int().min(1).max(100).default(1), discountBp: z.number().int().min(0).max(10000).default(0), termMonths: z.number().int().min(1).max(120).optional() })).min(1).max(20) }).parse(req.body);
|
||||
const staff = can(a.principal, 'orders.write');
|
||||
if (!canInOrg(a.principal, b.orgId, 'orders.create', 'orders.write')) { if (!canInOrg(a.principal, b.orgId, 'orders.read', 'orders.read')) throw notFound(); throw forbidden(); }
|
||||
const org = await one('SELECT id, status, customer_type FROM organizations WHERE id = ?', [b.orgId]);
|
||||
|
|
@ -74,9 +74,17 @@ export const ordersModule: KcModule = {
|
|||
if (!staff && !p.orderable_by_customer) throw badRequest('Dieses Produkt kann nicht selbst bestellt werden', 'NOT_ORDERABLE');
|
||||
if (!staff && it.discountBp > 0) throw forbidden('Rabatte können nur vom Personal gewährt werden', 'DISCOUNT_FORBIDDEN');
|
||||
if (p.connector_instance_id && it.quantity !== 1) throw badRequest('Provisionierte Produkte können nur einzeln bestellt werden', 'QUANTITY_NOT_ALLOWED');
|
||||
// Preis IMMER serverseitig aus der aktuellen Produktversion; Eingaben des Clients beeinflussen den Preis nicht
|
||||
const price = calculatePrice({ basis: p.price_basis, setupCents: p.setup_cents, recurringCents: p.recurring_cents, taxBp: p.tax_bp, interval: p.billing_interval, quantity: it.quantity, discountBp: it.discountBp });
|
||||
let terms = { termMonths: p.term_months as number, renewal: p.renewal as 'auto' | 'none', renewalTermMonths: p.renewal_term_months as number, noticeDays: p.notice_days as number };
|
||||
// Staffelpreis nach Laufzeit (optional): ersetzt den wiederkehrenden Basispreis, wenn eine passende Laufzeit gewählt wurde
|
||||
// (das Abrechnungsintervall bleibt für die Anzeige erhalten, z. B. "jährlich"; die tatsächliche Laufzeit steht separat in terms.termMonths).
|
||||
let recurringCents = p.recurring_cents, termMonths = p.term_months as number, renewalTermMonths = p.renewal_term_months as number;
|
||||
if (it.termMonths !== undefined) {
|
||||
const tier = await one('SELECT recurring_cents FROM product_term_prices WHERE product_version_id = ? AND term_months = ?', [p.vid, it.termMonths]);
|
||||
if (!tier) throw badRequest('Diese Laufzeit wird für dieses Produkt nicht angeboten', 'TERM_NOT_AVAILABLE');
|
||||
recurringCents = tier.recurring_cents; termMonths = it.termMonths; if (p.renewal === 'auto') renewalTermMonths = it.termMonths;
|
||||
}
|
||||
// Preis IMMER serverseitig aus der aktuellen Produktversion; Eingaben des Clients beeinflussen den Preis nicht (außer der Laufzeitwahl oben)
|
||||
const price = calculatePrice({ basis: p.price_basis, setupCents: p.setup_cents, recurringCents, taxBp: p.tax_bp, interval: p.billing_interval, quantity: it.quantity, discountBp: it.discountBp });
|
||||
let terms = { termMonths, renewal: p.renewal as 'auto' | 'none', renewalTermMonths, noticeDays: p.notice_days as number };
|
||||
if (org.customer_type === 'private' && terms.renewal === 'auto') terms = { ...terms, ...consumerTerms(terms.renewalTermMonths, terms.noticeDays) }; // Verbraucherregel, rechtlich zu prüfen
|
||||
lines.push({ p, it, snapshot: { productId: p.id, sku: p.sku, productVersionId: p.vid, version: p.version, name: p.vname, category: p.category, customerType: org.customer_type, ...price, terms } });
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue