@flowselections/mailbox-orders 1.0.10 → 1.0.11
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/MailboxProposalsPage.d.ts +16 -0
- package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +101 -10
- package/dist-lib/integrations/supabase/client.d.ts +162 -4
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +162 -4
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +163 -5
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +12 -1
- package/dist-lib/lib/ai-config.server.d.ts +14 -0
- package/dist-lib/lib/ai-config.server.d.ts.map +1 -1
- package/dist-lib/lib/ai-config.server.js +32 -19
- package/dist-lib/lib/mailbox-ai.server.d.ts +7 -0
- package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-ai.server.js +83 -38
- package/dist-lib/lib/mailbox-ingest.server.d.ts +13 -0
- package/dist-lib/lib/mailbox-ingest.server.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-ingest.server.js +77 -12
- package/dist-lib/lib/mailbox.functions.d.ts +23 -5
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.js +97 -4
- package/package.json +2 -2
- package/dist-lib/components/CartAssignmentWizard.d.ts +0 -6
- package/dist-lib/components/CartAssignmentWizard.d.ts.map +0 -1
- package/dist-lib/components/CartAssignmentWizard.js +0 -199
- package/dist-lib/components/MailboxInboxPage.d.ts +0 -2
- package/dist-lib/components/MailboxInboxPage.d.ts.map +0 -1
- package/dist-lib/components/MailboxInboxPage.js +0 -120
- package/dist-lib/components/settings/ImapAccountsCard.d.ts +0 -2
- package/dist-lib/components/settings/ImapAccountsCard.d.ts.map +0 -1
- package/dist-lib/components/settings/ImapAccountsCard.js +0 -136
- package/dist-lib/components/settings/ImapProfilesCard.d.ts +0 -2
- package/dist-lib/components/settings/ImapProfilesCard.d.ts.map +0 -1
- package/dist-lib/components/settings/ImapProfilesCard.js +0 -101
- package/dist-lib/components/settings/MailboxSettingsCard.d.ts +0 -2
- package/dist-lib/components/settings/MailboxSettingsCard.d.ts.map +0 -1
- package/dist-lib/components/settings/MailboxSettingsCard.js +0 -85
- package/dist-lib/lib/imap.functions.d.ts +0 -32852
- package/dist-lib/lib/imap.functions.d.ts.map +0 -1
- package/dist-lib/lib/imap.functions.js +0 -235
- package/dist-lib/lib/mailbox-auto.server.d.ts +0 -9
- package/dist-lib/lib/mailbox-auto.server.d.ts.map +0 -1
- 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: [
|
|
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,
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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:
|
|
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
|
|
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;
|
|
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
|
|
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
|
|
242
|
-
const
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
291
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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;
|
|
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 (
|
|
376
|
-
const {
|
|
377
|
-
|
|
378
|
-
|
|
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`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowselections/mailbox-orders",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@cloudflare/vite-plugin": "^1.25.5",
|
|
48
48
|
"@eslint/js": "^9.32.0",
|
|
49
49
|
"@flowselections/core": "^1.0.16",
|
|
50
|
-
"@lovable.dev/vite-tanstack-config": "2.
|
|
50
|
+
"@lovable.dev/vite-tanstack-config": "2.13.1",
|
|
51
51
|
"@rsbuild/core": "^1.0.0",
|
|
52
52
|
"@tailwindcss/vite": "^4.2.1",
|
|
53
53
|
"@tanstack/react-query": "^5.83.0",
|