@flowselections/mailbox-orders 1.0.10 → 1.0.12

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 (44) hide show
  1. package/dist-lib/components/MailboxProposalsPage.d.ts +16 -0
  2. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
  3. package/dist-lib/components/MailboxProposalsPage.js +101 -10
  4. package/dist-lib/integrations/supabase/client.d.ts +162 -4
  5. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  6. package/dist-lib/integrations/supabase/client.server.d.ts +162 -4
  7. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  8. package/dist-lib/integrations/supabase/types.d.ts +163 -5
  9. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  10. package/dist-lib/integrations/supabase/types.js +12 -1
  11. package/dist-lib/lib/ai-config.server.d.ts +14 -0
  12. package/dist-lib/lib/ai-config.server.d.ts.map +1 -1
  13. package/dist-lib/lib/ai-config.server.js +32 -19
  14. package/dist-lib/lib/mailbox-ai.server.d.ts +7 -0
  15. package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -1
  16. package/dist-lib/lib/mailbox-ai.server.js +83 -38
  17. package/dist-lib/lib/mailbox-ingest.server.d.ts +13 -0
  18. package/dist-lib/lib/mailbox-ingest.server.d.ts.map +1 -1
  19. package/dist-lib/lib/mailbox-ingest.server.js +77 -12
  20. package/dist-lib/lib/mailbox.functions.d.ts +23 -5
  21. package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
  22. package/dist-lib/lib/mailbox.functions.js +97 -4
  23. package/package.json +3 -3
  24. package/dist-lib/components/CartAssignmentWizard.d.ts +0 -6
  25. package/dist-lib/components/CartAssignmentWizard.d.ts.map +0 -1
  26. package/dist-lib/components/CartAssignmentWizard.js +0 -199
  27. package/dist-lib/components/MailboxInboxPage.d.ts +0 -2
  28. package/dist-lib/components/MailboxInboxPage.d.ts.map +0 -1
  29. package/dist-lib/components/MailboxInboxPage.js +0 -120
  30. package/dist-lib/components/settings/ImapAccountsCard.d.ts +0 -2
  31. package/dist-lib/components/settings/ImapAccountsCard.d.ts.map +0 -1
  32. package/dist-lib/components/settings/ImapAccountsCard.js +0 -136
  33. package/dist-lib/components/settings/ImapProfilesCard.d.ts +0 -2
  34. package/dist-lib/components/settings/ImapProfilesCard.d.ts.map +0 -1
  35. package/dist-lib/components/settings/ImapProfilesCard.js +0 -101
  36. package/dist-lib/components/settings/MailboxSettingsCard.d.ts +0 -2
  37. package/dist-lib/components/settings/MailboxSettingsCard.d.ts.map +0 -1
  38. package/dist-lib/components/settings/MailboxSettingsCard.js +0 -85
  39. package/dist-lib/lib/imap.functions.d.ts +0 -32852
  40. package/dist-lib/lib/imap.functions.d.ts.map +0 -1
  41. package/dist-lib/lib/imap.functions.js +0 -235
  42. package/dist-lib/lib/mailbox-auto.server.d.ts +0 -9
  43. package/dist-lib/lib/mailbox-auto.server.d.ts.map +0 -1
  44. package/dist-lib/lib/mailbox-auto.server.js +0 -198
@@ -8,7 +8,18 @@ export const Constants = {
8
8
  "klaar",
9
9
  "mislukt",
10
10
  ],
11
- app_role: ["admin", "planner", "monteur", "administratie", "ai_admin"],
11
+ app_role: [
12
+ "admin",
13
+ "planner",
14
+ "monteur",
15
+ "administratie",
16
+ "ai_admin",
17
+ "manager",
18
+ "warehouse",
19
+ "sales",
20
+ "developer",
21
+ "receptie",
22
+ ],
12
23
  doc_layout_type: [
13
24
  "pakbon",
14
25
  "factuur",
@@ -24,6 +24,18 @@ export type ModuleAiResolution = {
24
24
  ok: false;
25
25
  reason: string;
26
26
  };
27
+ export type ModuleAiListResolution = {
28
+ ok: true;
29
+ configs: ModuleAiConfig[];
30
+ } | {
31
+ ok: false;
32
+ reason: string;
33
+ };
34
+ /**
35
+ * Alle bruikbare AI-bindings voor een module, standaardbinding eerst. Zo kan de
36
+ * aanroeper bij een storing automatisch uitwijken naar het volgende model.
37
+ */
38
+ export declare function resolveModuleAiAll(admin: AdminClient, moduleKey: string): Promise<ModuleAiListResolution>;
27
39
  /**
28
40
  * Zoekt de actieve AI-binding voor een module. Retourneert nooit een throw:
29
41
  * de aanroeper beslist zelf wat er gebeurt als AI niet beschikbaar is.
@@ -39,6 +51,8 @@ export interface AiCallLogInput {
39
51
  durationMs?: number | null;
40
52
  errorCode?: string | null;
41
53
  errorMessage?: string | null;
54
+ /** Pogingnummer binnen dezelfde analyse (1 = eerste poging). */
55
+ attempt?: number | null;
42
56
  }
43
57
  /** Schrijft één regel in ai_call_log. Faalt nooit hard. */
