includio-cms 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/API.md +19 -3
  2. package/CHANGELOG.md +40 -0
  3. package/DOCS.md +1 -1
  4. package/dist/admin/client/shop/shop-order-detail-page.svelte +85 -0
  5. package/dist/admin/remote/shop.remote.d.ts +58 -0
  6. package/dist/admin/remote/shop.remote.js +18 -0
  7. package/dist/db-postgres/schema/shop/index.d.ts +1 -0
  8. package/dist/db-postgres/schema/shop/index.js +1 -0
  9. package/dist/db-postgres/schema/shop/invoice.d.ts +254 -0
  10. package/dist/db-postgres/schema/shop/invoice.js +27 -0
  11. package/dist/db-postgres/schema/shop/order.d.ts +70 -0
  12. package/dist/db-postgres/schema/shop/order.js +4 -0
  13. package/dist/shop/adapters/fakturownia/client.d.ts +28 -0
  14. package/dist/shop/adapters/fakturownia/client.js +67 -0
  15. package/dist/shop/adapters/fakturownia/index.d.ts +27 -0
  16. package/dist/shop/adapters/fakturownia/index.js +36 -0
  17. package/dist/shop/adapters/fakturownia/payload.d.ts +35 -0
  18. package/dist/shop/adapters/fakturownia/payload.js +45 -0
  19. package/dist/shop/client/index.d.ts +7 -0
  20. package/dist/shop/http/checkout-handler.js +11 -0
  21. package/dist/shop/index.d.ts +4 -1
  22. package/dist/shop/index.js +3 -0
  23. package/dist/shop/nip.d.ts +12 -0
  24. package/dist/shop/nip.js +23 -0
  25. package/dist/shop/server/invoices.d.ts +64 -0
  26. package/dist/shop/server/invoices.js +237 -0
  27. package/dist/shop/server/orders.d.ts +4 -0
  28. package/dist/shop/server/orders.js +11 -0
  29. package/dist/shop/types.d.ts +67 -1
  30. package/dist/updates/0.28.0/index.d.ts +2 -0
  31. package/dist/updates/0.28.0/index.js +38 -0
  32. package/dist/updates/index.js +3 -1
  33. package/package.json +1 -1
package/API.md CHANGED
@@ -1,8 +1,8 @@
1
- # includio-cms — Public API v0.27.0
1
+ # includio-cms — Public API v0.28.0
2
2
 
3
3
  > Auto-generated by `scripts/generate-api-md.ts`. Do not edit by hand.
4
4
 
5
- Entry points: **18** · Stable: **448** · Experimental: **4**
5
+ Entry points: **18** · Stable: **464** · Experimental: **4**
6
6
 
7
7
  Tags:
8
8
  - `@public` — frozen for v1.0; semver-protected.
@@ -223,6 +223,7 @@ Tags:
223
223
  - `const getMediaTags: <inferred>`
224
224
  - `const getMediaTagsWithCounts: <inferred>`
225
225
  - `const getOrderForAdmin: <inferred>`
226
+ - `const getOrderInvoiceAdmin: <inferred>`
226
227
  - `const getOrderRefundsAdmin: <inferred>`
227
228
  - `const getRawEntries: <inferred>`
228
229
  - `const getRawEntry: <inferred>`
@@ -236,6 +237,7 @@ Tags:
236
237
  - `const getSingles: <inferred>`
237
238
  - `const getSubmissionsOverview: <inferred>`
238
239
  - `const isAIAvailable: <inferred>`
240
+ - `const issueInvoiceCmd: <inferred>`
239
241
  - `const listCouponsAdmin: <inferred>`
240
242
  - `const listOrdersAdmin: <inferred>`
241
243
  - `const listShippingMethodsAdmin: <inferred>`
@@ -337,6 +339,8 @@ Tags:
337
339
  - `const shopCouponRedemptionsTable: <inferred>`
338
340
  - `const shopCouponsTable: <inferred>`
339
341
  - `type ShopCouponType = 'percent' | 'fixed'`
342
+ - `const shopInvoicesTable: <inferred>`
343
+ - `type ShopInvoiceStatus = 'pending' | 'issued' | 'sent' | 'failed'`
340
344
  - `const shopOrderItemsTable: <inferred>`
341
345
  - `const shopOrdersTable: <inferred>`
342
346
  - `const shopOrderStatusHistoryTable: <inferred>`
@@ -377,6 +381,8 @@ Tags:
377
381
  - `const shopCouponRedemptionsTable: <inferred>`
378
382
  - `const shopCouponsTable: <inferred>`
