@flowselections/layout-maker 1.32.0 → 1.36.7

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 (73) hide show
  1. package/dist-lib/components/ComputersLijst.d.ts.map +1 -1
  2. package/dist-lib/components/ComputersLijst.js +5 -5
  3. package/dist-lib/components/DocumentenPage.d.ts.map +1 -1
  4. package/dist-lib/components/DocumentenPage.js +3 -2
  5. package/dist-lib/components/LayoutPrinterSelect.d.ts +8 -0
  6. package/dist-lib/components/LayoutPrinterSelect.d.ts.map +1 -0
  7. package/dist-lib/components/LayoutPrinterSelect.js +23 -0
  8. package/dist-lib/components/LayoutsPage.d.ts.map +1 -1
  9. package/dist-lib/components/LayoutsPage.js +16 -3
  10. package/dist-lib/components/PakbonBuilderPage.d.ts.map +1 -1
  11. package/dist-lib/components/PakbonBuilderPage.js +3 -1
  12. package/dist-lib/components/PakbonSectiesPanel.d.ts.map +1 -1
  13. package/dist-lib/components/PakbonSectiesPanel.js +6 -2
  14. package/dist-lib/components/PdfMapAutoSaver.d.ts +6 -0
  15. package/dist-lib/components/PdfMapAutoSaver.d.ts.map +1 -0
  16. package/dist-lib/components/PdfMapAutoSaver.js +12 -0
  17. package/dist-lib/components/PdfMapGlobaleBanner.d.ts +12 -0
  18. package/dist-lib/components/PdfMapGlobaleBanner.d.ts.map +1 -0
  19. package/dist-lib/components/PdfMapGlobaleBanner.js +30 -0
  20. package/dist-lib/components/PdfMapToestemmingBanner.d.ts +7 -0
  21. package/dist-lib/components/PdfMapToestemmingBanner.d.ts.map +1 -0
  22. package/dist-lib/components/PdfMapToestemmingBanner.js +139 -0
  23. package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
  24. package/dist-lib/components/PrintQueuePage.js +42 -3
  25. package/dist-lib/components/PrintenPage.d.ts.map +1 -1
  26. package/dist-lib/components/PrintenPage.js +3 -2
  27. package/dist-lib/components/PrinterSetupWizard.d.ts.map +1 -1
  28. package/dist-lib/components/PrinterSetupWizard.js +25 -2
  29. package/dist-lib/components/PrintplekDetailPage.d.ts.map +1 -1
  30. package/dist-lib/components/PrintplekDetailPage.js +30 -4
  31. package/dist-lib/components/PrintplekWizard.d.ts.map +1 -1
  32. package/dist-lib/components/PrintplekWizard.js +81 -3
  33. package/dist-lib/components/ProductiebonBuilderPage.d.ts.map +1 -1
  34. package/dist-lib/components/ProductiebonBuilderPage.js +3 -1
  35. package/dist-lib/index.d.ts +6 -0
  36. package/dist-lib/index.d.ts.map +1 -1
  37. package/dist-lib/index.js +11 -1
  38. package/dist-lib/integrations/supabase/auth-middleware.d.ts +242 -1
  39. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  40. package/dist-lib/integrations/supabase/client.server.d.ts +242 -1
  41. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  42. package/dist-lib/integrations/supabase/types.d.ts +244 -1
  43. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  44. package/dist-lib/lib/aiLayout.functions.d.ts +242 -1
  45. package/dist-lib/lib/aiLayout.functions.d.ts.map +1 -1
  46. package/dist-lib/lib/htmlPdf.server.d.ts.map +1 -1
  47. package/dist-lib/lib/htmlPdf.server.js +43 -6
  48. package/dist-lib/lib/orderData.server.d.ts.map +1 -1
  49. package/dist-lib/lib/orderData.server.js +117 -10
  50. package/dist-lib/lib/pdfJobSaver.d.ts +20 -0
  51. package/dist-lib/lib/pdfJobSaver.d.ts.map +1 -0
  52. package/dist-lib/lib/pdfJobSaver.js +173 -0
  53. package/dist-lib/lib/pdfMap.d.ts +48 -0
  54. package/dist-lib/lib/pdfMap.d.ts.map +1 -0
  55. package/dist-lib/lib/pdfMap.js +219 -0
  56. package/dist-lib/lib/pdfPrinter.d.ts +9 -0
  57. package/dist-lib/lib/pdfPrinter.d.ts.map +1 -0
  58. package/dist-lib/lib/pdfPrinter.js +34 -0
  59. package/dist-lib/lib/previewPdf.d.ts.map +1 -1
  60. package/dist-lib/lib/previewPdf.js +41 -9
  61. package/dist-lib/lib/printApi.d.ts.map +1 -1
  62. package/dist-lib/lib/printApi.js +9 -1
  63. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  64. package/dist-lib/lib/printQueue.server.js +99 -26
  65. package/dist-lib/lib/printService.functions.d.ts +968 -4
  66. package/dist-lib/lib/printService.functions.d.ts.map +1 -1
  67. package/dist-lib/lib/useDocumentData.d.ts +10 -0
  68. package/dist-lib/lib/useDocumentData.d.ts.map +1 -1
  69. package/dist-lib/lib/useDocumentData.js +19 -0
  70. package/dist-lib/lib/voorbeeldPrint.d.ts +4 -0
  71. package/dist-lib/lib/voorbeeldPrint.d.ts.map +1 -1
  72. package/dist-lib/lib/voorbeeldPrint.js +13 -2
  73. package/package.json +1 -1
