@flowselections/mailbox-orders 1.0.8 → 1.0.10
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/MailboxAuthBanner.d.ts +16 -0
- package/dist-lib/components/MailboxAuthBanner.d.ts.map +1 -0
- package/dist-lib/components/MailboxAuthBanner.js +14 -0
- package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProcessedPage.js +10 -2
- package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +425 -104
- package/dist-lib/components/SearchableSelect.d.ts +1 -0
- package/dist-lib/components/SearchableSelect.d.ts.map +1 -1
- package/dist-lib/components/SearchableSelect.js +2 -1
- package/dist-lib/components/settings/MailboxAiPromptCard.d.ts +2 -0
- package/dist-lib/components/settings/MailboxAiPromptCard.d.ts.map +1 -0
- package/dist-lib/components/settings/MailboxAiPromptCard.js +39 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +2 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/auth-attacher.js +5 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +2 -11202
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/auth-middleware.js +8 -7
- package/dist-lib/integrations/supabase/client.d.ts +16514 -7163
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +16516 -7164
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +16768 -7202
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +59 -8
- package/dist-lib/lib/ai-config.server.d.ts +46 -0
- package/dist-lib/lib/ai-config.server.d.ts.map +1 -0
- package/dist-lib/lib/ai-config.server.js +99 -0
- package/dist-lib/lib/delivery-date.d.ts +37 -0
- package/dist-lib/lib/delivery-date.d.ts.map +1 -0
- package/dist-lib/lib/delivery-date.js +96 -0
- package/dist-lib/lib/load-carriers.d.ts +26 -0
- package/dist-lib/lib/load-carriers.d.ts.map +1 -0
- package/dist-lib/lib/load-carriers.js +73 -0
- package/dist-lib/lib/mailbox-ai-prompt.d.ts +9 -0
- package/dist-lib/lib/mailbox-ai-prompt.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-ai-prompt.js +30 -0
- package/dist-lib/lib/mailbox-ai-settings.functions.d.ts +23 -0
- package/dist-lib/lib/mailbox-ai-settings.functions.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-ai-settings.functions.js +58 -0
- package/dist-lib/lib/mailbox-ai.server.d.ts +49 -4
- package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-ai.server.js +284 -6
- package/dist-lib/lib/mailbox-body.d.ts +7 -0
- package/dist-lib/lib/mailbox-body.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-body.js +38 -0
- package/dist-lib/lib/mailbox-ingest.server.d.ts +76 -0
- package/dist-lib/lib/mailbox-ingest.server.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-ingest.server.js +863 -0
- package/dist-lib/lib/mailbox-template.functions.d.ts +7 -67213
- package/dist-lib/lib/mailbox-template.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.d.ts +107 -190506
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.js +544 -270
- package/package.json +4 -3
|
@@ -1,17 +1,68 @@
|
|
|
1
1
|
export const Constants = {
|
|
2
2
|
public: {
|
|
3
3
|
Enums: {
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"receptie",
|
|
11
|
-
"monteur",
|
|
4
|
+
ai_import_status: [
|
|
5
|
+
"nieuw",
|
|
6
|
+
"verwerken",
|
|
7
|
+
"bevestigen",
|
|
8
|
+
"klaar",
|
|
9
|
+
"mislukt",
|
|
12
10
|
],
|
|
11
|
+
app_role: ["admin", "planner", "monteur", "administratie", "ai_admin"],
|
|
12
|
+
doc_layout_type: [
|
|
13
|
+
"pakbon",
|
|
14
|
+
"factuur",
|
|
15
|
+
"offerte",
|
|
16
|
+
"orderbevestiging",
|
|
17
|
+
"etiket",
|
|
18
|
+
"verzendlabel",
|
|
19
|
+
"productlabel",
|
|
20
|
+
"barcode_label",
|
|
21
|
+
"magazijnlijst",
|
|
22
|
+
"controlelijst",
|
|
23
|
+
"transportdocument",
|
|
24
|
+
"vrij",
|
|
25
|
+
"sticker",
|
|
26
|
+
"productiebon",
|
|
27
|
+
],
|
|
28
|
+
doc_orientation: ["portrait", "landscape"],
|
|
13
29
|
exact_env: ["staging", "live"],
|
|
14
30
|
exact_push_status: ["pending", "sent", "error"],
|
|
31
|
+
itk_customer_segment: ["Business", "Private"],
|
|
32
|
+
itk_dynamic_field_type: ["text", "select", "date", "checkbox"],
|
|
33
|
+
planning_item_status: [
|
|
34
|
+
"gepland",
|
|
35
|
+
"bezig",
|
|
36
|
+
"afgerond",
|
|
37
|
+
"geannuleerd",
|
|
38
|
+
"concept",
|
|
39
|
+
"vrijgegeven",
|
|
40
|
+
],
|
|
41
|
+
print_job_status: [
|
|
42
|
+
"wachtend",
|
|
43
|
+
"bezig",
|
|
44
|
+
"voltooid",
|
|
45
|
+
"mislukt",
|
|
46
|
+
"geannuleerd",
|
|
47
|
+
],
|
|
48
|
+
printer_connection: [
|
|
49
|
+
"ipp",
|
|
50
|
+
"raw9100",
|
|
51
|
+
"printnode",
|
|
52
|
+
"cups",
|
|
53
|
+
"usb",
|
|
54
|
+
"windows",
|
|
55
|
+
"airprint",
|
|
56
|
+
"cloud",
|
|
57
|
+
],
|
|
58
|
+
printer_kind: [
|
|
59
|
+
"labelprinter",
|
|
60
|
+
"laserprinter",
|
|
61
|
+
"inkjet",
|
|
62
|
+
"thermal",
|
|
63
|
+
"kantoorprinter",
|
|
64
|
+
"overig",
|
|
65
|
+
],
|
|
15
66
|
quote_status: [
|
|
16
67
|
"concept",
|
|
17
68
|
"verzonden",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
import type { Database } from "../integrations/supabase/types";
|
|
3
|
+
type AdminClient = SupabaseClient<Database>;
|
|
4
|
+
export interface ModuleAiConfig {
|
|
5
|
+
moduleKey: string;
|
|
6
|
+
providerSlug: string;
|
|
7
|
+
providerName: string;
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
modelSlug: string;
|
|
10
|
+
modelName: string;
|
|
11
|
+
apiKey: string;
|
|
12
|
+
credentialLabel: string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Vingerafdruk van de gekoppelde AI-agent (binding + model + provider +
|
|
15
|
+
* sleutel, inclusief hun updated_at). Wijzigt de agent, dan wijzigt deze
|
|
16
|
+
* string — daarop detecteren we dat er opnieuw geparsed moet worden.
|
|
17
|
+
*/
|
|
18
|
+
fingerprint: string;
|
|
19
|
+
}
|
|
20
|
+
export type ModuleAiResolution = {
|
|
21
|
+
ok: true;
|
|
22
|
+
config: ModuleAiConfig;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
reason: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Zoekt de actieve AI-binding voor een module. Retourneert nooit een throw:
|
|
29
|
+
* de aanroeper beslist zelf wat er gebeurt als AI niet beschikbaar is.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveModuleAi(admin: AdminClient, moduleKey: string): Promise<ModuleAiResolution>;
|
|
32
|
+
export interface AiCallLogInput {
|
|
33
|
+
moduleKey: string;
|
|
34
|
+
providerSlug: string;
|
|
35
|
+
modelSlug: string;
|
|
36
|
+
status: "success" | "error";
|
|
37
|
+
inputTokens?: number | null;
|
|
38
|
+
outputTokens?: number | null;
|
|
39
|
+
durationMs?: number | null;
|
|
40
|
+
errorCode?: string | null;
|
|
41
|
+
errorMessage?: string | null;
|
|
42
|
+
}
|
|
43
|
+
/** Schrijft één regel in ai_call_log. Faalt nooit hard. */
|
|
44
|
+
export declare function logAiCall(admin: AdminClient, input: AiCallLogInput): Promise<void>;
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=ai-config.server.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Server-only: haalt de AI-configuratie voor een module op uit de centrale
|
|
2
|
+
// AI-Authenticatie module (tabellen ai_modules / ai_module_bindings /
|
|
3
|
+
// ai_models / ai_providers / ai_credentials).
|
|
4
|
+
//
|
|
5
|
+
// De API-sleutel verlaat NOOIT de server. Dit bestand mag alleen vanuit
|
|
6
|
+
// server-function handlers of andere *.server.ts helpers geladen worden.
|
|
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.
|
|
10
|
+
*/
|
|
11
|
+
export async function resolveModuleAi(admin, moduleKey) {
|
|
12
|
+
const { data: mod, error: modErr } = await admin
|
|
13
|
+
.from("ai_modules")
|
|
14
|
+
.select("module_key, ai_enabled")
|
|
15
|
+
.eq("module_key", moduleKey)
|
|
16
|
+
.maybeSingle();
|
|
17
|
+
if (modErr)
|
|
18
|
+
return { ok: false, reason: `ai_modules: ${modErr.message}` };
|
|
19
|
+
if (!mod)
|
|
20
|
+
return { ok: false, reason: `Module '${moduleKey}' bestaat niet in de AI-module.` };
|
|
21
|
+
if (!mod.ai_enabled)
|
|
22
|
+
return { ok: false, reason: "AI staat uit voor deze module." };
|
|
23
|
+
const { data: bindings, error: bindErr } = await admin
|
|
24
|
+
.from("ai_module_bindings")
|
|
25
|
+
.select("id, is_default, label, notes, updated_at, model_id, credential_id, ai_models(slug, display_name, active, updated_at, provider_id, ai_providers(slug, name, base_url, active, updated_at)), ai_credentials(id, api_key, label, active, updated_at)")
|
|
26
|
+
.eq("consumer_module_id", moduleKey)
|
|
27
|
+
.order("is_default", { ascending: false });
|
|
28
|
+
if (bindErr)
|
|
29
|
+
return { ok: false, reason: `ai_module_bindings: ${bindErr.message}` };
|
|
30
|
+
const list = Array.isArray(bindings) ? bindings : [];
|
|
31
|
+
if (list.length === 0) {
|
|
32
|
+
return { ok: false, reason: `Geen AI-koppeling ingesteld voor '${moduleKey}'.` };
|
|
33
|
+
}
|
|
34
|
+
for (const b of list) {
|
|
35
|
+
const model = b.ai_models;
|
|
36
|
+
const cred = b.ai_credentials;
|
|
37
|
+
const provider = model?.ai_providers;
|
|
38
|
+
if (!model?.active || !cred?.active || !provider?.active)
|
|
39
|
+
continue;
|
|
40
|
+
const apiKey = typeof cred.api_key === "string" ? cred.api_key.trim() : "";
|
|
41
|
+
const baseUrl = typeof provider.base_url === "string" ? provider.base_url.replace(/\/+$/, "") : "";
|
|
42
|
+
if (!apiKey || !baseUrl || !model.slug || !provider.slug)
|
|
43
|
+
continue;
|
|
44
|
+
const fingerprint = [
|
|
45
|
+
"v1",
|
|
46
|
+
b.id,
|
|
47
|
+
b.updated_at ?? "",
|
|
48
|
+
b.label ?? "",
|
|
49
|
+
b.notes ?? "",
|
|
50
|
+
model.slug,
|
|
51
|
+
model.updated_at ?? "",
|
|
52
|
+
provider.slug,
|
|
53
|
+
baseUrl,
|
|
54
|
+
provider.updated_at ?? "",
|
|
55
|
+
cred.id ?? "",
|
|
56
|
+
cred.updated_at ?? "",
|
|
57
|
+
// sleutelwissel zonder updated_at-bump moet ook tellen
|
|
58
|
+
String(apiKey.length),
|
|
59
|
+
apiKey.slice(-4),
|
|
60
|
+
].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
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return { ok: false, reason: "Geen actieve combinatie van model + sleutel gevonden." };
|
|
77
|
+
}
|
|
78
|
+
/** Schrijft één regel in ai_call_log. Faalt nooit hard. */
|
|
79
|
+
export async function logAiCall(admin, input) {
|
|
80
|
+
try {
|
|
81
|
+
const inTok = input.inputTokens ?? null;
|
|
82
|
+
const outTok = input.outputTokens ?? null;
|
|
83
|
+
await admin.from("ai_call_log").insert({
|
|
84
|
+
consumer_module_id: input.moduleKey,
|
|
85
|
+
provider_slug: input.providerSlug,
|
|
86
|
+
model_slug: input.modelSlug,
|
|
87
|
+
status: input.status,
|
|
88
|
+
input_tokens: inTok ?? undefined,
|
|
89
|
+
output_tokens: outTok ?? undefined,
|
|
90
|
+
total_tokens: inTok !== null || outTok !== null ? (inTok ?? 0) + (outTok ?? 0) : undefined,
|
|
91
|
+
duration_ms: input.durationMs ?? null,
|
|
92
|
+
error_code: input.errorCode ?? null,
|
|
93
|
+
error_message: input.errorMessage ? String(input.errorMessage).slice(0, 500) : null,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// logging mag de pipeline nooit breken
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface PickupMoment {
|
|
2
|
+
signup_time?: string | null;
|
|
3
|
+
signup_days?: number | null;
|
|
4
|
+
pickup_time?: string | null;
|
|
5
|
+
pickup_days?: number | null;
|
|
6
|
+
delivery_time?: string | null;
|
|
7
|
+
delivery_days?: number | null;
|
|
8
|
+
}
|
|
9
|
+
export interface CarrierSchedule {
|
|
10
|
+
delivery_days?: number | null;
|
|
11
|
+
pickup_weekdays?: number[] | null;
|
|
12
|
+
delivery_weekdays?: number[] | null;
|
|
13
|
+
pickup_moments?: PickupMoment[] | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function toISODate(d: Date): string;
|
|
16
|
+
export interface EarliestDeliveryResult {
|
|
17
|
+
date: Date;
|
|
18
|
+
iso: string;
|
|
19
|
+
/** Moment waarop de bestelling uiterlijk aangemeld moet zijn, indien bekend. */
|
|
20
|
+
signupAt: Date | null;
|
|
21
|
+
/** true wanneer de datum uit de aanmeldmomenten van de vervoerder volgt. */
|
|
22
|
+
fromSchedule: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Eerst mogelijke bezorgdatum:
|
|
26
|
+
* - Alleen ophaaldagen die de vervoerder rijdt tellen mee.
|
|
27
|
+
* - Een aanmeldmoment telt alleen als het deadline-tijdstip nog niet verstreken is.
|
|
28
|
+
* - De bezorgdag moet in delivery_weekdays vallen.
|
|
29
|
+
* - Orderpicktijd (pickDays) verschuift de vroegst toegestane bezorgdag.
|
|
30
|
+
*/
|
|
31
|
+
export declare function computeEarliestDelivery(carrier: CarrierSchedule | null | undefined, pickDays?: number, now?: Date): EarliestDeliveryResult;
|
|
32
|
+
/**
|
|
33
|
+
* Is deze ISO-datum een geldige bezorgdag voor deze vervoerder?
|
|
34
|
+
* Controleert de toegestane bezorgweekdagen en de eerst mogelijke datum.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isValidDeliveryDate(carrier: CarrierSchedule | null | undefined, iso: string, pickDays?: number, now?: Date): boolean;
|
|
37
|
+
//# sourceMappingURL=delivery-date.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-date.d.ts","sourceRoot":"","sources":["../../src/lib/delivery-date.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAClC,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;CACxC;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAEzC;AA0BD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,IAAI,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,4EAA4E;IAC5E,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,EAC3C,QAAQ,SAAI,EACZ,GAAG,GAAE,IAAiB,GACrB,sBAAsB,CA0CxB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,EAC3C,GAAG,EAAE,MAAM,EACX,QAAQ,SAAI,EACZ,GAAG,GAAE,IAAiB,GACrB,OAAO,CAWT"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Berekening van de eerst mogelijke bezorgdatum op basis van de aanmeldmomenten
|
|
2
|
+
// (pickup_moments) van de vervoerder. Pure functies — bruikbaar in browser én server.
|
|
3
|
+
export function toISODate(d) {
|
|
4
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
5
|
+
}
|
|
6
|
+
function addDays(d, n) {
|
|
7
|
+
const x = new Date(d);
|
|
8
|
+
x.setDate(x.getDate() + n);
|
|
9
|
+
return x;
|
|
10
|
+
}
|
|
11
|
+
function isoWeekday(d) {
|
|
12
|
+
const dow = d.getDay(); // 0 = zondag
|
|
13
|
+
return dow === 0 ? 7 : dow;
|
|
14
|
+
}
|
|
15
|
+
function parseTime(t) {
|
|
16
|
+
if (typeof t !== "string")
|
|
17
|
+
return null;
|
|
18
|
+
const match = t.match(/^(\d{1,2}):(\d{2})/);
|
|
19
|
+
if (!match)
|
|
20
|
+
return null;
|
|
21
|
+
return { h: Number(match[1]), m: Number(match[2]) };
|
|
22
|
+
}
|
|
23
|
+
function atTime(day, time) {
|
|
24
|
+
const x = new Date(day);
|
|
25
|
+
x.setHours(time.h, time.m, 0, 0);
|
|
26
|
+
return x;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Eerst mogelijke bezorgdatum:
|
|
30
|
+
* - Alleen ophaaldagen die de vervoerder rijdt tellen mee.
|
|
31
|
+
* - Een aanmeldmoment telt alleen als het deadline-tijdstip nog niet verstreken is.
|
|
32
|
+
* - De bezorgdag moet in delivery_weekdays vallen.
|
|
33
|
+
* - Orderpicktijd (pickDays) verschuift de vroegst toegestane bezorgdag.
|
|
34
|
+
*/
|
|
35
|
+
export function computeEarliestDelivery(carrier, pickDays = 0, now = new Date()) {
|
|
36
|
+
const carrierDeliveryDays = Math.max(0, Number(carrier?.delivery_days ?? 0));
|
|
37
|
+
const minDay = addDays(new Date(now.getFullYear(), now.getMonth(), now.getDate()), Math.max(0, pickDays));
|
|
38
|
+
const moments = Array.isArray(carrier?.pickup_moments) ? carrier.pickup_moments : [];
|
|
39
|
+
const pickupWeekdays = Array.isArray(carrier?.pickup_weekdays) && carrier.pickup_weekdays.length
|
|
40
|
+
? carrier.pickup_weekdays.map(Number)
|
|
41
|
+
: null;
|
|
42
|
+
const deliveryWeekdays = Array.isArray(carrier?.delivery_weekdays) && carrier.delivery_weekdays.length
|
|
43
|
+
? carrier.delivery_weekdays.map(Number)
|
|
44
|
+
: null;
|
|
45
|
+
const usable = moments.filter((m) => parseTime(m?.signup_time));
|
|
46
|
+
if (usable.length > 0) {
|
|
47
|
+
for (let offset = 0; offset <= 60; offset++) {
|
|
48
|
+
const pickupDay = addDays(new Date(now.getFullYear(), now.getMonth(), now.getDate()), offset);
|
|
49
|
+
if (pickupWeekdays && !pickupWeekdays.includes(isoWeekday(pickupDay)))
|
|
50
|
+
continue;
|
|
51
|
+
let best = null;
|
|
52
|
+
for (const m of usable) {
|
|
53
|
+
const time = parseTime(m.signup_time);
|
|
54
|
+
const signupAt = atTime(addDays(pickupDay, -Math.max(0, Number(m.signup_days ?? 0))), time);
|
|
55
|
+
if (signupAt.getTime() <= now.getTime())
|
|
56
|
+
continue; // deadline verstreken
|
|
57
|
+
const deliveryDay = addDays(pickupDay, Math.max(0, Number(m.delivery_days ?? carrierDeliveryDays)));
|
|
58
|
+
if (deliveryWeekdays && !deliveryWeekdays.includes(isoWeekday(deliveryDay)))
|
|
59
|
+
continue;
|
|
60
|
+
if (deliveryDay.getTime() < minDay.getTime())
|
|
61
|
+
continue;
|
|
62
|
+
if (!best || deliveryDay.getTime() < best.date.getTime()) {
|
|
63
|
+
best = { date: deliveryDay, iso: toISODate(deliveryDay), signupAt, fromSchedule: true };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (best)
|
|
67
|
+
return best;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Geen aanmeldmomenten bekend: val terug op picktijd + levertijd vervoerder.
|
|
71
|
+
let fallback = addDays(minDay, carrierDeliveryDays);
|
|
72
|
+
for (let i = 0; i < 14 && deliveryWeekdays && !deliveryWeekdays.includes(isoWeekday(fallback)); i++) {
|
|
73
|
+
fallback = addDays(fallback, 1);
|
|
74
|
+
}
|
|
75
|
+
return { date: fallback, iso: toISODate(fallback), signupAt: null, fromSchedule: false };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Is deze ISO-datum een geldige bezorgdag voor deze vervoerder?
|
|
79
|
+
* Controleert de toegestane bezorgweekdagen en de eerst mogelijke datum.
|
|
80
|
+
*/
|
|
81
|
+
export function isValidDeliveryDate(carrier, iso, pickDays = 0, now = new Date()) {
|
|
82
|
+
if (!iso)
|
|
83
|
+
return false;
|
|
84
|
+
const earliest = computeEarliestDelivery(carrier, pickDays, now);
|
|
85
|
+
if (iso < earliest.iso)
|
|
86
|
+
return false;
|
|
87
|
+
const deliveryWeekdays = Array.isArray(carrier?.delivery_weekdays) && carrier.delivery_weekdays.length
|
|
88
|
+
? carrier.delivery_weekdays.map(Number)
|
|
89
|
+
: null;
|
|
90
|
+
if (!deliveryWeekdays)
|
|
91
|
+
return true;
|
|
92
|
+
const [y, m, d] = iso.split("-").map(Number);
|
|
93
|
+
if (!y || !m || !d)
|
|
94
|
+
return false;
|
|
95
|
+
return deliveryWeekdays.includes(isoWeekday(new Date(y, m - 1, d)));
|
|
96
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface EngineLoadCarrier {
|
|
2
|
+
code: string;
|
|
3
|
+
label: string;
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
isDefault: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface AllowedLoadCarriers {
|
|
8
|
+
allowed: string[];
|
|
9
|
+
default: string | null;
|
|
10
|
+
carriers: EngineLoadCarrier[];
|
|
11
|
+
}
|
|
12
|
+
export declare const EMPTY_LOAD_CARRIERS: AllowedLoadCarriers;
|
|
13
|
+
/** Normaliseert de Json-uitvoer van de Engine naar een vaste vorm. */
|
|
14
|
+
export declare function normalizeAllowedLoadCarriers(raw: unknown): AllowedLoadCarriers;
|
|
15
|
+
/** Alleen de dragers die de Engine toestaat (enabled === true). */
|
|
16
|
+
export declare function enabledLoadCarriers(list: AllowedLoadCarriers): EngineLoadCarrier[];
|
|
17
|
+
export declare function loadCarrierLabel(list: AllowedLoadCarriers, code: string | null): string | null;
|
|
18
|
+
export declare function isLoadCarrierAllowed(list: AllowedLoadCarriers, code: string | null): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Herkent een ladingdrager in vrije tekst. De kandidaten komen uit de
|
|
21
|
+
* Engine-lijst (ook uitgeschakelde dragers, zodat we kunnen melden dat de mail
|
|
22
|
+
* een uitgeschakelde drager noemt). Er wordt niets vertaald naar codes die de
|
|
23
|
+
* Engine niet kent.
|
|
24
|
+
*/
|
|
25
|
+
export declare function detectLoadCarrierInText(list: AllowedLoadCarriers, text: string | null | undefined): string | null;
|
|
26
|
+
//# sourceMappingURL=load-carriers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-carriers.d.ts","sourceRoot":"","sources":["../../src/lib/load-carriers.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAED,eAAO,MAAM,mBAAmB,EAAE,mBAIjC,CAAC;AAEF,sEAAsE;AACtE,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,OAAO,GAAG,mBAAmB,CAsB9E;AAED,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,iBAAiB,EAAE,CAElF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAG9F;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAG5F;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC9B,MAAM,GAAG,IAAI,CAkBf"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Ladingdragers komen UITSLUITEND uit de Logistics Engine
|
|
2
|
+
// (RPC logistics_allowed_load_carriers). Deze module bevat geen eigen lijst:
|
|
3
|
+
// alleen types en herkenning van tekst op basis van de Engine-uitvoer.
|
|
4
|
+
export const EMPTY_LOAD_CARRIERS = {
|
|
5
|
+
allowed: [],
|
|
6
|
+
default: null,
|
|
7
|
+
carriers: [],
|
|
8
|
+
};
|
|
9
|
+
/** Normaliseert de Json-uitvoer van de Engine naar een vaste vorm. */
|
|
10
|
+
export function normalizeAllowedLoadCarriers(raw) {
|
|
11
|
+
const r = (raw ?? {});
|
|
12
|
+
const carriers = Array.isArray(r.carriers)
|
|
13
|
+
? r.carriers
|
|
14
|
+
.filter((c) => c && typeof c.code === "string")
|
|
15
|
+
.map((c) => ({
|
|
16
|
+
code: String(c.code),
|
|
17
|
+
label: typeof c.label === "string" && c.label ? c.label : String(c.code),
|
|
18
|
+
enabled: c.enabled === true,
|
|
19
|
+
isDefault: c.isDefault === true,
|
|
20
|
+
}))
|
|
21
|
+
: [];
|
|
22
|
+
const allowed = Array.isArray(r.allowed)
|
|
23
|
+
? r.allowed.filter((c) => typeof c === "string")
|
|
24
|
+
: carriers.filter((c) => c.enabled).map((c) => c.code);
|
|
25
|
+
const def = typeof r.default === "string" && r.default
|
|
26
|
+
? r.default
|
|
27
|
+
: (carriers.find((c) => c.isDefault && c.enabled)?.code ??
|
|
28
|
+
carriers.find((c) => c.enabled)?.code ??
|
|
29
|
+
null);
|
|
30
|
+
return { allowed, default: def, carriers };
|
|
31
|
+
}
|
|
32
|
+
/** Alleen de dragers die de Engine toestaat (enabled === true). */
|
|
33
|
+
export function enabledLoadCarriers(list) {
|
|
34
|
+
return list.carriers.filter((c) => c.enabled && list.allowed.includes(c.code));
|
|
35
|
+
}
|
|
36
|
+
export function loadCarrierLabel(list, code) {
|
|
37
|
+
if (!code)
|
|
38
|
+
return null;
|
|
39
|
+
return list.carriers.find((c) => c.code === code)?.label ?? code;
|
|
40
|
+
}
|
|
41
|
+
export function isLoadCarrierAllowed(list, code) {
|
|
42
|
+
if (!code)
|
|
43
|
+
return false;
|
|
44
|
+
return list.allowed.includes(code);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Herkent een ladingdrager in vrije tekst. De kandidaten komen uit de
|
|
48
|
+
* Engine-lijst (ook uitgeschakelde dragers, zodat we kunnen melden dat de mail
|
|
49
|
+
* een uitgeschakelde drager noemt). Er wordt niets vertaald naar codes die de
|
|
50
|
+
* Engine niet kent.
|
|
51
|
+
*/
|
|
52
|
+
export function detectLoadCarrierInText(list, text) {
|
|
53
|
+
if (!text)
|
|
54
|
+
return null;
|
|
55
|
+
const hay = ` ${text.toLowerCase().replace(/[\s\u00a0]+/g, " ")} `;
|
|
56
|
+
// Langste labels eerst, zodat "Euro-rolcontainer" wint van "rolcontainer".
|
|
57
|
+
const candidates = [...list.carriers].sort((a, b) => b.label.length - a.label.length);
|
|
58
|
+
for (const c of candidates) {
|
|
59
|
+
const variants = new Set([
|
|
60
|
+
c.label.toLowerCase(),
|
|
61
|
+
c.label.toLowerCase().replace(/-/g, " "),
|
|
62
|
+
c.code.toLowerCase().replace(/_/g, " "),
|
|
63
|
+
c.code.toLowerCase(),
|
|
64
|
+
]);
|
|
65
|
+
for (const v of variants) {
|
|
66
|
+
if (v.length < 4)
|
|
67
|
+
continue;
|
|
68
|
+
if (hay.includes(` ${v} `) || hay.includes(` ${v}s `) || hay.includes(`${v},`))
|
|
69
|
+
return c.code;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Standaard-systeemprompt. Beheerders kunnen deze in Instellingen overschrijven. */
|
|
2
|
+
export declare const DEFAULT_MAILBOX_SYSTEM_PROMPT: string;
|
|
3
|
+
/** Velden die standaard verplicht uit de mail moeten komen. */
|
|
4
|
+
export declare const DEFAULT_REQUIRED_FIELDS: string[];
|
|
5
|
+
export declare const REQUIRED_FIELD_CHOICES: Array<{
|
|
6
|
+
key: string;
|
|
7
|
+
label: string;
|
|
8
|
+
}>;
|
|
9
|
+
//# sourceMappingURL=mailbox-ai-prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mailbox-ai-prompt.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ai-prompt.ts"],"names":[],"mappings":"AAEA,qFAAqF;AACrF,eAAO,MAAM,6BAA6B,QAW9B,CAAC;AAEb,+DAA+D;AAC/D,eAAO,MAAM,uBAAuB,UAKnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAQxE,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Client-veilige constanten voor de mailbox-AI (gedeeld door server en UI).
|
|
2
|
+
/** Standaard-systeemprompt. Beheerders kunnen deze in Instellingen overschrijven. */
|
|
3
|
+
export const DEFAULT_MAILBOX_SYSTEM_PROMPT = [
|
|
4
|
+
"Je bent een orderverwerker voor een plantenkwekerij.",
|
|
5
|
+
"Je leest een bestelmail (Nederlands of Engels) en zet die om in gestructureerde JSON.",
|
|
6
|
+
"Verzin NOOIT gegevens. Wat niet in de mail staat is null met confidence 0 en komt in validation.missing.",
|
|
7
|
+
"Haal ALTIJD op als de mail ze noemt: klant, gewenste bezorgdatum, artikelen, aantallen, plantsoort/variëteit en afleverlocatie.",
|
|
8
|
+
"Bezorgdatum: reken relatieve aanduidingen ('morgen', 'overmorgen', 'aanstaande maandag', 'volgende week vrijdag', 'week 32', 'begin volgende maand') altijd om naar één concrete datum in YYYY-MM-DD, gebruikmakend van de meegegeven datumcontext. Kun je het niet eenduidig bepalen, zet dan null met needs_review = true en zet het veld in validation.uncertain.",
|
|
9
|
+
"Plantsoort: herken de plantsoort/variëteit (bijv. 'Acer palmatum', 'Hydrangea Annabelle') en kies uitsluitend een naam uit de meegegeven plant_types-lijst; is er geen goede match, geef dan de letterlijke tekst uit de mail terug en zet de regel in validation.uncertain.",
|
|
10
|
+
"product_id moet een id uit de productcatalogus zijn; twijfel je, dan null met een lage match_confidence.",
|
|
11
|
+
"Voor velden met een 'allowed'-lijst gebruik je uitsluitend een id uit die lijst, anders null.",
|
|
12
|
+
"Zet needs_review op true bij confidence onder 0.8.",
|
|
13
|
+
"Controleer aan het eind zelf of alle verplichte velden (required_fields) zijn ingevuld en betrouwbaar zijn; noem ontbrekende velden in validation.missing en twijfelgevallen in validation.uncertain, met een korte toelichting in validation.notes. Gok nooit een waarde om een veld te vullen.",
|
|
14
|
+
].join("\n");
|
|
15
|
+
/** Velden die standaard verplicht uit de mail moeten komen. */
|
|
16
|
+
export const DEFAULT_REQUIRED_FIELDS = [
|
|
17
|
+
"customer",
|
|
18
|
+
"delivery_date",
|
|
19
|
+
"delivery_location",
|
|
20
|
+
"lines",
|
|
21
|
+
];
|
|
22
|
+
export const REQUIRED_FIELD_CHOICES = [
|
|
23
|
+
{ key: "customer", label: "Klant" },
|
|
24
|
+
{ key: "delivery_date", label: "Bezorgdatum" },
|
|
25
|
+
{ key: "delivery_location", label: "Afleveradres" },
|
|
26
|
+
{ key: "pickup_location", label: "Afhaallocatie / Tuin" },
|
|
27
|
+
{ key: "lines", label: "Bestelregels (artikelen + aantallen)" },
|
|
28
|
+
{ key: "reference", label: "Referentie klant" },
|
|
29
|
+
{ key: "notes", label: "Opmerking" },
|
|
30
|
+
];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const getMailboxAiSettings: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
2
|
+
supabase: import("@supabase/supabase-js").SupabaseClient<import("../integrations/supabase/types").Database>;
|
|
3
|
+
userId: string;
|
|
4
|
+
claims: import("@supabase/auth-js").User;
|
|
5
|
+
}, undefined, undefined, undefined>], undefined, Promise<{
|
|
6
|
+
id: any;
|
|
7
|
+
enabled: any;
|
|
8
|
+
system_prompt: string | null;
|
|
9
|
+
required_fields: string[];
|
|
10
|
+
default_prompt: string;
|
|
11
|
+
}>>;
|
|
12
|
+
export declare const saveMailboxAiSettings: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
13
|
+
supabase: import("@supabase/supabase-js").SupabaseClient<import("../integrations/supabase/types").Database>;
|
|
14
|
+
userId: string;
|
|
15
|
+
claims: import("@supabase/auth-js").User;
|
|
16
|
+
}, undefined, undefined, undefined>], (input: unknown) => {
|
|
17
|
+
system_prompt: string | null;
|
|
18
|
+
required_fields: string[];
|
|
19
|
+
}, Promise<{
|
|
20
|
+
ok: true;
|
|
21
|
+
id: string;
|
|
22
|
+
}>>;
|
|
23
|
+
//# sourceMappingURL=mailbox-ai-settings.functions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mailbox-ai-settings.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ai-settings.functions.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,oBAAoB;;;;;;;;;;GAgB7B,CAAC;AAOL,eAAO,MAAM,qBAAqB;;;;8CAER,OAAO;;;;;;GA8B7B,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Beheer van de centrale systeemprompt en verplichte velden voor de mailbox-AI.
|
|
2
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
3
|
+
import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { DEFAULT_MAILBOX_SYSTEM_PROMPT, DEFAULT_REQUIRED_FIELDS } from "./mailbox-ai-prompt";
|
|
6
|
+
export const getMailboxAiSettings = createServerFn({ method: "GET" })
|
|
7
|
+
.middleware([requireSupabaseAuth])
|
|
8
|
+
.handler(async ({ context }) => {
|
|
9
|
+
const { data } = await context.supabase
|
|
10
|
+
.from("mailbox_ai_settings")
|
|
11
|
+
.select("id, enabled, system_prompt, required_fields")
|
|
12
|
+
.limit(1)
|
|
13
|
+
.maybeSingle();
|
|
14
|
+
const req = Array.isArray(data?.required_fields) ? data.required_fields : [];
|
|
15
|
+
return {
|
|
16
|
+
id: data?.id ?? null,
|
|
17
|
+
enabled: data?.enabled ?? true,
|
|
18
|
+
system_prompt: data?.system_prompt ?? null,
|
|
19
|
+
required_fields: req.length > 0 ? req : DEFAULT_REQUIRED_FIELDS,
|
|
20
|
+
default_prompt: DEFAULT_MAILBOX_SYSTEM_PROMPT,
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
const SaveInput = z.object({
|
|
24
|
+
system_prompt: z.string().max(20000).nullable(),
|
|
25
|
+
required_fields: z.array(z.string().max(64)).max(30),
|
|
26
|
+
});
|
|
27
|
+
export const saveMailboxAiSettings = createServerFn({ method: "POST" })
|
|
28
|
+
.middleware([requireSupabaseAuth])
|
|
29
|
+
.inputValidator((input) => SaveInput.parse(input))
|
|
30
|
+
.handler(async ({ data, context }) => {
|
|
31
|
+
const prompt = (data.system_prompt ?? "").trim();
|
|
32
|
+
const payload = {
|
|
33
|
+
system_prompt: prompt.length > 0 ? prompt : null,
|
|
34
|
+
required_fields: data.required_fields.length > 0 ? data.required_fields : DEFAULT_REQUIRED_FIELDS,
|
|
35
|
+
};
|
|
36
|
+
const { data: existing } = await context.supabase
|
|
37
|
+
.from("mailbox_ai_settings")
|
|
38
|
+
.select("id")
|
|
39
|
+
.limit(1)
|
|
40
|
+
.maybeSingle();
|
|
41
|
+
if (existing?.id) {
|
|
42
|
+
const { error } = await context.supabase
|
|
43
|
+
.from("mailbox_ai_settings")
|
|
44
|
+
.update(payload)
|
|
45
|
+
.eq("id", existing.id);
|
|
46
|
+
if (error)
|
|
47
|
+
throw new Error(error.message);
|
|
48
|
+
return { ok: true, id: existing.id };
|
|
49
|
+
}
|
|
50
|
+
const { data: inserted, error } = await context.supabase
|
|
51
|
+
.from("mailbox_ai_settings")
|
|
52
|
+
.insert({ ...payload, singleton: true })
|
|
53
|
+
.select("id")
|
|
54
|
+
.single();
|
|
55
|
+
if (error)
|
|
56
|
+
throw new Error(error.message);
|
|
57
|
+
return { ok: true, id: inserted.id };
|
|
58
|
+
});
|