379
383
  - `type ShopCouponType = 'percent' | 'fixed'`
384
+ - `const shopInvoicesTable: <inferred>`
385
+ - `type ShopInvoiceStatus = 'pending' | 'issued' | 'sent' | 'failed'`
380
386
  - `const shopOrderItemsTable: <inferred>`
381
387
  - `const shopOrdersTable: <inferred>`
382
388
  - `const shopOrderStatusHistoryTable: <inferred>`
@@ -411,7 +417,9 @@ Tags:
411
417
  - `interface CouponRef`
412
418
  - `type Currency = 'PLN'`
413
419
  - `defineShop(config: ShopConfig): ResolvedShopConfig`
414
- - `type DepositAmount = | { type: 'percent'; value: number } | { type: 'amount'; value: number }` — Deposit amount specifier. `percent` charges `floor(base * value / 100)` of
420
+ - `type DepositAmount = { type: 'percent'; value: number } | { type: 'amount'; value: number }` — Deposit amount specifier. `percent` charges `floor(base * value / 100)` of
421
+ - `fakturowniaAdapter(opts: FakturowniaAdapterOptions): InvoicingAdapter` — Invoicing adapter backed by Fakturownia (fakturownia.pl). Issues a paid VAT
422
+ - `interface FakturowniaAdapterOptions`
415
423
  - `filterUpcoming(variants: T[], config: VariantExpiryConfig | null, now?: Date): T[]` — Return the subset of `variants` that have not yet expired. Order is
416
424
  - `type GeowidgetConfigPreset = 'parcelcollect' | 'parcelsend' | 'parcelcollect247' | string`
417
425
  - `type I18nText = { [lang: string]: string; }`
@@ -421,6 +429,14 @@ Tags:
421
429
  - `interface InpostSenderAddress`
422
430
  - `interpolateTemplate(template: string, vars: Record<string, unknown>, locale: string): string` — String interpolation engine used by `defineShop({ variantLabel.template })`
423
431
  - `class InvalidVariantAttributesError`
432
+ - `interface InvoiceBuyer`
433
+ - `interface InvoiceContext`
434
+ - `interface InvoiceCreateResult`
435
+ - `type InvoiceIssuePolicy = 'b2bAndOnRequest' | 'always'` — When an invoice should be issued automatically after an order is fully paid.
436
+ - `interface InvoiceLineItem`
437
+ - `interface InvoicePayload`
438
+ - `interface InvoicingAdapter` — Adapter that issues invoices with an external provider (e.g. Fakturownia).
439
+ - `isValidNip(nip: string): boolean` — Validate a Polish NIP (tax id) — 10 digits with a weighted checksum.
424
440
  - `isVariantExpired(variant: VariantLike, config: VariantExpiryConfig | null, now: Date = new Date(Date.now())): boolean` — Check whether a variant has expired under the given config. Fail-open by
425
441
  - `manualAdapter(opts: ManualPaymentAdapterOptions = {}): PaymentAdapter` — Manual payment adapter — order goes to awaitingPayment, admin marks as paid later
426
442
  - `interface OrderRef`
package/CHANGELOG.md CHANGED
@@ -3,6 +3,46 @@
3
3
  All notable changes to includio-cms are documented here.
4
4
  Generated from `src/lib/updates/` — do not edit manually.
5
5
 
