@flowselections/layout-maker 1.36.7 → 1.45.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.
- package/dist-lib/components/AfleverbriefBuilderPage.d.ts +10 -0
- package/dist-lib/components/AfleverbriefBuilderPage.d.ts.map +1 -0
- package/dist-lib/components/AfleverbriefBuilderPage.js +101 -0
- package/dist-lib/components/BlokOntwerperPage.d.ts.map +1 -1
- package/dist-lib/components/BlokOntwerperPage.js +8 -3
- package/dist-lib/components/ComputersLijst.d.ts.map +1 -1
- package/dist-lib/components/ComputersLijst.js +1 -1
- package/dist-lib/components/FustKolomVolgorde.d.ts +11 -0
- package/dist-lib/components/FustKolomVolgorde.d.ts.map +1 -0
- package/dist-lib/components/FustKolomVolgorde.js +52 -0
- package/dist-lib/components/LayoutsPage.d.ts.map +1 -1
- package/dist-lib/components/LayoutsPage.js +10 -9
- package/dist-lib/components/PaginaDocument.d.ts +4 -1
- package/dist-lib/components/PaginaDocument.d.ts.map +1 -1
- package/dist-lib/components/PaginaDocument.js +10 -5
- package/dist-lib/components/PakbonBuilderPage.d.ts.map +1 -1
- package/dist-lib/components/PakbonBuilderPage.js +34 -14
- package/dist-lib/components/PakbonPreview.d.ts +4 -2
- package/dist-lib/components/PakbonPreview.d.ts.map +1 -1
- package/dist-lib/components/PakbonPreview.js +74 -24
- package/dist-lib/components/PakbonSectiesPanel.d.ts +5 -3
- package/dist-lib/components/PakbonSectiesPanel.d.ts.map +1 -1
- package/dist-lib/components/PakbonSectiesPanel.js +8 -5
- package/dist-lib/components/PdfMapToestemmingBanner.d.ts +2 -1
- package/dist-lib/components/PdfMapToestemmingBanner.d.ts.map +1 -1
- package/dist-lib/components/PdfMapToestemmingBanner.js +73 -14
- package/dist-lib/components/PdfVoorbeeld.d.ts +20 -0
- package/dist-lib/components/PdfVoorbeeld.d.ts.map +1 -0
- package/dist-lib/components/PdfVoorbeeld.js +95 -0
- package/dist-lib/components/PrintProgrammaUitleg.d.ts.map +1 -1
- package/dist-lib/components/PrintProgrammaUitleg.js +1 -1
- package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
- package/dist-lib/components/PrintQueuePage.js +81 -43
- package/dist-lib/components/PrintplekDetailPage.d.ts.map +1 -1
- package/dist-lib/components/PrintplekDetailPage.js +2 -2
- package/dist-lib/components/PrintplekWizard.d.ts.map +1 -1
- package/dist-lib/components/PrintplekWizard.js +6 -2
- package/dist-lib/components/ProductiebonBuilderPage.d.ts.map +1 -1
- package/dist-lib/components/ProductiebonBuilderPage.js +13 -7
- package/dist-lib/index.d.ts +16 -6
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +11 -5
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +3585 -1267
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +3585 -1267
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/moduleClient.d.ts +25898 -0
- package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/moduleClient.js +29 -0
- package/dist-lib/integrations/supabase/types.d.ts +3607 -1265
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +1 -0
- package/dist-lib/lib/aiLayout.functions.d.ts +3585 -1267
- package/dist-lib/lib/aiLayout.functions.d.ts.map +1 -1
- package/dist-lib/lib/code128.d.ts +5 -0
- package/dist-lib/lib/code128.d.ts.map +1 -0
- package/dist-lib/lib/code128.js +51 -0
- package/dist-lib/lib/documentVoorbeeld.functions.d.ts +23 -0
- package/dist-lib/lib/documentVoorbeeld.functions.d.ts.map +1 -0
- package/dist-lib/lib/documentVoorbeeld.functions.js +37 -0
- package/dist-lib/lib/fontMetrics.d.ts +15 -0
- package/dist-lib/lib/fontMetrics.d.ts.map +1 -0
- package/dist-lib/lib/fontMetrics.js +118 -0
- package/dist-lib/lib/htmlNaarPdf.server.d.ts +2 -2
- package/dist-lib/lib/htmlNaarPdf.server.d.ts.map +1 -1
- package/dist-lib/lib/htmlNaarPdf.server.js +2 -1
- package/dist-lib/lib/htmlPdf.server.d.ts +11 -0
- package/dist-lib/lib/htmlPdf.server.d.ts.map +1 -1
- package/dist-lib/lib/htmlPdf.server.js +170 -9
- package/dist-lib/lib/layoutPreview.d.ts.map +1 -1
- package/dist-lib/lib/layoutPreview.js +6 -3
- package/dist-lib/lib/orderData.server.d.ts.map +1 -1
- package/dist-lib/lib/orderData.server.js +49 -16
- package/dist-lib/lib/pakbonBarcodeVerzoek.d.ts +39 -0
- package/dist-lib/lib/pakbonBarcodeVerzoek.d.ts.map +1 -0
- package/dist-lib/lib/pakbonBarcodeVerzoek.js +80 -0
- package/dist-lib/lib/pakbonBuilder.d.ts +90 -5
- package/dist-lib/lib/pakbonBuilder.d.ts.map +1 -1
- package/dist-lib/lib/pakbonBuilder.js +455 -58
- package/dist-lib/lib/pakbonContent.d.ts +41 -0
- package/dist-lib/lib/pakbonContent.d.ts.map +1 -0
- package/dist-lib/lib/pakbonContent.js +668 -0
- package/dist-lib/lib/pakbonDocument.d.ts +199 -0
- package/dist-lib/lib/pakbonDocument.d.ts.map +1 -0
- package/dist-lib/lib/pakbonDocument.js +735 -0
- package/dist-lib/lib/pakbonHtml.d.ts +21 -2
- package/dist-lib/lib/pakbonHtml.d.ts.map +1 -1
- package/dist-lib/lib/pakbonHtml.js +144 -36
- package/dist-lib/lib/pakbonPresentation.d.ts +8 -0
- package/dist-lib/lib/pakbonPresentation.d.ts.map +1 -0
- package/dist-lib/lib/pakbonPresentation.js +31 -0
- package/dist-lib/lib/pakbonScan.d.ts +44 -0
- package/dist-lib/lib/pakbonScan.d.ts.map +1 -0
- package/dist-lib/lib/pakbonScan.js +87 -0
- package/dist-lib/lib/pdfJobSaver.d.ts +10 -6
- package/dist-lib/lib/pdfJobSaver.d.ts.map +1 -1
- package/dist-lib/lib/pdfJobSaver.js +302 -99
- package/dist-lib/lib/pdfMap.d.ts +37 -2
- package/dist-lib/lib/pdfMap.d.ts.map +1 -1
- package/dist-lib/lib/pdfMap.js +120 -7
- package/dist-lib/lib/pdfOpslagStatus.d.ts +23 -0
- package/dist-lib/lib/pdfOpslagStatus.d.ts.map +1 -0
- package/dist-lib/lib/pdfOpslagStatus.js +74 -0
- package/dist-lib/lib/printApi.d.ts +31 -0
- package/dist-lib/lib/printApi.d.ts.map +1 -1
- package/dist-lib/lib/printApi.js +62 -1
- package/dist-lib/lib/printQueue.server.d.ts +13 -0
- package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
- package/dist-lib/lib/printQueue.server.js +673 -96
- package/dist-lib/lib/printService.functions.d.ts +11840 -2568
- package/dist-lib/lib/printService.functions.d.ts.map +1 -1
- package/dist-lib/lib/profielHtml.d.ts.map +1 -1
- package/dist-lib/lib/profielHtml.js +8 -1
- package/dist-lib/lib/supabaseClient.d.ts +1 -3
- package/dist-lib/lib/supabaseClient.d.ts.map +1 -1
- package/dist-lib/lib/supabaseClient.js +4 -8
- package/dist-lib/lib/useDocumentData.d.ts +8 -0
- package/dist-lib/lib/useDocumentData.d.ts.map +1 -1
- package/dist-lib/lib/useDocumentData.js +1 -1
- package/dist-lib/lib/voorbeeldPrint.d.ts +3 -0
- package/dist-lib/lib/voorbeeldPrint.d.ts.map +1 -1
- package/dist-lib/lib/voorbeeldPrint.js +65 -17
- package/package.json +9 -2
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
// de juiste printer. Draait alleen op de server (cron / interne aanroep).
|
|
5
5
|
// ============================================================================
|
|
6
6
|
import { maakPakbonOrder, renderPakbonDocument, voorbeeldPakbonOrder, } from "./pakbonHtml.js";
|
|
7
|
+
import { bouwPakbonScanInhoud, bewaarPakbonScanInhoud } from "./pakbonScan.js";
|
|
8
|
+
import { bouwPakbonBarcodeVerzoek, geleverdeBarcodeVoorJob, vraagPakbonBarcodeAan, } from "./pakbonBarcodeVerzoek.js";
|
|
9
|
+
import { deelkarRegels, leesAfleverbriefConfig, pakbonLayoutVariant } from "./pakbonBuilder.js";
|
|
7
10
|
import { renderProductiebonDocument } from "./productiebonHtml.js";
|
|
8
|
-
import { heeftOrderInhoud, laadOrderVanuitDatabase } from "./orderData.server.js";
|
|
11
|
+
import { heeftOrderInhoud, laadOrderVanuitDatabase, laadPakbonData } from "./orderData.server.js";
|
|
9
12
|
import { profielHeeftInhoud, renderProfielDocument } from "./profielHtml.js";
|
|
10
13
|
import { bouwDocumentHtml, voorbeeldContext } from "./layoutPreview.js";
|
|
11
14
|
import { htmlNaarPdfServer, PdfRenderFout } from "./htmlNaarPdf.server.js";
|
|
@@ -47,6 +50,20 @@ export class GeenDocumentFout extends Error {
|
|
|
47
50
|
this.name = "GeenDocumentFout";
|
|
48
51
|
}
|
|
49
52
|
}
|
|
53
|
+
/** De Engine heeft de taak gepland, maar de layout is nog niet aangeleverd. */
|
|
54
|
+
export class WachtOpLayoutFout extends Error {
|
|
55
|
+
constructor() {
|
|
56
|
+
super(...arguments);
|
|
57
|
+
this.name = "WachtOpLayoutFout";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Blijvende contractfout: opnieuw proberen met dezelfde invoer kan niet slagen. */
|
|
61
|
+
export class EngineContractFout extends GeenDocumentFout {
|
|
62
|
+
constructor() {
|
|
63
|
+
super(...arguments);
|
|
64
|
+
this.name = "EngineContractFout";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
50
67
|
/**
|
|
51
68
|
* Oudere/andere benamingen voor hetzelfde document. De Logistiek Engine noemt
|
|
52
69
|
* de afleverbrief bijvoorbeeld nog "verzendbrief"; hier vertalen we dat naar
|
|
@@ -65,6 +82,149 @@ export function normaliseerDocumenttype(type) {
|
|
|
65
82
|
return "document";
|
|
66
83
|
return DOCUMENTTYPE_ALIASSEN[t] ?? t;
|
|
67
84
|
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Contract met de Logistics Engine.
|
|
87
|
+
// De Engine bepaalt documentsoort, variant, layout en printplek; deze module
|
|
88
|
+
// rendert en print alleen. Alles hieronder leest die keuzes uit de opdracht en
|
|
89
|
+
// overruled ze nooit.
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
/** Het layout-id zoals de Engine het aanlevert (kolom of payload). */
|
|
92
|
+
export function leesEngineLayoutId(job) {
|
|
93
|
+
const p = (job.payload ?? {});
|
|
94
|
+
const d = (p.data ?? {});
|
|
95
|
+
for (const k of [job.layout_id, p.layout_id, p.layoutId, d.layout_id, d.layoutId]) {
|
|
96
|
+
const v = String(k ?? "").trim();
|
|
97
|
+
if (v)
|
|
98
|
+
return v;
|
|
99
|
+
}
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
/** Documentsoort zoals de Engine hem noemt (`document_code`). */
|
|
103
|
+
function leesDocumentCode(job) {
|
|
104
|
+
const p = (job.payload ?? {});
|
|
105
|
+
const d = (p.data ?? {});
|
|
106
|
+
for (const k of [
|
|
107
|
+
job.document_code,
|
|
108
|
+
job.document_type_code,
|
|
109
|
+
p.document_code,
|
|
110
|
+
p.documentCode,
|
|
111
|
+
p.document_type_code,
|
|
112
|
+
p.documentTypeCode,
|
|
113
|
+
d.document_code,
|
|
114
|
+
d.document_type_code,
|
|
115
|
+
d.documentTypeCode,
|
|
116
|
+
]) {
|
|
117
|
+
const v = String(k ?? "").trim();
|
|
118
|
+
if (v)
|
|
119
|
+
return v;
|
|
120
|
+
}
|
|
121
|
+
return "";
|
|
122
|
+
}
|
|
123
|
+
/** Is dit een opdracht die door de Logistics Engine is gepland? */
|
|
124
|
+
function isEngineOpdracht(job) {
|
|
125
|
+
const p = (job.payload ?? {});
|
|
126
|
+
if (leesDocumentCode(job))
|
|
127
|
+
return true;
|
|
128
|
+
const bron = String(p.bron_module ?? p.owner_module ?? p.source_module ?? "").toLowerCase();
|
|
129
|
+
return bron.includes("logistic") || bron.includes("engine");
|
|
130
|
+
}
|
|
131
|
+
/** Handmatig opnieuw printen? */
|
|
132
|
+
function isHerprint(job) {
|
|
133
|
+
const p = (job.payload ?? {});
|
|
134
|
+
const d = (p.data ?? {});
|
|
135
|
+
return job.is_herprint === true || p.is_herprint === true || d.is_herprint === true;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Een herprint behoudt de eerder door de Engine aangeleverde barcode en kar.
|
|
139
|
+
* Er wordt nooit lokaal een barcode bedacht. We zoeken uitsluitend binnen
|
|
140
|
+
* dezelfde queue-/orderreferentie en hetzelfde documenttype.
|
|
141
|
+
*/
|
|
142
|
+
async function zoekHerprintBarcodeBron(db, job) {
|
|
143
|
+
if (!isHerprint(job))
|
|
144
|
+
return null;
|
|
145
|
+
const p = (job.payload ?? {});
|
|
146
|
+
const d = (p.data ?? {});
|
|
147
|
+
const bronJobId = String(job.bron_job_id ?? p.bron_job_id ?? "").trim();
|
|
148
|
+
if (bronJobId) {
|
|
149
|
+
const { data: bron } = await db
|
|
150
|
+
.from("print_jobs")
|
|
151
|
+
.select("payload")
|
|
152
|
+
.eq("id", bronJobId)
|
|
153
|
+
.maybeSingle();
|
|
154
|
+
const bp = (bron?.payload ?? {});
|
|
155
|
+
const bd = (bp.data ?? {});
|
|
156
|
+
const barcode = String(bp.barcode ?? bd.cart?.barcode ?? bp.cart?.barcode ?? "").trim();
|
|
157
|
+
if (barcode) {
|
|
158
|
+
const cart = (bd.cart ?? bp.cart ?? null);
|
|
159
|
+
return { barcode, cart: cart && typeof cart === "object" ? cart : null };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const queueItem = String(p.queue_item_id ?? d.queue_item_id ?? "").trim();
|
|
163
|
+
const orderNummer = String(p.order_number ?? p.ordernummer ?? d.order_number ?? d.ordernummer ?? "").trim();
|
|
164
|
+
if (!queueItem && !orderNummer)
|
|
165
|
+
return null;
|
|
166
|
+
const { data } = await db
|
|
167
|
+
.from("print_jobs")
|
|
168
|
+
.select("id, documenttype, payload, created_at")
|
|
169
|
+
.lt("created_at", String(job.created_at ?? new Date().toISOString()))
|
|
170
|
+
.order("created_at", { ascending: false })
|
|
171
|
+
.limit(30);
|
|
172
|
+
const type = normaliseerDocumenttype(leesDocumentCode(job) || job.documenttype);
|
|
173
|
+
for (const kandidaat of arr(data)) {
|
|
174
|
+
if (normaliseerDocumenttype(leesDocumentCode(kandidaat) || kandidaat.documenttype) !== type)
|
|
175
|
+
continue;
|
|
176
|
+
const kp = (kandidaat.payload ?? {});
|
|
177
|
+
const kd = (kp.data ?? {});
|
|
178
|
+
const zelfdeQueue = queueItem && String(kp.queue_item_id ?? kd.queue_item_id ?? "").trim() === queueItem;
|
|
179
|
+
const zelfdeOrder = orderNummer && String(kp.order_number ?? kp.ordernummer ?? kd.order_number ?? kd.ordernummer ?? "").trim() === orderNummer;
|
|
180
|
+
if (!zelfdeQueue && !zelfdeOrder)
|
|
181
|
+
continue;
|
|
182
|
+
const barcode = String(kp.barcode ?? kd.cart?.barcode ?? kp.cart?.barcode ?? "").trim();
|
|
183
|
+
if (!barcode)
|
|
184
|
+
continue;
|
|
185
|
+
const cart = (kd.cart ?? kp.cart ?? null);
|
|
186
|
+
return { barcode, cart: cart && typeof cart === "object" ? cart : null };
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
/** De ordersleutel waarop een herprint bij de oorspronkelijke print hoort. */
|
|
191
|
+
function leesOrderRef(job) {
|
|
192
|
+
const p = (job.payload ?? {});
|
|
193
|
+
const d = (p.data ?? {});
|
|
194
|
+
for (const k of [job.order_ref, p.order_ref, p.orderRef, d.order_ref, d.orderRef, job.order_id]) {
|
|
195
|
+
const v = String(k ?? "").trim();
|
|
196
|
+
if (v)
|
|
197
|
+
return v;
|
|
198
|
+
}
|
|
199
|
+
return "";
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Een herprint gebruikt dezelfde layout als de oorspronkelijke print. Stuurt de
|
|
203
|
+
* Engine geen layout mee, dan zoeken we die op bij de laatste geslaagde print
|
|
204
|
+
* van hetzelfde document van dezelfde order.
|
|
205
|
+
*/
|
|
206
|
+
async function zoekHerprintLayoutId(db, job) {
|
|
207
|
+
const orderRef = leesOrderRef(job);
|
|
208
|
+
if (!orderRef)
|
|
209
|
+
return "";
|
|
210
|
+
const type = normaliseerDocumenttype(leesDocumentCode(job) || job.documenttype);
|
|
211
|
+
const { data } = await db
|
|
212
|
+
.from("print_jobs")
|
|
213
|
+
.select("gebruikte_layout_id, documenttype, payload, created_at")
|
|
214
|
+
.not("gebruikte_layout_id", "is", null)
|
|
215
|
+
.order("created_at", { ascending: false })
|
|
216
|
+
.limit(50);
|
|
217
|
+
for (const rij of arr(data)) {
|
|
218
|
+
if (normaliseerDocumenttype(rij.documenttype) !== type)
|
|
219
|
+
continue;
|
|
220
|
+
if (leesOrderRef(rij) !== orderRef)
|
|
221
|
+
continue;
|
|
222
|
+
const id = String(rij.gebruikte_layout_id ?? "").trim();
|
|
223
|
+
if (id)
|
|
224
|
+
return id;
|
|
225
|
+
}
|
|
226
|
+
return "";
|
|
227
|
+
}
|
|
68
228
|
/**
|
|
69
229
|
* Zoekt de layout die bij de opdracht hoort.
|
|
70
230
|
* Staat er een layout_id op de opdracht (bijv. gezet door de Logistiek
|
|
@@ -94,15 +254,19 @@ function magOntwerpWinnen(layout) {
|
|
|
94
254
|
return String(layout.bron ?? "") !== "floriday";
|
|
95
255
|
}
|
|
96
256
|
async function zoekLayout(db, job) {
|
|
257
|
+
const engineJob = isEngineOpdracht(job);
|
|
97
258
|
const heeftEigenOpmaak = typeof job.render_html === "string" && job.render_html.trim().length > 40;
|
|
98
|
-
|
|
99
|
-
// koppeling: is er opmaak meegestuurd (bijv. een voorbeeldafdruk uit de
|
|
100
|
-
// ontwerper), dan is die opmaak leidend.
|
|
101
|
-
const uitPayload = heeftEigenOpmaak
|
|
259
|
+
const uitPayload = engineJob
|
|
102
260
|
? null
|
|
103
261
|
: (job.payload?.layoutId ?? job.payload?.data?.layoutId ?? job.payload?.layout_id ?? null);
|
|
104
|
-
|
|
105
|
-
|
|
262
|
+
let id = leesEngineLayoutId(job) || job.layout_id || uitPayload;
|
|
263
|
+
if (engineJob && !id && isHerprint(job)) {
|
|
264
|
+
id = await zoekHerprintLayoutId(db, job);
|
|
265
|
+
}
|
|
266
|
+
if (engineJob && !id) {
|
|
267
|
+
throw new WachtOpLayoutFout("Wacht op layout_id van de Logistics Engine. Deze taak wordt niet met een vervangende layout uitgevoerd.");
|
|
268
|
+
}
|
|
269
|
+
const voorbeeld = !engineJob && heeftEigenOpmaak && isVoorbeeldAfdruk(job);
|
|
106
270
|
if (id) {
|
|
107
271
|
const { data } = await db
|
|
108
272
|
.from("document_layouts")
|
|
@@ -113,19 +277,24 @@ async function zoekLayout(db, job) {
|
|
|
113
277
|
throw new GeenDocumentFout("De layout die aan deze opdracht hangt bestaat niet meer. Koppel een geldige layout.");
|
|
114
278
|
}
|
|
115
279
|
const layout = data;
|
|
116
|
-
return {
|
|
280
|
+
return {
|
|
281
|
+
layout,
|
|
282
|
+
// Een Engine-layout is altijd leidend, ook als er render_html is meegestuurd.
|
|
283
|
+
ontwerpWint: engineJob || (!voorbeeld && magOntwerpWinnen(layout)),
|
|
284
|
+
};
|
|
117
285
|
}
|
|
286
|
+
const documenttype = normaliseerDocumenttype(leesDocumentCode(job) || job.documenttype);
|
|
118
287
|
const { data } = await db
|
|
119
288
|
.from("document_layouts")
|
|
120
289
|
.select("*")
|
|
121
|
-
.eq("documenttype",
|
|
290
|
+
.eq("documenttype", documenttype)
|
|
122
291
|
.eq("actief", true)
|
|
123
292
|
.order("is_standaard", { ascending: false })
|
|
124
293
|
.order("updated_at", { ascending: false })
|
|
125
294
|
.limit(1);
|
|
126
295
|
const gevonden = (arr(data)[0] ?? null);
|
|
127
296
|
if (!gevonden && !job.render_html) {
|
|
128
|
-
throw new GeenDocumentFout(`Geen layout gevonden voor documenttype '${
|
|
297
|
+
throw new GeenDocumentFout(`Geen layout gevonden voor documenttype '${documenttype}'. ` +
|
|
129
298
|
"Maak een layout aan en zet die op actief.");
|
|
130
299
|
}
|
|
131
300
|
return { layout: gevonden, ontwerpWint: !voorbeeld && magOntwerpWinnen(gevonden) };
|
|
@@ -148,7 +317,7 @@ async function zoekBronregel(db, job) {
|
|
|
148
317
|
const regel = data;
|
|
149
318
|
if (!regel || regel.bron === "auto")
|
|
150
319
|
return null;
|
|
151
|
-
if (regel.bron === "eigen_layout" && regel.layout_id && !job.layout_id) {
|
|
320
|
+
if (regel.bron === "eigen_layout" && regel.layout_id && !job.layout_id && !isEngineOpdracht(job)) {
|
|
152
321
|
job.layout_id = regel.layout_id;
|
|
153
322
|
}
|
|
154
323
|
return regel.bron;
|
|
@@ -159,6 +328,8 @@ async function zoekBronregel(db, job) {
|
|
|
159
328
|
*/
|
|
160
329
|
async function isFloridayLayoutKeuze(db, job) {
|
|
161
330
|
const payload = (job.payload ?? {});
|
|
331
|
+
if (isEngineOpdracht(job))
|
|
332
|
+
return false;
|
|
162
333
|
const id = job.layout_id ?? payload?.["layoutId"] ?? payload?.["layout_id"] ?? null;
|
|
163
334
|
if (!id)
|
|
164
335
|
return false;
|
|
@@ -269,8 +440,37 @@ async function zoekPrintPlekId(db, job) {
|
|
|
269
440
|
.select("print_plek_id")
|
|
270
441
|
.eq("computer_naam", werkplek)
|
|
271
442
|
.maybeSingle();
|
|
272
|
-
|
|
443
|
+
const id = String(data?.print_plek_id ?? "").trim();
|
|
444
|
+
if (id)
|
|
445
|
+
return id;
|
|
273
446
|
}
|
|
447
|
+
// Terugval: de opdracht kwam zonder plek binnen (bijvoorbeeld van de scanner
|
|
448
|
+
// bij "kar compleet"). Gebruik dan de plek die in de documentroutering voor
|
|
449
|
+
// deze documentsoort staat. Zo blijft een PDF-opdracht niet eindeloos op een
|
|
450
|
+
// werkplek wachten die niemand heeft toegewezen.
|
|
451
|
+
const documenttype = normaliseerDocumenttype(job.documenttype);
|
|
452
|
+
if (documenttype) {
|
|
453
|
+
const { data } = await db
|
|
454
|
+
.from("document_printplek_printers")
|
|
455
|
+
.select("print_plek_id")
|
|
456
|
+
.eq("documenttype", documenttype)
|
|
457
|
+
.not("print_plek_id", "is", null);
|
|
458
|
+
const plekken = Array.isArray(data) ? data : [];
|
|
459
|
+
const uniek = Array.from(new Set(plekken.map((r) => String(r?.print_plek_id ?? "").trim()).filter(Boolean)));
|
|
460
|
+
if (uniek.length === 1)
|
|
461
|
+
return uniek[0];
|
|
462
|
+
}
|
|
463
|
+
// Laatste terugval: is er in de hele routering maar één plek bekend, dan kan
|
|
464
|
+
// er niets misgaan door die te kiezen. Bij meerdere plekken raden we nooit.
|
|
465
|
+
const { data: alle } = await db
|
|
466
|
+
.from("document_printplek_printers")
|
|
467
|
+
.select("print_plek_id")
|
|
468
|
+
.not("print_plek_id", "is", null);
|
|
469
|
+
const alleUniek = Array.from(new Set((Array.isArray(alle) ? alle : [])
|
|
470
|
+
.map((r) => String(r?.print_plek_id ?? "").trim())
|
|
471
|
+
.filter(Boolean)));
|
|
472
|
+
if (alleUniek.length === 1)
|
|
473
|
+
return alleUniek[0];
|
|
274
474
|
return "";
|
|
275
475
|
}
|
|
276
476
|
/** Naam van de printplek waar deze opdracht bij hoort, voor nette meldingen. */
|
|
@@ -329,13 +529,36 @@ async function zoekPrinter(db, job, layout) {
|
|
|
329
529
|
.maybeSingle();
|
|
330
530
|
return data && data.actief !== false ? data : null;
|
|
331
531
|
};
|
|
332
|
-
const type = normaliseerDocumenttype(job.documenttype);
|
|
333
|
-
//
|
|
334
|
-
|
|
532
|
+
const type = normaliseerDocumenttype(leesDocumentCode(job) || job.documenttype);
|
|
533
|
+
// Bij Engine-opdrachten is de centrale routering altijd leidend. De Engine
|
|
534
|
+
// kan een printer_id als momentopname meesturen, maar die kan inmiddels zijn
|
|
535
|
+
// gewijzigd (bijvoorbeeld van PDF naar een fysieke printer). Laat zo'n oud
|
|
536
|
+
// id daarom nooit de actuele printplek-, werkplek- of standaardregel
|
|
537
|
+
// overschrijven.
|
|
538
|
+
let printer = isEngineOpdracht(job) ? null : await pak(job.printer_id);
|
|
335
539
|
if (printer)
|
|
336
540
|
return { printer, reden: "" };
|
|
337
541
|
const werkplek = String(job.payload?.werkplek ?? "").trim();
|
|
338
542
|
const pickstation = leesPickstation(job);
|
|
543
|
+
const explicietePlek = leesPrintPlekId(job);
|
|
544
|
+
// Een Engine-printplek is een harde keuze. Nooit terugvallen op computer,
|
|
545
|
+
// layout of bedrijfsregels wanneer de Engine een print_plek_id meegaf.
|
|
546
|
+
if (explicietePlek) {
|
|
547
|
+
const { data: pp } = await db
|
|
548
|
+
.from("document_printplek_printers")
|
|
549
|
+
.select("printer_id")
|
|
550
|
+
.eq("print_plek_id", explicietePlek)
|
|
551
|
+
.eq("documenttype", type)
|
|
552
|
+
.maybeSingle();
|
|
553
|
+
printer = await pak(pp?.printer_id);
|
|
554
|
+
if (printer)
|
|
555
|
+
return { printer, reden: "" };
|
|
556
|
+
const { data: plek } = await db.from("print_plekken").select("naam").eq("id", explicietePlek).maybeSingle();
|
|
557
|
+
return {
|
|
558
|
+
printer: null,
|
|
559
|
+
reden: `Printplek '${String(plek?.naam ?? explicietePlek)}' heeft geen printer voor documenttype '${type}'.`,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
339
562
|
// 2. Regel van de printplek waar de opdracht vandaan komt.
|
|
340
563
|
const plekId = await zoekPrintPlekId(db, job);
|
|
341
564
|
if (plekId) {
|
|
@@ -508,6 +731,40 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
|
|
|
508
731
|
throw new GeenDocumentFout("Deze layout gebruikt altijd het ontwerp van de klant, maar er is geen klantontwerp meegestuurd.");
|
|
509
732
|
}
|
|
510
733
|
}
|
|
734
|
+
// 0) Afleverbrief-/veilingbrieflayouts. Deze hebben hun eigen sectie-opbouw
|
|
735
|
+
// onder `afleverbrief`. Een afleverbrief hoort altijd bij precies één kar:
|
|
736
|
+
// hij vertelt wat er op díé kar staat.
|
|
737
|
+
const afleverType = String(layout.documenttype) === "afleverbrief" ||
|
|
738
|
+
String(layout.documenttype) === "veilingbrief";
|
|
739
|
+
if (afleverType || Array.isArray(cfg.afleverbrief?.secties)) {
|
|
740
|
+
const orderBron = p.order ?? p.data?.order ?? p.data ?? p;
|
|
741
|
+
const cartId = String(p.data?.cart?.cart_id ?? p.cart?.cart_id ?? p.data?.cart?.cartId ?? p.cart?.cartId ?? "").trim();
|
|
742
|
+
const uitDb = heeftOrderInhoud(orderBron)
|
|
743
|
+
? null
|
|
744
|
+
: cartId
|
|
745
|
+
? ((await laadPakbonData(db, { scope: "cart", id: cartId })) ??
|
|
746
|
+
(await laadOrderVanuitDatabase(db, p)))
|
|
747
|
+
: await laadOrderVanuitDatabase(db, p);
|
|
748
|
+
const bron = uitDb ??
|
|
749
|
+
(p.voorbeeld === true && !heeftOrderInhoud(orderBron)
|
|
750
|
+
? voorbeeldPakbonOrder()
|
|
751
|
+
: orderBron);
|
|
752
|
+
const volledig = maakPakbonOrder((bron && typeof bron === "object" ? bron : {}));
|
|
753
|
+
// Beperk tot de aangewezen kar; zonder aanwijzing tot de eerste kar, zodat
|
|
754
|
+
// een afleverbrief nooit de inhoud van meerdere karren door elkaar toont.
|
|
755
|
+
const karren = arr(volledig.karren);
|
|
756
|
+
const gekozen = (cartId ? karren.find((k) => String(k?.cart_id ?? "").trim() === cartId) : null) ??
|
|
757
|
+
karren[0] ??
|
|
758
|
+
null;
|
|
759
|
+
const order = { ...volledig, karren: gekozen ? [gekozen] : [] };
|
|
760
|
+
return renderPakbonDocument(leesAfleverbriefConfig(layout.config), order, {
|
|
761
|
+
breedteMm,
|
|
762
|
+
hoogteMm,
|
|
763
|
+
margeMm: Number(layout.marge_boven ?? 10),
|
|
764
|
+
titel: job.documentnaam ?? String(layout.naam ?? "Afleverbrief"),
|
|
765
|
+
logos: await logosVoor(db, layout),
|
|
766
|
+
});
|
|
767
|
+
}
|
|
511
768
|
// 1) Pakbon-layouts. De secties staan onder `pakbon.secties`; oudere
|
|
512
769
|
// layouts hebben ze op het hoogste niveau. Beide vormen tellen mee.
|
|
513
770
|
const pakbonSecties = Array.isArray(cfg.secties)
|
|
@@ -517,21 +774,154 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
|
|
|
517
774
|
: null;
|
|
518
775
|
if ((pakbonSecties && pakbonSecties.length) || String(layout.documenttype) === "pakbon") {
|
|
519
776
|
const orderBron = p.order ?? p.data?.order ?? p.data ?? p;
|
|
520
|
-
const
|
|
777
|
+
const pakbonMethode = Array.isArray(pakbonSecties)
|
|
778
|
+
? String(pakbonSecties.find((s) => s?.type === "pickmethode")?.opties?.methode ?? "kar")
|
|
779
|
+
: "kar";
|
|
780
|
+
const engineVariant = job.layout_variant ?? p.layout_variant ?? p.layoutVariant ?? p.data?.layout_variant ?? null;
|
|
781
|
+
if (isEngineOpdracht(job) && pakbonMethode === "beide" && !["full_cart", "partial_order"].includes(String(engineVariant ?? ""))) {
|
|
782
|
+
throw new WachtOpLayoutFout("Wacht op layout_variant van de Logistics Engine: kies full_cart of partial_order.");
|
|
783
|
+
}
|
|
784
|
+
// Barcode en kargegevens van de Engine mogen niet verloren gaan wanneer
|
|
785
|
+
// de ordergegevens uit een subobject komen.
|
|
786
|
+
const herprintBron = await zoekHerprintBarcodeBron(db, job);
|
|
787
|
+
const engineBarcode = p.barcode ?? p.data?.cart?.barcode ?? p.cart?.barcode ?? herprintBron?.barcode ?? null;
|
|
788
|
+
const ruweEngineCart = p.data?.cart ?? p.cart ?? herprintBron?.cart ?? null;
|
|
789
|
+
const engineCart = ruweEngineCart && typeof ruweEngineCart === "object"
|
|
790
|
+
? {
|
|
791
|
+
...ruweEngineCart,
|
|
792
|
+
cart_id: ruweEngineCart.cart_id ?? ruweEngineCart.cartId,
|
|
793
|
+
cart_label: ruweEngineCart.cart_label ?? ruweEngineCart.cartLabel,
|
|
794
|
+
cart_index: ruweEngineCart.cart_index ?? ruweEngineCart.cartIndex,
|
|
795
|
+
cart_total: ruweEngineCart.cart_total ?? ruweEngineCart.cartTotal,
|
|
796
|
+
cart_items: ruweEngineCart.cart_items ?? ruweEngineCart.items,
|
|
797
|
+
barcode: ruweEngineCart.barcode ?? engineBarcode,
|
|
798
|
+
}
|
|
799
|
+
: null;
|
|
800
|
+
const engineCartId = String(engineCart?.cart_id ?? "").trim();
|
|
801
|
+
// Wijst de Engine één kar aan, dan halen we de producten rechtstreeks bij
|
|
802
|
+
// díé kar op. Zo staan altijd exact de artikelen op de pakbon die fysiek op
|
|
803
|
+
// de kar liggen — ook bij gedeelde karren met regels van meerdere orders.
|
|
804
|
+
const uitDb = heeftOrderInhoud(orderBron)
|
|
805
|
+
? null
|
|
806
|
+
: engineCartId
|
|
807
|
+
? ((await laadPakbonData(db, { scope: "cart", id: engineCartId })) ??
|
|
808
|
+
(await laadOrderVanuitDatabase(db, p)))
|
|
809
|
+
: await laadOrderVanuitDatabase(db, p);
|
|
521
810
|
// Een voorbeeldafdruk zonder ordergegevens krijgt een voorbeeldorder, zodat
|
|
522
811
|
// je ziet hoe het document er in de praktijk uitziet.
|
|
523
812
|
const bron = uitDb ??
|
|
524
813
|
(p.voorbeeld === true && !heeftOrderInhoud(orderBron)
|
|
525
814
|
? voorbeeldPakbonOrder()
|
|
526
815
|
: orderBron);
|
|
527
|
-
const
|
|
528
|
-
|
|
816
|
+
const bronRecord = (bron && typeof bron === "object" ? bron : {});
|
|
817
|
+
// Verwijder order-/payloadbarcodes: bij Engine-opdrachten zijn alleen de
|
|
818
|
+
// twee contractvelden hierboven toegestaan als bron voor de afleverbriefscan.
|
|
819
|
+
const { barcode: _bronBarcode, cart: _bronCart, ...bronZonderEngineBarcode } = bronRecord;
|
|
820
|
+
let order = maakPakbonOrder({
|
|
821
|
+
...bronZonderEngineBarcode,
|
|
822
|
+
...(engineBarcode ? { barcode: engineBarcode } : {}),
|
|
823
|
+
...(engineCart ? { cart: engineCart } : {}),
|
|
824
|
+
});
|
|
825
|
+
if (isEngineOpdracht(job)) {
|
|
826
|
+
let barcode = String(engineBarcode ?? "").trim();
|
|
827
|
+
if (!barcode) {
|
|
828
|
+
// De Engine heeft (nog) geen barcode meegestuurd. Heeft zij er
|
|
829
|
+
// inmiddels één geleverd op ons eerdere verzoek, dan gebruiken we die.
|
|
830
|
+
barcode = (await geleverdeBarcodeVoorJob(db, String(job.id ?? ""))) ?? "";
|
|
831
|
+
}
|
|
832
|
+
if (!barcode) {
|
|
833
|
+
// Zelf een barcode bedenken mag nooit. Wél laten we de Engine expliciet
|
|
834
|
+
// weten dat er voor deze pakbon een barcode ontbreekt, zodat zij er één
|
|
835
|
+
// kan aanleveren in plaats van dat de opdracht stil blijft hangen.
|
|
836
|
+
try {
|
|
837
|
+
await vraagPakbonBarcodeAan(db, bouwPakbonBarcodeVerzoek(order, {
|
|
838
|
+
printJobId: String(job.id ?? ""),
|
|
839
|
+
documentnaam: job.documentnaam,
|
|
840
|
+
cart: engineCart,
|
|
841
|
+
layoutVariant: pakbonLayoutVariant(engineVariant),
|
|
842
|
+
layoutId: String(layout.id ?? "") || null,
|
|
843
|
+
isHerprint: isHerprint(job),
|
|
844
|
+
}));
|
|
845
|
+
}
|
|
846
|
+
catch (e) {
|
|
847
|
+
await log(db, job.id, "barcode_verzoek_niet_opgeslagen", e instanceof Error ? e.message : "Barcodeverzoek kon niet worden vastgelegd");
|
|
848
|
+
}
|
|
849
|
+
throw new WachtOpLayoutFout("Barcode opgevraagd bij de Logistics Engine: deze kar heeft nog geen pakbonbarcode.");
|
|
850
|
+
}
|
|
851
|
+
// Restpakbon: zonder meegestuurde restregels weten we niet wát er nog
|
|
852
|
+
// verzameld moet worden. Dan liever geen pakbon dan een pakbon met de
|
|
853
|
+
// volledige karinhoud erop.
|
|
854
|
+
if (pakbonLayoutVariant(engineVariant) === "partial_order" && !deelkarRegels(order).length) {
|
|
855
|
+
throw new EngineContractFout("Restpakbon zonder restregels: stuur de resterende regels mee als cart_items.");
|
|
856
|
+
}
|
|
857
|
+
const cartId = engineCartId;
|
|
858
|
+
const karren = Array.isArray(order.karren) ? order.karren : [];
|
|
859
|
+
const passendeKarren = cartId
|
|
860
|
+
? karren.filter((kar) => String(kar.cart_id ?? "").trim() === cartId)
|
|
861
|
+
: karren;
|
|
862
|
+
// Eén Engine-opdracht staat voor precies één kar en dus één pakbon. Een
|
|
863
|
+
// opdracht met meerdere karren mag niet één barcode aan meerdere karren
|
|
864
|
+
// koppelen; de Engine moet daarvoor afzonderlijke jobs aanmaken.
|
|
865
|
+
if (passendeKarren.length > 1 || (!cartId && karren.length > 1)) {
|
|
866
|
+
throw new WachtOpLayoutFout("Wacht op afzonderlijke kar-opdrachten van de Logistics Engine: maak per kar één print_job met een eigen barcode.");
|
|
867
|
+
}
|
|
868
|
+
// Wijst de Engine een kar aan die niet in de gegevens voorkomt, dan mag
|
|
869
|
+
// er nóóit een andere kar worden afgedrukt: dan staan er producten op de
|
|
870
|
+
// pakbon die er niet op horen. De opdracht mislukt dan bewust.
|
|
871
|
+
if (cartId && passendeKarren.length === 0) {
|
|
872
|
+
throw new EngineContractFout("De door de Logistics Engine gekozen kar is niet gevonden in de pakbongegevens.");
|
|
873
|
+
}
|
|
874
|
+
const gekozenKar = passendeKarren[0] ?? (karren.length === 1 ? karren[0] : null);
|
|
875
|
+
// Een kar zonder productregels levert een lege pakbon op. Liever een
|
|
876
|
+
// duidelijke fout dan een pakbon zonder de producten die erop horen.
|
|
877
|
+
const heeftProducten = !!gekozenKar &&
|
|
878
|
+
arr(gekozenKar.lagen).some((laag) => arr(laag?.producten).length > 0);
|
|
879
|
+
if (gekozenKar && !heeftProducten) {
|
|
880
|
+
throw new EngineContractFout("De gekozen kar bevat geen productregels; de pakbon zou zonder producten worden afgedrukt.");
|
|
881
|
+
}
|
|
882
|
+
if (gekozenKar) {
|
|
883
|
+
order = {
|
|
884
|
+
...order,
|
|
885
|
+
cart: {
|
|
886
|
+
...order.cart,
|
|
887
|
+
...engineCart,
|
|
888
|
+
barcode,
|
|
889
|
+
cart_id: cartId || gekozenKar.cart_id,
|
|
890
|
+
cart_label: String(engineCart?.cart_label ?? gekozenKar.cart_label ?? "").trim() || undefined,
|
|
891
|
+
cart_index: Number(engineCart?.cart_index ?? gekozenKar.cart_index ?? 0) || undefined,
|
|
892
|
+
cart_total: Number(engineCart?.cart_total ?? gekozenKar.cart_total ?? 0) || undefined,
|
|
893
|
+
},
|
|
894
|
+
karren: [{ ...gekozenKar, barcode }],
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const html = renderPakbonDocument(layout.config, order, {
|
|
529
899
|
breedteMm,
|
|
530
900
|
hoogteMm,
|
|
531
901
|
margeMm: Number(layout.marge_boven ?? 12),
|
|
532
902
|
titel: job.documentnaam ?? "Document",
|
|
903
|
+
layoutVariant: engineVariant,
|
|
533
904
|
logos: await logosVoor(db, layout),
|
|
534
905
|
});
|
|
906
|
+
// De Engine bepaalt de barcode. De module registreert uitsluitend de
|
|
907
|
+
// gerenderde inhoud onder die bestaande waarde voor de scanner. Als de
|
|
908
|
+
// registratie tijdelijk niet kan, mag dat een afdruk nooit blokkeren.
|
|
909
|
+
const scanInhoud = bouwPakbonScanInhoud(order, {
|
|
910
|
+
barcode: order?.cart?.barcode ?? engineBarcode,
|
|
911
|
+
layoutVariant: pakbonLayoutVariant(engineVariant),
|
|
912
|
+
layoutId: String(layout.id ?? "") || null,
|
|
913
|
+
printJobId: String(job.id ?? "") || null,
|
|
914
|
+
isHerprint: isHerprint(job),
|
|
915
|
+
});
|
|
916
|
+
if (scanInhoud) {
|
|
917
|
+
try {
|
|
918
|
+
await bewaarPakbonScanInhoud(db, scanInhoud);
|
|
919
|
+
}
|
|
920
|
+
catch (e) {
|
|
921
|
+
await log(db, job.id, "barcode_inhoud_niet_opgeslagen", e instanceof Error ? e.message : "Registratie van barcode-inhoud mislukt");
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return html;
|
|
535
925
|
}
|
|
536
926
|
// 2) Productiebon-layouts (blokken onder `productiebon.blokken`).
|
|
537
927
|
if (Array.isArray(cfg.productiebon?.blokken) ||
|
|
@@ -575,15 +965,30 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
|
|
|
575
965
|
"Zet in de layoutinstellingen minstens één onderdeel aan.");
|
|
576
966
|
}
|
|
577
967
|
const logos = await logosVoor(db, layout);
|
|
578
|
-
const
|
|
968
|
+
const meegegeven = (p.data ?? p);
|
|
969
|
+
// De opdracht bevat vaak alleen verwijzingen (order-id, kar-id). Zonder de
|
|
970
|
+
// echte ordergegevens zou het document als een lijst lege veldnamen worden
|
|
971
|
+
// afgedrukt, terwijl het ontwerp op het scherm wél gevuld is. Daarom halen
|
|
972
|
+
// we dezelfde ordergegevens op als de pakbon gebruikt.
|
|
973
|
+
const uitDatabase = heeftOrderInhoud(meegegeven)
|
|
974
|
+
? null
|
|
975
|
+
: await laadOrderVanuitDatabase(db, p).catch(() => null);
|
|
976
|
+
const gegevens = { ...(uitDatabase ?? {}), ...(meegegeven ?? {}) };
|
|
579
977
|
const profielData = p.voorbeeld === true ? { ...voorbeeldContext(), ...(gegevens ?? {}) } : gegevens;
|
|
580
|
-
|
|
978
|
+
const profielHtml = renderProfielDocument(String(layout.documenttype), cfg, profielData, {
|
|
581
979
|
breedteMm,
|
|
582
980
|
hoogteMm,
|
|
583
981
|
margeMm: Number(layout.marge_boven ?? 8),
|
|
584
982
|
titel: job.documentnaam ?? String(layout.naam ?? "Document"),
|
|
585
983
|
logoUrl: logos[0]?.viewUrl ?? null,
|
|
586
984
|
});
|
|
985
|
+
// Fail closed: liever een duidelijke fout dan een vel papier met alleen
|
|
986
|
+
// veldnamen en streepjes erop.
|
|
987
|
+
if (!/<div class="blok"/.test(profielHtml)) {
|
|
988
|
+
throw new GeenDocumentFout(`Voor "${String(layout.naam ?? "")}" zijn geen gegevens gevonden om af te drukken. ` +
|
|
989
|
+
"Controleer of de opdracht een order of kar meestuurt.");
|
|
990
|
+
}
|
|
991
|
+
return profielHtml;
|
|
587
992
|
}
|
|
588
993
|
/**
|
|
589
994
|
* Controleert of het document echt iets afdrukt. Zonder deze controle kan een
|
|
@@ -623,6 +1028,22 @@ function plattePayload(payload) {
|
|
|
623
1028
|
loop(payload, "", 0);
|
|
624
1029
|
return uit;
|
|
625
1030
|
}
|
|
1031
|
+
async function wachtOpLayout(db, job, reden) {
|
|
1032
|
+
await db
|
|
1033
|
+
.from("print_jobs")
|
|
1034
|
+
.update({
|
|
1035
|
+
status: "wachtend",
|
|
1036
|
+
lokale_pdf_status: "wachtend",
|
|
1037
|
+
lokale_pdf_fout: reden.slice(0, 500),
|
|
1038
|
+
foutmelding: reden.slice(0, 500),
|
|
1039
|
+
volgende_poging: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
|
1040
|
+
opgepakt_op: null,
|
|
1041
|
+
voltooid_op: null,
|
|
1042
|
+
updated_at: new Date().toISOString(),
|
|
1043
|
+
})
|
|
1044
|
+
.eq("id", job.id);
|
|
1045
|
+
await log(db, job.id, "wacht_op_engine_layout", reden);
|
|
1046
|
+
}
|
|
626
1047
|
async function mislukt(db, job, reden, terugNaarWachtend) {
|
|
627
1048
|
const pogingen = Number(job.pogingen ?? 0);
|
|
628
1049
|
const opnieuw = terugNaarWachtend && pogingen < MAX_POGINGEN;
|
|
@@ -632,6 +1053,9 @@ async function mislukt(db, job, reden, terugNaarWachtend) {
|
|
|
632
1053
|
.from("print_jobs")
|
|
633
1054
|
.update({
|
|
634
1055
|
status: opnieuw ? "wachtend" : "mislukt",
|
|
1056
|
+
verwerkingsfase: opnieuw ? "retry_wacht" : "mislukt",
|
|
1057
|
+
fase_voltooid_op: opnieuw ? null : new Date().toISOString(),
|
|
1058
|
+
foutcode: opnieuw ? "tijdelijke_fout" : "blijvende_fout",
|
|
635
1059
|
foutmelding: reden.slice(0, 500),
|
|
636
1060
|
volgende_poging: opnieuw ? new Date(Date.now() + wachtMin * 60000).toISOString() : null,
|
|
637
1061
|
voltooid_op: opnieuw ? null : new Date().toISOString(),
|
|
@@ -640,7 +1064,67 @@ async function mislukt(db, job, reden, terugNaarWachtend) {
|
|
|
640
1064
|
.eq("id", job.id);
|
|
641
1065
|
await log(db, job.id, opnieuw ? "opnieuw_proberen" : "mislukt", reden);
|
|
642
1066
|
}
|
|
1067
|
+
function pdfBytes(base64) {
|
|
1068
|
+
const bron = Buffer.from(base64, "base64");
|
|
1069
|
+
const inhoud = new Uint8Array(new ArrayBuffer(bron.byteLength));
|
|
1070
|
+
inhoud.set(bron);
|
|
1071
|
+
return inhoud;
|
|
1072
|
+
}
|
|
1073
|
+
async function bewaarPdfArtefact(db, jobId, base64) {
|
|
1074
|
+
const inhoud = pdfBytes(base64);
|
|
1075
|
+
const pad = `${jobId}.pdf`;
|
|
1076
|
+
const digest = await crypto.subtle.digest("SHA-256", inhoud);
|
|
1077
|
+
const sha256 = Array.from(new Uint8Array(digest), (waarde) => waarde.toString(16).padStart(2, "0")).join("");
|
|
1078
|
+
const { error } = await db.storage
|
|
1079
|
+
.from("print-artifacts")
|
|
1080
|
+
.upload(pad, inhoud, { contentType: "application/pdf", upsert: true });
|
|
1081
|
+
if (error)
|
|
1082
|
+
throw new PdfRenderFout("mislukt", `Het PDF-bestand kon niet veilig worden opgeslagen: ${error.message}`);
|
|
1083
|
+
return { pad, sha256, bytes: inhoud.byteLength };
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Kleine faseklok: meet per opdracht hoe lang claimen, layout kiezen, printer
|
|
1087
|
+
* kiezen, renderen en versturen duren. Eén logregel per opdracht, zodat de
|
|
1088
|
+
* meting zelf het printpad niet vertraagt.
|
|
1089
|
+
*/
|
|
1090
|
+
class Faseklok {
|
|
1091
|
+
constructor(job) {
|
|
1092
|
+
this.start = Date.now();
|
|
1093
|
+
this.vorige = Date.now();
|
|
1094
|
+
this.fasen = [];
|
|
1095
|
+
const aangemaakt = job.created_at ? Date.parse(String(job.created_at)) : NaN;
|
|
1096
|
+
if (Number.isFinite(aangemaakt))
|
|
1097
|
+
this.fasen.push(`claimed=${this.start - aangemaakt}ms`);
|
|
1098
|
+
}
|
|
1099
|
+
markeer(fase) {
|
|
1100
|
+
const nu = Date.now();
|
|
1101
|
+
this.fasen.push(`${fase}=${nu - this.vorige}ms`);
|
|
1102
|
+
this.vorige = nu;
|
|
1103
|
+
}
|
|
1104
|
+
samenvatting() {
|
|
1105
|
+
return [...this.fasen, `totaal=${Date.now() - this.start}ms`].join(" ");
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Verwerkt items met een vaste maximale gelijktijdigheid. Geen onbeperkte
|
|
1110
|
+
* Promise.all: één trage of mislukte opdracht mag de rest niet meeslepen.
|
|
1111
|
+
*/
|
|
1112
|
+
async function metPool(items, max, fn) {
|
|
1113
|
+
const uit = new Array(items.length);
|
|
1114
|
+
let volgende = 0;
|
|
1115
|
+
const werkers = Array.from({ length: Math.max(1, Math.min(max, items.length)) }, async () => {
|
|
1116
|
+
for (;;) {
|
|
1117
|
+
const i = volgende++;
|
|
1118
|
+
if (i >= items.length)
|
|
1119
|
+
return;
|
|
1120
|
+
uit[i] = await fn(items[i]);
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
await Promise.all(werkers);
|
|
1124
|
+
return uit;
|
|
1125
|
+
}
|
|
643
1126
|
async function verwerkJob(db, job) {
|
|
1127
|
+
const klok = new Faseklok(job);
|
|
644
1128
|
try {
|
|
645
1129
|
// Dubbel printen voorkomen: deze opdracht staat al bij de printer.
|
|
646
1130
|
if (job.extern_job_id) {
|
|
@@ -650,26 +1134,29 @@ async function verwerkJob(db, job) {
|
|
|
650
1134
|
// 1) Bronregel: bepaalt of het meegestuurde document wordt gebruikt of dat
|
|
651
1135
|
// er altijd een eigen ontwerp wordt opgemaakt.
|
|
652
1136
|
let bron = await zoekBronregel(db, job);
|
|
653
|
-
// 1b)
|
|
654
|
-
//
|
|
655
|
-
if (await isFloridayLayoutKeuze(db, job)) {
|
|
1137
|
+
// 1b) Legacy Floriday-keuze; Engine-opdrachten volgen uitsluitend hun
|
|
1138
|
+
// eigen layout_id en worden nooit teruggezet naar een externe PDF.
|
|
1139
|
+
if (!isEngineOpdracht(job) && await isFloridayLayoutKeuze(db, job)) {
|
|
656
1140
|
bron = "extern_pdf";
|
|
657
1141
|
job.layout_id = null;
|
|
658
1142
|
}
|
|
659
|
-
if (bron === "eigen_layout") {
|
|
1143
|
+
if (bron === "eigen_layout" || isEngineOpdracht(job)) {
|
|
660
1144
|
job.pdf_base64 = null;
|
|
661
1145
|
job.pdf_url = null;
|
|
662
1146
|
}
|
|
663
|
-
// 2) Kant-en-klare PDF
|
|
664
|
-
|
|
665
|
-
|
|
1147
|
+
// 2) Kant-en-klare PDF gaat alleen buiten het Engine-contract ongewijzigd
|
|
1148
|
+
// door. De Engine bepaalt altijd de layout, ook als er een PDF aanwezig is.
|
|
1149
|
+
const klaar = isEngineOpdracht(job) ? null : await kantEnKlarePdf(db, job);
|
|
1150
|
+
if (!klaar && bron === "extern_pdf" && !isEngineOpdracht(job)) {
|
|
666
1151
|
await mislukt(db, job, "Floriday-document ontbreekt: voor dit document is ingesteld dat het " +
|
|
667
1152
|
"aangeleverde document wordt gebruikt, maar er is geen PDF meegestuurd.", true);
|
|
668
1153
|
return "mislukt";
|
|
669
1154
|
}
|
|
670
1155
|
const keuze = klaar ? { layout: null, ontwerpWint: false } : await zoekLayout(db, job);
|
|
1156
|
+
klok.markeer("layout_resolved");
|
|
671
1157
|
const layout = keuze.layout;
|
|
672
1158
|
const printerKeuze = await zoekPrinter(db, job, layout);
|
|
1159
|
+
klok.markeer("printer_resolved");
|
|
673
1160
|
const printer = printerKeuze.printer;
|
|
674
1161
|
// Virtuele PDF-printer: geen PrintNode nodig; de PDF wordt bewaard.
|
|
675
1162
|
const isPdfPrinter = String(printer?.afdrukmethode ?? "") === "pdf";
|
|
@@ -701,26 +1188,38 @@ async function verwerkJob(db, job) {
|
|
|
701
1188
|
landschap: liggend,
|
|
702
1189
|
});
|
|
703
1190
|
}
|
|
1191
|
+
klok.markeer("rendered");
|
|
704
1192
|
// Virtuele PDF-printer: niets gaat naar een fysieke printer; de opgemaakte
|
|
705
1193
|
// PDF wordt op de opdracht bewaard en is daarna te downloaden in de
|
|
706
1194
|
// wachtrij. De opdracht is meteen voltooid.
|
|
707
1195
|
if (isPdfPrinter) {
|
|
708
|
-
//
|
|
709
|
-
//
|
|
710
|
-
|
|
1196
|
+
// Bewaar de PDF één keer als binair artefact. De wachtrij en Realtime
|
|
1197
|
+
// vervoeren daarna alleen metadata; de werkplek downloadt precies dit bestand.
|
|
1198
|
+
const artefact = await bewaarPdfArtefact(db, String(job.id), pdfBase64);
|
|
1199
|
+
// De opdracht blijft bewust op 'bezig' staan: hij is pas voltooid als de
|
|
1200
|
+
// werkplek het bestand echt in de PDF-map heeft gezet. De lokale service
|
|
1201
|
+
// zet daarna 'opgeslagen' + 'voltooid', of 'mislukt' met een leesbare zin.
|
|
711
1202
|
const { error: statusFout } = await db
|
|
712
1203
|
.from("print_jobs")
|
|
713
1204
|
.update({
|
|
714
1205
|
printer_id: printer.id,
|
|
715
1206
|
gebruikte_layout_id: uitkomst.bron === "extern_pdf" ? null : (layout?.id ?? null),
|
|
716
1207
|
gebruikte_bron: uitkomst.bron,
|
|
717
|
-
status: "
|
|
1208
|
+
status: "bezig",
|
|
1209
|
+
verwerkingsfase: "pdf_gereed",
|
|
1210
|
+
fase_voltooid_op: new Date().toISOString(),
|
|
1211
|
+
foutcode: null,
|
|
718
1212
|
lokale_pdf_status: "gereed",
|
|
1213
|
+
lokale_pdf_print_plek_id: (await zoekPrintPlekId(db, job)) || null,
|
|
719
1214
|
lokale_pdf_fout: null,
|
|
720
1215
|
lokaal_opgeslagen_op: null,
|
|
721
1216
|
foutmelding: null,
|
|
1217
|
+
artefact_pad: artefact.pad,
|
|
1218
|
+
artefact_sha256: artefact.sha256,
|
|
1219
|
+
artefact_bytes: artefact.bytes,
|
|
1220
|
+
artefact_gereed_op: new Date().toISOString(),
|
|
1221
|
+
resultaat_pdf_base64: null,
|
|
722
1222
|
volgende_poging: null,
|
|
723
|
-
voltooid_op: new Date().toISOString(),
|
|
724
1223
|
updated_at: new Date().toISOString(),
|
|
725
1224
|
})
|
|
726
1225
|
.eq("id", job.id);
|
|
@@ -728,23 +1227,8 @@ async function verwerkJob(db, job) {
|
|
|
728
1227
|
await mislukt(db, job, `Opdracht afronden mislukt: ${statusFout.message}`, true);
|
|
729
1228
|
return "mislukt";
|
|
730
1229
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
.update({ resultaat_pdf_base64: pdfBase64 })
|
|
734
|
-
.eq("id", job.id);
|
|
735
|
-
if (pdfFout) {
|
|
736
|
-
await db
|
|
737
|
-
.from("print_jobs")
|
|
738
|
-
.update({
|
|
739
|
-
lokale_pdf_status: "fout",
|
|
740
|
-
lokale_pdf_fout: `PDF kon niet bewaard worden: ${pdfFout.message}`.slice(0, 500),
|
|
741
|
-
updated_at: new Date().toISOString(),
|
|
742
|
-
})
|
|
743
|
-
.eq("id", job.id);
|
|
744
|
-
await log(db, job.id, "mislukt", `PDF bewaren mislukt: ${pdfFout.message}`);
|
|
745
|
-
return "verstuurd";
|
|
746
|
-
}
|
|
747
|
-
await log(db, job.id, "pdf_gereed", `PDF gereed voor lokale opslag via ${printer.naam ?? "PDF-printer"}.`);
|
|
1230
|
+
klok.markeer("pdf_ready");
|
|
1231
|
+
await log(db, job.id, "pdf_gereed", `PDF gereed voor lokale opslag via ${printer.naam ?? "PDF-printer"}. ${klok.samenvatting()}`);
|
|
748
1232
|
return "verstuurd";
|
|
749
1233
|
}
|
|
750
1234
|
const formaten = await zorgVoorFormaten(db, printer);
|
|
@@ -768,15 +1252,23 @@ async function verwerkJob(db, job) {
|
|
|
768
1252
|
gebruikte_layout_id: uitkomst.bron === "extern_pdf" ? null : (layout?.id ?? null),
|
|
769
1253
|
gebruikte_bron: uitkomst.bron,
|
|
770
1254
|
extern_job_id: externJobId,
|
|
1255
|
+
verwerkingsfase: "afleveren",
|
|
1256
|
+
fase_voltooid_op: new Date().toISOString(),
|
|
1257
|
+
foutcode: null,
|
|
771
1258
|
foutmelding: null,
|
|
772
1259
|
volgende_poging: null,
|
|
773
1260
|
updated_at: new Date().toISOString(),
|
|
774
1261
|
})
|
|
775
1262
|
.eq("id", job.id);
|
|
776
|
-
|
|
1263
|
+
klok.markeer("submitted");
|
|
1264
|
+
await log(db, job.id, "verstuurd", `Naar printer ${printer.naam ?? ""}. ${papier.uitleg} ${klok.samenvatting()}`);
|
|
777
1265
|
return "verstuurd";
|
|
778
1266
|
}
|
|
779
1267
|
catch (e) {
|
|
1268
|
+
if (e instanceof WachtOpLayoutFout) {
|
|
1269
|
+
await wachtOpLayout(db, job, e.message);
|
|
1270
|
+
return "wachtend";
|
|
1271
|
+
}
|
|
780
1272
|
// Ontbreekt er een document of layout? Dan heeft opnieuw proberen geen zin.
|
|
781
1273
|
const blijvend = e instanceof GeenDocumentFout;
|
|
782
1274
|
const tijdelijk = !blijvend && (e instanceof PdfRenderFout || e?.name === "PrintToegangFout");
|
|
@@ -788,16 +1280,26 @@ async function verwerkJob(db, job) {
|
|
|
788
1280
|
const VERVAL_BEZIG_MIN = 30;
|
|
789
1281
|
/** Een claim zonder verzending mag hooguit zo lang blijven hangen. */
|
|
790
1282
|
const VERVAL_CLAIM_MIN = 10;
|
|
791
|
-
/**
|
|
1283
|
+
/** Hoeveel statuscontroles hooguit per ronde, en met welke gelijktijdigheid. */
|
|
1284
|
+
const STATUS_MAX_PER_RONDE = 12;
|
|
1285
|
+
const STATUS_PARALLEL = 3;
|
|
1286
|
+
/**
|
|
1287
|
+
* Werkt de status bij van opdrachten die al naar een printer zijn gestuurd.
|
|
1288
|
+
* Dit is historisch werk: het draait pas nadat nieuwe opdrachten zijn
|
|
1289
|
+
* afgehandeld, met een begrensde pool en een vaste limiet per ronde.
|
|
1290
|
+
*/
|
|
792
1291
|
async function synchroniseerStatussen(db) {
|
|
793
1292
|
const { data } = await db
|
|
794
1293
|
.from("print_jobs")
|
|
795
1294
|
.select("id, extern_job_id, pogingen, opgepakt_op")
|
|
796
1295
|
.eq("status", "bezig")
|
|
797
1296
|
.not("extern_job_id", "is", null)
|
|
798
|
-
.
|
|
799
|
-
|
|
800
|
-
|
|
1297
|
+
.order("created_at", { ascending: true })
|
|
1298
|
+
.limit(STATUS_MAX_PER_RONDE);
|
|
1299
|
+
const jobs = arr(data);
|
|
1300
|
+
if (!jobs.length)
|
|
1301
|
+
return 0;
|
|
1302
|
+
const uitkomsten = await metPool(jobs, STATUS_PARALLEL, async (job) => {
|
|
801
1303
|
try {
|
|
802
1304
|
const status = await opdrachtStatus(String(job.extern_job_id));
|
|
803
1305
|
if (status === "voltooid" || status === "mislukt") {
|
|
@@ -811,8 +1313,9 @@ async function synchroniseerStatussen(db) {
|
|
|
811
1313
|
})
|
|
812
1314
|
.eq("id", job.id);
|
|
813
1315
|
await log(db, job.id, status);
|
|
814
|
-
|
|
1316
|
+
return 1;
|
|
815
1317
|
}
|
|
1318
|
+
return 0;
|
|
816
1319
|
}
|
|
817
1320
|
catch (e) {
|
|
818
1321
|
// Een onbereikbare printdienst mag niet stil blijven: tel hem als poging
|
|
@@ -828,9 +1331,10 @@ async function synchroniseerStatussen(db) {
|
|
|
828
1331
|
.eq("id", job.id);
|
|
829
1332
|
await log(db, job.id, "status_onbekend", reden);
|
|
830
1333
|
console.warn(`[printwachtrij] status ophalen mislukt voor job ${job.id}: ${reden}`);
|
|
1334
|
+
return 0;
|
|
831
1335
|
}
|
|
832
|
-
}
|
|
833
|
-
return
|
|
1336
|
+
});
|
|
1337
|
+
return uitkomsten.reduce((n, v) => n + v, 0);
|
|
834
1338
|
}
|
|
835
1339
|
/**
|
|
836
1340
|
* Vangnet voor opdrachten die blijven hangen op 'bezig'. Twee gevallen:
|
|
@@ -872,7 +1376,8 @@ async function vervalControle(db) {
|
|
|
872
1376
|
.select("id, pogingen")
|
|
873
1377
|
.eq("status", "bezig")
|
|
874
1378
|
.is("extern_job_id", null)
|
|
875
|
-
.
|
|
1379
|
+
// PDF-opdrachten die op de werkplek wachten zijn niet vastgelopen.
|
|
1380
|
+
.or("lokale_pdf_status.is.null,lokale_pdf_status.not.in.(wachtend,bezig,gereed)")
|
|
876
1381
|
.or(`opgepakt_op.lt.${grensClaim},and(opgepakt_op.is.null,updated_at.lt.${grensClaim})`)
|
|
877
1382
|
.limit(50);
|
|
878
1383
|
for (const job of arr(vastgelopen)) {
|
|
@@ -896,46 +1401,116 @@ async function vervalControle(db) {
|
|
|
896
1401
|
}
|
|
897
1402
|
return aantal;
|
|
898
1403
|
}
|
|
1404
|
+
/** Hoeveel opdrachten tegelijk worden gerenderd binnen één ronde. */
|
|
1405
|
+
const JOB_PARALLEL = 3;
|
|
1406
|
+
/** Zo lang mag één ronde het slot houden; daarna is hij vanzelf herstelbaar. */
|
|
1407
|
+
const LEASE_SECONDEN = 120;
|
|
1408
|
+
const LEASE_NAAM = "print-queue-worker";
|
|
1409
|
+
/** Rustpauze voordat een volgende ronde wordt gewekt. */
|
|
1410
|
+
const VERVOLG_COOLDOWN_MS = 1500;
|
|
1411
|
+
/**
|
|
1412
|
+
* Wekt de wachtrij opnieuw, maar uitsluitend wanneer er aantoonbaar wachtend
|
|
1413
|
+
* werk overblijft. Het adres van de runner staat in de database, nooit in code.
|
|
1414
|
+
*/
|
|
1415
|
+
async function wekVervolgRonde(db) {
|
|
1416
|
+
const { count } = await db
|
|
1417
|
+
.from("print_jobs")
|
|
1418
|
+
.select("id", { count: "exact", head: true })
|
|
1419
|
+
.eq("status", "wachtend")
|
|
1420
|
+
.or(`volgende_poging.is.null,volgende_poging.lte.${new Date().toISOString()}`);
|
|
1421
|
+
if (!count)
|
|
1422
|
+
return;
|
|
1423
|
+
await new Promise((r) => setTimeout(r, VERVOLG_COOLDOWN_MS));
|
|
1424
|
+
const { error } = await db.rpc("wake_print_queue", { _min_interval_ms: 1000 });
|
|
1425
|
+
if (error)
|
|
1426
|
+
console.warn(`[printwachtrij] vervolgronde wekken mislukt: ${error.message}`);
|
|
1427
|
+
}
|
|
899
1428
|
/** Eén ronde: vastgelopen opdrachten opruimen, afdrukken en statussen bijwerken. */
|
|
900
1429
|
export async function verwerkWachtrij(db, max = 5) {
|
|
901
|
-
|
|
1430
|
+
const leeg = {
|
|
1431
|
+
opgepakt: 0,
|
|
1432
|
+
verstuurd: 0,
|
|
1433
|
+
mislukt: 0,
|
|
1434
|
+
wachtend: 0,
|
|
1435
|
+
bijgewerkt: 0,
|
|
1436
|
+
};
|
|
1437
|
+
// Single-flight: hooguit één ronde tegelijk. Het slot verloopt vanzelf, dus
|
|
1438
|
+
// een gecrashte ronde blokkeert de wachtrij nooit langer dan de leasetijd.
|
|
1439
|
+
const houder = crypto.randomUUID();
|
|
1440
|
+
const { data: slot, error: slotFout } = await db.rpc("claim_print_queue_lease", {
|
|
1441
|
+
_naam: LEASE_NAAM,
|
|
1442
|
+
_houder: houder,
|
|
1443
|
+
_seconden: LEASE_SECONDEN,
|
|
1444
|
+
});
|
|
1445
|
+
if (slotFout)
|
|
1446
|
+
throw new Error(`Printronde kon niet starten: ${slotFout.message}`);
|
|
1447
|
+
if (slot !== true)
|
|
1448
|
+
return { ...leeg, overgeslagen: "Er draait al een printronde." };
|
|
1449
|
+
let vervolgNodig = false;
|
|
902
1450
|
try {
|
|
903
|
-
|
|
1451
|
+
// Eerst het vangnet: dit draait ook als er niets te claimen valt.
|
|
1452
|
+
try {
|
|
1453
|
+
await vervalControle(db);
|
|
1454
|
+
}
|
|
1455
|
+
catch (e) {
|
|
1456
|
+
console.warn(`[printwachtrij] vervalcontrole mislukt: ${e instanceof Error ? e.message : "onbekend"}`);
|
|
1457
|
+
}
|
|
1458
|
+
// PDF-opdrachten hebben een eigen claimroute. Daardoor kunnen oudere
|
|
1459
|
+
// gepubliceerde moduleversies, die een PDF-printer nog als PrintNode-printer
|
|
1460
|
+
// behandelen, deze opdrachten niet meer vóór de actuele runner oppakken.
|
|
1461
|
+
const { data: pdfData, error: pdfError } = await db.rpc("claim_pdf_print_jobs", { _max: max });
|
|
1462
|
+
if (pdfError)
|
|
1463
|
+
throw new Error(pdfError.message);
|
|
1464
|
+
const pdfJobs = arr(pdfData);
|
|
1465
|
+
const resterend = Math.max(0, max - pdfJobs.length);
|
|
1466
|
+
let gewoneJobs = [];
|
|
1467
|
+
if (resterend > 0) {
|
|
1468
|
+
const { data, error } = await db.rpc("claim_cloud_print_jobs", { _max: resterend });
|
|
1469
|
+
if (error)
|
|
1470
|
+
throw new Error(error.message);
|
|
1471
|
+
gewoneJobs = arr(data);
|
|
1472
|
+
}
|
|
1473
|
+
// Begrensd parallel: meerdere pakbonnen van één order lopen gelijktijdig,
|
|
1474
|
+
// maar één mislukte opdracht annuleert de andere nooit.
|
|
1475
|
+
const jobs = [...pdfJobs, ...gewoneJobs];
|
|
1476
|
+
const uitkomsten = await metPool(jobs, JOB_PARALLEL, async (job) => {
|
|
1477
|
+
try {
|
|
1478
|
+
return await verwerkJob(db, job);
|
|
1479
|
+
}
|
|
1480
|
+
catch (e) {
|
|
1481
|
+
console.error(`[printwachtrij] opdracht ${job.id} viel onverwacht uit: ${e instanceof Error ? e.message : "onbekend"}`);
|
|
1482
|
+
return "mislukt";
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
const resultaat = {
|
|
1486
|
+
opgepakt: jobs.length,
|
|
1487
|
+
verstuurd: uitkomsten.filter((u) => u === "verstuurd").length,
|
|
1488
|
+
wachtend: uitkomsten.filter((u) => u === "wachtend").length,
|
|
1489
|
+
mislukt: uitkomsten.filter((u) => u === "mislukt").length,
|
|
1490
|
+
// Historische statuscontrole komt ná het nieuwe werk.
|
|
1491
|
+
bijgewerkt: await synchroniseerStatussen(db),
|
|
1492
|
+
};
|
|
1493
|
+
// Alleen wanneer de batchlimiet vol zat kan er nog werk klaarstaan.
|
|
1494
|
+
vervolgNodig = jobs.length >= max;
|
|
1495
|
+
return resultaat;
|
|
904
1496
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
// behandelen, deze opdrachten niet meer vóór de actuele runner oppakken.
|
|
911
|
-
const { data: pdfData, error: pdfError } = await db.rpc("claim_pdf_print_jobs", { _max: max });
|
|
912
|
-
if (pdfError)
|
|
913
|
-
throw new Error(pdfError.message);
|
|
914
|
-
const pdfJobs = arr(pdfData);
|
|
915
|
-
const resterend = Math.max(0, max - pdfJobs.length);
|
|
916
|
-
let gewoneJobs = [];
|
|
917
|
-
if (resterend > 0) {
|
|
918
|
-
const { data, error } = await db.rpc("claim_cloud_print_jobs", { _max: resterend });
|
|
1497
|
+
finally {
|
|
1498
|
+
const { error } = await db.rpc("release_print_queue_lease", {
|
|
1499
|
+
_naam: LEASE_NAAM,
|
|
1500
|
+
_houder: houder,
|
|
1501
|
+
});
|
|
919
1502
|
if (error)
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
else
|
|
931
|
-
mislukteJobs++;
|
|
1503
|
+
console.warn(`[printwachtrij] slot vrijgeven mislukt: ${error.message}`);
|
|
1504
|
+
if (vervolgNodig) {
|
|
1505
|
+
try {
|
|
1506
|
+
await wekVervolgRonde(db);
|
|
1507
|
+
}
|
|
1508
|
+
catch (fout) {
|
|
1509
|
+
const melding = fout instanceof Error ? fout.message : "onbekend";
|
|
1510
|
+
console.warn(`[printwachtrij] vervolgronde mislukt: ${melding}`);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
932
1513
|
}
|
|
933
|
-
return {
|
|
934
|
-
opgepakt: jobs.length,
|
|
935
|
-
verstuurd,
|
|
936
|
-
mislukt: mislukteJobs,
|
|
937
|
-
bijgewerkt: await synchroniseerStatussen(db),
|
|
938
|
-
};
|
|
939
1514
|
}
|
|
940
1515
|
/**
|
|
941
1516
|
* Dezelfde documentkeuze als de printwachtrij maakt, maar zonder printer en
|
|
@@ -944,16 +1519,18 @@ export async function verwerkWachtrij(db, max = 5) {
|
|
|
944
1519
|
*/
|
|
945
1520
|
export async function bepaalDocumentKeuze(db, job) {
|
|
946
1521
|
let bron = await zoekBronregel(db, job);
|
|
947
|
-
|
|
1522
|
+
// Engine-opdrachten houden hun eigen layout_id; nooit vervangen door een
|
|
1523
|
+
// Floriday-PDF-keuze.
|
|
1524
|
+
if (!isEngineOpdracht(job) && (await isFloridayLayoutKeuze(db, job))) {
|
|
948
1525
|
bron = "extern_pdf";
|
|
949
1526
|
job.layout_id = null;
|
|
950
1527
|
}
|
|
951
|
-
if (bron === "eigen_layout") {
|
|
1528
|
+
if (bron === "eigen_layout" || isEngineOpdracht(job)) {
|
|
952
1529
|
job.pdf_base64 = null;
|
|
953
1530
|
job.pdf_url = null;
|
|
954
1531
|
}
|
|
955
|
-
const klaar = await kantEnKlarePdf(db, job);
|
|
956
|
-
if (!klaar && bron === "extern_pdf") {
|
|
1532
|
+
const klaar = isEngineOpdracht(job) ? null : await kantEnKlarePdf(db, job);
|
|
1533
|
+
if (!klaar && bron === "extern_pdf" && !isEngineOpdracht(job)) {
|
|
957
1534
|
throw new GeenDocumentFout("Voor dit document is ingesteld dat het aangeleverde document wordt " +
|
|
958
1535
|
"gebruikt, maar er is geen PDF meegestuurd.");
|
|
959
1536
|
}
|