includio-cms 0.27.0 → 0.33.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 (115) hide show
  1. package/API.md +58 -14
  2. package/CHANGELOG.md +59 -0
  3. package/DOCS.md +1 -1
  4. package/ROADMAP.md +1 -0
  5. package/dist/admin/api/handler.js +4 -0
  6. package/dist/admin/api/integrations.d.ts +13 -0
  7. package/dist/admin/api/integrations.js +61 -0
  8. package/dist/admin/api/test-email.d.ts +9 -0
  9. package/dist/admin/api/test-email.js +39 -0
  10. package/dist/admin/auth-client.d.ts +543 -543
  11. package/dist/admin/client/index.d.ts +10 -0
  12. package/dist/admin/client/index.js +12 -0
  13. package/dist/admin/client/maintenance/maintenance-page.svelte +210 -0
  14. package/dist/admin/client/shop/coupon-schema.d.ts +1 -1
  15. package/dist/admin/client/shop/restore-order-cell.svelte +29 -0
  16. package/dist/admin/client/shop/restore-order-cell.svelte.d.ts +8 -0
  17. package/dist/admin/client/shop/shop-order-detail-page.svelte +156 -1
  18. package/dist/admin/client/shop/shop-orders-list-page.svelte +113 -53
  19. package/dist/admin/components/layout/app-sidebar.svelte +2 -0
  20. package/dist/admin/components/layout/nav-custom.svelte +26 -0
  21. package/dist/admin/components/layout/nav-custom.svelte.d.ts +3 -0
  22. package/dist/admin/components/layout/page-header.svelte +13 -3
  23. package/dist/admin/components/layout/page-header.svelte.d.ts +13 -3
  24. package/dist/admin/remote/admin.remote.d.ts +7 -0
  25. package/dist/admin/remote/admin.remote.js +10 -0
  26. package/dist/admin/remote/entry.remote.d.ts +2 -2
  27. package/dist/admin/remote/index.d.ts +1 -0
  28. package/dist/admin/remote/index.js +1 -0
  29. package/dist/admin/remote/invite.d.ts +1 -1
  30. package/dist/admin/remote/shop.remote.d.ts +125 -40
  31. package/dist/admin/remote/shop.remote.js +59 -10
  32. package/dist/admin/types.d.ts +15 -0
  33. package/dist/admin/utils/csv-export.d.ts +45 -0
  34. package/dist/admin/utils/csv-export.js +61 -0
  35. package/dist/cli/scaffold/admin.js +1 -1
  36. package/dist/components/ui/input/input.svelte.d.ts +1 -1
  37. package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
  38. package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
  39. package/dist/core/cms.d.ts +44 -2
  40. package/dist/core/cms.js +64 -0
  41. package/dist/core/index.d.ts +2 -4
  42. package/dist/core/index.js +1 -4
  43. package/dist/core/server/index.d.ts +4 -1
  44. package/dist/core/server/index.js +4 -1
  45. package/dist/db-postgres/schema/shop/index.d.ts +1 -0
  46. package/dist/db-postgres/schema/shop/index.js +1 -0
  47. package/dist/db-postgres/schema/shop/invoice.d.ts +254 -0
  48. package/dist/db-postgres/schema/shop/invoice.js +27 -0
  49. package/dist/db-postgres/schema/shop/order.d.ts +104 -0
  50. package/dist/db-postgres/schema/shop/order.js +8 -0
  51. package/dist/shop/adapters/fakturownia/client.d.ts +33 -0
  52. package/dist/shop/adapters/fakturownia/client.js +87 -0
  53. package/dist/shop/adapters/fakturownia/index.d.ts +27 -0
  54. package/dist/shop/adapters/fakturownia/index.js +47 -0
  55. package/dist/shop/adapters/fakturownia/payload.d.ts +35 -0
  56. package/dist/shop/adapters/fakturownia/payload.js +45 -0
  57. package/dist/shop/adapters/payu/index.js +11 -0
  58. package/dist/shop/client/index.d.ts +7 -0
  59. package/dist/shop/http/checkout-handler.js +11 -0
  60. package/dist/shop/index.d.ts +4 -1
  61. package/dist/shop/index.js +3 -0
  62. package/dist/shop/nip.d.ts +12 -0
  63. package/dist/shop/nip.js +23 -0
  64. package/dist/shop/server/coupons.d.ts +10 -0
  65. package/dist/shop/server/coupons.js +19 -0
  66. package/dist/shop/server/email.d.ts +7 -3
  67. package/dist/shop/server/email.js +86 -112
  68. package/dist/shop/server/emailTemplateRegistry.d.ts +47 -0
  69. package/dist/shop/server/emailTemplateRegistry.js +288 -0
  70. package/dist/shop/server/invoices.d.ts +64 -0
  71. package/dist/shop/server/invoices.js +237 -0
  72. package/dist/shop/server/orders.d.ts +64 -1
  73. package/dist/shop/server/orders.js +155 -15
  74. package/dist/shop/templates/_partials/footer.en.html +4 -0
  75. package/dist/shop/templates/_partials/footer.pl.html +4 -0
  76. package/dist/shop/templates/_partials/header.en.html +4 -0
  77. package/dist/shop/templates/_partials/header.pl.html +4 -0
  78. package/dist/shop/templates/_partials/items.en.html +14 -0
  79. package/dist/shop/templates/_partials/items.pl.html +14 -0
  80. package/dist/shop/templates/_partials/tracking.en.html +7 -0
  81. package/dist/shop/templates/_partials/tracking.pl.html +7 -0
  82. package/dist/shop/templates/awaiting-payment.en.html +6 -0
  83. package/dist/shop/templates/awaiting-payment.pl.html +6 -0
  84. package/dist/shop/templates/cancelled.en.html +6 -0
  85. package/dist/shop/templates/cancelled.pl.html +6 -0
  86. package/dist/shop/templates/low-stock.en.html +14 -0
  87. package/dist/shop/templates/low-stock.pl.html +14 -0
  88. package/dist/shop/templates/order-completed.en.html +6 -0
  89. package/dist/shop/templates/order-completed.pl.html +6 -0
  90. package/dist/shop/templates/order-received.en.html +7 -0
  91. package/dist/shop/templates/order-received.pl.html +7 -0
  92. package/dist/shop/templates/payment-received.en.html +7 -0
  93. package/dist/shop/templates/payment-received.pl.html +7 -0
  94. package/dist/shop/templates/payment-rejected.en.html +6 -0
  95. package/dist/shop/templates/payment-rejected.pl.html +6 -0
  96. package/dist/shop/templates/preparing.en.html +7 -0
  97. package/dist/shop/templates/preparing.pl.html +7 -0
  98. package/dist/shop/templates/refunded.en.html +6 -0
  99. package/dist/shop/templates/refunded.pl.html +6 -0
  100. package/dist/shop/templates/shipped.en.html +7 -0
  101. package/dist/shop/templates/shipped.pl.html +7 -0
  102. package/dist/shop/types.d.ts +130 -1
  103. package/dist/sveltekit/index.d.ts +0 -1
  104. package/dist/sveltekit/index.js +0 -1
  105. package/dist/sveltekit/server/index.d.ts +1 -0
  106. package/dist/sveltekit/server/index.js +1 -0
  107. package/dist/types/adapters/email.d.ts +13 -0
  108. package/dist/types/cms.d.ts +30 -0
  109. package/dist/types/index.d.ts +1 -1
  110. package/dist/updates/0.28.0/index.d.ts +2 -0
  111. package/dist/updates/0.28.0/index.js +38 -0
  112. package/dist/updates/0.34.0/index.d.ts +2 -0
  113. package/dist/updates/0.34.0/index.js +17 -0
  114. package/dist/updates/index.js +5 -1
  115. package/package.json +7 -2