6
+ ## 0.28.0 — 2026-06-01
7
+
8
+ Fakturowanie — integracja sklepu z Fakturownią: automatyczne wystawianie i wysyłka faktury po opłaceniu zamówienia. Nowy generyczny `InvoicingAdapter` (spójny z payment/carrier) + `fakturowniaAdapter()` jako pierwsza implementacja. Faktura wystawiana fire-and-forget gdy zamówienie jest w pełni opłacone (`!balanceOwed` — zaliczki czekają na dopłatę balansu), wysyłana przez Fakturownię (`send_by_email`). Trigger konfigurowalny per-adapter (`issueWhen`: `b2bAndOnRequest` domyślnie / `always`). Checkout zbiera dane B2B (NIP z twardą walidacją sumy kontrolnej, nazwa firmy, adres do faktury, opt-in „chcę fakturę"). Idempotencja przez `shop_invoices` (unikat per zamówienie); ręczny retry w adminie. Additive only — żadnych breaking zmian.
9
+
10
+ ### Added
11
+ - `defineShop({ invoicing })` (`includio-cms/shop`) — nowy opcjonalny slot adaptera fakturowania, analogiczny do `payment` / `carriers`. `InvoicingAdapter { id, issueWhen?, createInvoice(payload, ctx), send?(externalId, ctx) }` + typy payloadu (`InvoicePayload`, `InvoiceBuyer`, `InvoiceLineItem`, `InvoiceCreateResult`, `InvoiceContext`, `InvoiceIssuePolicy`) wyeksportowane jako `@public` z `includio-cms/shop`.
12
+ - `fakturowniaAdapter({ domain, apiToken, kind?, issueWhen?, sendEmail? })` (`includio-cms/shop`, `@public`) — adapter dla Fakturowni (fakturownia.pl). `createInvoice` mapuje zamówienie na fakturę VAT oznaczoną jako opłacona (`paid_date` = data potwierdzenia płatności), `send` woła `send_by_email` (domyślnie włączone). Numeracja i dane sprzedawcy zarządzane po stronie konta Fakturowni. Sekret `apiToken` przekazywany przez konsumenta z `$env/dynamic/private` (jak `STRIPE_SECRET_KEY`).
13
+ - Auto-faktura po opłaceniu — `maybeIssueInvoiceForOrder(orderId)` (`$lib/shop/server/invoices.ts`) wołane fire-and-forget z `updateOrderStatus` (przy `paid`) oraz z `markBalancePaid` (po dopłacie balansu). Fail-open: błąd providera nie blokuje webhooka płatności. Guard wystawia fakturę tylko gdy zamówienie w pełni opłacone (`status ∈ {paid, preparing, sent, done}` && `!balanceOwed`) — deposit czeka na dopłatę.
14
+ - Konfigurowalny trigger `issueWhen`: `b2bAndOnRequest` (domyślny — faktura gdy podano NIP lub zaznaczono „chcę fakturę") albo `always` (każde opłacone zamówienie, np. dla klienta wymagającego faktur również dla osób fizycznych).
15
+ - `shop_invoices` (nowa tabela) — jedna faktura per zamówienie (`order_id` UNIQUE → idempotencja). Pola `status` (`pending`/`issued`/`sent`/`failed`), `external_id`, `number`, `pdf_url`, `attempts`, `next_retry_at`, `last_error`, `raw`. Kolumny `attempts`/`next_retry_at` zarezerwowane pod przyszły automatyczny retry (obecnie tylko ręczny).
16
+ - Checkout B2B — `shop_orders` dostaje kolumny `customer_nip`, `customer_company_name`, `billing_address` (jsonb), `invoice_requested` (boolean). `createOrderFromCart` / `checkout-handler` przyjmują i zapisują te pola; `CheckoutInput` (`includio-cms/shop/client`) rozszerzony. Storefront form (pola NIP/firma/checkbox/adres) = warstwa konsumenta.
17
+ - `isValidNip(nip)` (`includio-cms/shop`, `@public`) — twarda walidacja polskiego NIP (10 cyfr + suma kontrolna, wagi `[6,5,7,2,3,4,5,6,7]`). Checkout odrzuca nieprawidłowy NIP (400) zanim trafi do bazy — Fakturownia odrzuciłaby błędny NIP, a faktura idzie dalej do KSeF, więc walidujemy u źródła.
18
+ - `getOrderInvoiceAdmin` (query) + `issueInvoiceCmd` (command) — admin remote functions (`includio-cms/admin/remote`). `shop-order-detail-page.svelte` zyskuje sekcję „Faktura": numer, link do PDF, status, przycisk „Wystaw fakturę" / „Wystaw ponownie" (ręczny retry, `force`). Auth-protected (`requireAuth`).
19
+
20
+ ### Migration
21
+
22
+ ```sql
23
+ CREATE TABLE IF NOT EXISTS shop_invoices (
24
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
25
+ order_id uuid NOT NULL UNIQUE REFERENCES shop_orders(id) ON DELETE CASCADE,
26
+ provider text NOT NULL,
27
+ external_id text,
28
+ number text,
29
+ pdf_url text,
30
+ kind text NOT NULL DEFAULT 'vat',
31
+ status text NOT NULL DEFAULT 'pending',
32
+ attempts integer NOT NULL DEFAULT 0,
33
+ next_retry_at timestamptz,
34
+ last_error text,
35
+ raw jsonb,
36
+ created_at timestamptz NOT NULL DEFAULT now(),
37
+ updated_at timestamptz NOT NULL DEFAULT now()
38
+ );
39
+
40
+ ALTER TABLE shop_orders ADD COLUMN IF NOT EXISTS customer_nip text;
41
+ ALTER TABLE shop_orders ADD COLUMN IF NOT EXISTS customer_company_name text;
42
+ ALTER TABLE shop_orders ADD COLUMN IF NOT EXISTS billing_address jsonb;
43
+ ALTER TABLE shop_orders ADD COLUMN IF NOT EXISTS invoice_requested boolean NOT NULL DEFAULT false;
44
+ ```
45
+
6
46
  ## 0.27.0 — 2026-05-29
7
47
 
8
48
  Shop platformowy — Fazy 1 + 2 + 3 + 4 + 5: schema-driven `variantAttributes` (typed `defineShop` schema, Zod walidacja, GIN index helper, ts-gen typed `variant.attributes`) + admin renderer per attribute type + `variantLabel.template` interpolacja z admin auto-prefill nazwy wariantu + `variantExpiry` opt-in (storefront filter, cart/checkout guard, admin "Zakończony" badge) + `paymentPolicy` A-lite (deposit + balance + signed token + refund per kind + admin balance link + per-kind refund dialog). Faza 6 envet pilot (consumer-side end-to-end QA: szkolenia variants + deposit flow + balance link — nie wymaga zmian w core). Post-Faza-5 hot-fixy uwzględnione w tym wydaniu (single release, nie patch): datetime walidator z offsetem, auto-detect deposit kind przy admin manual mark-paid, admin paymentPolicy UI + preserve-on-undefined w upsert. Additive only — żadnych breaking zmian. Plan: `/Users/patryk/.claude/plans/implementacja-ariacms-0-27-partitioned-kahn.md`.
package/DOCS.md CHANGED
@@ -1,4 +1,4 @@
1
- # Includio CMS Documentation (v0.27.0)
1
+ # Includio CMS Documentation (v0.28.0)
2
2
 
3
3
  > This file is auto-generated from the docs site. For the latest version, update the package.
4
4
 
@@ -36,6 +36,37 @@
36
36
  const refundsQuery = $derived(remotes.getOrderRefundsAdmin(orderId));
37
37
  let refundDialogOpen = $state(false);
38
38
 
39
+ const invoiceQuery = $derived(remotes.getOrderInvoiceAdmin(orderId));
40
+ let issuingInvoice = $state(false);
41
+ let invoiceError = $state<string | null>(null);
42
+
43
+ async function issueInvoice(force: boolean) {
44
+ issuingInvoice = true;
45
+ invoiceError = null;
46
+ try {
47
+ const result = await remotes.issueInvoiceCmd({ orderId, force });
48
+ if (!result.success) invoiceError = result.error;
49
+ await invoiceQuery.refresh();
50
+ } finally {
51
+ issuingInvoice = false;
52
+ }
53
+ }
54
+
55
+ function invoiceStatusLabel(status: string): string {
56
+ switch (status) {
57
+ case 'pending':
58
+ return 'W trakcie';
59
+ case 'issued':
60
+ return 'Wystawiona';
61
+ case 'sent':
62
+ return 'Wysłana';
63
+ case 'failed':
64
+ return 'Błąd';
65
+ default:
66
+ return status;
67
+ }
68
+ }
69
+
39
70
  type OrderStatus =
40
71
  | 'new'
41
72
  | 'awaitingPayment'
@@ -501,6 +532,60 @@
501
532
  </section>
502
533
  {/if}
503
534
 
535
+ {#if invoiceQuery.ready && invoiceQuery.current?.invoicingEnabled}
536
+ {@const inv = invoiceQuery.current.invoice}
537
+ {@const orderPaid =
538
+ order.status === 'paid' ||
539
+ order.status === 'preparing' ||
540
+ order.status === 'sent' ||
541
+ order.status === 'done'}
542
+ <section class="border-border bg-card space-y-3 rounded-xl border p-3 text-sm sm:p-5">
543
+ <h2 class="text-base font-bold">Faktura</h2>
544
+ {#if inv}
545
+ <div class="space-y-1">
546
+ <div class="flex items-center justify-between gap-2">
547
+ <span class="font-medium">{inv.number ?? '—'}</span>
548
+ <span class="text-muted-foreground text-xs">{invoiceStatusLabel(inv.status)}</span>
549
+ </div>
550
+ {#if inv.pdfUrl}
551
+ <a
552
+ href={inv.pdfUrl}
553
+ target="_blank"
554
+ rel="noopener noreferrer"
555
+ class="text-primary text-xs underline"
556
+ >
557
+ Otwórz fakturę
558
+ </a>
559
+ {/if}
560
+ {#if inv.status === 'failed' && inv.lastError}
561
+ <p class="text-destructive text-xs">{inv.lastError}</p>
562
+ {/if}
563
+ </div>
564
+ {:else}
565
+ <p class="text-muted-foreground text-xs">Brak faktury dla tego zamówienia.</p>
566
+ {/if}
567
+ {#if invoiceError}
568
+ <p class="text-destructive text-xs">{invoiceError}</p>
569
+ {/if}
570
+ {#if orderPaid}
571
+ <Button
572
+ onclick={() => issueInvoice(true)}
573
+ variant="outline"
574
+ class="w-full"
575
+ disabled={issuingInvoice}
576
+ >
577
+ {inv && (inv.status === 'issued' || inv.status === 'sent')
578
+ ? 'Wystaw ponownie'
579
+ : 'Wystaw fakturę'}
580
+ </Button>
581
+ {:else}
582
+ <p class="text-muted-foreground text-xs">
583
+ Fakturę można wystawić po opłaceniu zamówienia.
584
+ </p>
585
+ {/if}
586
+ </section>
587
+ {/if}
588
+
504
589
  {#if order.carrierType && order.carrierType !== 'none'}
505
590
  <section class="border-border bg-card space-y-3 rounded-xl border p-3 text-sm sm:p-5">
506
591
  <h2 class="text-base font-bold">
@@ -114,7 +114,11 @@ export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunctio
114
114
  customerEmail: string;
115
115
  customerName: string | null;
116
116
  customerPhone: string | null;
117
+ customerNip: string | null;
118
+ customerCompanyName: string | null;
117
119
  shippingAddress: Record<string, string> | null;
120
+ billingAddress: Record<string, string> | null;
121
+ invoiceRequested: boolean;
118
122
  totalNet: number;
119
123
  totalGross: number;
120
124
  vatAmount: number;
@@ -155,7 +159,11 @@ export declare const getOrderForAdmin: import("@sveltejs/kit").RemoteQueryFuncti
155
159
  customerEmail: string;
156
160
  customerName: string | null;
157
161
  customerPhone: string | null;
162
+ customerNip: string | null;
163
+ customerCompanyName: string | null;
158
164
  shippingAddress: Record<string, string> | null;
165
+ billingAddress: Record<string, string> | null;
166
+ invoiceRequested: boolean;
159
167
  totalNet: number;
160
168
  totalGross: number;
161
169
  vatAmount: number;
@@ -216,7 +224,11 @@ export declare const updateOrderStatusCmd: import("@sveltejs/kit").RemoteCommand
216
224
  customerEmail: string;
217
225
  customerName: string | null;
218
226
  customerPhone: string | null;
227
+ customerNip: string | null;
228
+ customerCompanyName: string | null;
219
229
  shippingAddress: Record<string, string> | null;
230
+ billingAddress: Record<string, string> | null;
231
+ invoiceRequested: boolean;
220
232
  totalNet: number;
221
233
  totalGross: number;
222
234
  vatAmount: number;
@@ -312,6 +324,52 @@ export declare const refundOrderCmd: import("@sveltejs/kit").RemoteCommand<{
312
324
  code: "order_not_found" | "order_not_paid" | "no_provider_ref" | "unknown_provider" | "refund_unsupported" | "invalid_amount" | "amount_exceeds_remaining" | "provider_error" | "no_payment_kind";
313
325
  error: string;
314
326
  }>>;
327
+ export declare const getOrderInvoiceAdmin: import("@sveltejs/kit").RemoteQueryFunction<string, {
328
+ invoice: {
329
+ number: string | null;
330
+ raw: unknown;
331
+ id: string;
332
+ status: import("../../db-postgres/schema/shop/index.js").ShopInvoiceStatus;
333
+ createdAt: Date;
334
+ updatedAt: Date;
335
+ orderId: string;
336
+ provider: string;
337
+ kind: string;
338
+ externalId: string | null;
339
+ pdfUrl: string | null;
340
+ attempts: number;
341
+ nextRetryAt: Date | null;
342
+ lastError: string | null;
343
+ } | null;
344
+ invoicingEnabled: boolean;
345
+ }>;
346
+ export declare const issueInvoiceCmd: import("@sveltejs/kit").RemoteCommand<{
347
+ orderId: string;
348
+ force?: boolean | undefined;
349
+ }, Promise<{
350
+ success: true;
351
+ invoice: {
352
+ number: string | null;
353
+ raw: unknown;
354
+ id: string;
355
+ status: import("../../db-postgres/schema/shop/index.js").ShopInvoiceStatus;
356
+ createdAt: Date;
357
+ updatedAt: Date;
358
+ orderId: string;
359
+ provider: string;
360
+ kind: string;
361
+ externalId: string | null;
362
+ pdfUrl: string | null;
363
+ attempts: number;
364
+ nextRetryAt: Date | null;
365
+ lastError: string | null;
366
+ } | null;
367
+ error?: undefined;
368
+ } | {
369
+ success: false;
370
+ error: string;
371
+ invoice?: undefined;
372
+ }>>;
315
373
  export declare const generateBalanceLinkForOrder: import("@sveltejs/kit").RemoteCommand<string, Promise<{
316
374
  success: false;
317
375
  error: string;
@@ -6,6 +6,7 @@ import { createShippingMethod, deleteShippingMethod, getShippingMethod, listShip
6
6
  import { countOrders, getOrderById, getOrderItems, getOrderStatusHistory, listOrders, updateOrderStatus } from '../../shop/server/orders.js';
7
7
  import { cancelShipmentForOrder, createShipmentForOrder } from '../../shop/server/shipments.js';
8
8
  import { sendOrderStatusEmail } from '../../shop/server/email.js';
9
+ import { getInvoiceByOrderId, issueInvoiceForOrder } from '../../shop/server/invoices.js';
9
10
  import { getRefundedAmount, listRefunds, refundOrder, RefundError } from '../../shop/server/refund.js';
10
11
  import { getShopDb } from '../../shop/server/db.js';
11
12
  import { shopCouponsTable } from '../../db-postgres/schema/shop/index.js';
@@ -261,6 +262,23 @@ export const refundOrderCmd = command(z.object({
261
262
  return { success: false, code: 'provider_error', error: message };
262
263
  }
263
264
  });
265
+ export const getOrderInvoiceAdmin = query(z.string(), async (orderId) => {
266
+ requireAuth();
267
+ const invoice = await getInvoiceByOrderId(orderId);
268
+ const invoicingEnabled = !!getCMS().shopConfig?.invoicing;
269
+ return { invoice, invoicingEnabled };
270
+ });
271
+ export const issueInvoiceCmd = command(z.object({ orderId: z.string(), force: z.boolean().optional() }), async ({ orderId, force }) => {
272
+ requireAuth();
273
+ try {
274
+ const invoice = await issueInvoiceForOrder(orderId, { force });
275
+ return { success: true, invoice };
276
+ }
277
+ catch (err) {
278
+ const message = err instanceof Error ? err.message : 'Invoice issuance failed';
279
+ return { success: false, error: message };
280
+ }
281
+ });
264
282
  export const generateBalanceLinkForOrder = command(z.string(), async (orderId) => {
265
283
  requireAuth();
266
284
  const { generateBalanceToken, requireBalanceTokenSecret } = await import('../../shop/server/balance-payment.js');
@@ -7,6 +7,7 @@ export * from './orderStatusHistory.js';
7
7
  export * from './payment.js';
8
8
  export * from './stockReservation.js';
9
9
  export * from './refunds.js';
10
+ export * from './invoice.js';
10
11
  export * from './webhookEvents.js';
11
12
  export * from './coupons.js';
12
13
  export * from './couponRedemptions.js';
@@ -7,6 +7,7 @@ export * from './orderStatusHistory.js';
7
7
  export * from './payment.js';
8
8
  export * from './stockReservation.js';
9
9
  export * from './refunds.js';
10
+ export * from './invoice.js';
10
11
  export * from './webhookEvents.js';
11
12
  export * from './coupons.js';
12
13
  export * from './couponRedemptions.js';
@@ -0,0 +1,254 @@
1
+ export type ShopInvoiceStatus = 'pending' | 'issued' | 'sent' | 'failed';
2
+ /**
3
+ * One invoice per order (`order_id` is unique → idempotency guard). `external_id`
4
+ * / `number` / `pdf_url` come from the invoicing provider once issued. The
5
+ * `attempts` / `next_retry_at` columns are reserved for a future automatic-retry
6
+ * mechanism; the current flow only retries manually from the admin.
7
+ */
8
+ export declare const shopInvoicesTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
9
+ name: "shop_invoices";
10
+ schema: undefined;
11
+ columns: {
12
+ id: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
13
+ name: "id";
14
+ tableName: "shop_invoices";
15
+ dataType: "string";
16
+ columnType: "PgUUID";
17
+ data: string;
18
+ driverParam: string;
19
+ notNull: true;
20
+ hasDefault: true;
21
+ isPrimaryKey: true;
22
+ isAutoincrement: false;
23
+ hasRuntimeDefault: false;
24
+ enumValues: undefined;
25
+ baseColumn: never;
26
+ identity: undefined;
27
+ generated: undefined;
28
+ }, {}, {}>;
29
+ orderId: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
30
+ name: "order_id";
31
+ tableName: "shop_invoices";
32
+ dataType: "string";
33
+ columnType: "PgUUID";
34
+ data: string;
35
+ driverParam: string;
36
+ notNull: true;
37
+ hasDefault: false;
38
+ isPrimaryKey: false;
39
+ isAutoincrement: false;
40
+ hasRuntimeDefault: false;
41
+ enumValues: undefined;
42
+ baseColumn: never;
43
+ identity: undefined;
44
+ generated: undefined;
45
+ }, {}, {}>;
46
+ provider: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
47
+ name: "provider";
48
+ tableName: "shop_invoices";
49
+ dataType: "string";
50
+ columnType: "PgText";
51
+ data: string;
52
+ driverParam: string;
53
+ notNull: true;
54
+ hasDefault: false;
55
+ isPrimaryKey: false;
56
+ isAutoincrement: false;
57
+ hasRuntimeDefault: false;
58
+ enumValues: [string, ...string[]];
59
+ baseColumn: never;
60
+ identity: undefined;
61
+ generated: undefined;
62
+ }, {}, {}>;
63
+ externalId: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
64
+ name: "external_id";
65
+ tableName: "shop_invoices";
66
+ dataType: "string";
67
+ columnType: "PgText";
68
+ data: string;
69
+ driverParam: string;
70
+ notNull: false;
71
+ hasDefault: false;
72
+ isPrimaryKey: false;
73
+ isAutoincrement: false;
74
+ hasRuntimeDefault: false;
75
+ enumValues: [string, ...string[]];
76
+ baseColumn: never;
77
+ identity: undefined;
78
+ generated: undefined;
79
+ }, {}, {}>;
80
+ number: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
81
+ name: "number";
82
+ tableName: "shop_invoices";
83
+ dataType: "string";
84
+ columnType: "PgText";
85
+ data: string;
86
+ driverParam: string;
87
+ notNull: false;
88
+ hasDefault: false;
89
+ isPrimaryKey: false;
90
+ isAutoincrement: false;
91
+ hasRuntimeDefault: false;
92
+ enumValues: [string, ...string[]];
93
+ baseColumn: never;
94
+ identity: undefined;
95
+ generated: undefined;
96
+ }, {}, {}>;
97
+ pdfUrl: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
98
+ name: "pdf_url";
99
+ tableName: "shop_invoices";
100
+ dataType: "string";
101
+ columnType: "PgText";
102
+ data: string;
103
+ driverParam: string;
104
+ notNull: false;
105
+ hasDefault: false;
106
+ isPrimaryKey: false;
107
+ isAutoincrement: false;
108
+ hasRuntimeDefault: false;
109
+ enumValues: [string, ...string[]];
110
+ baseColumn: never;
111
+ identity: undefined;
112
+ generated: undefined;
113
+ }, {}, {}>;
114
+ kind: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
115
+ name: "kind";
116
+ tableName: "shop_invoices";
117
+ dataType: "string";
118
+ columnType: "PgText";
119
+ data: string;
120
+ driverParam: string;
121
+ notNull: true;
122
+ hasDefault: true;
123
+ isPrimaryKey: false;
124
+ isAutoincrement: false;
125
+ hasRuntimeDefault: false;
126
+ enumValues: [string, ...string[]];
127
+ baseColumn: never;
128
+ identity: undefined;
129
+ generated: undefined;
130
+ }, {}, {}>;
131
+ status: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
132
+ name: "status";
133
+ tableName: "shop_invoices";
134
+ dataType: "string";
135
+ columnType: "PgText";
136
+ data: ShopInvoiceStatus;
137
+ driverParam: string;
138
+ notNull: true;
139
+ hasDefault: true;
140
+ isPrimaryKey: false;
141
+ isAutoincrement: false;
142
+ hasRuntimeDefault: false;
143
+ enumValues: [string, ...string[]];
144
+ baseColumn: never;
145
+ identity: undefined;
146
+ generated: undefined;
147
+ }, {}, {
148
+ $type: ShopInvoiceStatus;
149
+ }>;
150
+ attempts: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
151
+ name: "attempts";
152
+ tableName: "shop_invoices";
153
+ dataType: "number";
154
+ columnType: "PgInteger";
155
+ data: number;
156
+ driverParam: string | number;
157
+ notNull: true;
158
+ hasDefault: true;
159
+ isPrimaryKey: false;
160
+ isAutoincrement: false;
161
+ hasRuntimeDefault: false;
162
+ enumValues: undefined;
163
+ baseColumn: never;
164
+ identity: undefined;
165
+ generated: undefined;
166
+ }, {}, {}>;
167
+ nextRetryAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
168
+ name: "next_retry_at";
169
+ tableName: "shop_invoices";
170
+ dataType: "date";
171
+ columnType: "PgTimestamp";
172
+ data: Date;
173
+ driverParam: string;
174
+ notNull: false;
175
+ hasDefault: false;
176
+ isPrimaryKey: false;
177
+ isAutoincrement: false;
178
+ hasRuntimeDefault: false;
179
+ enumValues: undefined;
180
+ baseColumn: never;
181
+ identity: undefined;
182
+ generated: undefined;
183
+ }, {}, {}>;
184
+ lastError: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
185
+ name: "last_error";
186
+ tableName: "shop_invoices";
187
+ dataType: "string";
188
+ columnType: "PgText";
189
+ data: string;
190
+ driverParam: string;
191
+ notNull: false;
192
+ hasDefault: false;
193
+ isPrimaryKey: false;
194
+ isAutoincrement: false;
195
+ hasRuntimeDefault: false;
196
+ enumValues: [string, ...string[]];
197
+ baseColumn: never;
198
+ identity: undefined;
199
+ generated: undefined;
200
+ }, {}, {}>;
201
+ raw: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
202
+ name: "raw";
203
+ tableName: "shop_invoices";
204
+ dataType: "json";
205
+ columnType: "PgJsonb";
206
+ data: unknown;
207
+ driverParam: unknown;
208
+ notNull: false;
209
+ hasDefault: false;
210
+ isPrimaryKey: false;
211
+ isAutoincrement: false;
212
+ hasRuntimeDefault: false;
213
+ enumValues: undefined;
214
+ baseColumn: never;
215
+ identity: undefined;
216
+ generated: undefined;
217
+ }, {}, {}>;
218
+ createdAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
219
+ name: "created_at";
220
+ tableName: "shop_invoices";
221
+ dataType: "date";
222
+ columnType: "PgTimestamp";
223
+ data: Date;
224
+ driverParam: string;
225
+ notNull: true;
226
+ hasDefault: true;
227
+ isPrimaryKey: false;
228
+ isAutoincrement: false;
229
+ hasRuntimeDefault: false;
230
+ enumValues: undefined;
231
+ baseColumn: never;
232
+ identity: undefined;
233
+ generated: undefined;
234
+ }, {}, {}>;
235
+ updatedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
236
+ name: "updated_at";
237
+ tableName: "shop_invoices";
238
+ dataType: "date";
239
+ columnType: "PgTimestamp";
240
+ data: Date;
241
+ driverParam: string;
242
+ notNull: true;
243
+ hasDefault: true;
244
+ isPrimaryKey: false;
245
+ isAutoincrement: false;
246
+ hasRuntimeDefault: false;
247
+ enumValues: undefined;
248
+ baseColumn: never;
249
+ identity: undefined;
250
+ generated: undefined;
251
+ }, {}, {}>;
252
+ };
253
+ dialect: "pg";
254
+ }>;
@@ -0,0 +1,27 @@
1
+ import { integer, jsonb, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
2
+ import { shopOrdersTable } from './order.js';
3
+ /**
4
+ * One invoice per order (`order_id` is unique → idempotency guard). `external_id`
5
+ * / `number` / `pdf_url` come from the invoicing provider once issued. The
6
+ * `attempts` / `next_retry_at` columns are reserved for a future automatic-retry
7
+ * mechanism; the current flow only retries manually from the admin.
8
+ */
9
+ export const shopInvoicesTable = pgTable('shop_invoices', {
10
+ id: uuid('id').primaryKey().defaultRandom(),
11
+ orderId: uuid('order_id')
12
+ .notNull()
13
+ .unique()
14
+ .references(() => shopOrdersTable.id, { onDelete: 'cascade' }),
15
+ provider: text('provider').notNull(),
16
+ externalId: text('external_id'),
17
+ number: text('number'),
18
+ pdfUrl: text('pdf_url'),
19
+ kind: text('kind').notNull().default('vat'),
20
+ status: text('status').$type().notNull().default('pending'),
21
+ attempts: integer('attempts').notNull().default(0),
22
+ nextRetryAt: timestamp('next_retry_at', { withTimezone: true }),
23
+ lastError: text('last_error'),
24
+ raw: jsonb('raw'),
25
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
26
+ updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
27
+ });