@@ -0,0 +1,219 @@
1
+ // ============================================================================
2
+ // src/lib/pdfMap.ts — keuze van de map op de eigen computer waarin PDF's van
3
+ // de virtuele "Opslaan als PDF"-printer worden bewaard. De maphandle blijft
4
+ // lokaal in IndexedDB staan (per printer); de servers zien alleen de naam.
5
+ // ============================================================================
6
+ const DB_NAAM = "layout-maker-pdfmap";
7
+ const STORE = "handles";
8
+ const BEWAARD_SLEUTEL = "layout-maker-pdf-bewaard";
9
+ const GLOBALE_HANDLE_SLEUTEL = "__gekozen_pdf_map__";
10
+ const PDF_MAP_GEWIJZIGD_EVENT = "layout-maker:pdf-map-gewijzigd";
11
+ export function mapKiezenMogelijk() {
12
+ return typeof window !== "undefined" && "showDirectoryPicker" in window;
13
+ }
14
+ function openDb() {
15
+ return new Promise((resolve, reject) => {
16
+ const req = indexedDB.open(DB_NAAM, 1);
17
+ req.onupgradeneeded = () => {
18
+ if (!req.result.objectStoreNames.contains(STORE))
19
+ req.result.createObjectStore(STORE);
20
+ };
21
+ req.onsuccess = () => resolve(req.result);
22
+ req.onerror = () => reject(req.error);
23
+ });
24
+ }
25
+ async function bewaarHandle(printerId, handle) {
26
+ const db = await openDb();
27
+ await new Promise((resolve, reject) => {
28
+ const tx = db.transaction(STORE, "readwrite");
29
+ const store = tx.objectStore(STORE);
30
+ // De map hoort bij deze browser/computer, niet bij een database-printer-id.
31
+ // Bewaar daarom ook onder één vaste sleutel. Dit voorkomt dat een nieuwe of
32
+ // dubbele virtuele PDF-printer de reeds goedgekeurde map onzichtbaar maakt.
33
+ store.put(handle, GLOBALE_HANDLE_SLEUTEL);
34
+ store.put(handle, printerId);
35
+ tx.oncomplete = () => resolve();
36
+ tx.onerror = () => reject(tx.error);
37
+ });
38
+ window.dispatchEvent(new CustomEvent(PDF_MAP_GEWIJZIGD_EVENT, { detail: { printerId } }));
39
+ }
40
+ export function luisterNaarPdfMapWijziging(handler) {
41
+ if (typeof window === "undefined")
42
+ return () => undefined;
43
+ window.addEventListener(PDF_MAP_GEWIJZIGD_EVENT, handler);
44
+ return () => window.removeEventListener(PDF_MAP_GEWIJZIGD_EVENT, handler);
45
+ }
46
+ async function leesHandle(printerId) {
47
+ try {
48
+ const db = await openDb();
49
+ const lees = (sleutel) => new Promise((resolve, reject) => {
50
+ const tx = db.transaction(STORE, "readonly");
51
+ const req = tx.objectStore(STORE).get(sleutel);
52
+ req.onsuccess = () => resolve(req.result ?? null);
53
+ req.onerror = () => reject(req.error);
54
+ });
55
+ const globaal = await lees(GLOBALE_HANDLE_SLEUTEL);
56
+ if (globaal)
57
+ return globaal;
58
+ const direct = await lees(printerId);
59
+ if (direct) {
60
+ await bewaarHandle(printerId, direct);
61
+ return direct;
62
+ }
63
+ const gevonden = await new Promise((resolve, reject) => {
64
+ const tx = db.transaction(STORE, "readonly");
65
+ const store = tx.objectStore(STORE);
66
+ const cursor = store.openCursor();
67
+ cursor.onsuccess = () => {
68
+ const rij = cursor.result;
69
+ if (!rij) {
70
+ resolve(null);
71
+ return;
72
+ }
73
+ const handle = rij.value;
74
+ if (handle?.name && typeof handle.getFileHandle === "function") {
75
+ resolve(handle);
76
+ return;
77
+ }
78
+ rij.continue();
79
+ };
80
+ cursor.onerror = () => reject(cursor.error);
81
+ });
82
+ if (gevonden)
83
+ await bewaarHandle(printerId, gevonden);
84
+ return gevonden;
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ /**
91
+ * Browsers blokkeren showDirectoryPicker in een cross-origin iframe (zoals de
92
+ * Lovable-preview). De aanroeper kan dan aanbieden om de app in een eigen
93
+ * tabblad te openen, waar de verkenner wel werkt.
94
+ */
95
+ export function draaitInIframe() {
96
+ try {
97
+ return typeof window !== "undefined" && window.self !== window.top;
98
+ }
99
+ catch {
100
+ return true;
101
+ }
102
+ }
103
+ /** Opent de huidige pagina in een eigen tabblad (buiten het preview-iframe). */
104
+ export function openInEigenTabblad() {
105
+ window.open(window.location.href, "_blank", "noopener");
106
+ }
107
+ /**
108
+ * Opent de mappenverkenner van het besturingssysteem zodat de gebruiker een
109
+ * map kan selecteren. Start in Downloads zodat het pad snel te vinden is.
110
+ * Geeft terug wat er is gebeurd zodat de aanroeper gericht kan reageren.
111
+ */
112
+ export async function kiesPdfMap(printerId) {
113
+ if (!mapKiezenMogelijk())
114
+ return { status: "niet-ondersteund" };
115
+ try {
116
+ const handle = (await window.showDirectoryPicker({
117
+ mode: "readwrite",
118
+ startIn: "downloads",
119
+ }));
120
+ await bewaarHandle(printerId, handle);
121
+ // Een nieuwe mapkeuze is ook een expliciete opdracht om reeds voltooide
122
+ // PDF-jobs opnieuw naar deze map te schrijven.
123
+ try {
124
+ localStorage.removeItem(BEWAARD_SLEUTEL);
125
+ }
126
+ catch {
127
+ // Lokale opslag kan door browserbeleid uitgeschakeld zijn.
128
+ }
129
+ return { status: "gekozen", naam: handle.name };
130
+ }
131
+ catch (fout) {
132
+ // De gebruiker drukt op Annuleren in de verkenner → AbortError, geen fout.
133
+ if (fout instanceof DOMException && fout.name === "AbortError") {
134
+ return { status: "geannuleerd" };
135
+ }
136
+ // Cross-origin iframe → SecurityError ("Cross origin sub frames aren't
137
+ // allowed to show a file picker"). Aparte status zodat de UI een
138
+ // "open in eigen tabblad"-knop kan tonen.
139
+ if (fout instanceof DOMException &&
140
+ (fout.name === "SecurityError" || /cross.?origin/i.test(fout.message))) {
141
+ return { status: "iframe-geblokkeerd" };
142
+ }
143
+ return {
144
+ status: "fout",
145
+ melding: fout instanceof Error ? fout.message : "Onbekende fout",
146
+ };
147
+ }
148
+ }
149
+ /** Is er voor deze printer een map gekozen op deze computer? */
150
+ export async function heeftPdfMap(printerId) {
151
+ return (await leesHandle(printerId)) !== null;
152
+ }
153
+ /** Naam van de gekozen map (voor meldingen), of null. */
154
+ export async function pdfMapNaam(printerId) {
155
+ const h = await leesHandle(printerId);
156
+ return h?.name ?? null;
157
+ }
158
+ /**
159
+ * Probeert het bestand rechtstreeks in de gekozen map te zetten. Lukt dat niet
160
+ * (andere computer, geen toestemming, browser zonder ondersteuning), dan geeft
161
+ * de functie false terug en kan de aanroeper gewoon downloaden.
162
+ */
163
+ export async function bewaarInPdfMap(printerId, bestandsnaam, blob) {
164
+ const handle = await leesHandle(printerId);
165
+ if (!handle)
166
+ return false;
167
+ try {
168
+ const toestemming = (await handle.queryPermission?.({ mode: "readwrite" })) ?? "granted";
169
+ if (toestemming !== "granted") {
170
+ const nieuw = await handle.requestPermission?.({ mode: "readwrite" });
171
+ if (nieuw !== "granted")
172
+ return false;
173
+ }
174
+ const bestand = await handle.getFileHandle(bestandsnaam, { create: true });
175
+ const writable = await bestand.createWritable();
176
+ await writable.write(blob);
177
+ await writable.close();
178
+ return true;
179
+ }
180
+ catch (fout) {
181
+ console.error("[layout-maker] PDF kon niet lokaal worden opgeslagen", fout);
182
+ return false;
183
+ }
184
+ }
185
+ /** Controleert toestemming zonder een bestandsactie te starten. */
186
+ export async function pdfMapToestemming(printerId) {
187
+ const handle = await leesHandle(printerId);
188
+ if (!handle)
189
+ return "ontbreekt";
190
+ try {
191
+ return ((await handle.queryPermission?.({ mode: "readwrite" })) ?? "granted");
192
+ }
193
+ catch {
194
+ return "denied";
195
+ }
196
+ }
197
+ /**
198
+ * Vraagt vanuit een expliciete gebruikersklik opnieuw schrijftoestemming voor
199
+ * een eerder gekozen map. Achtergrondtaken mogen deze browserprompt niet zelf
200
+ * openen, omdat browsers daarvoor een actieve gebruikershandeling vereisen.
201
+ */
202
+ export async function herstelPdfMapToestemming(printerId) {
203
+ const handle = await leesHandle(printerId);
204
+ if (!handle)
205
+ return "ontbreekt";
206
+ try {
207
+ // requestPermission moet rechtstreeks in deze klik-callstack plaatsvinden;
208
+ // een voorafgaande await kan de tijdelijke gebruikersactivatie laten verlopen.
209
+ if (handle.requestPermission) {
210
+ const nieuw = await handle.requestPermission({ mode: "readwrite" });
211
+ return nieuw === "granted" ? "granted" : "denied";
212
+ }
213
+ const huidig = (await handle.queryPermission?.({ mode: "readwrite" })) ?? "granted";
214
+ return huidig === "granted" ? "granted" : "denied";
215
+ }
216
+ catch {
217
+ return "denied";
218
+ }
219
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Zorgt dat de virtuele PDF-printer bestaat. Geeft true terug als de printer
3
+ * er na afloop is (bestaand of net aangemaakt), en welke melding past.
4
+ */
5
+ export declare function zorgVoorPdfPrinter(): Promise<{
6
+ ok: boolean;
7
+ alAanwezig: boolean;
8
+ }>;
9
+ //# sourceMappingURL=pdfPrinter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdfPrinter.d.ts","sourceRoot":"","sources":["../../src/lib/pdfPrinter.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC;IAClD,EAAE,EAAE,OAAO,CAAC;IACZ,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC,CAsBD"}
@@ -0,0 +1,34 @@
1
+ // ============================================================================
2
+ // src/lib/pdfPrinter.ts — gedeelde helper voor de virtuele "Opslaan als
3
+ // PDF"-printer. Documenten die hierheen gaan worden niet geprint maar als
4
+ // downloadbare PDF in de printwachtrij bewaard. Er is maar één PDF-printer
5
+ // nodig; een tweede wordt niet aangemaakt.
6
+ // ============================================================================
7
+ import { supabase } from "./supabaseClient.js";
8
+ /**
9
+ * Zorgt dat de virtuele PDF-printer bestaat. Geeft true terug als de printer
10
+ * er na afloop is (bestaand of net aangemaakt), en welke melding past.
11
+ */
12
+ export async function zorgVoorPdfPrinter() {
13
+ const { data: bestaand } = await supabase
14
+ .from("printers")
15
+ .select("id")
16
+ .eq("afdrukmethode", "pdf")
17
+ .eq("actief", true)
18
+ .limit(1);
19
+ if ((bestaand ?? []).length > 0)
20
+ return { ok: true, alAanwezig: true };
21
+ const { error } = await supabase
22
+ .from("printers")
23
+ .insert({
24
+ naam: "Opslaan als PDF",
25
+ omschrijving: "Virtuele printer: documenten worden als PDF in de printwachtrij bewaard.",
26
+ actief: true,
27
+ online: true,
28
+ afdrukmethode: "pdf",
29
+ papierformaat: "A4",
30
+ });
31
+ if (error)
32
+ throw error;
33
+ return { ok: true, alAanwezig: false };
34
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"previewPdf.d.ts","sourceRoot":"","sources":["../../src/lib/previewPdf.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,KAAK,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAkExC;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoK1F"}
1
+ {"version":3,"file":"previewPdf.d.ts","sourceRoot":"","sources":["../../src/lib/previewPdf.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,KAAK,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAqFxC;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoM1F"}
@@ -24,24 +24,39 @@ function leesSleutel(request) {
24
24
  }
25
25
  /** Mag deze aanroeper voorbeelden opvragen? Zelfde regels als de queue-tick. */
26
26
  async function magAanroepen(request, db) {
27
+ const geprobeerd = [];
28
+ const ontbrekend = [];
27
29
  const meegegeven = leesSleutel(request);
28
- if (!meegegeven)
29
- return false;
30
+ if (!meegegeven) {
31
+ ontbrekend.push("toegangsbewijs in de apikey- of authorization-header");
32
+ return { ok: false, geprobeerd, ontbrekend };
33
+ }
34
+ geprobeerd.push("runner-sleutel");
30
35
  const { isRunnerSleutel } = await import("./runnerRegistry.server.js");
31
36
  if (await isRunnerSleutel(db, meegegeven))
32
- return true;
37
+ return { ok: true, geprobeerd, ontbrekend };
38
+ geprobeerd.push("publishable-key");
33
39
  const projectSleutel = process.env["SUPABASE_PUBLISHABLE_KEY"] ??
34
40
  process.env["VITE_SUPABASE_PUBLISHABLE_KEY"] ??
35
41
  "";
36
- if (projectSleutel && gelijk(projectSleutel, meegegeven))
37
- return true;
42
+ if (!projectSleutel)
43
+ ontbrekend.push("SUPABASE_PUBLISHABLE_KEY");
44
+ else if (gelijk(projectSleutel, meegegeven))
45
+ return { ok: true, geprobeerd, ontbrekend };
46
+ geprobeerd.push("gebruikerstoken");
38
47
  try {
39
48
  const { data } = await db.auth.getUser(meegegeven);
40
- return !!data?.user?.id;
49
+ if (data?.user?.id)
50
+ return { ok: true, geprobeerd, ontbrekend };
41
51
  }
42
52
  catch {
43
- return false;
53
+ /* val door naar weigering */
44
54
  }
55
+ return { ok: false, geprobeerd, ontbrekend };
56
+ }
57
+ /** Herkent een databaseweigering (de server mag niet) los van "jij mag niet". */
58
+ function isRechtenFout(melding, code) {
59
+ return code === "42501" || /permission denied|must be owner|not allowed/i.test(melding);
45
60
  }
46
61
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
47
62
  /** Legt een weigering vindbaar vast; faalt nooit hard. */
@@ -68,8 +83,15 @@ export async function behandelPreviewPdf(request, supabase) {
68
83
  if (!supabase) {
69
84
  return Response.json({ ok: false, code: "geen_database", fout: "De server kreeg geen databaseverbinding." }, { status: 500 });
70
85
  }
71
- if (!(await magAanroepen(request, supabase))) {
72
- return Response.json({ ok: false, code: "geen_toegang", fout: "Het voorbeeld weigerde het verzoek: geen geldig toegangsbewijs." }, { status: 401 });
86
+ const toegang = await magAanroepen(request, supabase);
87
+ if (!toegang.ok) {
88
+ return Response.json({
89
+ ok: false,
90
+ code: "geen_toegang",
91
+ fout: "Het voorbeeld weigerde het verzoek: geen geldig toegangsbewijs meegestuurd (dit is een aanroeper-probleem, geen databaserechten-probleem).",
92
+ geprobeerd: toegang.geprobeerd,
93
+ ontbrekend: toegang.ontbrekend,
94
+ }, { status: 401 });
73
95
  }
74
96
  let body = {};
75
97
  try {
@@ -91,6 +113,13 @@ export async function behandelPreviewPdf(request, supabase) {
91
113
  _venster_sec: VENSTER_SEC,
92
114
  });
93
115
  if (tempoFout) {
116
+ if (isRechtenFout(String(tempoFout.message ?? ""), tempoFout.code)) {
117
+ return Response.json({
118
+ ok: false,
119
+ code: "server_geen_rechten",
120
+ fout: "De server mag print_preview_toestaan niet uitvoeren. De route draait niet met de service-role sleutel of het EXECUTE-recht ontbreekt.",
121
+ }, { status: 503 });
122
+ }
94
123
  return weiger(supabase, station, queueItem, "render_mislukt", `Voorbeeld niet mogelijk: ${tempoFout.message}`, 500);
95
124
  }
96
125
  const toestemming = (tempo ?? {});
@@ -110,6 +139,9 @@ export async function behandelPreviewPdf(request, supabase) {
110
139
  });
111
140
  if (rpcFout) {
112
141
  const melding = String(rpcFout.message ?? "");
142
+ if (isRechtenFout(melding, rpcFout.code)) {
143
+ return weiger(supabase, station, queueItem, "server_geen_rechten", "De server mag pick_preview_job niet uitvoeren. De route draait niet met de service-role sleutel of het EXECUTE-recht ontbreekt.", 503);
144
+ }
113
145
  // Ontbreekt de functie zelf (Engine nog niet bijgewerkt)? Dat is geen
114
146
  // stationprobleem maar een installatieprobleem.
115
147
  if (/could not find the function|does not exist/i.test(melding)) {
@@ -1 +1 @@
1
- {"version":3,"file":"printApi.d.ts","sourceRoot":"","sources":["../../src/lib/printApi.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"printApi.d.ts","sourceRoot":"","sources":["../../src/lib/printApi.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import { supabase } from "./supabaseClient.js";
7
7
  import { printerVoorWerkplekDocument } from "./documentRoutering.js";
8
8
  import { printerVoorPrintplek, printplekNaam, printplekVoorComputer } from "./printplekken.js";
9
9
  import { bronVoorDocument } from "./documentBron.js";
10
+ import { bewaarPdfJobZodraKlaar } from "./pdfJobSaver.js";
10
11
  const arr = (v) => (Array.isArray(v) ? v : []);
11
12
  /** Zoekt de te gebruiken layout: expliciet, anders standaard, anders de nieuwste. */
12
13
  async function bepaalLayout(documenttype, layoutId) {
@@ -104,8 +105,15 @@ async function zetInWachtrij(rij) {
104
105
  const id = data?.id ?? null;
105
106
  // Direct laten verwerken; wacht niet op de cron-ronde. Mislukt dit, dan
106
107
  // blijft de opdracht gewoon wachtend staan voor een volgende poging.
107
- if (id)
108
+ if (id) {
108
109
  void startWachtrijVerwerking();
110
+ const printerId = typeof rij.printer_id === "string" ? rij.printer_id : null;
111
+ // Bij de virtuele PDF-printer blijft deze browser wachten op de gemaakte
112
+ // PDF en schrijft hem meteen naar de lokaal gekozen map. Voor gewone
113
+ // printers stopt de helper direct omdat daar geen maphandle voor bestaat.
114
+ if (printerId)
115
+ void bewaarPdfJobZodraKlaar(id, printerId);
116
+ }
109
117
  return id;
110
118
  }
111
119
  /** Fout wanneer een identieke aanvraag kort geleden al is aangemaakt. */
@@ -1 +1 @@
1
- {"version":3,"file":"printQueue.server.d.ts","sourceRoot":"","sources":["../../src/lib/printQueue.server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAsB5D,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/B,mFAAmF;AACnF,MAAM,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEpD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAW1E;AAUD,2EAA2E;AAC3E,qBAAa,gBAAiB,SAAQ,KAAK;IAChC,IAAI,SAAsB;CACpC;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IAAE,MAAM,EAAE,GAAG,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvE,yEAAyE;AACzE,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,cAAc,GAAG,aAAa,GAAG,aAAa,CAAC;AAE1F,6EAA6E;AAC7E,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAcnD,4EAA4E;AAC5E,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAI7D;AAkHD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA+BnF;AA+PD;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,WAAW,EAClB,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,MAAM,CAAC,CA8IjB;AAGD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAY1D;AAmRD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,oFAAoF;AACpF,wBAAsB,eAAe,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,SAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA4BtF;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,GAAG,GACP,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CA0B1D"}
1
+ {"version":3,"file":"printQueue.server.d.ts","sourceRoot":"","sources":["../../src/lib/printQueue.server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAsB5D,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/B,mFAAmF;AACnF,MAAM,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEpD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAW1E;AAUD,2EAA2E;AAC3E,qBAAa,gBAAiB,SAAQ,KAAK;IAChC,IAAI,SAAsB;CACpC;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IAAE,MAAM,EAAE,GAAG,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvE,yEAAyE;AACzE,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,cAAc,GAAG,aAAa,GAAG,aAAa,CAAC;AAE1F,6EAA6E;AAC7E,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAcnD,4EAA4E;AAC5E,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAI7D;AAkHD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA+BnF;AAwQD;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,WAAW,EAClB,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,MAAM,CAAC,CA8IjB;AAGD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAY1D;AAmVD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,oFAAoF;AACpF,wBAAsB,eAAe,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,SAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAwCtF;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,GAAG,GACP,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CA0B1D"}
@@ -324,7 +324,7 @@ async function zoekPrinter(db, job, layout) {
324
324
  return null;
325
325
  const { data } = await db
326
326
  .from("printers")
327
- .select("id, naam, extern_printer_id, actief, papierformaat, ondersteunde_formaten, ondersteunde_laden, formaten_bijgewerkt_op")
327
+ .select("id, naam, extern_printer_id, actief, papierformaat, ondersteunde_formaten, ondersteunde_laden, formaten_bijgewerkt_op, afdrukmethode")
328
328
  .eq("id", id)
329
329
  .maybeSingle();
330
330
  return data && data.actief !== false ? data : null;
@@ -414,24 +414,31 @@ async function zoekPrinter(db, job, layout) {
414
414
  reden: `Documenttype '${type}' heeft geen standaardprinter. Stel dit in bij Printen > Standaardregels.`,
415
415
  };
416
416
  }
417
+ /** Verzamelt elk logo-id ergens in de layoutconfiguratie (op elke diepte). */
418
+ function verzamelLogoIds(waarde, uit, diepte = 0) {
419
+ if (diepte > 8 || waarde === null || typeof waarde !== "object")
420
+ return;
421
+ if (Array.isArray(waarde)) {
422
+ for (const item of waarde)
423
+ verzamelLogoIds(item, uit, diepte + 1);
424
+ return;
425
+ }
426
+ for (const [sleutel, val] of Object.entries(waarde)) {
427
+ if ((sleutel === "logoId" || sleutel === "logo_id") && typeof val === "string" && val) {
428
+ uit.add(val);
429
+ }
430
+ else {
431
+ verzamelLogoIds(val, uit, diepte + 1);
432
+ }
433
+ }
434
+ }
417
435
  /** Haalt de logo's op die in de layout gebruikt worden, met tijdelijke URL. */
418
436
  async function logosVoor(db, layout) {
419
437
  const config = layout?.config ?? null;
420
438
  const ids = new Set();
421
- const documentLogo = config?.logo?.logoId;
422
- if (documentLogo)
423
- ids.add(String(documentLogo));
424
- // Ook logo's die als blok in de ontwerper staan meenemen.
425
- for (const sectie of Array.isArray(config?.sections) ? config.sections : []) {
426
- const id = sectie?.opties?.logoId ?? sectie?.logoId;
427
- if (id)
428
- ids.add(String(id));
429
- }
430
- for (const blok of Array.isArray(config?.blokken) ? config.blokken : []) {
431
- const id = blok?.opties?.logoId ?? blok?.logoId;
432
- if (id)
433
- ids.add(String(id));
434
- }
439
+ // Logo's staan op verschillende plekken in de configuratie: als documentlogo,
440
+ // als blok in de ontwerper, of binnen `pakbon.secties` / `productiebon.blokken`.
441
+ verzamelLogoIds(config, ids);
435
442
  if (!ids.size)
436
443
  return [];
437
444
  const { data } = await db
@@ -664,7 +671,9 @@ async function verwerkJob(db, job) {
664
671
  const layout = keuze.layout;
665
672
  const printerKeuze = await zoekPrinter(db, job, layout);
666
673
  const printer = printerKeuze.printer;
667
- if (!printer?.extern_printer_id) {
674
+ // Virtuele PDF-printer: geen PrintNode nodig; de PDF wordt bewaard.
675
+ const isPdfPrinter = String(printer?.afdrukmethode ?? "") === "pdf";
676
+ if (!printer || (!isPdfPrinter && !printer.extern_printer_id)) {
668
677
  await mislukt(db, job, printerKeuze.reden ||
669
678
  `Printer '${printer?.naam ?? "onbekend"}' is niet aan PrintNode gekoppeld.`, true);
670
679
  return "mislukt";
@@ -679,18 +688,69 @@ async function verwerkJob(db, job) {
679
688
  throw new GeenDocumentFout("Het document was leeg: er zijn geen gegevens gevonden om af te drukken. " +
680
689
  "Controleer of de layout blokken bevat en of de order gegevens heeft.");
681
690
  }
682
- const liggend = String(layout?.orientatie ?? "portrait") === "landscape";
691
+ // Een voorbeeldafdruk koppelt bewust geen layout (het meegestuurde
692
+ // ontwerp is leidend), maar geeft wél het papierformaat van de bewerkte
693
+ // layout mee. Zonder dit zou het voorbeeld altijd op A4 uitkomen.
694
+ const vf = (job.payload ?? {})["voorbeeld_formaat"];
695
+ const liggend = String(layout?.orientatie ?? vf?.["orientatie"] ?? "portrait") === "landscape";
683
696
  pdfBase64 = await htmlNaarPdfServer({
684
697
  html,
685
- breedteMm: Number(layout?.breedte_mm ?? 210),
686
- hoogteMm: Number(layout?.hoogte_mm ?? 297),
687
- margeMm: Number(layout?.marge_boven ?? 10),
698
+ breedteMm: Number(layout?.breedte_mm ?? vf?.["breedte_mm"] ?? 210),
699
+ hoogteMm: Number(layout?.hoogte_mm ?? vf?.["hoogte_mm"] ?? 297),
700
+ margeMm: Number(layout?.marge_boven ?? vf?.["marge_boven"] ?? 10),
688
701
  landschap: liggend,
689
702
  });
690
703
  }
704
+ // Virtuele PDF-printer: niets gaat naar een fysieke printer; de opgemaakte
705
+ // PDF wordt op de opdracht bewaard en is daarna te downloaden in de
706
+ // wachtrij. De opdracht is meteen voltooid.
707
+ if (isPdfPrinter) {
708
+ // In twee stappen: eerst de status (klein en altijd betrouwbaar), daarna
709
+ // pas de PDF zelf. Een grote PDF kan door de databaselimiet geweigerd
710
+ // worden; de opdracht mag daar nooit door blijven hangen op 'bezig'.
711
+ const { error: statusFout } = await db
712
+ .from("print_jobs")
713
+ .update({
714
+ printer_id: printer.id,
715
+ gebruikte_layout_id: uitkomst.bron === "extern_pdf" ? null : (layout?.id ?? null),
716
+ gebruikte_bron: uitkomst.bron,
717
+ status: "voltooid",
718
+ lokale_pdf_status: "gereed",
719
+ lokale_pdf_fout: null,
720
+ lokaal_opgeslagen_op: null,
721
+ foutmelding: null,
722
+ volgende_poging: null,
723
+ voltooid_op: new Date().toISOString(),
724
+ updated_at: new Date().toISOString(),
725
+ })
726
+ .eq("id", job.id);
727
+ if (statusFout) {
728
+ await mislukt(db, job, `Opdracht afronden mislukt: ${statusFout.message}`, true);
729
+ return "mislukt";
730
+ }
731
+ const { error: pdfFout } = await db
732
+ .from("print_jobs")
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"}.`);
748
+ return "verstuurd";
749
+ }
691
750
  const formaten = await zorgVoorFormaten(db, printer);
692
- const breedteMm = Number(layout?.breedte_mm ?? standaardMaat(printer).breedte);
693
- const hoogteMm = Number(layout?.hoogte_mm ?? standaardMaat(printer).hoogte);
751
+ const vfPapier = (job.payload ?? {})["voorbeeld_formaat"];
752
+ const breedteMm = Number(layout?.breedte_mm ?? vfPapier?.["breedte_mm"] ?? standaardMaat(printer).breedte);
753
+ const hoogteMm = Number(layout?.hoogte_mm ?? vfPapier?.["hoogte_mm"] ?? standaardMaat(printer).hoogte);
694
754
  const papier = kiesPapier(formaten, breedteMm, hoogteMm);
695
755
  const externJobId = await stuurPdfNaarPrinter({
696
756
  externPrinterId: String(printer.extern_printer_id),
@@ -812,6 +872,7 @@ async function vervalControle(db) {
812
872
  .select("id, pogingen")
813
873
  .eq("status", "bezig")
814
874
  .is("extern_job_id", null)
875
+ .neq("lokale_pdf_status", "gereed")
815
876
  .or(`opgepakt_op.lt.${grensClaim},and(opgepakt_op.is.null,updated_at.lt.${grensClaim})`)
816
877
  .limit(50);
817
878
  for (const job of arr(vastgelopen)) {
@@ -844,10 +905,22 @@ export async function verwerkWachtrij(db, max = 5) {
844
905
  catch (e) {
845
906
  console.warn(`[printwachtrij] vervalcontrole mislukt: ${e instanceof Error ? e.message : "onbekend"}`);
846
907
  }
847
- const { data, error } = await db.rpc("claim_cloud_print_jobs", { _max: max });
848
- if (error)
849
- throw new Error(error.message);
850
- const jobs = arr(data);
908
+ // PDF-opdrachten hebben een eigen claimroute. Daardoor kunnen oudere
909
+ // gepubliceerde moduleversies, die een PDF-printer nog als PrintNode-printer
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 });
919
+ if (error)
920
+ throw new Error(error.message);
921
+ gewoneJobs = arr(data);
922
+ }
923
+ const jobs = [...pdfJobs, ...gewoneJobs];
851
924
  let verstuurd = 0;
852
925
  let mislukteJobs = 0;
853
926
  for (const job of jobs) {