package/dist/core/cms.js CHANGED
@@ -23,6 +23,7 @@ export class CMS {
23
23
  sidebarHelp;
24
24
  shopConfig;
25
25
  cmpConfig;
26
+ adminConfig;
26
27
  /**
27
28
  * Resolves once the shop's variant-attribute GIN indexes have been applied
28
29
  * by `initCMS()`. `null` when the CMS is configured without a shop. Tests
@@ -47,6 +48,7 @@ export class CMS {
47
48
  this.sidebarHelp = config.sidebarHelp ?? true;
48
49
  this.shopConfig = config.shop ?? null;
49
50
  this.cmpConfig = config.cmp ?? null;
51
+ this.adminConfig = config.admin ?? null;
50
52
  this.collections = {};
51
53
  this.singles = {};
52
54
  this.forms = {};
@@ -134,6 +136,7 @@ export class CMS {
134
136
  database: drizzleAdapter(drizzleDb, { provider: 'pg', schema: authSchema }),
135
137
  secret: this.authConfig.secret,
136
138
  baseURL: this.authConfig.baseURL,
139
+ ...(this.authConfig.rateLimit ? { rateLimit: this.authConfig.rateLimit } : {}),
137
140
  emailAndPassword: {
138
141
  enabled: true,
139
142
  async sendResetPassword({ user, url }, request) {
@@ -185,6 +188,18 @@ export function initCMS(config) {
185
188
  console.warn('[shop] Failed to apply variant attribute indexes:', e);
186
189
  });
187
190
  }
191
+ // Smoke-test built-in shop email templates — fast lookup-only sweep.
192
+ // Catches corrupt installs (missing files in dist) at boot instead of
193
+ // at first email send. Dynamic import avoids loading shop server code
194
+ // when there is no shop configured.
195
+ import('../shop/server/email.js')
196
+ .then(({ REQUIRED_TEMPLATE_NAMES }) => import('../shop/server/emailTemplateRegistry.js').then(({ validateBuiltinTemplates }) => {
197
+ const defaultLang = cms.languages[0] ?? 'pl';
198
+ validateBuiltinTemplates([...REQUIRED_TEMPLATE_NAMES], defaultLang);
199
+ }))
200
+ .catch((e) => {
201
+ console.error('[shop] Built-in email template validation failed:', e);
202
+ });
188
203
  }
189
204
  return cms;
190
205
  }
@@ -210,3 +225,52 @@ export function getCMS() {
210
225
  }
211
226
  return cms;
212
227
  }
228
+ /**
229
+ * Returns the configured email adapter from the active CMS singleton, or
230
+ * `null` when no `email` was passed to `defineConfig`. Use this in userland
231
+ * hooks (e.g. `ShopConfig.onOrderPaid`) to reuse the same SMTP transport the
232
+ * CMS already opened — no second nodemailer instance, no duplicated ENV.
233
+ *
234
+ * @returns The current `EmailAdapter` or `null`.
235
+ * @throws {Error} when called before `initCMS()` has run.
236
+ * @public
237
+ * @example
238
+ * ```ts
239
+ * import { getMailer } from 'includio-cms/core';
240
+ *
241
+ * const mailer = getMailer();
242
+ * if (mailer) await mailer.sendMail({ to: 'admin@…', subject: '…', html: '…' });
243
+ * ```
244
+ */
245
+ export function getMailer() {
246
+ return getCMS().emailAdapter;
247
+ }
248
+ /**
249
+ * Send an email through the configured CMS email adapter. Thin convenience
250
+ * wrapper around `getCMS().emailAdapter.sendMail` — same options shape, same
251
+ * From address, same SMTP transport as the built-in flows (reset password,
252
+ * shop order status, form notifications).
253
+ *
254
+ * @throws {Error} when no email adapter is configured (matches
255
+ * `defineConfig({ email: ... })` not being set) or when the CMS has not
256
+ * been initialized yet.
257
+ * @public
258
+ * @example
259
+ * ```ts
260
+ * import { sendMail } from 'includio-cms/core';
261
+ *
262
+ * await sendMail({
263
+ * to: process.env.ADMIN_EMAIL!,
264
+ * subject: '[shop] New consent',
265
+ * text: 'Plain fallback for clients that prefer text.',
266
+ * html: '<p>Body…</p>'
267
+ * });
268
+ * ```
269
+ */
270
+ export async function sendMail(options) {
271
+ const mailer = getCMS().emailAdapter;
272
+ if (!mailer) {
273
+ throw new Error('[includio-cms] sendMail() called but no `email` adapter is configured in defineConfig.');
274
+ }
275
+ await mailer.sendMail(options);
276
+ }
@@ -1,5 +1,3 @@
1
- export { getCMS } from './cms.js';
2
- export { resolveMediaWithStyles, type ResolvedMedia } from './server/fields/utils/resolveMedia.js';
3
1
  export { resolveSeo, type ResolvedSeo } from './fields/resolveSeo.js';
4
- export { createEntityAPI } from '../entity/index.js';
5
- export { getAuth } from '../server/auth.js';
2
+ export { getMailer, sendMail } from './cms.js';
3
+ export type { EmailAdapter, SendMailOptions } from '../types/adapters/email.js';
@@ -1,5 +1,2 @@
1
- export { getCMS } from './cms.js';
2
- export { resolveMediaWithStyles } from './server/fields/utils/resolveMedia.js';
3
1
  export { resolveSeo } from './fields/resolveSeo.js';
4
- export { createEntityAPI } from '../entity/index.js';
5
- export { getAuth } from '../server/auth.js';
2
+ export { getMailer, sendMail } from './cms.js';
@@ -1 +1,4 @@
1
- export {};
1
+ export { getCMS } from '../cms.js';
2
+ export { resolveMediaWithStyles, type ResolvedMedia } from './fields/utils/resolveMedia.js';
3
+ export { createEntityAPI } from '../../entity/index.js';
4
+ export { getAuth } from '../../server/auth.js';
@@ -1 +1,4 @@
1
- "use strict";
1
+ export { getCMS } from '../cms.js';
2
+ export { resolveMediaWithStyles } from './fields/utils/resolveMedia.js';
3
+ export { createEntityAPI } from '../../entity/index.js';
4
+ export { getAuth } from '../../server/auth.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
+ });
@@ -124,6 +124,40 @@ export declare const shopOrdersTable: import("drizzle-orm/pg-core/table", { with
124
124
  identity: undefined;
125
125
  generated: undefined;
126
126
  }, {}, {}>;
127
+ customerNip: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
128
+ name: "customer_nip";
129
+ tableName: "shop_orders";
130
+ dataType: "string";
131
+ columnType: "PgText";
132
+ data: string;
133
+ driverParam: string;
134
+ notNull: false;
135
+ hasDefault: false;
136
+ isPrimaryKey: false;
137
+ isAutoincrement: false;
138
+ hasRuntimeDefault: false;
139
+ enumValues: [string, ...string[]];
140
+ baseColumn: never;
141
+ identity: undefined;
142
+ generated: undefined;
143
+ }, {}, {}>;
144
+ customerCompanyName: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
145
+ name: "customer_company_name";
146
+ tableName: "shop_orders";
147
+ dataType: "string";
148
+ columnType: "PgText";
149
+ data: string;
150
+ driverParam: string;
151
+ notNull: false;
152
+ hasDefault: false;
153
+ isPrimaryKey: false;
154
+ isAutoincrement: false;
155
+ hasRuntimeDefault: false;
156
+ enumValues: [string, ...string[]];
157
+ baseColumn: never;
158
+ identity: undefined;
159
+ generated: undefined;
160
+ }, {}, {}>;
127
161
  shippingAddress: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
