@flowselections/mailbox-orders 1.0.30 → 1.0.33
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/MailboxProcessedPage.js +1 -1
- package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +202 -122
- package/dist-lib/components/MailboxSkippedPage.d.ts +2 -0
- package/dist-lib/components/MailboxSkippedPage.d.ts.map +1 -0
- package/dist-lib/components/MailboxSkippedPage.js +71 -0
- package/dist-lib/components/settings/MailboxFilterSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/MailboxFilterSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/MailboxFilterSettingsCard.js +78 -0
- package/dist-lib/components/settings/MailboxTestSetCard.d.ts +2 -0
- package/dist-lib/components/settings/MailboxTestSetCard.d.ts.map +1 -0
- package/dist-lib/components/settings/MailboxTestSetCard.js +69 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +8 -2
- package/dist-lib/integrations/supabase/types.d.ts +1442 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-ai.server.d.ts +33 -12
- package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-ai.server.js +47 -17
- package/dist-lib/lib/mailbox-filter-settings.functions.d.ts +143 -0
- package/dist-lib/lib/mailbox-filter-settings.functions.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-filter-settings.functions.js +241 -0
- package/dist-lib/lib/mailbox-filter.d.ts +34 -0
- package/dist-lib/lib/mailbox-filter.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-filter.js +121 -0
- package/dist-lib/lib/mailbox-ingest.server.d.ts +114 -17
- package/dist-lib/lib/mailbox-ingest.server.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-ingest.server.js +594 -266
- package/dist-lib/lib/mailbox-match.d.ts +28 -0
- package/dist-lib/lib/mailbox-match.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-match.js +74 -0
- package/dist-lib/lib/mailbox-units.d.ts +18 -0
- package/dist-lib/lib/mailbox-units.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-units.js +80 -0
- package/dist-lib/lib/mailbox.functions.d.ts +74 -0
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.js +170 -25
- package/package.json +6 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface MatchProduct {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
codes: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function normalizeCode(v: unknown): string;
|
|
7
|
+
/** Codes die in een tekst staan (cijferreeksen en artikelcodes). */
|
|
8
|
+
export declare function extractCodes(text: string): string[];
|
|
9
|
+
/** Fractie van de producttokens die in de tekst voorkomen (0..1). */
|
|
10
|
+
export declare function nameSimilarity(productName: string, text: string): number;
|
|
11
|
+
/** Exact één product met deze code, anders null (geen gok bij dubbele codes). */
|
|
12
|
+
export declare function exactCodeMatch(code: unknown, products: MatchProduct[]): MatchProduct | null;
|
|
13
|
+
/** Beste kandidaten voor een stuk tekst: codetreffers eerst, dan naam. */
|
|
14
|
+
export declare function rankCandidates(text: string, products: MatchProduct[], max: number): MatchProduct[];
|
|
15
|
+
export interface LineMatch {
|
|
16
|
+
productId: string | null;
|
|
17
|
+
source: "code" | "ai" | "naam" | null;
|
|
18
|
+
confidence: number;
|
|
19
|
+
}
|
|
20
|
+
/** Koppelt één regel: code uit de mail wint, dan AI-keuze uit de kandidaten, dan naam. */
|
|
21
|
+
export declare function matchLine(line: {
|
|
22
|
+
code?: string | null;
|
|
23
|
+
raw_text?: string | null;
|
|
24
|
+
name_in_mail?: string | null;
|
|
25
|
+
product_id?: string | null;
|
|
26
|
+
ai_confidence?: number | null;
|
|
27
|
+
}, products: MatchProduct[], allowedIds: Set<string>): LineMatch;
|
|
28
|
+
//# sourceMappingURL=mailbox-match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mailbox-match.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-match.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAEhD;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAOnD;AAUD,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMxE;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,YAAY,GAAG,IAAI,CAK3F;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAWlG;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,0FAA0F;AAC1F,wBAAgB,SAAS,CACvB,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACjJ,QAAQ,EAAE,YAAY,EAAE,EACxB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,SAAS,CAmBX"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Productmatching zonder AI: eerst exacte codes, daarna naam-gelijkenis.
|
|
2
|
+
// Matcht altijd op product-id; de naam is alleen een zoekhulp.
|
|
3
|
+
export function normalizeCode(v) {
|
|
4
|
+
return String(v ?? "").toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
5
|
+
}
|
|
6
|
+
/** Codes die in een tekst staan (cijferreeksen en artikelcodes). */
|
|
7
|
+
export function extractCodes(text) {
|
|
8
|
+
const out = new Set();
|
|
9
|
+
for (const m of (text ?? "").matchAll(/\b[A-Za-z]{0,4}\d[\dA-Za-z-]{3,20}\b/g)) {
|
|
10
|
+
const c = normalizeCode(m[0]);
|
|
11
|
+
if (c.length >= 4)
|
|
12
|
+
out.add(c);
|
|
13
|
+
}
|
|
14
|
+
return Array.from(out);
|
|
15
|
+
}
|
|
16
|
+
const tokens = (s) => (s ?? "")
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
.normalize("NFD")
|
|
19
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
20
|
+
.split(/[^a-z0-9#]+/)
|
|
21
|
+
.filter((t) => t.length >= 2);
|
|
22
|
+
/** Fractie van de producttokens die in de tekst voorkomen (0..1). */
|
|
23
|
+
export function nameSimilarity(productName, text) {
|
|
24
|
+
const p = tokens(productName);
|
|
25
|
+
if (p.length === 0)
|
|
26
|
+
return 0;
|
|
27
|
+
const t = new Set(tokens(text));
|
|
28
|
+
const hit = p.filter((x) => t.has(x)).length;
|
|
29
|
+
return hit / p.length;
|
|
30
|
+
}
|
|
31
|
+
/** Exact één product met deze code, anders null (geen gok bij dubbele codes). */
|
|
32
|
+
export function exactCodeMatch(code, products) {
|
|
33
|
+
const c = normalizeCode(code);
|
|
34
|
+
if (c.length < 4)
|
|
35
|
+
return null;
|
|
36
|
+
const hits = products.filter((p) => p.codes.some((x) => normalizeCode(x) === c));
|
|
37
|
+
return hits.length === 1 ? hits[0] : null;
|
|
38
|
+
}
|
|
39
|
+
/** Beste kandidaten voor een stuk tekst: codetreffers eerst, dan naam. */
|
|
40
|
+
export function rankCandidates(text, products, max) {
|
|
41
|
+
const codes = new Set(extractCodes(text));
|
|
42
|
+
const scored = products.map((p) => {
|
|
43
|
+
const codeHit = p.codes.some((c) => codes.has(normalizeCode(c)));
|
|
44
|
+
return { p, score: (codeHit ? 2 : 0) + nameSimilarity(p.name, text) };
|
|
45
|
+
});
|
|
46
|
+
return scored
|
|
47
|
+
.filter((x) => x.score > 0)
|
|
48
|
+
.sort((a, b) => b.score - a.score)
|
|
49
|
+
.slice(0, Math.max(1, max))
|
|
50
|
+
.map((x) => x.p);
|
|
51
|
+
}
|
|
52
|
+
/** Koppelt één regel: code uit de mail wint, dan AI-keuze uit de kandidaten, dan naam. */
|
|
53
|
+
export function matchLine(line, products, allowedIds) {
|
|
54
|
+
const byCode = exactCodeMatch(line.code, products) ??
|
|
55
|
+
extractCodes(`${line.raw_text ?? ""}`)
|
|
56
|
+
.map((c) => exactCodeMatch(c, products))
|
|
57
|
+
.find((x) => x) ??
|
|
58
|
+
null;
|
|
59
|
+
if (byCode)
|
|
60
|
+
return { productId: byCode.id, source: "code", confidence: 1 };
|
|
61
|
+
if (line.product_id && allowedIds.has(line.product_id)) {
|
|
62
|
+
return { productId: line.product_id, source: "ai", confidence: Math.min(1, Math.max(0, Number(line.ai_confidence ?? 0.6))) };
|
|
63
|
+
}
|
|
64
|
+
const text = line.name_in_mail || line.raw_text || "";
|
|
65
|
+
let best = null;
|
|
66
|
+
for (const p of products) {
|
|
67
|
+
const s = nameSimilarity(p.name, text);
|
|
68
|
+
if (!best || s > best.s)
|
|
69
|
+
best = { p, s };
|
|
70
|
+
}
|
|
71
|
+
if (best && best.s >= 0.75)
|
|
72
|
+
return { productId: best.p.id, source: "naam", confidence: Math.round(best.s * 60) / 100 };
|
|
73
|
+
return { productId: null, source: null, confidence: 0 };
|
|
74
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type UnitKind = "stuk" | "fust" | "laag" | "kar" | "onbekend";
|
|
2
|
+
export interface BeladingDims {
|
|
3
|
+
plants_per_crate: number;
|
|
4
|
+
crates_per_layer: number;
|
|
5
|
+
layers_per_cart: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const UNIT_KIND_LABEL: Record<UnitKind, string>;
|
|
8
|
+
export declare function normalizeUnitKind(raw: unknown): UnitKind;
|
|
9
|
+
/** Planten per eenheid voor een belading. */
|
|
10
|
+
export declare function plantsPerUnit(kind: UnitKind, b: BeladingDims | null): number | null;
|
|
11
|
+
/** Aantal uit de mail × planten per eenheid. Null als het niet te bepalen is. */
|
|
12
|
+
export declare function toPlants(quantity: unknown, kind: UnitKind, b: BeladingDims | null): number | null;
|
|
13
|
+
/** Prijs per plant, ongeacht of de mail per stuk of per eenheid noemt. */
|
|
14
|
+
export declare function unitPricePerPlant(price: unknown, pricePer: "stuk" | "eenheid" | null | undefined, kind: UnitKind, b: BeladingDims | null): number | null;
|
|
15
|
+
/** Uitleg zoals in het scherm: "2 fust × 20 = 40 planten". */
|
|
16
|
+
export declare function describeConversion(quantity: unknown, kind: UnitKind, b: BeladingDims | null): string | null;
|
|
17
|
+
export declare function lineTotal(plants: number | null, pricePerPlant: number | null): number;
|
|
18
|
+
//# sourceMappingURL=mailbox-units.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mailbox-units.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-units.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAMpD,CAAC;AASF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,CAQxD;AAID,6CAA6C;AAC7C,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAOnF;AAED,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAKjG;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,SAAS,EAC/C,IAAI,EAAE,QAAQ,EACd,CAAC,EAAE,YAAY,GAAG,IAAI,GACrB,MAAM,GAAG,IAAI,CAQf;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAO3G;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGrF"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Aantal + eenheid uit de mail omrekenen naar planten via de belading.
|
|
2
|
+
// Puur: geen database, gedeeld door server en scherm.
|
|
3
|
+
export const UNIT_KIND_LABEL = {
|
|
4
|
+
stuk: "stuks",
|
|
5
|
+
fust: "fust",
|
|
6
|
+
laag: "laag",
|
|
7
|
+
kar: "kar",
|
|
8
|
+
onbekend: "onbekend",
|
|
9
|
+
};
|
|
10
|
+
const UNIT_WORDS = [
|
|
11
|
+
["kar", /^(kar|karren|cart|carts|trolley|trolleys|cc|cc-kar|container|containers|deense kar)$/],
|
|
12
|
+
["laag", /^(laag|lagen|layer|layers|lage|plank|planken|shelf|shelves)$/],
|
|
13
|
+
["fust", /^(fust|fusten|tray|trays|doos|dozen|box|boxes|kist|kisten|krat|kratten|crate|crates|karton|kartons|colli)$/],
|
|
14
|
+
["stuk", /^(stuk|stuks|st|stk|plant|planten|pcs|pc|piece|pieces|stück|stueck|pflanzen|ex|exemplaren)$/],
|
|
15
|
+
];
|
|
16
|
+
export function normalizeUnitKind(raw) {
|
|
17
|
+
const t = String(raw ?? "").trim().toLowerCase().replace(/[.:]/g, "");
|
|
18
|
+
if (!t)
|
|
19
|
+
return "onbekend";
|
|
20
|
+
if (t === "stuk" || t === "fust" || t === "laag" || t === "kar" || t === "onbekend")
|
|
21
|
+
return t;
|
|
22
|
+
for (const [kind, re] of UNIT_WORDS)
|
|
23
|
+
if (re.test(t))
|
|
24
|
+
return kind;
|
|
25
|
+
const first = t.split(/\s+/)[0];
|
|
26
|
+
for (const [kind, re] of UNIT_WORDS)
|
|
27
|
+
if (re.test(first))
|
|
28
|
+
return kind;
|
|
29
|
+
return "onbekend";
|
|
30
|
+
}
|
|
31
|
+
const pos = (n) => Math.max(1, Math.round(Number(n ?? 1)) || 1);
|
|
32
|
+
/** Planten per eenheid voor een belading. */
|
|
33
|
+
export function plantsPerUnit(kind, b) {
|
|
34
|
+
if (kind === "stuk")
|
|
35
|
+
return 1;
|
|
36
|
+
if (!b || kind === "onbekend")
|
|
37
|
+
return null;
|
|
38
|
+
const ppc = pos(b.plants_per_crate);
|
|
39
|
+
if (kind === "fust")
|
|
40
|
+
return ppc;
|
|
41
|
+
if (kind === "laag")
|
|
42
|
+
return ppc * pos(b.crates_per_layer);
|
|
43
|
+
return ppc * pos(b.crates_per_layer) * pos(b.layers_per_cart);
|
|
44
|
+
}
|
|
45
|
+
/** Aantal uit de mail × planten per eenheid. Null als het niet te bepalen is. */
|
|
46
|
+
export function toPlants(quantity, kind, b) {
|
|
47
|
+
const q = Number(quantity);
|
|
48
|
+
if (!Number.isFinite(q) || q <= 0)
|
|
49
|
+
return null;
|
|
50
|
+
const per = plantsPerUnit(kind, b);
|
|
51
|
+
return per === null ? null : Math.round(q * per);
|
|
52
|
+
}
|
|
53
|
+
/** Prijs per plant, ongeacht of de mail per stuk of per eenheid noemt. */
|
|
54
|
+
export function unitPricePerPlant(price, pricePer, kind, b) {
|
|
55
|
+
const p = Number(price);
|
|
56
|
+
if (!Number.isFinite(p) || p <= 0)
|
|
57
|
+
return null;
|
|
58
|
+
if (pricePer === "eenheid" && kind !== "stuk") {
|
|
59
|
+
const per = plantsPerUnit(kind, b);
|
|
60
|
+
return per ? Math.round((p / per) * 10000) / 10000 : null;
|
|
61
|
+
}
|
|
62
|
+
return p;
|
|
63
|
+
}
|
|
64
|
+
/** Uitleg zoals in het scherm: "2 fust × 20 = 40 planten". */
|
|
65
|
+
export function describeConversion(quantity, kind, b) {
|
|
66
|
+
const q = Number(quantity);
|
|
67
|
+
if (!Number.isFinite(q) || q <= 0)
|
|
68
|
+
return null;
|
|
69
|
+
if (kind === "stuk")
|
|
70
|
+
return `${q} planten`;
|
|
71
|
+
const per = plantsPerUnit(kind, b);
|
|
72
|
+
if (per === null)
|
|
73
|
+
return null;
|
|
74
|
+
return `${q} ${UNIT_KIND_LABEL[kind]} × ${per} = ${Math.round(q * per)} planten`;
|
|
75
|
+
}
|
|
76
|
+
export function lineTotal(plants, pricePerPlant) {
|
|
77
|
+
if (!plants || !pricePerPlant)
|
|
78
|
+
return 0;
|
|
79
|
+
return Math.round(plants * pricePerPlant * 100) / 100;
|
|
80
|
+
}
|
|
@@ -150,11 +150,20 @@ export declare const approveProposal: import("@tanstack/start-client-core").Opti
|
|
|
150
150
|
quantity: number | null;
|
|
151
151
|
unit: string | null;
|
|
152
152
|
id?: string | undefined;
|
|
153
|
+
unit_price?: number | null | undefined;
|
|
154
|
+
belading?: {
|
|
155
|
+
plants_per_crate: number;
|
|
156
|
+
crates_per_layer: number;
|
|
157
|
+
layers_per_cart: number;
|
|
158
|
+
} | null | undefined;
|
|
153
159
|
}[];
|
|
154
160
|
fieldValues?: {
|
|
155
161
|
field_key: string;
|
|
156
162
|
value: any;
|
|
157
163
|
}[] | undefined;
|
|
164
|
+
customerReference?: string | null | undefined;
|
|
165
|
+
momentKey?: string | null | undefined;
|
|
166
|
+
acceptMissingPrice?: boolean | undefined;
|
|
158
167
|
}, Promise<{
|
|
159
168
|
ok: true;
|
|
160
169
|
orderId: string;
|
|
@@ -402,4 +411,69 @@ export declare const listBeladingOptionsForProducts: import("@tanstack/start-cli
|
|
|
402
411
|
plants_per_cart: number;
|
|
403
412
|
})[];
|
|
404
413
|
}>>;
|
|
414
|
+
export declare const getDeparturePreview: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
415
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
416
|
+
userId: string;
|
|
417
|
+
claims: import("@supabase/auth-js").User;
|
|
418
|
+
}, undefined, undefined, undefined>], (input: unknown) => {
|
|
419
|
+
carrierId: string;
|
|
420
|
+
deliveryLocationId: string;
|
|
421
|
+
deliveryDate: string;
|
|
422
|
+
momentKey?: string | null | undefined;
|
|
423
|
+
}, Promise<{
|
|
424
|
+
ok: false;
|
|
425
|
+
error: any;
|
|
426
|
+
preview: null;
|
|
427
|
+
} | {
|
|
428
|
+
ok: true;
|
|
429
|
+
error: string | null;
|
|
430
|
+
preview: any;
|
|
431
|
+
}>>;
|
|
432
|
+
export declare const getAgreementPrices: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
433
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
434
|
+
userId: string;
|
|
435
|
+
claims: import("@supabase/auth-js").User;
|
|
436
|
+
}, undefined, undefined, undefined>], (input: unknown) => {
|
|
437
|
+
customerId: string;
|
|
438
|
+
onDate: string;
|
|
439
|
+
lines: {
|
|
440
|
+
productId: string;
|
|
441
|
+
quantity: number;
|
|
442
|
+
}[];
|
|
443
|
+
}, Promise<{
|
|
444
|
+
prices: Record<string, {
|
|
445
|
+
net_price: number;
|
|
446
|
+
currency: string | null;
|
|
447
|
+
source: string | null;
|
|
448
|
+
} | null>;
|
|
449
|
+
}>>;
|
|
450
|
+
export declare const listSkippedMessages: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
451
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
452
|
+
userId: string;
|
|
453
|
+
claims: import("@supabase/auth-js").User;
|
|
454
|
+
}, undefined, undefined, undefined>], undefined, Promise<{
|
|
455
|
+
items: any[];
|
|
456
|
+
}>>;
|
|
457
|
+
/** "Toch analyseren" / "Nu analyseren": slaat het filter en de limiet over. */
|
|
458
|
+
export declare const analyzeMessageNow: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
459
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
460
|
+
userId: string;
|
|
461
|
+
claims: import("@supabase/auth-js").User;
|
|
462
|
+
}, undefined, undefined, undefined>], (input: unknown) => {
|
|
463
|
+
messageId: string;
|
|
464
|
+
}, Promise<{
|
|
465
|
+
ok: true;
|
|
466
|
+
error: string | null;
|
|
467
|
+
} | {
|
|
468
|
+
ok: false;
|
|
469
|
+
error: string;
|
|
470
|
+
}>>;
|
|
471
|
+
/** Aantal voorstellen met een verouderde analyse (na wijziging van de AI-agent). */
|
|
472
|
+
export declare const countOutdatedProposals: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
473
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
474
|
+
userId: string;
|
|
475
|
+
claims: import("@supabase/auth-js").User;
|
|
476
|
+
}, undefined, undefined, undefined>], undefined, Promise<{
|
|
477
|
+
count: number;
|
|
478
|
+
}>>;
|
|
405
479
|
//# sourceMappingURL=mailbox.functions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,6 @@ import { createServerFn } from "@tanstack/react-start";
|
|
|
3
3
|
import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware.js";
|
|
4
4
|
import { createClient } from "@supabase/supabase-js";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { computeEarliestDelivery } from "./delivery-date.js";
|
|
7
6
|
import { emailBodyText, inlineCidImages, normalizeAddressList, normalizeAttachments, sanitizeEmailHtml, stripInvisible, } from "./mailbox-body.js";
|
|
8
7
|
import { EMPTY_LOAD_CARRIERS, isLoadCarrierAllowed, loadCarrierLabel, normalizeAllowedLoadCarriers, } from "./load-carriers.js";
|
|
9
8
|
// ─── Actieve mailbox (uit gedeelde e-mail-auth module) ──────────────────────
|
|
@@ -133,7 +132,7 @@ export const parseMessage = createServerFn({ method: "POST" })
|
|
|
133
132
|
if (msgErr || !msg)
|
|
134
133
|
throw new Error("Bericht niet gevonden");
|
|
135
134
|
// AI staat uit: maak een leeg voorstel zodat het bericht in "Voorstellen" verschijnt.
|
|
136
|
-
await createStubProposal(supabaseAdmin, msg.id, msg.from_email ?? null);
|
|
135
|
+
await createStubProposal(supabaseAdmin, msg.id, msg.from_email ?? null, { force: true, trigger: "handmatig" });
|
|
137
136
|
await supabaseAdmin.from("mailbox_messages")
|
|
138
137
|
.update({ status: "parsed", error_message: null }).eq("id", msg.id);
|
|
139
138
|
return { ok: true };
|
|
@@ -149,7 +148,8 @@ export const listProposals = createServerFn({ method: "GET" })
|
|
|
149
148
|
const sel = (s) => s;
|
|
150
149
|
let q = supabaseAdmin
|
|
151
150
|
.from("mailbox_order_proposals")
|
|
152
|
-
.select(sel("id, status, created_at, reviewed_at, matched_customer_id, ai_ok, " +
|
|
151
|
+
.select(sel("id, status, created_at, reviewed_at, matched_customer_id, ai_ok, customer_reference, " +
|
|
152
|
+
"readiness:parsed_payload->>readiness, duplicate:parsed_payload->duplicate_of, " +
|
|
153
153
|
"mailbox_messages(subject, from_email, from_name, received_at, connection_id, preview), " +
|
|
154
154
|
"mailbox_proposal_field_values(field_key, value, needs_review)"))
|
|
155
155
|
.order("created_at", { ascending: false })
|
|
@@ -171,7 +171,29 @@ export const listProposals = createServerFn({ method: "GET" })
|
|
|
171
171
|
.slice(0, 180);
|
|
172
172
|
return { ...row, mailbox_messages: { ...m, preview } };
|
|
173
173
|
});
|
|
174
|
-
|
|
174
|
+
// Klantnamen direct meeleveren (op id), zodat de lijst nooit het
|
|
175
|
+
// e-mailadres toont als de klant bekend is.
|
|
176
|
+
const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
177
|
+
const idFor = (row) => {
|
|
178
|
+
const fvs = Array.isArray(row?.mailbox_proposal_field_values) ? row.mailbox_proposal_field_values : [];
|
|
179
|
+
const v = fvs.find((x) => x.field_key === "customer")?.value;
|
|
180
|
+
if (typeof v === "string" && uuidRe.test(v))
|
|
181
|
+
return v;
|
|
182
|
+
return row?.matched_customer_id ?? null;
|
|
183
|
+
};
|
|
184
|
+
const ids = Array.from(new Set(items.map(idFor).filter(Boolean)));
|
|
185
|
+
const names = new Map();
|
|
186
|
+
for (let i = 0; i < ids.length; i += 200) {
|
|
187
|
+
const { data: cs } = await supabaseAdmin.from("customers").select("id, company_name").in("id", ids.slice(i, i + 200));
|
|
188
|
+
for (const c of Array.isArray(cs) ? cs : [])
|
|
189
|
+
if (c.company_name)
|
|
190
|
+
names.set(c.id, c.company_name);
|
|
191
|
+
}
|
|
192
|
+
const withNames = items.map((row) => {
|
|
193
|
+
const id = idFor(row);
|
|
194
|
+
return { ...row, customer_id: id, customer_name: id ? names.get(id) ?? null : null };
|
|
195
|
+
});
|
|
196
|
+
return { items: withNames };
|
|
175
197
|
});
|
|
176
198
|
/** Eén voorstel volledig: mailtekst, bestelregels en veldwaarden. */
|
|
177
199
|
export const getProposalDetail = createServerFn({ method: "GET" })
|
|
@@ -233,8 +255,18 @@ const LineEdit = z.object({
|
|
|
233
255
|
id: z.string().uuid().optional(),
|
|
234
256
|
product_id: z.string().uuid().nullable(),
|
|
235
257
|
raw_product_text: z.string().max(500),
|
|
258
|
+
/** Aantal in planten (al omgerekend via de belading). */
|
|
236
259
|
quantity: z.number().nullable(),
|
|
237
260
|
unit: z.string().max(50).nullable(),
|
|
261
|
+
unit_price: z.number().nonnegative().nullable().optional(),
|
|
262
|
+
belading: z
|
|
263
|
+
.object({
|
|
264
|
+
plants_per_crate: z.number().int().positive(),
|
|
265
|
+
crates_per_layer: z.number().int().positive(),
|
|
266
|
+
layers_per_cart: z.number().int().positive(),
|
|
267
|
+
})
|
|
268
|
+
.nullable()
|
|
269
|
+
.optional(),
|
|
238
270
|
});
|
|
239
271
|
const FieldValueInput = z.object({
|
|
240
272
|
field_key: z.string().min(1).max(64),
|
|
@@ -250,6 +282,9 @@ export const approveProposal = createServerFn({ method: "POST" })
|
|
|
250
282
|
notes: z.string().max(2000).nullable(),
|
|
251
283
|
lines: z.array(LineEdit).min(1),
|
|
252
284
|
fieldValues: z.array(FieldValueInput).max(100).optional(),
|
|
285
|
+
customerReference: z.string().max(120).nullable().optional(),
|
|
286
|
+
momentKey: z.string().max(200).nullable().optional(),
|
|
287
|
+
acceptMissingPrice: z.boolean().optional(),
|
|
253
288
|
}).parse(input))
|
|
254
289
|
.handler(async ({ data, context }) => {
|
|
255
290
|
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
@@ -270,28 +305,42 @@ export const approveProposal = createServerFn({ method: "POST" })
|
|
|
270
305
|
throw new Error(`Ladingdrager "${loadCarrierLabel(lcList, loadCarrierType) ?? "onbekend"}" is uitgeschakeld in de Rule Engine — bestelling niet aangemaakt.`);
|
|
271
306
|
}
|
|
272
307
|
customFields["load_carrier"] = loadCarrierType;
|
|
273
|
-
// ───
|
|
308
|
+
// ─── Leverdatum: haalbaarheid volgens de Logistics Engine ───────────────
|
|
309
|
+
// De gevraagde datum is leidend; de Engine beslist of hij haalbaar is.
|
|
274
310
|
const carrierId = typeof customFields["carrier"] === "string" ? customFields["carrier"] : null;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
311
|
+
const deliveryLocationId = typeof customFields["delivery_location"] === "string" ? customFields["delivery_location"] : null;
|
|
312
|
+
if (!carrierId)
|
|
313
|
+
throw new Error("Kies een vervoerder.");
|
|
314
|
+
if (!deliveryLocationId)
|
|
315
|
+
throw new Error("Kies een afleverlocatie.");
|
|
316
|
+
const { data: preview, error: previewErr } = await context.supabase.rpc("logistics_departure_preview", {
|
|
317
|
+
p_carrier_id: carrierId,
|
|
318
|
+
p_delivery_location: deliveryLocationId,
|
|
319
|
+
p_delivery_date: data.deliveryDate,
|
|
320
|
+
p_moment_key: data.momentKey ?? null,
|
|
321
|
+
});
|
|
322
|
+
if (previewErr)
|
|
323
|
+
throw new Error(`Vertrekcontrole mislukt: ${previewErr.message}`);
|
|
324
|
+
const pv = (preview ?? {});
|
|
325
|
+
if (pv.ok !== true)
|
|
326
|
+
throw new Error(pv.reason ? `Vertrekcontrole: ${pv.reason}` : "Vertrekcontrole gaf geen uitkomst.");
|
|
327
|
+
if (pv.deliveryDateFeasible !== true) {
|
|
328
|
+
throw new Error(`Leverdatum ${data.deliveryDate} is niet haalbaar: ${pv.reason ?? "geen reden opgegeven"}` +
|
|
329
|
+
(pv.firstFeasibleDeliveryDate ? ` Eerst haalbaar: ${pv.firstFeasibleDeliveryDate}.` : ""));
|
|
284
330
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (data.deliveryDate < earliestISO) {
|
|
293
|
-
throw new Error(`Leverdatum (${data.deliveryDate}) ligt voor het eerstvolgende aanmeldmoment van de vervoerder (${earliestISO}).`);
|
|
331
|
+
if (pv.chosen?.momentKey)
|
|
332
|
+
customFields["moment_key"] = pv.chosen.momentKey;
|
|
333
|
+
if (pv.chosen?.departureAt)
|
|
334
|
+
customFields["departure_at"] = pv.chosen.departureAt;
|
|
335
|
+
// ─── Prijs en referentie ────────────────────────────────────────────────
|
|
336
|
+
if (data.lines.some((l) => l.unit_price === null || l.unit_price === undefined) && data.acceptMissingPrice !== true) {
|
|
337
|
+
throw new Error("Niet alle regels hebben een prijs. Bevestig dat je zonder prijs wilt doorgaan.");
|
|
294
338
|
}
|
|
339
|
+
const reference = (data.customerReference ?? "").trim();
|
|
340
|
+
if (reference)
|
|
341
|
+
customFields["customer_reference"] = reference;
|
|
342
|
+
else
|
|
343
|
+
delete customFields["customer_reference"];
|
|
295
344
|
// ─── Voorraadcontrole door de Logistics Engine (nooit zelf beslissen) ────
|
|
296
345
|
const checkLines = data.lines
|
|
297
346
|
.filter((l) => l.product_id)
|
|
@@ -343,7 +392,11 @@ export const approveProposal = createServerFn({ method: "POST" })
|
|
|
343
392
|
p_lines: data.lines.map((line) => ({
|
|
344
393
|
product_id: line.product_id,
|
|
345
394
|
quantity: line.quantity,
|
|
346
|
-
unit:
|
|
395
|
+
unit: "planten",
|
|
396
|
+
unit_price: line.unit_price ?? null,
|
|
397
|
+
plants_per_crate: line.belading?.plants_per_crate ?? null,
|
|
398
|
+
crates_per_layer: line.belading?.crates_per_layer ?? null,
|
|
399
|
+
layers_per_cart: line.belading?.layers_per_cart ?? null,
|
|
347
400
|
})),
|
|
348
401
|
p_field_values: data.fieldValues ?? [],
|
|
349
402
|
});
|
|
@@ -361,7 +414,8 @@ export const approveProposal = createServerFn({ method: "POST" })
|
|
|
361
414
|
p_order_ref: result.orderNumber,
|
|
362
415
|
p_payload: {
|
|
363
416
|
checkinAt: data.deliveryDate,
|
|
364
|
-
pickupAt: data.deliveryDate,
|
|
417
|
+
pickupAt: pv.chosen?.departureAt ?? data.deliveryDate,
|
|
418
|
+
momentKey: pv.chosen?.momentKey ?? null,
|
|
365
419
|
customerRef: data.customerId,
|
|
366
420
|
deliveryLocationRef,
|
|
367
421
|
bufferRef: null,
|
|
@@ -1069,3 +1123,94 @@ export const listBeladingOptionsForProducts = createServerFn({ method: "POST" })
|
|
|
1069
1123
|
}
|
|
1070
1124
|
return { byProduct, allBeladingen: sortCombos(globalList) };
|
|
1071
1125
|
});
|
|
1126
|
+
// ─── Vertrekcontrole (Logistics Engine) ─────────────────────────────────────
|
|
1127
|
+
// Antwoord van logistics_departure_preview (gecontroleerd 19-10-2026):
|
|
1128
|
+
// { ok, reason, carrierId, timezone, moments[{momentKey, departureAt, signupDeadline, feasible}],
|
|
1129
|
+
// chosen{momentKey, departureAt, signupDeadline}, deliveryDateFeasible,
|
|
1130
|
+
// firstFeasibleDeliveryDate, firstFeasibleReason }
|
|
1131
|
+
export const getDeparturePreview = createServerFn({ method: "POST" })
|
|
1132
|
+
.middleware([requireSupabaseAuth])
|
|
1133
|
+
.inputValidator((input) => z.object({
|
|
1134
|
+
carrierId: z.string().uuid(),
|
|
1135
|
+
deliveryLocationId: z.string().min(1).max(100),
|
|
1136
|
+
deliveryDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
1137
|
+
momentKey: z.string().max(200).nullable().optional(),
|
|
1138
|
+
}).parse(input))
|
|
1139
|
+
.handler(async ({ data, context }) => {
|
|
1140
|
+
const { data: res, error } = await context.supabase.rpc("logistics_departure_preview", {
|
|
1141
|
+
p_carrier_id: data.carrierId,
|
|
1142
|
+
p_delivery_location: data.deliveryLocationId,
|
|
1143
|
+
p_delivery_date: data.deliveryDate,
|
|
1144
|
+
p_moment_key: data.momentKey ?? null,
|
|
1145
|
+
});
|
|
1146
|
+
if (error)
|
|
1147
|
+
return { ok: false, error: error.message, preview: null };
|
|
1148
|
+
return { ok: true, error: null, preview: (res ?? null) };
|
|
1149
|
+
});
|
|
1150
|
+
// ─── Prijsafspraak (local_effective_price) ──────────────────────────────────
|
|
1151
|
+
export const getAgreementPrices = createServerFn({ method: "POST" })
|
|
1152
|
+
.middleware([requireSupabaseAuth])
|
|
1153
|
+
.inputValidator((input) => z.object({
|
|
1154
|
+
customerId: z.string().uuid(),
|
|
1155
|
+
onDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
1156
|
+
lines: z.array(z.object({ productId: z.string().uuid(), quantity: z.number().nonnegative() })).max(100),
|
|
1157
|
+
}).parse(input))
|
|
1158
|
+
.handler(async ({ data, context }) => {
|
|
1159
|
+
const out = {};
|
|
1160
|
+
for (const l of data.lines) {
|
|
1161
|
+
const { data: rows, error } = await context.supabase.rpc("local_effective_price", {
|
|
1162
|
+
_customer_id: data.customerId,
|
|
1163
|
+
_product_id: l.productId,
|
|
1164
|
+
_on_date: data.onDate,
|
|
1165
|
+
_quantity: l.quantity,
|
|
1166
|
+
});
|
|
1167
|
+
const r = !error && Array.isArray(rows) ? rows[0] : null;
|
|
1168
|
+
out[l.productId] = r && r.net_price !== null && r.net_price !== undefined
|
|
1169
|
+
? { net_price: Number(r.net_price), currency: r.currency ?? null, source: r.source ?? null }
|
|
1170
|
+
: null;
|
|
1171
|
+
}
|
|
1172
|
+
return { prices: out };
|
|
1173
|
+
});
|
|
1174
|
+
// ─── Overgeslagen en wachtende berichten ────────────────────────────────────
|
|
1175
|
+
export const listSkippedMessages = createServerFn({ method: "GET" })
|
|
1176
|
+
.middleware([requireSupabaseAuth])
|
|
1177
|
+
.handler(async () => {
|
|
1178
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
1179
|
+
const { data, error } = await supabaseAdmin
|
|
1180
|
+
.from("mailbox_messages")
|
|
1181
|
+
.select("id, from_email, from_name, subject, received_at, preview, filter_outcome, filter_reason, analysis_state, analysis_note, mailbox_order_proposals(id)")
|
|
1182
|
+
.not("filter_outcome", "is", null)
|
|
1183
|
+
.order("received_at", { ascending: false })
|
|
1184
|
+
.limit(500);
|
|
1185
|
+
if (error)
|
|
1186
|
+
throw new Error(error.message);
|
|
1187
|
+
const items = (Array.isArray(data) ? data : [])
|
|
1188
|
+
.filter((m) => !(Array.isArray(m.mailbox_order_proposals) && m.mailbox_order_proposals.length > 0))
|
|
1189
|
+
.map(({ mailbox_order_proposals: _p, ...m }) => m);
|
|
1190
|
+
return { items };
|
|
1191
|
+
});
|
|
1192
|
+
/** "Toch analyseren" / "Nu analyseren": slaat het filter en de limiet over. */
|
|
1193
|
+
export const analyzeMessageNow = createServerFn({ method: "POST" })
|
|
1194
|
+
.middleware([requireSupabaseAuth])
|
|
1195
|
+
.inputValidator((input) => z.object({ messageId: z.string().uuid() }).parse(input))
|
|
1196
|
+
.handler(async ({ data }) => {
|
|
1197
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
1198
|
+
const { createStubProposal } = await import("./mailbox-ingest.server.js");
|
|
1199
|
+
try {
|
|
1200
|
+
const res = await createStubProposal(supabaseAdmin, data.messageId, null, { force: true, trigger: "handmatig" });
|
|
1201
|
+
if (res.status === "busy")
|
|
1202
|
+
return { ok: false, error: "Deze mail wordt al geanalyseerd." };
|
|
1203
|
+
return { ok: true, error: null };
|
|
1204
|
+
}
|
|
1205
|
+
catch (e) {
|
|
1206
|
+
return { ok: false, error: String(e?.message ?? e) };
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
/** Aantal voorstellen met een verouderde analyse (na wijziging van de AI-agent). */
|
|
1210
|
+
export const countOutdatedProposals = createServerFn({ method: "GET" })
|
|
1211
|
+
.middleware([requireSupabaseAuth])
|
|
1212
|
+
.handler(async () => {
|
|
1213
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
1214
|
+
const { countOutdatedAgentProposals } = await import("./mailbox-ingest.server.js");
|
|
1215
|
+
return { count: await countOutdatedAgentProposals(supabaseAdmin) };
|
|
1216
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowselections/mailbox-orders",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.33",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -91,6 +91,11 @@
|
|
|
91
91
|
"export": "MailboxProcessedPage",
|
|
92
92
|
"route": "mailbox-orders/verwerkt",
|
|
93
93
|
"title": "Verwerkt"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"export": "MailboxSkippedPage",
|
|
97
|
+
"route": "mailbox-orders/overgeslagen",
|
|
98
|
+
"title": "Overgeslagen"
|
|
94
99
|
}
|
|
95
100
|
],
|
|
96
101
|
"serverRoutes": [
|