44
58
  export declare function logAiCall(admin: AdminClient, input: AiCallLogInput): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"ai-config.server.d.ts","sourceRoot":"","sources":["../../src/lib/ai-config.server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAE9D,KAAK,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,GACpC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;;GAGG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kBAAkB,CAAC,CAoE7B;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,2DAA2D;AAC3D,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBxF"}
1
+ {"version":3,"file":"ai-config.server.d.ts","sourceRoot":"","sources":["../../src/lib/ai-config.server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAE9D,KAAK,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,GACpC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,MAAM,MAAM,sBAAsB,GAC9B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,EAAE,CAAA;CAAE,GACvC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,sBAAsB,CAAC,CAqEjC;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kBAAkB,CAAC,CAI7B;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,2DAA2D;AAC3D,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBxF"}
@@ -5,10 +5,10 @@
5
5
  // De API-sleutel verlaat NOOIT de server. Dit bestand mag alleen vanuit
6
6
  // server-function handlers of andere *.server.ts helpers geladen worden.
7
7
  /**
8
- * Zoekt de actieve AI-binding voor een module. Retourneert nooit een throw:
9
- * de aanroeper beslist zelf wat er gebeurt als AI niet beschikbaar is.
8
+ * Alle bruikbare AI-bindings voor een module, standaardbinding eerst. Zo kan de
9
+ * aanroeper bij een storing automatisch uitwijken naar het volgende model.
10
10
  */
11
- export async function resolveModuleAi(admin, moduleKey) {
11
+ export async function resolveModuleAiAll(admin, moduleKey) {
12
12
  const { data: mod, error: modErr } = await admin
13
13
  .from("ai_modules")
14
14
  .select("module_key, ai_enabled")
@@ -31,6 +31,7 @@ export async function resolveModuleAi(admin, moduleKey) {
31
31
  if (list.length === 0) {
32
32
  return { ok: false, reason: `Geen AI-koppeling ingesteld voor '${moduleKey}'.` };
33
33
  }
34
+ const configs = [];
34
35
  for (const b of list) {
35
36
  const model = b.ai_models;
36
37
  const cred = b.ai_credentials;
@@ -58,22 +59,32 @@ export async function resolveModuleAi(admin, moduleKey) {
58
59
  String(apiKey.length),
59
60
  apiKey.slice(-4),
60
61
  ].join("|");
61
- return {
62
- ok: true,
63
- config: {
64
- moduleKey,
65
- providerSlug: String(provider.slug),
66
- providerName: String(provider.name ?? provider.slug),
67
- baseUrl,
68
- modelSlug: String(model.slug),
69
- modelName: String(model.display_name ?? model.slug),
70
- apiKey,
71
- credentialLabel: cred.label ?? null,
72
- fingerprint,
73
- },
74
- };
62
+ configs.push({
63
+ moduleKey,
64
+ providerSlug: String(provider.slug),
65
+ providerName: String(provider.name ?? provider.slug),
66
+ baseUrl,
67
+ modelSlug: String(model.slug),
68
+ modelName: String(model.display_name ?? model.slug),
69
+ apiKey,
70
+ credentialLabel: cred.label ?? null,
71
+ fingerprint,
72
+ });
73
+ }
74
+ if (configs.length === 0) {
75
+ return { ok: false, reason: "Geen actieve combinatie van model + sleutel gevonden." };
75
76
  }
76
- return { ok: false, reason: "Geen actieve combinatie van model + sleutel gevonden." };
77
+ return { ok: true, configs };
78
+ }
79
+ /**
80
+ * Zoekt de actieve AI-binding voor een module. Retourneert nooit een throw:
81
+ * de aanroeper beslist zelf wat er gebeurt als AI niet beschikbaar is.
82
+ */
83
+ export async function resolveModuleAi(admin, moduleKey) {
84
+ const res = await resolveModuleAiAll(admin, moduleKey);
85
+ if (!res.ok)
86
+ return res;
87
+ return { ok: true, config: res.configs[0] };
77
88
  }
78
89
  /** Schrijft één regel in ai_call_log. Faalt nooit hard. */
79
90
  export async function logAiCall(admin, input) {
@@ -90,7 +101,9 @@ export async function logAiCall(admin, input) {
90
101
  total_tokens: inTok !== null || outTok !== null ? (inTok ?? 0) + (outTok ?? 0) : undefined,
91
102
  duration_ms: input.durationMs ?? null,
92
103
  error_code: input.errorCode ?? null,
93
- error_message: input.errorMessage ? String(input.errorMessage).slice(0, 500) : null,
104
+ error_message: input.errorMessage
105
+ ? `${input.attempt ? `[poging ${input.attempt}] ` : ""}${String(input.errorMessage)}`.slice(0, 500)
106
+ : null,
94
107
  });
95
108
  }
96
109
  catch {
@@ -55,6 +55,13 @@ export declare function buildDateContext(now?: Date): {
55
55
  aanstaande: Record<string, string>;
56
56
  volgende_week: Record<string, string>;
57
57
  };
58
+ /** Tijdelijke storing → opnieuw proberen loont. */
59
+ export declare function isTransientAiError(message: string): boolean;
60
+ export declare class AiUnavailableError extends Error {
61
+ readonly transient: boolean;
62
+ readonly attempts: number;
63
+ constructor(message: string, transient: boolean, attempts: number);
64
+ }
58
65
  export declare function parseOrderEmail(admin: AdminClient, args: {
59
66
  bodyText: string;
60
67
  subject: string;
@@ -1 +1 @@
1
- {"version":3,"file":"mailbox-ai.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ai.server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAE9D,OAAO,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,EAAE,6BAA6B,EAAE,CAAC;AAEzC,KAAK,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;AAE5C,eAAO,MAAM,qBAAqB,yBAAyB,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACzC,KAAK,EAAE,KAAK,CAAC;QACX,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;IACH,UAAU,EAAE;QACV,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,CAAC;IACF,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,2FAA2F;AAC3F,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGrF;AAgJD,yFAAyF;AACzF,wBAAgB,gBAAgB,CAAC,GAAG,OAAa;;;;;;;EA4BhD;AA0FD,wBAAsB,eAAe,CACnC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,iBAAiB,EAAE,CAAC;IACpC,cAAc,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,iBAAiB,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GACA,OAAO,CAAC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAqDrD"}
1
+ {"version":3,"file":"mailbox-ai.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ai.server.ts"],"names":[],"mappings":"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"}
@@ -235,51 +235,96 @@ function normalize(raw) {
235
235
  // ---------------------------------------------------------------------------
236
236
  // Publieke API
237
237
  // ---------------------------------------------------------------------------
238
+ /** HTTP-status uit een providerfout ("Gemini 503: …"). */
239
+ function errorStatus(message) {
240
+ const m = /\b(\d{3})\b/.exec(message);
241
+ const code = m ? Number(m[1]) : NaN;
242
+ return code >= 400 && code <= 599 ? code : null;
243
+ }
244
+ /** Tijdelijke storing → opnieuw proberen loont. */
245
+ export function isTransientAiError(message) {
246
+ const s = errorStatus(message);
247
+ if (s !== null)
248
+ return s === 408 || s === 409 || s === 425 || s === 429 || s >= 500;
249
+ return /timeout|timed out|fetch failed|network|ECONN|socket|overload|unavailable|temporar/i.test(message);
250
+ }
251
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
252
+ export class AiUnavailableError extends Error {
253
+ constructor(message, transient, attempts) {
254
+ super(message);
255
+ this.name = "AiUnavailableError";
256
+ this.transient = transient;
257
+ this.attempts = attempts;
258
+ }
259
+ }
238
260
  export async function parseOrderEmail(admin, args) {
239
- const resolution = await resolveModuleAi(admin, MAILBOX_AI_MODULE_KEY);
261
+ const { resolveModuleAiAll } = await import("./ai-config.server");
262
+ const resolution = await resolveModuleAiAll(admin, MAILBOX_AI_MODULE_KEY);
240
263
  if (!resolution.ok)
241
- throw new Error(resolution.reason);
242
- const cfg = resolution.config;
264
+ throw new AiUnavailableError(resolution.reason, false, 0);
265
+ const configs = resolution.configs;
266
+ const primary = configs[0];
243
267
  const { system, user } = buildPrompt(args.bodyText, args.subject, args.templateFields, args.productCatalog, {
244
268
  deliveryLocations: args.deliveryLocations,
245
269
  plantTypes: args.plantTypes,
246
270
  requiredFields: args.requiredFields,
247
271
  systemPrompt: args.systemPrompt,
248
272
  });
249
- const started = Date.now();
250
- try {
251
- const completion = await callProvider(cfg, system, user);
252
- const parsed = normalize(extractJson(completion.text));
253
- const durationMs = Date.now() - started;
254
- await logAiCall(admin, {
255
- moduleKey: cfg.moduleKey,
256
- providerSlug: cfg.providerSlug,
257
- modelSlug: cfg.modelSlug,
258
- status: "success",
259
- inputTokens: completion.inputTokens,
260
- outputTokens: completion.outputTokens,
261
- durationMs,
262
- });
263
- return {
264
- parsed,
265
- meta: {
266
- providerSlug: cfg.providerSlug,
267
- modelSlug: cfg.modelSlug,
268
- modelName: cfg.modelName,
269
- durationMs,
270
- fingerprint: cfg.fingerprint,
271
- },
272
- };
273
- }
274
- catch (e) {
275
- await logAiCall(admin, {
276
- moduleKey: cfg.moduleKey,
277
- providerSlug: cfg.providerSlug,
278
- modelSlug: cfg.modelSlug,
279
- status: "error",
280
- durationMs: Date.now() - started,
281
- errorMessage: String(e?.message ?? e),
282
- });
283
- throw e;
273
+ // Per model maximaal 3 pogingen met exponentiële backoff; daarna uitwijken
274
+ // naar het volgende model in de keten. De vingerafdruk blijft die van het
275
+ // standaardmodel, zodat een fallback geen eindeloze her-analyse veroorzaakt.
276
+ const MAX_ATTEMPTS_PER_MODEL = 3;
277
+ let attemptNo = 0;
278
+ let lastMessage = "AI niet beschikbaar.";
279
+ let lastTransient = false;
280
+ for (const cfg of configs) {
281
+ for (let i = 0; i < MAX_ATTEMPTS_PER_MODEL; i++) {
282
+ attemptNo++;
283
+ const started = Date.now();
284
+ try {
285
+ const completion = await callProvider(cfg, system, user);
286
+ const parsed = normalize(extractJson(completion.text));
287
+ const durationMs = Date.now() - started;
288
+ await logAiCall(admin, {
289
+ moduleKey: cfg.moduleKey,
290
+ providerSlug: cfg.providerSlug,
291
+ modelSlug: cfg.modelSlug,
292
+ status: "success",
293
+ inputTokens: completion.inputTokens,
294
+ outputTokens: completion.outputTokens,
295
+ durationMs,
296
+ attempt: attemptNo,
297
+ });
298
+ return {
299
+ parsed,
300
+ meta: {
301
+ providerSlug: cfg.providerSlug,
302
+ modelSlug: cfg.modelSlug,
303
+ modelName: cfg.modelName,
304
+ durationMs,
305
+ fingerprint: primary.fingerprint,
306
+ },
307
+ };
308
+ }
309
+ catch (e) {
310
+ lastMessage = String(e?.message ?? e);
311
+ lastTransient = isTransientAiError(lastMessage);
312
+ await logAiCall(admin, {
313
+ moduleKey: cfg.moduleKey,
314
+ providerSlug: cfg.providerSlug,
315
+ modelSlug: cfg.modelSlug,
316
+ status: "error",
317
+ durationMs: Date.now() - started,
318
+ errorMessage: lastMessage,
319
+ errorCode: errorStatus(lastMessage) ? String(errorStatus(lastMessage)) : null,
320
+ attempt: attemptNo,
321
+ });
322
+ if (!lastTransient)
323
+ break; // permanente fout: direct volgend model
324
+ if (i < MAX_ATTEMPTS_PER_MODEL - 1)
325
+ await sleep(600 * 2 ** i + Math.floor(Math.random() * 250));
326
+ }
327
+ }
284
328
  }
329
+ throw new AiUnavailableError(lastMessage, lastTransient, attemptNo);
285
330
  }
@@ -19,6 +19,8 @@ export declare function computeValidation(parsed: import("./mailbox-ai.server").
19
19
  notes: string | null;
20
20
  ok: boolean;
21
21
  };
22
+ /** Maximaal aantal achtergrondpogingen voordat we het voorstel handmatig laten. */
23
+ export declare const AI_MAX_BACKGROUND_RETRIES = 6;
22
24
  /**
23
25
  * Vult het afleveradres aan met de hoofdafleverlocatie (is_primary) van de klant
24
26
  * wanneer de mail/AI géén geldige locatie heeft herkend. Herkent de AI wél een
@@ -60,6 +62,17 @@ export declare function reparseChangedAgentProposals(admin: AdminClient, limit?:
60
62
  failed: number;
61
63
  fingerprint: string | null;
62
64
  }>;
65
+ /**
66
+ * Achtergrondherstel: voorstellen waarvan de AI-analyse door een tijdelijke
67
+ * storing (bijv. model overbelast) mislukte, worden bij een volgende run
68
+ * automatisch opnieuw geanalyseerd — tot AI_MAX_BACKGROUND_RETRIES pogingen.
69
+ * Blijft strikt idempotent: alleen 'pending' voorstellen worden aangeraakt.
70
+ */
71
+ export declare function retryFailedAiProposals(admin: AdminClient, limit?: number): Promise<{
72
+ retried: number;
73
+ recovered: number;
74
+ stillFailing: number;
75
+ }>;
63
76
  type Trigger = "cron" | "realtime" | "manual";
64
77
  export type IngestRunResult = {
65
78
  ok: true;
@@ -1 +1 @@
1
- {"version":3,"file":"mailbox-ingest.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ingest.server.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAU9D,KAAK,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;AAuK5C,8EAA8E;AAC9E,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;IACvE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC,CAWD;AAiHD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,qBAAqB,EAAE,WAAW,EACjD,cAAc,EAAE,MAAM,EAAE,EACxB,KAAK,EAAE;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACnC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,EAAE,EAAE,OAAO,CAAA;CAAE,CAkC/E;AAoBD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,WAAW,EAClB,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,CAiBlD;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,CAiBlD;AAqCD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAAC,IAAI,CAAC,CAmJf;AAGD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,WAAW,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAuIhD;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,WAAW,EAClB,KAAK,SAAK,GACT,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CA4B5F;AA4ED,KAAK,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,sBAAsB,CAAC;CAClC,CAAC;AAGF,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CA0H1E"}
1
+ {"version":3,"file":"mailbox-ingest.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ingest.server.ts"],"names":[],"mappings":"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"}
@@ -227,10 +227,13 @@ async function aiParseMessage(admin, messageId, customerId) {
227
227
  requiredFields: ctx.settings.requiredFields,
228
228
  systemPrompt: ctx.settings.systemPrompt,
229
229
  });
230
- return { parsed: res.parsed, meta: res.meta, error: null, requiredFields: ctx.settings.requiredFields };
230
+ return { parsed: res.parsed, meta: res.meta, error: null, transient: false, requiredFields: ctx.settings.requiredFields };
231
231
  }
232
232
  catch (e) {
233
- return { parsed: null, meta: null, error: String(e?.message ?? e).slice(0, 500), requiredFields: [] };
233
+ const msg = String(e?.message ?? e);
234
+ const { isTransientAiError } = await import("./mailbox-ai.server");
235
+ const transient = e?.name === "AiUnavailableError" ? Boolean(e.transient) : isTransientAiError(msg);
236
+ return { parsed: null, meta: null, error: msg.slice(0, 500), transient, requiredFields: [] };
234
237
  }
235
238
  }
236
239
  const FIELD_LABELS = {
@@ -287,22 +290,34 @@ export function computeValidation(parsed, requiredFields, extra) {
287
290
  ok: m.length === 0 && u.length === 0,
288
291
  };
289
292
  }
290
- function aiPayloadBlock(attempt, fingerprint) {
291
- return attempt.parsed
292
- ? {
293
+ /** Maximaal aantal achtergrondpogingen voordat we het voorstel handmatig laten. */
294
+ export const AI_MAX_BACKGROUND_RETRIES = 6;
295
+ function aiPayloadBlock(attempt, fingerprint, prevAi) {
296
+ if (attempt.parsed) {
297
+ return {
293
298
  ok: true,
294
299
  provider: attempt.meta?.providerSlug,
295
300
  model: attempt.meta?.modelName,
296
301
  duration_ms: attempt.meta?.durationMs,
297
302
  fingerprint: attempt.meta?.fingerprint ?? fingerprint,
298
303
  parsed_at: new Date().toISOString(),
299
- }
300
- : {
301
- ok: false,
302
- error: attempt.error,
303
- fingerprint,
304
- parsed_at: new Date().toISOString(),
304
+ retry_count: 0,
305
+ retry_pending: false,
305
306
  };
307
+ }
308
+ const prevCount = Number(prevAi?.retry_count ?? 0);
309
+ const retryCount = Number.isFinite(prevCount) && prevCount > 0 ? prevCount + 1 : 1;
310
+ const transient = attempt.transient === true;
311
+ return {
312
+ ok: false,
313
+ error: attempt.error,
314
+ transient,
315
+ retry_count: retryCount,
316
+ retry_pending: transient && retryCount < AI_MAX_BACKGROUND_RETRIES,
317
+ last_attempt_at: new Date().toISOString(),
318
+ fingerprint,
319
+ parsed_at: new Date().toISOString(),
320
+ };
306
321
  }
307
322
  /**
308
323
  * Vult het afleveradres aan met de hoofdafleverlocatie (is_primary) van de klant
@@ -561,7 +576,7 @@ export async function reparseProposal(admin, proposalId) {
561
576
  // AI-status bijwerken zodat we niet in een lus blijven proberen.
562
577
  await admin
563
578
  .from("mailbox_order_proposals")
564
- .update({ parsed_payload: { ...base, ai: aiPayloadBlock(attempt, fingerprint) } })
579
+ .update({ parsed_payload: { ...base, ai: aiPayloadBlock(attempt, fingerprint, prevBase?.ai) } })
565
580
  .eq("id", proposalId);
566
581
  return { ok: false, error: attempt.error };
567
582
  }
@@ -677,6 +692,47 @@ export async function reparseChangedAgentProposals(admin, limit = 25) {
677
692
  }
678
693
  return { checked, reparsed, failed, fingerprint };
679
694
  }
695
+ /**
696
+ * Achtergrondherstel: voorstellen waarvan de AI-analyse door een tijdelijke
697
+ * storing (bijv. model overbelast) mislukte, worden bij een volgende run
698
+ * automatisch opnieuw geanalyseerd — tot AI_MAX_BACKGROUND_RETRIES pogingen.
699
+ * Blijft strikt idempotent: alleen 'pending' voorstellen worden aangeraakt.
700
+ */
701
+ export async function retryFailedAiProposals(admin, limit = 10) {
702
+ const { data: rows } = await admin
703
+ .from("mailbox_order_proposals")
704
+ .select("id, parsed_payload")
705
+ .eq("status", "pending")
706
+ .order("created_at", { ascending: false })
707
+ .limit(200);
708
+ const list = Array.isArray(rows) ? rows : [];
709
+ let retried = 0;
710
+ let recovered = 0;
711
+ let stillFailing = 0;
712
+ for (const r of list) {
713
+ const ai = r.parsed_payload?.ai ?? null;
714
+ if (!ai || ai.ok !== false)
715
+ continue;
716
+ if (ai.retry_pending !== true)
717
+ continue;
718
+ if (Number(ai.retry_count ?? 0) >= AI_MAX_BACKGROUND_RETRIES)
719
+ continue;
720
+ if (retried >= limit)
721
+ break;
722
+ retried++;
723
+ try {
724
+ const res = await reparseProposal(admin, r.id);
725
+ if (res.ok)
726
+ recovered++;
727
+ else
728
+ stillFailing++;
729
+ }
730
+ catch {
731
+ stillFailing++;
732
+ }
733
+ }
734
+ return { retried, recovered, stillFailing };
735
+ }
680
736
  async function ingestSingleEvent(admin, connectionId, ev) {
681
737
  const p = ev.payload ?? {};
682
738
  const externalId = ev.external_id ?? pickStr(p, "message_id", "messageId", "id") ?? ev.id;
@@ -844,6 +900,15 @@ export async function runIngest(trigger) {
844
900
  if (!errorSample)
845
901
  errorSample = `reparse: ${String(e?.message ?? e)}`.slice(0, 500);
846
902
  }
903
+ // 7. Herstelronde: mislukte AI-analyses door tijdelijke storingen opnieuw proberen.
904
+ try {
905
+ await retryFailedAiProposals(supabaseAdmin);
906
+ }
907
+ catch (e) {
908
+ errors++;
909
+ if (!errorSample)
910
+ errorSample = `reparse: ${String(e?.message ?? e)}`.slice(0, 500);
911
+ }
847
912
  }
848
913
  finally {
849
914
  if (runId) {
@@ -176,11 +176,29 @@ export declare const listCustomersForPicker: import("@tanstack/start-client-core
176
176
  userId: string;
177
177
  claims: import("@supabase/auth-js").User;
178
178
  }, undefined, undefined, undefined>], undefined, Promise<{
179
- items: {
180
- id: string;
181
- company_name: string;
182
- email: string | null;
183
- }[];
179
+ items: any[];
180
+ }>>;
181
+ export declare const createCustomerFromGroup: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
182
+ supabase: import("@supabase/supabase-js").SupabaseClient<Database>;
183
+ userId: string;
184
+ claims: import("@supabase/auth-js").User;
185
+ }, undefined, undefined, undefined>], (input: unknown) => {
186
+ groupKey: string;
187
+ companyName: string;
188
+ email?: string | null | undefined;
189
+ street?: string | null | undefined;
190
+ houseNumber?: string | null | undefined;
191
+ postalCode?: string | null | undefined;
192
+ city?: string | null | undefined;
193
+ country?: string | null | undefined;
194
+ gln?: string | null | undefined;
195
+ phone?: string | null | undefined;
196
+ }, Promise<{
197
+ id: string;
198
+ created: false;
199
+ } | {
200
+ id: string;
201
+ created: true;
184
202
  }>>;
185
203
  export declare const listProductsForPicker: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
186
204
  supabase: import("@supabase/supabase-js").SupabaseClient<Database>;
@@ -1 +1 @@
1
- {"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAgB9D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;QAiBf,MAAM;cACM,MAAM;mBACI,MAAM;kBACE,MAAM,GAAG,IAAI;YACzB,MAAM,GAAG,IAAI;gBACL,MAAM,GAAG,IAAI;sBACD,MAAM,GAAG,IAAI;;;GAGnE,CAAC;AAWL,eAAO,MAAM,cAAc,2EACD,OAAO;;;;;;;;GA+B7B,CAAC;AAGL,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;GAWzB,CAAC;AAEL,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB;;;;8CAEL,OAAO;;;;;GAK7B,CAAC;AAEL,iFAAiF;AACjF,eAAO,MAAM,8BAA8B;;;;;;;;;GAMvC,CAAC;AAML,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;GAWrB,CAAC;AAEL,eAAO,MAAM,YAAY;;;;8CAEC,OAAO;;;;GAa7B,CAAC;AAKL,eAAO,MAAM,aAAa;;;;8CAEA,OAAO;;;;GAuB7B,CAAC;AAOL,eAAO,MAAM,uBAAuB;;;;;WAO+B,MAAM,GAAG,IAAI;;;;GAC5E,CAAC;AAgBL,eAAO,MAAM,eAAe;;;;8CAEF,OAAO;;;;;;;;;;;;;;;;;;;;;;GAwL7B,CAAC;AAEL,eAAO,MAAM,cAAc;;;;8CAED,OAAO;;;;;GAY7B,CAAC;AAGL,eAAO,MAAM,cAAc;;;;8CAED,OAAO;;;;GAW7B,CAAC;AAIL,eAAO,MAAM,sBAAsB;;;;;;;;;;GAM/B,CAAC;AAQL,eAAO,MAAM,qBAAqB;;;;;;;GAyN9B,CAAC;AAiBL,eAAO,MAAM,oBAAoB;;;;8CAEP,OAAO;;;;;qBAqBS,MAAM;sBAAgB,MAAM;gBAAU,MAAM;gBAAU,MAAM;kBAAY,MAAM;;;;uBAhCvG,MAAM;oBACT,MAAM,GAAG,IAAI;sBACX,MAAM;0BAEF,MAAM;0BACN,MAAM;yBACP,MAAM;;;qBA0FW,MAAM;eAClB,MAAM,GAAG,IAAI;;;;GAoBjC,CAAC;AAiBL,eAAO,MAAM,eAAe;;;;8CAEF,OAAO;;;;;;;;;;;;;;;;GAuC7B,CAAC;AAEL,eAAO,MAAM,cAAc;;;;0CAEL;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE;aAAR,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;GAQnC,CAAC;AAEL,eAAO,MAAM,gCAAgC;;;;0CAEvB;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE;gBAAR,MAAM;;;;;;;;;;;;;;;;;;;GAUtC,CAAC;AAKL,eAAO,MAAM,mBAAmB;;;;;;YAqBD,MAAM;;;;;GAQjC,CAAC;AAEL,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;GAS9B,CAAC;AAGL,eAAO,MAAM,gBAAgB;;;;;;GAUzB,CAAC;AAiBL,eAAO,MAAM,8BAA8B;;;;8CAEjB,OAAO;;;;0BAOT,MAAM;0BAAoB,MAAM;yBAAmB,MAAM;oBAC/D,OAAO;oBACP,MAAM,GAAG,IAAI;oBAAc,MAAM,GAAG,IAAI;sBACtC,MAAM,GAAG,IAAI;sBAAgB,MAAM,GAAG,IAAI;;yBAEO,MAAM;yBAAmB,MAAM;;;0BAL5E,MAAM;0BAAoB,MAAM;yBAAmB,MAAM;oBAC/D,OAAO;oBACP,MAAM,GAAG,IAAI;oBAAc,MAAM,GAAG,IAAI;sBACtC,MAAM,GAAG,IAAI;sBAAgB,MAAM,GAAG,IAAI;;;;;GAsM1D,CAAC"}
1
+ {"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAgB9D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;QAiBf,MAAM;cACM,MAAM;mBACI,MAAM;kBACE,MAAM,GAAG,IAAI;YACzB,MAAM,GAAG,IAAI;gBACL,MAAM,GAAG,IAAI;sBACD,MAAM,GAAG,IAAI;;;GAGnE,CAAC;AAWL,eAAO,MAAM,cAAc,2EACD,OAAO;;;;;;;;GA+B7B,CAAC;AAGL,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;GAWzB,CAAC;AAEL,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB;;;;8CAEL,OAAO;;;;;GAK7B,CAAC;AAEL,iFAAiF;AACjF,eAAO,MAAM,8BAA8B;;;;;;;;;GAMvC,CAAC;AAML,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;GAWrB,CAAC;AAEL,eAAO,MAAM,YAAY;;;;8CAEC,OAAO;;;;GAa7B,CAAC;AAKL,eAAO,MAAM,aAAa;;;;8CAEA,OAAO;;;;GAuB7B,CAAC;AAOL,eAAO,MAAM,uBAAuB;;;;;WAO+B,MAAM,GAAG,IAAI;;;;GAC5E,CAAC;AAgBL,eAAO,MAAM,eAAe;;;;8CAEF,OAAO;;;;;;;;;;;;;;;;;;;;;;GAwL7B,CAAC;AAEL,eAAO,MAAM,cAAc;;;;8CAED,OAAO;;;;;GAY7B,CAAC;AAGL,eAAO,MAAM,cAAc;;;;8CAED,OAAO;;;;GAW7B,CAAC;AAQL,eAAO,MAAM,sBAAsB;;;;;;GAiD/B,CAAC;AAIL,eAAO,MAAM,uBAAuB;;;;8CAEV,OAAO;;;;;;;;;;;;QAyBiB,MAAM;;;QAoBxB,MAAM;;GAClC,CAAC;AAQL,eAAO,MAAM,qBAAqB;;;;;;;GAyN9B,CAAC;AAiBL,eAAO,MAAM,oBAAoB;;;;8CAEP,OAAO;;;;;qBAqBS,MAAM;sBAAgB,MAAM;gBAAU,MAAM;gBAAU,MAAM;kBAAY,MAAM;;;;uBAhCvG,MAAM;oBACT,MAAM,GAAG,IAAI;sBACX,MAAM;0BAEF,MAAM;0BACN,MAAM;yBACP,MAAM;;;qBA0FW,MAAM;eAClB,MAAM,GAAG,IAAI;;;;GAoBjC,CAAC;AAiBL,eAAO,MAAM,eAAe;;;;8CAEF,OAAO;;;;;;;;;;;;;;;;GAuC7B,CAAC;AAEL,eAAO,MAAM,cAAc;;;;0CAEL;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE;aAAR,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;GAQnC,CAAC;AAEL,eAAO,MAAM,gCAAgC;;;;0CAEvB;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE;gBAAR,MAAM;;;;;;;;;;;;;;;;;;;GAUtC,CAAC;AAKL,eAAO,MAAM,mBAAmB;;;;;;YAqBD,MAAM;;;;;GAQjC,CAAC;AAEL,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;GAS9B,CAAC;AAGL,eAAO,MAAM,gBAAgB;;;;;;GAUzB,CAAC;AAiBL,eAAO,MAAM,8BAA8B;;;;8CAEjB,OAAO;;;;0BAOT,MAAM;0BAAoB,MAAM;yBAAmB,MAAM;oBAC/D,OAAO;oBACP,MAAM,GAAG,IAAI;oBAAc,MAAM,GAAG,IAAI;sBACtC,MAAM,GAAG,IAAI;sBAAgB,MAAM,GAAG,IAAI;;yBAEO,MAAM;yBAAmB,MAAM;;;0BAL5E,MAAM;0BAAoB,MAAM;yBAAmB,MAAM;oBAC/D,OAAO;oBACP,MAAM,GAAG,IAAI;oBAAc,MAAM,GAAG,IAAI;sBACtC,MAAM,GAAG,IAAI;sBAAgB,MAAM,GAAG,IAAI;;;;;GAsM1D,CAAC"}
@@ -370,12 +370,105 @@ export const reopenProposal = createServerFn({ method: "POST" })
370
370
  }).eq("id", data.proposalId);
371
371
  return { ok: true };
372
372
  });
373
+ // Klanten voor de picker — gecombineerde lijst uit `external_customers_grouped`
374
+ // (lokaal + Twinfield + Floriday). Rijen met een lokale bron krijgen het
375
+ // bestaande `customers.id`; rijen zonder lokale bron krijgen een tijdelijke
376
+ // sleutel `grp:<group_key>` en moeten eerst als klant worden aangemaakt.
373
377
  export const listCustomersForPicker = createServerFn({ method: "GET" })
374
378
  .middleware([requireSupabaseAuth])
375
- .handler(async ({ context }) => {
376
- const { data } = await context.supabase
377
- .from("customers").select("id, company_name, email").eq("is_active", true).order("company_name").range(0, 4999);
378
- return { items: data ?? [] };
379
+ .handler(async () => {
380
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server");
381
+ const sb = supabaseAdmin;
382
+ const { data: grouped } = await sb
383
+ .from("external_customers_grouped")
384
+ .select("group_key, display_name, email, city, country, gln, providers, sources, is_active")
385
+ .range(0, 4999);
386
+ const rows = Array.isArray(grouped) ? grouped : [];
387
+ const items = rows
388
+ .filter((r) => r?.is_active !== false && (r?.display_name ?? "").trim() !== "")
389
+ .map((r) => {
390
+ const sources = Array.isArray(r.sources) ? r.sources : [];
391
+ const local = sources.find((s) => s?.provider === "local" && typeof s?.external_id === "string");
392
+ const best = local ?? sources[0] ?? {};
393
+ const providers = Array.isArray(r.providers) ? r.providers : [];
394
+ return {
395
+ id: local?.external_id ?? `grp:${r.group_key}`,
396
+ group_key: r.group_key,
397
+ is_local: Boolean(local),
398
+ company_name: (r.display_name ?? best.company_name ?? "Onbekend"),
399
+ email: (r.email ?? best.email ?? null),
400
+ city: (r.city ?? best.city ?? null),
401
+ country: (r.country ?? best.country ?? null),
402
+ gln: (r.gln ?? best.gln ?? null),
403
+ street: (best.street ?? null),
404
+ house_number: (best.house_number ?? null),
405
+ postal_code: (best.postal_code ?? null),
406
+ phone: (best.phone ?? null),
407
+ providers: providers,
408
+ };
409
+ })
410
+ .sort((a, b) => a.company_name.localeCompare(b.company_name, "nl"));
411
+ if (items.length > 0)
412
+ return { items };
413
+ // Terugval: als de gecombineerde view leeg is, toch de lokale klanten tonen.
414
+ const { data: fallback } = await sb
415
+ .from("customers").select("id, company_name, email, city, country")
416
+ .eq("is_active", true).order("company_name").range(0, 4999);
417
+ return {
418
+ items: (Array.isArray(fallback) ? fallback : []).map((c) => ({
419
+ ...c, group_key: `local:${c.id}`, is_local: true, providers: ["local"],
420
+ gln: null, street: null, house_number: null, postal_code: null, phone: null,
421
+ })),
422
+ };
423
+ });
424
+ // Nieuwe klant aanmaken vanuit een externe bron. De gebruiker bevestigt de
425
+ // velden zelf in de UI — er wordt nooit stilzwijgend een klant aangemaakt.
426
+ export const createCustomerFromGroup = createServerFn({ method: "POST" })
427
+ .middleware([requireSupabaseAuth])
428
+ .inputValidator((input) => z.object({
429
+ groupKey: z.string().min(1),
430
+ companyName: z.string().min(1).max(255),
431
+ email: z.string().email().nullable().optional(),
432
+ street: z.string().max(255).nullable().optional(),
433
+ houseNumber: z.string().max(50).nullable().optional(),
434
+ postalCode: z.string().max(50).nullable().optional(),
435
+ city: z.string().max(255).nullable().optional(),
436
+ country: z.string().max(255).nullable().optional(),
437
+ gln: z.string().max(50).nullable().optional(),
438
+ phone: z.string().max(50).nullable().optional(),
439
+ }).parse(input))
440
+ .handler(async ({ data }) => {
441
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server");
442
+ const sb = supabaseAdmin;
443
+ const email = (data.email ?? "").trim().toLowerCase() || null;
444
+ const name = data.companyName.trim();
445
+ // Bestaat er al een lokale klant met dit e-mailadres of deze naam?
446
+ if (email) {
447
+ const { data: byEmail } = await sb.from("customers")
448
+ .select("id").ilike("email", email).limit(1).maybeSingle();
449
+ if (byEmail?.id)
450
+ return { id: byEmail.id, created: false };
451
+ }
452
+ const { data: byName } = await sb.from("customers")
453
+ .select("id").ilike("company_name", name).limit(1).maybeSingle();
454
+ if (byName?.id)
455
+ return { id: byName.id, created: false };
456
+ const { data: inserted, error } = await sb.from("customers").insert({
457
+ company_name: name,
458
+ email,
459
+ street: data.street ?? null,
460
+ house_number: data.houseNumber ?? null,
461
+ postal_code: data.postalCode ?? null,
462
+ city: data.city ?? null,
463
+ country: data.country ?? null,
464
+ gln: data.gln ?? null,
465
+ phone: data.phone ?? null,
466
+ is_active: true,
467
+ custom_fields: { created_from_group_key: data.groupKey },
468
+ }).select("id").single();
469
+ if (error)
470
+ throw new Error(error.message);
471
+ return { id: inserted.id, created: true };
379
472
  });
380
473
  // Producten voor de picker — ALLE artikelen uit de voorraad:
381
474
  // 1. alles in `products`