128
162
  name: "shipping_address";
129
163
  tableName: "shop_orders";
@@ -143,6 +177,42 @@ export declare const shopOrdersTable: import("drizzle-orm/pg-core/table", { with
143
177
  }, {}, {
144
178
  $type: Record<string, string>;
145
179
  }>;
180
+ billingAddress: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
181
+ name: "billing_address";
182
+ tableName: "shop_orders";
183
+ dataType: "json";
184
+ columnType: "PgJsonb";
185
+ data: Record<string, string>;
186
+ driverParam: unknown;
187
+ notNull: false;
188
+ hasDefault: false;
189
+ isPrimaryKey: false;
190
+ isAutoincrement: false;
191
+ hasRuntimeDefault: false;
192
+ enumValues: undefined;
193
+ baseColumn: never;
194
+ identity: undefined;
195
+ generated: undefined;
196
+ }, {}, {
197
+ $type: Record<string, string>;
198
+ }>;
199
+ invoiceRequested: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
200
+ name: "invoice_requested";
201
+ tableName: "shop_orders";
202
+ dataType: "boolean";
203
+ columnType: "PgBoolean";
204
+ data: boolean;
205
+ driverParam: boolean;
206
+ notNull: true;
207
+ hasDefault: true;
208
+ isPrimaryKey: false;
209
+ isAutoincrement: false;
210
+ hasRuntimeDefault: false;
211
+ enumValues: undefined;
212
+ baseColumn: never;
213
+ identity: undefined;
214
+ generated: undefined;
215
+ }, {}, {}>;
146
216
  totalNet: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
