Rechnungen: Positionen auch aus der Domain-Aufstellung übernehmen (Verkaufspreis)
This commit is contained in:
parent
6f9efb91dc
commit
e0fd57532d
3 changed files with 38 additions and 4 deletions
|
|
@ -156,6 +156,16 @@ export const invoicesModule: KcModule = {
|
|||
if (items.length === 0) items.push({ description: p.name, quantity: 1, unitPriceNetCents: 0, taxBp: p.tax_bp });
|
||||
return { items };
|
||||
});
|
||||
/** Vorschlag aus der Domain-Aufstellung: der dort errechnete Verkaufspreis (nie der Einkauf), plus Einrichtung falls vorhanden. */
|
||||
app.get('/invoices/suggest-from-domain/:domainRecordId', async (req) => {
|
||||
const a = requirePermission(req, 'invoices.write'); const { domainRecordId } = z.object({ domainRecordId: z.string().uuid() }).parse(req.params);
|
||||
const d = await one('SELECT * FROM domain_records WHERE id = ?', [domainRecordId]); if (!d) throw notFound();
|
||||
if (d.sell_net_cents === null) throw badRequest('Für diese Domain ist noch kein Verkaufspreis hinterlegt (Aufschlag fehlt unter Domains → Preisliste). Bitte dort ergänzen oder die Position von Hand erfassen.', 'NO_SELL_PRICE');
|
||||
const items = [];
|
||||
if (d.setup_cost_cents > 0) { const setupNet = Math.round((d.setup_cost_cents * 10000) / (10000 + d.tax_bp)); items.push({ description: `Einrichtung ${d.domain}`, quantity: 1, unitPriceNetCents: setupNet, taxBp: d.tax_bp }); }
|
||||
items.push({ description: d.term_months ? `${d.domain} (${d.term_months} Monate)` : d.domain, quantity: 1, unitPriceNetCents: d.sell_net_cents, taxBp: d.tax_bp });
|
||||
return { orgId: d.org_id, items };
|
||||
});
|
||||
|
||||
app.post('/invoices/:id/issue', async (req) => {
|
||||
const a = requirePermission(req, 'invoices.write'); const { id } = z.object({ id: z.string().uuid() }).parse(req.params);
|
||||
|
|
|
|||
|
|
@ -92,3 +92,24 @@ describe('Rechnungspositionen aus Produkt/Vertrag übernehmen', () => {
|
|||
expect((await call(app, acc, 'GET', `/invoices/${draft.id}`)).json().totalNetCents).toBe(3500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rechnungspositionen aus der Domain-Aufstellung', () => {
|
||||
it('Verkaufspreis (nie Einkauf) wird übernommen, inkl. Einrichtung; ohne Aufschlag klare Fehlermeldung', async () => {
|
||||
const admin = await staff('domsug-admin@example.com', 'superadmin'); const acc = await staff('domsug-acc@example.com', 'accounting');
|
||||
const A = await customer(admin, 'Kunde A', 'domsug-a@example.com');
|
||||
await call(app, admin, 'POST', '/admin/domain-tlds/import', { text: 'com\t12\t12,50\t12,00\t11,00\t10,50\t7,50 €' });
|
||||
|
||||
const unpriced = (await call(app, admin, 'POST', '/admin/domain-records', { domain: 'domsug-x.com', orgId: A.org })).json();
|
||||
expect((await call(app, acc, 'GET', `/invoices/suggest-from-domain/${unpriced.id}`)).statusCode).toBe(400);
|
||||
|
||||
await call(app, admin, 'PUT', '/admin/domain-settings', { tier: 1, rounding: false, basis: 'gross', margin: { type: 'percent', value: 2000 } });
|
||||
const priced = (await call(app, admin, 'POST', '/admin/domain-records', { domain: 'domsug-y.com', orgId: A.org })).json();
|
||||
const sugg = (await call(app, acc, 'GET', `/invoices/suggest-from-domain/${priced.id}`)).json();
|
||||
expect(sugg.orgId).toBe(A.org);
|
||||
expect(sugg.items).toEqual([{ description: 'Einrichtung domsug-y.com', quantity: 1, unitPriceNetCents: 630, taxBp: 1900 }, { description: 'domsug-y.com (12 Monate)', quantity: 1, unitPriceNetCents: 1261, taxBp: 1900 }]);
|
||||
|
||||
const draft = (await call(app, acc, 'POST', '/invoices', { orgId: A.org })).json();
|
||||
await call(app, acc, 'PUT', `/invoices/${draft.id}/items`, { items: sugg.items });
|
||||
expect((await call(app, acc, 'GET', `/invoices/${draft.id}`)).json().totalNetCents).toBe(1891);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue