includio-cms 0.36.6 → 0.36.8

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 (68) hide show
  1. package/API.md +8 -2
  2. package/CHANGELOG.md +30 -0
  3. package/DOCS.md +1 -1
  4. package/dist/admin/api/handler.js +2 -0
  5. package/dist/admin/api/media-jobs.d.ts +5 -0
  6. package/dist/admin/api/media-jobs.js +33 -0
  7. package/dist/admin/client/maintenance/maintenance-page.svelte +9 -0
  8. package/dist/admin/client/maintenance/media-jobs-panel.svelte +310 -0
  9. package/dist/admin/client/maintenance/media-jobs-panel.svelte.d.ts +3 -0
  10. package/dist/core/cms.js +7 -0
  11. package/dist/core/server/fields/utils/imageStyles.js +41 -4
  12. package/dist/core/server/index.d.ts +3 -0
  13. package/dist/core/server/index.js +3 -0
  14. package/dist/core/server/media/jobs/enqueue.d.ts +6 -0
  15. package/dist/core/server/media/jobs/enqueue.js +20 -0
  16. package/dist/core/server/media/jobs/processImageStyleJob.d.ts +19 -0
  17. package/dist/core/server/media/jobs/processImageStyleJob.js +23 -0
  18. package/dist/core/server/media/jobs/startWorker.d.ts +2 -0
  19. package/dist/core/server/media/jobs/startWorker.js +29 -0
  20. package/dist/core/server/media/jobs/worker.d.ts +29 -0
  21. package/dist/core/server/media/jobs/worker.js +54 -0
  22. package/dist/core/server/media/operations/replaceFile.js +2 -2
  23. package/dist/core/server/media/operations/uploadFile.js +10 -3
  24. package/dist/core/server/media/styles/buildStyleSpecs.d.ts +12 -0
  25. package/dist/core/server/media/styles/buildStyleSpecs.js +36 -0
  26. package/dist/core/server/media/styles/imagesConfig.d.ts +3 -0
  27. package/dist/core/server/media/styles/imagesConfig.js +16 -0
  28. package/dist/core/server/media/styles/operations/batchGenerateStyles.js +19 -41
  29. package/dist/core/server/media/styles/operations/generateFallbackVariant.d.ts +10 -0
  30. package/dist/core/server/media/styles/operations/generateFallbackVariant.js +21 -0
  31. package/dist/core/server/media/styles/operations/getImageStyle.js +12 -8
  32. package/dist/core/server/media/styles/sharp/sharpConfig.d.ts +7 -0
  33. package/dist/core/server/media/styles/sharp/sharpConfig.js +12 -0
  34. package/dist/db-postgres/index.js +113 -1
  35. package/dist/db-postgres/schema/imageStyle.js +1 -1
  36. package/dist/db-postgres/schema/index.d.ts +1 -0
  37. package/dist/db-postgres/schema/index.js +1 -0
  38. package/dist/db-postgres/schema/mediaFile.d.ts +17 -0
  39. package/dist/db-postgres/schema/mediaFile.js +1 -0
  40. package/dist/db-postgres/schema/mediaJob.d.ts +279 -0
  41. package/dist/db-postgres/schema/mediaJob.js +23 -0
  42. package/dist/paraglide/messages/_index.d.ts +36 -3
  43. package/dist/paraglide/messages/_index.js +71 -3
  44. package/dist/paraglide/messages/en.d.ts +5 -0
  45. package/dist/paraglide/messages/en.js +14 -0
  46. package/dist/paraglide/messages/pl.d.ts +5 -0
  47. package/dist/paraglide/messages/pl.js +14 -0
  48. package/dist/shop/adapters/fakturownia/payload.d.ts +2 -0
  49. package/dist/shop/adapters/fakturownia/payload.js +1 -0
  50. package/dist/shop/server/invoices.d.ts +16 -2
  51. package/dist/shop/server/invoices.js +86 -9
  52. package/dist/shop/types.d.ts +5 -0
  53. package/dist/sveltekit/components/image.svelte +1 -1
  54. package/dist/types/adapters/db.d.ts +51 -0
  55. package/dist/types/cms.d.ts +46 -0
  56. package/dist/types/media.d.ts +1 -0
  57. package/dist/updates/0.36.7/index.d.ts +2 -0
  58. package/dist/updates/0.36.7/index.js +13 -0
  59. package/dist/updates/0.36.8/index.d.ts +2 -0
  60. package/dist/updates/0.36.8/index.js +20 -0
  61. package/dist/updates/index.js +5 -1
  62. package/package.json +1 -1
  63. package/dist/paraglide/messages/hello_world.d.ts +0 -5
  64. package/dist/paraglide/messages/hello_world.js +0 -33
  65. package/dist/paraglide/messages/login_hello.d.ts +0 -16
  66. package/dist/paraglide/messages/login_hello.js +0 -34
  67. package/dist/paraglide/messages/login_please_login.d.ts +0 -16
  68. package/dist/paraglide/messages/login_please_login.js +0 -34
@@ -0,0 +1,279 @@
1
+ export declare const mediaJobTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
2
+ name: "media_job";
3
+ schema: undefined;
4
+ columns: {
5
+ id: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
6
+ name: "id";
7
+ tableName: "media_job";
8
+ dataType: "string";
9
+ columnType: "PgUUID";
10
+ data: string;
11
+ driverParam: string;
12
+ notNull: true;
13
+ hasDefault: true;
14
+ isPrimaryKey: true;
15
+ isAutoincrement: false;
16
+ hasRuntimeDefault: false;
17
+ enumValues: undefined;
18
+ baseColumn: never;
19
+ identity: undefined;
20
+ generated: undefined;
21
+ }, {}, {}>;
22
+ type: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
23
+ name: "type";
24
+ tableName: "media_job";
25
+ dataType: "string";
26
+ columnType: "PgText";
27
+ data: string;
28
+ driverParam: string;
29
+ notNull: true;
30
+ hasDefault: false;
31
+ isPrimaryKey: false;
32
+ isAutoincrement: false;
33
+ hasRuntimeDefault: false;
34
+ enumValues: [string, ...string[]];
35
+ baseColumn: never;
36
+ identity: undefined;
37
+ generated: undefined;
38
+ }, {}, {}>;
39
+ mediaFileId: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
40
+ name: "media_file_id";
41
+ tableName: "media_job";
42
+ dataType: "string";
43
+ columnType: "PgUUID";
44
+ data: string;
45
+ driverParam: string;
46
+ notNull: true;
47
+ hasDefault: false;
48
+ isPrimaryKey: false;
49
+ isAutoincrement: false;
50
+ hasRuntimeDefault: false;
51
+ enumValues: undefined;
52
+ baseColumn: never;
53
+ identity: undefined;
54
+ generated: undefined;
55
+ }, {}, {}>;
56
+ payload: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
57
+ name: "payload";
58
+ tableName: "media_job";
59
+ dataType: "string";
60
+ columnType: "PgText";
61
+ data: string;
62
+ driverParam: string;
63
+ notNull: true;
64
+ hasDefault: false;
65
+ isPrimaryKey: false;
66
+ isAutoincrement: false;
67
+ hasRuntimeDefault: false;
68
+ enumValues: [string, ...string[]];
69
+ baseColumn: never;
70
+ identity: undefined;
71
+ generated: undefined;
72
+ }, {}, {}>;
73
+ dedupeKey: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
74
+ name: "dedupe_key";
75
+ tableName: "media_job";
76
+ dataType: "string";
77
+ columnType: "PgText";
78
+ data: string;
79
+ driverParam: string;
80
+ notNull: true;
81
+ hasDefault: false;
82
+ isPrimaryKey: false;
83
+ isAutoincrement: false;
84
+ hasRuntimeDefault: false;
85
+ enumValues: [string, ...string[]];
86
+ baseColumn: never;
87
+ identity: undefined;
88
+ generated: undefined;
89
+ }, {}, {}>;
90
+ status: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
91
+ name: "status";
92
+ tableName: "media_job";
93
+ dataType: "string";
94
+ columnType: "PgText";
95
+ data: string;
96
+ driverParam: string;
97
+ notNull: true;
98
+ hasDefault: true;
99
+ isPrimaryKey: false;
100
+ isAutoincrement: false;
101
+ hasRuntimeDefault: false;
102
+ enumValues: [string, ...string[]];
103
+ baseColumn: never;
104
+ identity: undefined;
105
+ generated: undefined;
106
+ }, {}, {}>;
107
+ priority: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
108
+ name: "priority";
109
+ tableName: "media_job";
110
+ dataType: "number";
111
+ columnType: "PgInteger";
112
+ data: number;
113
+ driverParam: string | number;
114
+ notNull: true;
115
+ hasDefault: true;
116
+ isPrimaryKey: false;
117
+ isAutoincrement: false;
118
+ hasRuntimeDefault: false;
119
+ enumValues: undefined;
120
+ baseColumn: never;
121
+ identity: undefined;
122
+ generated: undefined;
123
+ }, {}, {}>;
124
+ attempts: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
125
+ name: "attempts";
126
+ tableName: "media_job";
127
+ dataType: "number";
128
+ columnType: "PgInteger";
129
+ data: number;
130
+ driverParam: string | number;
131
+ notNull: true;
132
+ hasDefault: true;
133
+ isPrimaryKey: false;
134
+ isAutoincrement: false;
135
+ hasRuntimeDefault: false;
136
+ enumValues: undefined;
137
+ baseColumn: never;
138
+ identity: undefined;
139
+ generated: undefined;
140
+ }, {}, {}>;
141
+ maxAttempts: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
142
+ name: "max_attempts";
143
+ tableName: "media_job";
144
+ dataType: "number";
145
+ columnType: "PgInteger";
146
+ data: number;
147
+ driverParam: string | number;
148
+ notNull: true;
149
+ hasDefault: true;
150
+ isPrimaryKey: false;
151
+ isAutoincrement: false;
152
+ hasRuntimeDefault: false;
153
+ enumValues: undefined;
154
+ baseColumn: never;
155
+ identity: undefined;
156
+ generated: undefined;
157
+ }, {}, {}>;
158
+ lastError: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
159
+ name: "last_error";
160
+ tableName: "media_job";
161
+ dataType: "string";
162
+ columnType: "PgText";
163
+ data: string;
164
+ driverParam: string;
165
+ notNull: false;
166
+ hasDefault: false;
167
+ isPrimaryKey: false;
168
+ isAutoincrement: false;
169
+ hasRuntimeDefault: false;
170
+ enumValues: [string, ...string[]];
171
+ baseColumn: never;
172
+ identity: undefined;
173
+ generated: undefined;
174
+ }, {}, {}>;
175
+ runAfter: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
176
+ name: "run_after";
177
+ tableName: "media_job";
178
+ dataType: "date";
179
+ columnType: "PgTimestamp";
180
+ data: Date;
181
+ driverParam: string;
182
+ notNull: true;
183
+ hasDefault: true;
184
+ isPrimaryKey: false;
185
+ isAutoincrement: false;
186
+ hasRuntimeDefault: false;
187
+ enumValues: undefined;
188
+ baseColumn: never;
189
+ identity: undefined;
190
+ generated: undefined;
191
+ }, {}, {}>;
192
+ lockedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
193
+ name: "locked_at";
194
+ tableName: "media_job";
195
+ dataType: "date";
196
+ columnType: "PgTimestamp";
197
+ data: Date;
198
+ driverParam: string;
199
+ notNull: false;
200
+ hasDefault: false;
201
+ isPrimaryKey: false;
202
+ isAutoincrement: false;
203
+ hasRuntimeDefault: false;
204
+ enumValues: undefined;
205
+ baseColumn: never;
206
+ identity: undefined;
207
+ generated: undefined;
208
+ }, {}, {}>;
209
+ lockedBy: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
210
+ name: "locked_by";
211
+ tableName: "media_job";
212
+ dataType: "string";
213
+ columnType: "PgText";
214
+ data: string;
215
+ driverParam: string;
216
+ notNull: false;
217
+ hasDefault: false;
218
+ isPrimaryKey: false;
219
+ isAutoincrement: false;
220
+ hasRuntimeDefault: false;
221
+ enumValues: [string, ...string[]];
222
+ baseColumn: never;
223
+ identity: undefined;
224
+ generated: undefined;
225
+ }, {}, {}>;
226
+ createdAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
227
+ name: "created_at";
228
+ tableName: "media_job";
229
+ dataType: "date";
230
+ columnType: "PgTimestamp";
231
+ data: Date;
232
+ driverParam: string;
233
+ notNull: true;
234
+ hasDefault: true;
235
+ isPrimaryKey: false;
236
+ isAutoincrement: false;
237
+ hasRuntimeDefault: false;
238
+ enumValues: undefined;
239
+ baseColumn: never;
240
+ identity: undefined;
241
+ generated: undefined;
242
+ }, {}, {}>;
243
+ updatedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
244
+ name: "updated_at";
245
+ tableName: "media_job";
246
+ dataType: "date";
247
+ columnType: "PgTimestamp";
248
+ data: Date;
249
+ driverParam: string;
250
+ notNull: true;
251
+ hasDefault: true;
252
+ isPrimaryKey: false;
253
+ isAutoincrement: false;
254
+ hasRuntimeDefault: false;
255
+ enumValues: undefined;
256
+ baseColumn: never;
257
+ identity: undefined;
258
+ generated: undefined;
259
+ }, {}, {}>;
260
+ completedAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
261
+ name: "completed_at";
262
+ tableName: "media_job";
263
+ dataType: "date";
264
+ columnType: "PgTimestamp";
265
+ data: Date;
266
+ driverParam: string;
267
+ notNull: false;
268
+ hasDefault: false;
269
+ isPrimaryKey: false;
270
+ isAutoincrement: false;
271
+ hasRuntimeDefault: false;
272
+ enumValues: undefined;
273
+ baseColumn: never;
274
+ identity: undefined;
275
+ generated: undefined;
276
+ }, {}, {}>;
277
+ };
278
+ dialect: "pg";
279
+ }>;
@@ -0,0 +1,23 @@
1
+ import { integer, pgTable, text, timestamp, uniqueIndex, index, uuid } from 'drizzle-orm/pg-core';
2
+ export const mediaJobTable = pgTable('media_job', {
3
+ id: uuid('id').primaryKey().defaultRandom(),
4
+ type: text('type').notNull(), // 'image_style' (na start; rozszerzalne: video_poster, video_transcode)
5
+ mediaFileId: uuid('media_file_id').notNull(),
6
+ payload: text('payload').notNull(), // JSON.stringify(ImageFieldStyle)
7
+ dedupeKey: text('dedupe_key').notNull(),
8
+ status: text('status').notNull().default('pending'), // pending|processing|done|failed|skipped
9
+ priority: integer('priority').notNull().default(0), // wyżej = pilniejsze
10
+ attempts: integer('attempts').notNull().default(0),
11
+ maxAttempts: integer('max_attempts').notNull().default(3),
12
+ lastError: text('last_error'),
13
+ runAfter: timestamp('run_after').notNull().defaultNow(),
14
+ lockedAt: timestamp('locked_at'),
15
+ lockedBy: text('locked_by'),
16
+ createdAt: timestamp('created_at').notNull().defaultNow(),
17
+ updatedAt: timestamp('updated_at').notNull().defaultNow(),
18
+ completedAt: timestamp('completed_at')
19
+ }, (t) => [
20
+ uniqueIndex('media_job_dedupe_key').on(t.dedupeKey),
21
+ index('media_job_claim_idx').on(t.status, t.runAfter, t.priority),
22
+ index('media_job_media_idx').on(t.mediaFileId)
23
+ ]);
@@ -1,3 +1,36 @@
1
- export * from "./hello_world.js";
2
- export * from "./login_hello.js";
3
- export * from "./login_please_login.js";
1
+ export function hello_world(inputs: {
2
+ name: NonNullable<unknown>;
3
+ }, options?: {
4
+ locale?: "en" | "pl";
5
+ }): string;
6
+ /**
7
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
8
+ *
9
+ * - Changing this function will be over-written by the next build.
10
+ *
11
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
12
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
13
+ *
14
+ * @param {{}} inputs
15
+ * @param {{ locale?: "en" | "pl" }} options
16
+ * @returns {string}
17
+ */
18
+ declare function login_hello(inputs?: {}, options?: {
19
+ locale?: "en" | "pl";
20
+ }): string;
21
+ /**
22
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
23
+ *
24
+ * - Changing this function will be over-written by the next build.
25
+ *
26
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
27
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
28
+ *
29
+ * @param {{}} inputs
30
+ * @param {{ locale?: "en" | "pl" }} options
31
+ * @returns {string}
32
+ */
33
+ declare function login_please_login(inputs?: {}, options?: {
34
+ locale?: "en" | "pl";
35
+ }): string;
36
+ export { login_hello as login.hello, login_please_login as login.please_login };
@@ -1,4 +1,72 @@
1
1
  /* eslint-disable */
2
- export * from './hello_world.js'
3
- export * from './login_hello.js'
4
- export * from './login_please_login.js'
2
+ import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from "../runtime.js"
3
+ import * as en from "./en.js"
4
+ import * as pl from "./pl.js"
5
+ /**
6
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
7
+ *
8
+ * - Changing this function will be over-written by the next build.
9
+ *
10
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
11
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
12
+ *
13
+ * @param {{ name: NonNullable<unknown> }} inputs
14
+ * @param {{ locale?: "en" | "pl" }} options
15
+ * @returns {string}
16
+ */
17
+ /* @__NO_SIDE_EFFECTS__ */
18
+ export const hello_world = (inputs, options = {}) => {
19
+ if (experimentalMiddlewareLocaleSplitting && isServer === false) {
20
+ return /** @type {any} */ (globalThis).__paraglide_ssr.hello_world(inputs)
21
+ }
22
+ const locale = options.locale ?? getLocale()
23
+ trackMessageCall("hello_world", locale)
24
+ if (locale === "en") return en.hello_world(inputs)
25
+ return pl.hello_world(inputs)
26
+ };
27
+ /**
28
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
29
+ *
30
+ * - Changing this function will be over-written by the next build.
31
+ *
32
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
33
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
34
+ *
35
+ * @param {{}} inputs
36
+ * @param {{ locale?: "en" | "pl" }} options
37
+ * @returns {string}
38
+ */
39
+ /* @__NO_SIDE_EFFECTS__ */
40
+ const login_hello = (inputs = {}, options = {}) => {
41
+ if (experimentalMiddlewareLocaleSplitting && isServer === false) {
42
+ return /** @type {any} */ (globalThis).__paraglide_ssr.login_hello(inputs)
43
+ }
44
+ const locale = options.locale ?? getLocale()
45
+ trackMessageCall("login_hello", locale)
46
+ if (locale === "en") return en.login_hello(inputs)
47
+ return pl.login_hello(inputs)
48
+ };
49
+ export { login_hello as "login.hello" }
50
+ /**
51
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
52
+ *
53
+ * - Changing this function will be over-written by the next build.
54
+ *
55
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
56
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
57
+ *
58
+ * @param {{}} inputs
59
+ * @param {{ locale?: "en" | "pl" }} options
60
+ * @returns {string}
61
+ */
62
+ /* @__NO_SIDE_EFFECTS__ */
63
+ const login_please_login = (inputs = {}, options = {}) => {
64
+ if (experimentalMiddlewareLocaleSplitting && isServer === false) {
65
+ return /** @type {any} */ (globalThis).__paraglide_ssr.login_please_login(inputs)
66
+ }
67
+ const locale = options.locale ?? getLocale()
68
+ trackMessageCall("login_please_login", locale)
69
+ if (locale === "en") return en.login_please_login(inputs)
70
+ return pl.login_please_login(inputs)
71
+ };
72
+ export { login_please_login as "login.please_login" }
@@ -0,0 +1,5 @@
1
+ export const hello_world: (inputs: {
2
+ name: NonNullable<unknown>;
3
+ }) => string;
4
+ export const login_hello: (inputs: {}) => string;
5
+ export const login_please_login: (inputs: {}) => string;
@@ -0,0 +1,14 @@
1
+ /* eslint-disable */
2
+
3
+
4
+ export const hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
5
+ return `Hello, ${i.name} from en!`
6
+ };
7
+
8
+ export const login_hello = /** @type {(inputs: {}) => string} */ () => {
9
+ return `Welcome back`
10
+ };
11
+
12
+ export const login_please_login = /** @type {(inputs: {}) => string} */ () => {
13
+ return `Login to your account`
14
+ };
@@ -0,0 +1,5 @@
1
+ export const hello_world: (inputs: {
2
+ name: NonNullable<unknown>;
3
+ }) => string;
4
+ export const login_hello: (inputs: {}) => string;
5
+ export const login_please_login: (inputs: {}) => string;
@@ -0,0 +1,14 @@
1
+ /* eslint-disable */
2
+
3
+
4
+ export const hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
5
+ return `Hello, ${i.name} from pl!`
6
+ };
7
+
8
+ export const login_hello = /** @type {(inputs: {}) => string} */ () => {
9
+ return `Witaj ponownie`
10
+ };
11
+
12
+ export const login_please_login = /** @type {(inputs: {}) => string} */ () => {
13
+ return `Zaloguj się na swoje konto`
14
+ };
@@ -25,6 +25,8 @@ export interface FakturowniaInvoiceBody {
25
25
  buyer_city?: string;
26
26
  buyer_post_code?: string;
27
27
  buyer_country?: string;
28
+ /** Printed remarks ("Uwagi") — e.g. the applied coupon code. */
29
+ description?: string;
28
30
  positions: FakturowniaPosition[];
29
31
  }
30
32
  /**
@@ -39,6 +39,7 @@ export function buildFakturowniaInvoice(payload, opts = {}) {
39
39
  ? { buyer_post_code: pick(buyer.address, 'postCode', 'zip', 'postalCode') }
40
40
  : {}),
41
41
  ...(pick(buyer.address, 'country') ? { buyer_country: pick(buyer.address, 'country') } : {}),
42
+ ...(payload.note ? { description: payload.note } : {}),
42
43
  positions: payload.items.map((item) => ({
43
44
  name: item.name,
44
45
  quantity: item.quantity,
@@ -44,8 +44,22 @@ export declare function decideInvoiceAction(order: InvoiceOrderState, existing:
44
44
  } | null, policy: InvoiceIssuePolicy, opts?: {
45
45
  force?: boolean;
46
46
  }): InvoiceAction;
47
- /** Map order + items onto the provider-agnostic {@link InvoicePayload}. Pure. */
48
- export declare function buildInvoicePayload(order: InvoiceOrderData, items: InvoiceItemData[], paidAt: string): InvoicePayload;
47
+ /**
48
+ * Map order + items onto the provider-agnostic {@link InvoicePayload}. Pure.
49
+ * `opts.discount` lowers the item prices to the post-discount amounts and adds a
50
+ * remark (`note`) with the coupon code; `opts.shipping` appends a "Wysyłka" line
51
+ * when charged (>0). Together the line totals equal `order.totalGross`.
52
+ */
53
+ export declare function buildInvoicePayload(order: InvoiceOrderData, items: InvoiceItemData[], paidAt: string, opts?: {
54
+ discount?: {
55
+ code: string;
56
+ amount: number;
57
+ };
58
+ shipping?: {
59
+ net: number;
60
+ gross: number;
61
+ };
62
+ }): InvoicePayload;
49
63
  export declare function getInvoiceByOrderId(orderId: string): Promise<InvoiceRow | null>;
50
64
  /**
51
65
  * Issue an invoice for an order if it qualifies. Fire-and-forget, fail-open —
@@ -1,6 +1,7 @@
1
1
  import { eq } from 'drizzle-orm';
2
2
  import { shopInvoicesTable, shopOrderItemsTable, shopOrdersTable } from '../../db-postgres/schema/shop/index.js';
3
3
  import { getShopDb, requireShopConfig } from './db.js';
4
+ import { getOrderCoupon } from './coupons.js';
4
5
  const FULLY_PAID_STATES = new Set(['paid', 'preparing', 'sent', 'done']);
5
6
  export class InvoiceError extends Error {
6
7
  code;
@@ -59,8 +60,82 @@ function lineName(name) {
59
60
  return `${product} — ${variant}`;
60
61
  return product || variant || Object.values(name)[0] || '';
61
62
  }
62
- /** Map order + items onto the provider-agnostic {@link InvoicePayload}. Pure. */
63
- export function buildInvoicePayload(order, items, paidAt) {
63
+ /**
64
+ * Build the shipping invoice line, or `null` when shipping is free. VAT is
65
+ * reconstructed from the actually-charged net/gross (falls back to 23% when the
66
+ * net is zero but a gross was charged). Pure.
67
+ */
68
+ function buildShippingLine(shipping) {
69
+ if (shipping.gross <= 0)
70
+ return null;
71
+ const vatRate = shipping.net > 0 ? Math.round(((shipping.gross - shipping.net) / shipping.net) * 100) : 23;
72
+ return { name: 'Wysyłka', quantity: 1, unitPriceGross: shipping.gross, vatRate };
73
+ }
74
+ /**
75
+ * Reduce item line prices by a gross coupon discount, so the invoice shows the
76
+ * actually-charged (post-discount) prices — the final total equals the amount
77
+ * paid (`order.totalGross`). The gross discount is distributed across lines
78
+ * proportionally to each line's gross share; the last line absorbs the rounding
79
+ * remainder so the reduced line totals sum to `subtotal − discountAmount`
80
+ * exactly. The per-unit price may carry fractional grosze; the provider
81
+ * recomputes it from the line total (Fakturownia: `total_price_gross ÷ quantity`).
82
+ * Pure.
83
+ */
84
+ function applyDiscountToLines(lines, discountAmount) {
85
+ if (discountAmount <= 0 || lines.length === 0)
86
+ return lines;
87
+ const lineGross = (l) => l.unitPriceGross * l.quantity;
88
+ const subtotal = lines.reduce((s, l) => s + lineGross(l), 0);
89
+ if (subtotal <= 0)
90
+ return lines;
91
+ let allocated = 0;
92
+ return lines.map((line, idx) => {
93
+ const gross = lineGross(line);
94
+ const isLast = idx === lines.length - 1;
95
+ const lineDiscount = isLast
96
+ ? discountAmount - allocated
97
+ : Math.round((discountAmount * gross) / subtotal);
98
+ allocated += lineDiscount;
99
+ return { ...line, unitPriceGross: (gross - lineDiscount) / line.quantity };
100
+ });
101
+ }
102
+ /** Format minor units (grosze) as a localized currency string. Pure. */
103
+ function formatMoney(grosze, currency, language) {
104
+ return new Intl.NumberFormat(language === 'en' ? 'en-GB' : 'pl-PL', {
105
+ style: 'currency',
106
+ currency,
107
+ minimumFractionDigits: 2
108
+ }).format(grosze / 100);
109
+ }
110
+ /** Build the printed invoice remark describing the applied coupon. Pure. */
111
+ function discountNote(discount, currency, language) {
112
+ const amount = formatMoney(discount.amount, currency, language);
113
+ return language === 'en'
114
+ ? `Discount code ${discount.code} applied (-${amount}).`
115
+ : `Zastosowano kod rabatowy ${discount.code} (rabat -${amount}).`;
116
+ }
117
+ /**
118
+ * Map order + items onto the provider-agnostic {@link InvoicePayload}. Pure.
119
+ * `opts.discount` lowers the item prices to the post-discount amounts and adds a
120
+ * remark (`note`) with the coupon code; `opts.shipping` appends a "Wysyłka" line
121
+ * when charged (>0). Together the line totals equal `order.totalGross`.
122
+ */
123
+ export function buildInvoicePayload(order, items, paidAt, opts = {}) {
124
+ let lines = items.map((item) => ({
125
+ name: lineName(item.nameSnapshot),
126
+ quantity: item.qty,
127
+ unitPriceGross: item.priceGrossSnapshot,
128
+ vatRate: item.vatRate
129
+ }));
130
+ const hasDiscount = Boolean(opts.discount && opts.discount.amount > 0);
131
+ if (opts.discount && hasDiscount) {
132
+ lines = applyDiscountToLines(lines, opts.discount.amount);
133
+ }
134
+ if (opts.shipping) {
135
+ const shippingLine = buildShippingLine(opts.shipping);
136
+ if (shippingLine)
137
+ lines.push(shippingLine);
138
+ }
64
139
  return {
65
140
  orderNumber: order.number,
66
141
  currency: order.currency,
@@ -72,12 +147,10 @@ export function buildInvoicePayload(order, items, paidAt) {
72
147
  companyName: order.customerCompanyName,
73
148
  address: order.billingAddress ?? order.shippingAddress ?? null
74
149
  },
75
- items: items.map((item) => ({
76
- name: lineName(item.nameSnapshot),
77
- quantity: item.qty,
78
- unitPriceGross: item.priceGrossSnapshot,
79
- vatRate: item.vatRate
80
- }))
150
+ items: lines,
151
+ ...(opts.discount && hasDiscount
152
+ ? { note: discountNote(opts.discount, order.currency, order.language) }
153
+ : {})
81
154
  };
82
155
  }
83
156
  export async function getInvoiceByOrderId(orderId) {
@@ -144,7 +217,11 @@ async function runCreate(order, existing, adapter) {
144
217
  .from(shopOrderItemsTable)
145
218
  .where(eq(shopOrderItemsTable.orderId, order.id));
146
219
  const paidAt = order.partialPayment?.paidAt ?? now.toISOString();
147
- const payload = buildInvoicePayload(toOrderData(order), toItemData(items), paidAt);
220
+ const coupon = await getOrderCoupon(order.id);
221
+ const payload = buildInvoicePayload(toOrderData(order), toItemData(items), paidAt, {
222
+ discount: coupon ? { code: coupon.code, amount: coupon.discountAmount } : undefined,
223
+ shipping: { net: order.shippingNet, gross: order.shippingGross }
224
+ });
148
225
  try {
149
226
  const result = await adapter.createInvoice(payload, { language: order.language });
150
227
  let status = 'issued';
@@ -359,6 +359,11 @@ export interface InvoicePayload {
359
359
  items: InvoiceLineItem[];
360
360
  /** ISO timestamp the payment was confirmed — used as issue / paid date. */
361
361
  paidAt: string;
362
+ /**
363
+ * Free-text remark printed on the invoice (e.g. the applied coupon code).
364
+ * Adapters map it onto their notes field (Fakturownia: `description`).
365
+ */
366
+ note?: string;
362
367
  }
363
368
  /** @public */
364
369
  export interface InvoiceCreateResult {
@@ -59,7 +59,7 @@
59
59
  {/each}
60
60
 
61
61
  <img
62
- src={data.data.url}
62
+ src={data.data.fallbackUrl ?? data.data.url}
63
63
  alt={data.data.alt}
64
64
  width={data.data.width}
65
65
  height={data.data.height}