147
217
  name: "total_net";
148
218
  tableName: "shop_orders";
@@ -495,6 +565,40 @@ export declare const shopOrdersTable: import("drizzle-orm/pg-core/table", { with
495
565
  identity: undefined;
496
566
  generated: undefined;
497
567
  }, {}, {}>;
568
+ deletedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
569
+ name: "deleted_at";
570
+ tableName: "shop_orders";
571
+ dataType: "date";
572
+ columnType: "PgTimestamp";
573
+ data: Date;
574
+ driverParam: string;
575
+ notNull: false;
576
+ hasDefault: false;
577
+ isPrimaryKey: false;
578
+ isAutoincrement: false;
579
+ hasRuntimeDefault: false;
580
+ enumValues: undefined;
581
+ baseColumn: never;
582
+ identity: undefined;
583
+ generated: undefined;
584
+ }, {}, {}>;
585
+ deletedBy: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
586
+ name: "deleted_by";
587
+ tableName: "shop_orders";
588
+ dataType: "string";
589
+ columnType: "PgText";
590
+ data: string;
591
+ driverParam: string;
592
+ notNull: false;
593
+ hasDefault: false;
594
+ isPrimaryKey: false;
595
+ isAutoincrement: false;
596
+ hasRuntimeDefault: false;
597
+ enumValues: [string, ...string[]];
598
+ baseColumn: never;
599
+ identity: undefined;
600
+ generated: undefined;
601
+ }, {}, {}>;
498
602
  createdAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
499
603
  name: "created_at";
500
604
  tableName: "shop_orders";
@@ -8,7 +8,11 @@ export const shopOrdersTable = pgTable('shop_orders', {
8
8
  customerEmail: text('customer_email').notNull(),
9
9
  customerName: text('customer_name'),
10
10
  customerPhone: text('customer_phone'),
11
+ customerNip: text('customer_nip'),
12
+ customerCompanyName: text('customer_company_name'),
11
13
  shippingAddress: jsonb('shipping_address').$type(),
14
+ billingAddress: jsonb('billing_address').$type(),
15
+ invoiceRequested: boolean('invoice_requested').default(false).notNull(),
12
16
  totalNet: integer('total_net').notNull(),
13
17
  totalGross: integer('total_gross').notNull(),
14
18
  vatAmount: integer('vat_amount').notNull(),
@@ -31,6 +35,10 @@ export const shopOrdersTable = pgTable('shop_orders', {
31
35
  accessToken: uuid('access_token').defaultRandom().notNull(),
32
36
  partialPayment: jsonb('partial_payment').$type(),
33
37
  balanceOwed: boolean('balance_owed').default(false).notNull(),
38
+ // Soft-delete: admin can hide an order from the admin list without losing
39
+ // the row (accounting/audit safety). NULL = visible. See softDeleteOrder.
40
+ deletedAt: timestamp('deleted_at', { withTimezone: true }),
41
+ deletedBy: text('deleted_by'),
34
42
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
35
43
  updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
36
44
  });
@@ -0,0 +1,33 @@
1
+ import type { FakturowniaInvoiceBody } from './payload.js';
2
+ export interface FakturowniaClientOptions {
3
+ /** Account subdomain (`acme`) or full host/URL (`acme.fakturownia.pl`) — normalised either way. */
4
+ domain: string;
5
+ apiToken: string;
6
+ fetch?: typeof fetch;
7
+ }
8
+ export interface FakturowniaInvoice {
9
+ id: number | string;
10
+ number?: string;
11
+ view_url?: string;
12
+ [key: string]: unknown;
13
+ }
14
+ export declare class FakturowniaApiError extends Error {
15
+ readonly status: number;
16
+ readonly body?: unknown | undefined;
17
+ constructor(message: string, status: number, body?: unknown | undefined);
18
+ }
19
+ export declare class FakturowniaClient {
20
+ private readonly base;
21
+ private readonly apiToken;
22
+ private readonly fetchFn;
23
+ constructor(opts: FakturowniaClientOptions);
24
+ createInvoice(invoice: FakturowniaInvoiceBody): Promise<FakturowniaInvoice>;
25
+ sendByEmail(id: number | string): Promise<void>;
26
+ /**
27
+ * Read-only account lookup — used for connectivity/auth health checks.
28
+ * Does NOT create or modify anything. A 200 means domain + token are valid.
29
+ */
30
+ getAccount(): Promise<unknown>;
31
+ private postRaw;
32
+ private post;
33
+ }