@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.
Files changed (39) hide show
  1. package/dist-lib/components/MailboxProcessedPage.js +1 -1
  2. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
  3. package/dist-lib/components/MailboxProposalsPage.js +202 -122
  4. package/dist-lib/components/MailboxSkippedPage.d.ts +2 -0
  5. package/dist-lib/components/MailboxSkippedPage.d.ts.map +1 -0
  6. package/dist-lib/components/MailboxSkippedPage.js +71 -0
  7. package/dist-lib/components/settings/MailboxFilterSettingsCard.d.ts +2 -0
  8. package/dist-lib/components/settings/MailboxFilterSettingsCard.d.ts.map +1 -0
  9. package/dist-lib/components/settings/MailboxFilterSettingsCard.js +78 -0
  10. package/dist-lib/components/settings/MailboxTestSetCard.d.ts +2 -0
  11. package/dist-lib/components/settings/MailboxTestSetCard.d.ts.map +1 -0
  12. package/dist-lib/components/settings/MailboxTestSetCard.js +69 -0
  13. package/dist-lib/index.d.ts +1 -0
  14. package/dist-lib/index.d.ts.map +1 -1
  15. package/dist-lib/index.js +8 -2
  16. package/dist-lib/integrations/supabase/types.d.ts +1442 -0
  17. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  18. package/dist-lib/lib/mailbox-ai.server.d.ts +33 -12
  19. package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -1
  20. package/dist-lib/lib/mailbox-ai.server.js +47 -17
  21. package/dist-lib/lib/mailbox-filter-settings.functions.d.ts +143 -0
  22. package/dist-lib/lib/mailbox-filter-settings.functions.d.ts.map +1 -0
  23. package/dist-lib/lib/mailbox-filter-settings.functions.js +241 -0
  24. package/dist-lib/lib/mailbox-filter.d.ts +34 -0
  25. package/dist-lib/lib/mailbox-filter.d.ts.map +1 -0
  26. package/dist-lib/lib/mailbox-filter.js +121 -0
  27. package/dist-lib/lib/mailbox-ingest.server.d.ts +114 -17
  28. package/dist-lib/lib/mailbox-ingest.server.d.ts.map +1 -1
  29. package/dist-lib/lib/mailbox-ingest.server.js +594 -266
  30. package/dist-lib/lib/mailbox-match.d.ts +28 -0
  31. package/dist-lib/lib/mailbox-match.d.ts.map +1 -0
  32. package/dist-lib/lib/mailbox-match.js +74 -0
  33. package/dist-lib/lib/mailbox-units.d.ts +18 -0
  34. package/dist-lib/lib/mailbox-units.d.ts.map +1 -0
  35. package/dist-lib/lib/mailbox-units.js +80 -0
  36. package/dist-lib/lib/mailbox.functions.d.ts +74 -0
  37. package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
  38. package/dist-lib/lib/mailbox.functions.js +170 -25
  39. 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":"AA6BA,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;;;;GAmC7B,CAAC;AAIL,qEAAqE;AACrE,eAAO,MAAM,iBAAiB;;;;8CAEJ,OAAO;;;;GAyC7B,CAAC;AASL,eAAO,MAAM,uBAAuB;;;;;WAO+B,MAAM,GAAG,IAAI;;;;GAC5E,CAAC;AAgBL,eAAO,MAAM,eAAe;;;;8CAEF,OAAO;;;;;;;;;;;;;;;;;;;;;;;GA4K7B,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;AAKL,eAAO,MAAM,qBAAqB;;;;;;;GAiH9B,CAAC;AAiBL,eAAO,MAAM,oBAAoB;;;;8CAEP,OAAO;;;;;qBAqBS,MAAM;sBAAgB,MAAM;gBAAU,MAAM;gBAAU,MAAM;kBAAY,MAAM;;;;uBAhCvG,MAAM;oBACT,MAAM,GAAG,IAAI;sBACX,MAAM;0BAEF,MAAM;0BACN,MAAM;yBACP,MAAM;;;qBA0FW,MAAM;eAClB,MAAM,GAAG,IAAI;;;;GAoBjC,CAAC;AAiBL,eAAO,MAAM,eAAe;;;;8CAEF,OAAO;;;;;;;;;;;;;;;;GAuC7B,CAAC;AAEL,eAAO,MAAM,cAAc;;;;0CAEL;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE;aAAR,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAQnC,CAAC;AAEL,eAAO,MAAM,gCAAgC;;;;0CAEvB;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE;gBAAR,MAAM;;;;;;;;;;;;;;;;;;;GAUtC,CAAC;AAKL,eAAO,MAAM,mBAAmB;;;;;;YAqBD,MAAM;;;;;GAQjC,CAAC;AAEL,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;GAS9B,CAAC;AAGL,eAAO,MAAM,gBAAgB;;;;;;GAUzB,CAAC;AAiBL,eAAO,MAAM,8BAA8B;;;;8CAEjB,OAAO;;;;0BAOT,MAAM;0BAAoB,MAAM;yBAAmB,MAAM;oBAC/D,OAAO;oBACP,MAAM,GAAG,IAAI;oBAAc,MAAM,GAAG,IAAI;sBACtC,MAAM,GAAG,IAAI;sBAAgB,MAAM,GAAG,IAAI;;yBAEO,MAAM;yBAAmB,MAAM;;;0BAL5E,MAAM;0BAAoB,MAAM;yBAAmB,MAAM;oBAC/D,OAAO;oBACP,MAAM,GAAG,IAAI;oBAAc,MAAM,GAAG,IAAI;sBACtC,MAAM,GAAG,IAAI;sBAAgB,MAAM,GAAG,IAAI;;;;;GAsM1D,CAAC"}
1
+ {"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"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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
- return { items };
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
- // ─── Validatie eerst mogelijke leverdatum ────────────────────────────────
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
- let carrierDeliveryDays = 0;
276
- let carrierSchedule = null;
277
- if (carrierId) {
278
- const { data: c } = await context.supabase
279
- .from("carriers")
280
- .select("delivery_days, pickup_weekdays, delivery_weekdays, pickup_moments")
281
- .eq("id", carrierId).maybeSingle();
282
- carrierSchedule = c ?? null;
283
- carrierDeliveryDays = Math.max(0, Number(c?.delivery_days ?? 0));
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
- const { data: settings } = await context.supabase
286
- .from("afhandeling_settings")
287
- .select("pick_days_before_transport")
288
- .order("updated_at", { ascending: false })
289
- .limit(1).maybeSingle();
290
- const pickDays = Math.max(0, Number(settings?.pick_days_before_transport ?? 0));
291
- const earliestISO = computeEarliestDelivery(carrierSchedule, pickDays).iso;
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: line.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.30",
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": [