@flowselections/floriday-afhandeling-module 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist-lib/components/afhandeling/ControleLijstPrint.d.ts +7 -0
  2. package/dist-lib/components/afhandeling/ControleLijstPrint.d.ts.map +1 -0
  3. package/dist-lib/components/afhandeling/ControleLijstPrint.js +67 -0
  4. package/dist-lib/components/afhandeling/ControlePage.d.ts +2 -0
  5. package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -0
  6. package/dist-lib/components/afhandeling/ControlePage.js +456 -0
  7. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts +9 -0
  8. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -0
  9. package/dist-lib/components/afhandeling/DagDetailDialog.js +149 -0
  10. package/dist-lib/components/afhandeling/LeveringenPage.d.ts.map +1 -1
  11. package/dist-lib/components/afhandeling/LeveringenPage.js +11 -5
  12. package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -1
  13. package/dist-lib/components/afhandeling/PickAgendaView.js +13 -4
  14. package/dist-lib/components/afhandeling/PlanningAgendaPage.d.ts.map +1 -1
  15. package/dist-lib/components/afhandeling/PlanningAgendaPage.js +2 -2
  16. package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts +12 -0
  17. package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts.map +1 -0
  18. package/dist-lib/components/afhandeling/ServiceAreaSelect.js +79 -0
  19. package/dist-lib/components/afhandeling/VervoerderForm.d.ts.map +1 -1
  20. package/dist-lib/components/afhandeling/VervoerderForm.js +62 -6
  21. package/dist-lib/components/afhandeling/VervoerdersPage.d.ts.map +1 -1
  22. package/dist-lib/components/afhandeling/VervoerdersPage.js +25 -6
  23. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts +2 -1
  24. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
  25. package/dist-lib/components/afhandeling/WeekAgendaView.js +63 -18
  26. package/dist-lib/components/afhandeling/WeekdayPicker.d.ts +12 -0
  27. package/dist-lib/components/afhandeling/WeekdayPicker.d.ts.map +1 -0
  28. package/dist-lib/components/afhandeling/WeekdayPicker.js +30 -0
  29. package/dist-lib/components/pickstation/PakbonTemplate.d.ts +6 -2
  30. package/dist-lib/components/pickstation/PakbonTemplate.d.ts.map +1 -1
  31. package/dist-lib/components/pickstation/PakbonTemplate.js +37 -12
  32. package/dist-lib/components/settings/AfhandelingSettingsCard.d.ts.map +1 -1
  33. package/dist-lib/components/settings/AfhandelingSettingsCard.js +25 -6
  34. package/dist-lib/components/ui/responsive-dialog.d.ts +20 -0
  35. package/dist-lib/components/ui/responsive-dialog.d.ts.map +1 -0
  36. package/dist-lib/components/ui/responsive-dialog.js +44 -0
  37. package/dist-lib/hooks/useOrderPlanning.d.ts +3 -0
  38. package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
  39. package/dist-lib/hooks/useOrderPlanning.js +64 -12
  40. package/dist-lib/index.d.ts +1 -0
  41. package/dist-lib/index.d.ts.map +1 -1
  42. package/dist-lib/index.js +4 -2
  43. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2898 -147
  44. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  45. package/dist-lib/integrations/supabase/client.d.ts +2898 -147
  46. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  47. package/dist-lib/integrations/supabase/client.server.d.ts +2898 -147
  48. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  49. package/dist-lib/integrations/supabase/types.d.ts +2986 -155
  50. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  51. package/dist-lib/integrations/supabase/types.js +1 -0
  52. package/dist-lib/lib/afhandelingSettings.functions.d.ts +10666 -5151
  53. package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
  54. package/dist-lib/lib/afhandelingSettings.functions.js +42 -2
  55. package/dist-lib/lib/carrierMatch.server.d.ts +27 -0
  56. package/dist-lib/lib/carrierMatch.server.d.ts.map +1 -0
  57. package/dist-lib/lib/carrierMatch.server.js +75 -0
  58. package/dist-lib/lib/carrierSignupMail.server.d.ts +56 -0
  59. package/dist-lib/lib/carrierSignupMail.server.d.ts.map +1 -0
  60. package/dist-lib/lib/carrierSignupMail.server.js +229 -0
  61. package/dist-lib/lib/carriers.functions.d.ts +36404 -15470
  62. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  63. package/dist-lib/lib/carriers.functions.js +43 -2
  64. package/dist-lib/lib/cartAssignments.functions.d.ts +49609 -0
  65. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -0
  66. package/dist-lib/lib/cartAssignments.functions.js +130 -0
  67. package/dist-lib/lib/cartControl.functions.d.ts +99259 -0
  68. package/dist-lib/lib/cartControl.functions.d.ts.map +1 -0
  69. package/dist-lib/lib/cartControl.functions.js +489 -0
  70. package/dist-lib/lib/cartMoveImpact.d.ts +8 -0
  71. package/dist-lib/lib/cartMoveImpact.d.ts.map +1 -0
  72. package/dist-lib/lib/cartMoveImpact.js +26 -0
  73. package/dist-lib/lib/cartRescheduling.d.ts +30 -0
  74. package/dist-lib/lib/cartRescheduling.d.ts.map +1 -0
  75. package/dist-lib/lib/cartRescheduling.js +71 -0
  76. package/dist-lib/lib/floriday.functions.d.ts +2902 -147
  77. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  78. package/dist-lib/lib/floriday.functions.js +178 -98
  79. package/dist-lib/lib/geo/euServiceAreas.d.ts +30 -0
  80. package/dist-lib/lib/geo/euServiceAreas.d.ts.map +1 -0
  81. package/dist-lib/lib/geo/euServiceAreas.js +277 -0
  82. package/dist-lib/lib/invalidatePlanning.d.ts +11 -0
  83. package/dist-lib/lib/invalidatePlanning.d.ts.map +1 -0
  84. package/dist-lib/lib/invalidatePlanning.js +25 -0
  85. package/dist-lib/lib/pickstationI18n.d.ts +12 -0
  86. package/dist-lib/lib/pickstationI18n.d.ts.map +1 -1
  87. package/dist-lib/lib/pickstationI18n.js +72 -0
  88. package/dist-lib/lib/pickstationOrders.server.d.ts +2 -1
  89. package/dist-lib/lib/pickstationOrders.server.d.ts.map +1 -1
  90. package/dist-lib/lib/pickstationOrders.server.js +393 -178
  91. package/dist-lib/lib/pickstationTypes.d.ts +23 -2
  92. package/dist-lib/lib/pickstationTypes.d.ts.map +1 -1
  93. package/dist-lib/lib/planning.d.ts +41 -2
  94. package/dist-lib/lib/planning.d.ts.map +1 -1
  95. package/dist-lib/lib/planning.js +159 -36
  96. package/package.json +2 -2
@@ -0,0 +1,75 @@
1
+ // Server-only helper: pick the responsible carrier for an order based on
2
+ // the carrier's service area (country/province/city). Falls back to the
3
+ // carrier flagged is_default when nothing matches.
4
+ import { matchesServiceArea } from "./geo/euServiceAreas";
5
+ // Common country names → ISO 3166-1 alpha-2. Keep it small; anything else
6
+ // falls back to the default carrier.
7
+ const COUNTRY_MAP = {
8
+ nl: "NL", nederland: "NL", holland: "NL", netherlands: "NL", "the netherlands": "NL",
9
+ be: "BE", belgie: "BE", belgië: "BE", belgium: "BE", belgique: "BE",
10
+ de: "DE", duitsland: "DE", germany: "DE", deutschland: "DE",
11
+ fr: "FR", frankrijk: "FR", france: "FR",
12
+ lu: "LU", luxemburg: "LU", luxembourg: "LU",
13
+ gb: "GB", uk: "GB", "verenigd koninkrijk": "GB", "united kingdom": "GB", england: "GB",
14
+ ch: "CH", zwitserland: "CH", switzerland: "CH",
15
+ at: "AT", oostenrijk: "AT", austria: "AT",
16
+ it: "IT", italie: "IT", italië: "IT", italy: "IT",
17
+ es: "ES", spanje: "ES", spain: "ES",
18
+ pt: "PT", portugal: "PT",
19
+ pl: "PL", polen: "PL", poland: "PL",
20
+ dk: "DK", denemarken: "DK", denmark: "DK",
21
+ se: "SE", zweden: "SE", sweden: "SE",
22
+ no: "NO", noorwegen: "NO", norway: "NO",
23
+ fi: "FI", finland: "FI",
24
+ ie: "IE", ierland: "IE", ireland: "IE",
25
+ cz: "CZ", tsjechie: "CZ", tsjechië: "CZ", "czech republic": "CZ", czechia: "CZ",
26
+ hu: "HU", hongarije: "HU", hungary: "HU",
27
+ ro: "RO", roemenie: "RO", roemenië: "RO", romania: "RO",
28
+ bg: "BG", bulgarije: "BG", bulgaria: "BG",
29
+ sk: "SK", slowakije: "SK", slovakia: "SK",
30
+ si: "SI", slovenie: "SI", slovenië: "SI", slovenia: "SI",
31
+ hr: "HR", kroatie: "HR", kroatië: "HR", croatia: "HR",
32
+ gr: "GR", griekenland: "GR", greece: "GR",
33
+ ee: "EE", estland: "EE", estonia: "EE",
34
+ lv: "LV", letland: "LV", latvia: "LV",
35
+ lt: "LT", litouwen: "LT", lithuania: "LT",
36
+ mt: "MT", malta: "MT",
37
+ cy: "CY", cyprus: "CY",
38
+ };
39
+ export function normalizeCountry(raw) {
40
+ if (!raw)
41
+ return null;
42
+ const s = raw.trim();
43
+ if (!s)
44
+ return null;
45
+ if (/^[A-Z]{2}$/.test(s))
46
+ return s;
47
+ const iso = COUNTRY_MAP[s.toLowerCase()];
48
+ return iso ?? null;
49
+ }
50
+ /**
51
+ * Pick the carrier responsible for the given order address. Carriers are
52
+ * tried in ascending sort_order; the first one whose service area covers
53
+ * the address wins. If no carrier matches, the carrier with is_default =
54
+ * true is returned. Returns null when neither is available.
55
+ */
56
+ export function pickCarrierForAddress(carriers, address) {
57
+ const sorted = [...carriers].sort((a, b) => (a.sort_order ?? 9999) - (b.sort_order ?? 9999));
58
+ const target = {
59
+ country: normalizeCountry(address.country),
60
+ province: address.province ?? null,
61
+ city: address.city ?? null,
62
+ };
63
+ for (const c of sorted) {
64
+ if (!c.email)
65
+ continue;
66
+ const matched = matchesServiceArea({
67
+ countries: c.service_countries ?? [],
68
+ provinces: c.service_provinces ?? [],
69
+ cities: c.service_cities ?? [],
70
+ }, target);
71
+ if (matched)
72
+ return c;
73
+ }
74
+ return sorted.find((c) => c.is_default && c.email) ?? null;
75
+ }
@@ -0,0 +1,56 @@
1
+ export interface SignupMailOrderItem {
2
+ name: string;
3
+ quantity: number | null;
4
+ unit: string | null;
5
+ }
6
+ export interface SignupMailCustomer {
7
+ name: string;
8
+ contactPerson?: string | null;
9
+ email?: string | null;
10
+ phone?: string | null;
11
+ customerNumber?: string | null;
12
+ }
13
+ export interface SignupMailAddress {
14
+ name?: string | null;
15
+ street?: string | null;
16
+ houseNumber?: string | null;
17
+ postalCode?: string | null;
18
+ city?: string | null;
19
+ province?: string | null;
20
+ country?: string | null;
21
+ }
22
+ export interface SignupMailOrderDetail {
23
+ orderNumber: string;
24
+ orderDate?: string | null;
25
+ deliveryDate?: string | null;
26
+ deliveryType: string;
27
+ notes?: string | null;
28
+ cartCount: number | null;
29
+ plantCount: number | null;
30
+ customer: SignupMailCustomer;
31
+ deliveryLocation?: SignupMailAddress | null;
32
+ pickupLocations?: string[];
33
+ items?: SignupMailOrderItem[];
34
+ }
35
+ export interface SignupMailOrderRow {
36
+ orderNumber: string;
37
+ customerName: string;
38
+ deliveryType: string;
39
+ cartCount: number | null;
40
+ plantCount: number | null;
41
+ }
42
+ export interface SignupMailInput {
43
+ carrierName: string;
44
+ pickupDate: Date;
45
+ pickupTime: string;
46
+ signupDeadline: Date;
47
+ orders: SignupMailOrderDetail[];
48
+ }
49
+ export declare function buildCarrierSignupMail(input: SignupMailInput): {
50
+ subject: string;
51
+ html: string;
52
+ text: string;
53
+ totalCarts: number;
54
+ totalPlants: number;
55
+ };
56
+ //# sourceMappingURL=carrierSignupMail.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carrierSignupMail.server.d.ts","sourceRoot":"","sources":["../../src/lib/carrierSignupMail.server.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,gBAAgB,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC5C,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAC/B;AAGD,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,IAAI,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,IAAI,CAAC;IACrB,MAAM,EAAE,qBAAqB,EAAE,CAAC;CACjC;AAgJD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,eAAe,GAAG;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAoGA"}
@@ -0,0 +1,229 @@
1
+ // Server-only HTML/text builder for carrier signup digest e-mails.
2
+ function fmtDate(d) {
3
+ return d.toLocaleDateString("nl-NL", {
4
+ weekday: "long",
5
+ day: "2-digit",
6
+ month: "long",
7
+ year: "numeric",
8
+ });
9
+ }
10
+ function fmtDateTime(d) {
11
+ return d.toLocaleString("nl-NL", {
12
+ weekday: "short",
13
+ day: "2-digit",
14
+ month: "short",
15
+ hour: "2-digit",
16
+ minute: "2-digit",
17
+ });
18
+ }
19
+ function fmtIsoDate(s) {
20
+ if (!s)
21
+ return "Onbekend";
22
+ const d = new Date(s);
23
+ if (Number.isNaN(d.getTime()))
24
+ return s;
25
+ return d.toLocaleDateString("nl-NL", { day: "2-digit", month: "long", year: "numeric" });
26
+ }
27
+ function val(s) {
28
+ const v = (s ?? "").trim();
29
+ return v === "" ? "Onbekend" : escape(v);
30
+ }
31
+ function renderAddress(a) {
32
+ if (!a)
33
+ return `<div style="color:#6b7280;">Onbekend</div>`;
34
+ const streetLine = [a.street, a.houseNumber].filter(Boolean).join(" ").trim();
35
+ const cityLine = [a.postalCode, a.city].filter(Boolean).join(" ").trim();
36
+ const lines = [];
37
+ if (a.name && a.name.trim())
38
+ lines.push(`<strong>${escape(a.name)}</strong>`);
39
+ if (streetLine)
40
+ lines.push(escape(streetLine));
41
+ if (cityLine)
42
+ lines.push(escape(cityLine));
43
+ if (a.province && a.province.trim())
44
+ lines.push(escape(a.province));
45
+ if (a.country && a.country.trim())
46
+ lines.push(escape(a.country));
47
+ if (lines.length === 0)
48
+ return `<div style="color:#6b7280;">Onbekend</div>`;
49
+ return lines.map((l) => `<div>${l}</div>`).join("");
50
+ }
51
+ function renderDefinitionList(rows) {
52
+ return `<table style="border-collapse:collapse;font-size:13px;width:100%;">${rows
53
+ .map((r) => `
54
+ <tr>
55
+ <td style="padding:2px 12px 2px 0;color:#6b7280;width:140px;vertical-align:top;">${escape(r.label)}</td>
56
+ <td style="padding:2px 0;vertical-align:top;">${r.value}</td>
57
+ </tr>`)
58
+ .join("")}</table>`;
59
+ }
60
+ function renderItemsTable(items) {
61
+ if (!items || items.length === 0) {
62
+ return `<div style="color:#6b7280;font-size:13px;">Geen artikelen geregistreerd.</div>`;
63
+ }
64
+ const rows = items
65
+ .map((it) => `
66
+ <tr>
67
+ <td style="padding:6px 8px;border-bottom:1px solid #e5e7eb;">${escape(it.name)}</td>
68
+ <td style="padding:6px 8px;border-bottom:1px solid #e5e7eb;text-align:right;">${it.quantity ?? "—"}</td>
69
+ <td style="padding:6px 8px;border-bottom:1px solid #e5e7eb;">${escape(it.unit ?? "")}</td>
70
+ </tr>`)
71
+ .join("");
72
+ return `<table style="border-collapse:collapse;width:100%;font-size:13px;">
73
+ <thead><tr style="background:#f3f4f6;text-align:left;">
74
+ <th style="padding:6px 8px;">Artikel</th>
75
+ <th style="padding:6px 8px;text-align:right;width:90px;">Aantal</th>
76
+ <th style="padding:6px 8px;width:100px;">Eenheid</th>
77
+ </tr></thead>
78
+ <tbody>${rows}</tbody>
79
+ </table>`;
80
+ }
81
+ function renderOrderBlock(o) {
82
+ const orderRows = renderDefinitionList([
83
+ { label: "Ordernummer", value: `<strong>${val(o.orderNumber)}</strong>` },
84
+ { label: "Besteldatum", value: escape(fmtIsoDate(o.orderDate)) },
85
+ { label: "Gewenste leverdatum", value: escape(fmtIsoDate(o.deliveryDate)) },
86
+ { label: "Type levering", value: val(o.deliveryType) },
87
+ { label: "Karren", value: String(o.cartCount ?? "—") },
88
+ { label: "Planten", value: String(o.plantCount ?? "—") },
89
+ ]);
90
+ const customerRows = renderDefinitionList([
91
+ { label: "Klantnaam", value: val(o.customer.name) },
92
+ { label: "E-mailadres", value: val(o.customer.email) },
93
+ ]);
94
+ const pickupHtml = o.pickupLocations && o.pickupLocations.length > 0
95
+ ? o.pickupLocations.map((p) => `<div>${escape(p)}</div>`).join("")
96
+ : `<div style="color:#6b7280;">Onbekend</div>`;
97
+ const notesHtml = o.notes && o.notes.trim()
98
+ ? `<div style="background:#fffbeb;border:1px solid #fde68a;border-radius:6px;padding:10px 12px;font-size:13px;white-space:pre-wrap;">${escape(o.notes)}</div>`
99
+ : "";
100
+ return `
101
+ <div style="border:1px solid #e5e7eb;border-radius:8px;padding:16px;margin-bottom:20px;">
102
+ <h3 style="margin:0 0 12px 0;font-size:16px;">Order ${escape(o.orderNumber)}</h3>
103
+
104
+ <div style="margin-bottom:14px;">
105
+ <div style="font-weight:bold;font-size:13px;margin-bottom:6px;color:#111827;">Ordergegevens</div>
106
+ ${orderRows}
107
+ </div>
108
+
109
+ <div style="margin-bottom:14px;">
110
+ <div style="font-weight:bold;font-size:13px;margin-bottom:6px;color:#111827;">Klantgegevens</div>
111
+ ${customerRows}
112
+ </div>
113
+
114
+ <table style="width:100%;border-collapse:collapse;margin-bottom:14px;">
115
+ <tr>
116
+ <td style="vertical-align:top;width:50%;padding-right:8px;">
117
+ <div style="font-weight:bold;font-size:13px;margin-bottom:6px;color:#111827;">Afleverlocatie</div>
118
+ <div style="font-size:13px;">${renderAddress(o.deliveryLocation)}</div>
119
+ </td>
120
+ <td style="vertical-align:top;width:50%;padding-left:8px;">
121
+ <div style="font-weight:bold;font-size:13px;margin-bottom:6px;color:#111827;">Ophaallocatie</div>
122
+ <div style="font-size:13px;">${pickupHtml}</div>
123
+ </td>
124
+ </tr>
125
+ </table>
126
+
127
+ <div style="margin-bottom:14px;">
128
+ <div style="font-weight:bold;font-size:13px;margin-bottom:6px;color:#111827;">Bestelde artikelen</div>
129
+ ${renderItemsTable(o.items)}
130
+ </div>
131
+
132
+ ${notesHtml ? `<div><div style="font-weight:bold;font-size:13px;margin-bottom:6px;color:#111827;">Opmerkingen</div>${notesHtml}</div>` : ""}
133
+ </div>`;
134
+ }
135
+ export function buildCarrierSignupMail(input) {
136
+ const totalCarts = input.orders.reduce((s, o) => s + (o.cartCount ?? 0), 0);
137
+ const totalPlants = input.orders.reduce((s, o) => s + (o.plantCount ?? 0), 0);
138
+ const subject = `Aanmelden karren ${input.carrierName} — ${input.pickupDate.toLocaleDateString("nl-NL", { day: "2-digit", month: "2-digit", year: "numeric" })} ${input.pickupTime}`;
139
+ const summaryRows = input.orders
140
+ .map((o) => `
141
+ <tr>
142
+ <td style="padding:6px 8px;border-bottom:1px solid #e5e7eb;">${escape(o.orderNumber)}</td>
143
+ <td style="padding:6px 8px;border-bottom:1px solid #e5e7eb;">${escape(o.customer.name)}</td>
144
+ <td style="padding:6px 8px;border-bottom:1px solid #e5e7eb;">${escape(o.deliveryType)}</td>
145
+ <td style="padding:6px 8px;border-bottom:1px solid #e5e7eb;text-align:right;">${o.cartCount ?? "—"}</td>
146
+ <td style="padding:6px 8px;border-bottom:1px solid #e5e7eb;text-align:right;">${o.plantCount ?? "—"}</td>
147
+ </tr>`)
148
+ .join("");
149
+ const orderBlocks = input.orders.map(renderOrderBlock).join("");
150
+ const html = `<!doctype html>
151
+ <html lang="nl"><body style="font-family:Arial,sans-serif;color:#111;background:#f9fafb;margin:0;padding:24px;">
152
+ <div style="max-width:760px;margin:0 auto;background:#ffffff;padding:24px;border-radius:8px;">
153
+ <h2 style="margin:0 0 8px 0;">Aanmelden karren — ${escape(input.carrierName)}</h2>
154
+ <p style="margin:0 0 20px 0;color:#374151;font-size:14px;">
155
+ Ophaalmoment: <strong>${escape(fmtDate(input.pickupDate))} om ${escape(input.pickupTime)}</strong><br/>
156
+ Aanmelddeadline: <strong>${escape(fmtDateTime(input.signupDeadline))}</strong>
157
+ </p>
158
+
159
+ <div style="font-weight:bold;font-size:14px;margin-bottom:6px;">Overzicht</div>
160
+ <table style="border-collapse:collapse;width:100%;font-size:13px;margin-bottom:24px;">
161
+ <thead><tr style="background:#f3f4f6;text-align:left;">
162
+ <th style="padding:6px 8px;">Order</th>
163
+ <th style="padding:6px 8px;">Klant</th>
164
+ <th style="padding:6px 8px;">Type</th>
165
+ <th style="padding:6px 8px;text-align:right;">Karren</th>
166
+ <th style="padding:6px 8px;text-align:right;">Planten</th>
167
+ </tr></thead>
168
+ <tbody>${summaryRows}</tbody>
169
+ <tfoot><tr style="font-weight:bold;background:#f9fafb;">
170
+ <td colspan="3" style="padding:6px 8px;text-align:right;">Totaal</td>
171
+ <td style="padding:6px 8px;text-align:right;">${totalCarts}</td>
172
+ <td style="padding:6px 8px;text-align:right;">${totalPlants}</td>
173
+ </tr></tfoot>
174
+ </table>
175
+
176
+ ${orderBlocks}
177
+
178
+ <p style="margin-top:16px;font-size:12px;color:#6b7280;">
179
+ Automatisch verzonden door Flowselections Afhandeling.
180
+ </p>
181
+ </div>
182
+ </body></html>`;
183
+ const textOrders = input.orders
184
+ .map((o) => {
185
+ const addr = o.deliveryLocation
186
+ ? [
187
+ o.deliveryLocation.name,
188
+ [o.deliveryLocation.street, o.deliveryLocation.houseNumber].filter(Boolean).join(" "),
189
+ [o.deliveryLocation.postalCode, o.deliveryLocation.city].filter(Boolean).join(" "),
190
+ o.deliveryLocation.province,
191
+ o.deliveryLocation.country,
192
+ ]
193
+ .filter((v) => v && String(v).trim())
194
+ .join(", ")
195
+ : "Onbekend";
196
+ const items = (o.items ?? [])
197
+ .map((it) => ` - ${it.name}: ${it.quantity ?? "-"} ${it.unit ?? ""}`.trim())
198
+ .join("\n");
199
+ return [
200
+ `Order ${o.orderNumber}`,
201
+ ` Besteldatum: ${fmtIsoDate(o.orderDate)}`,
202
+ ` Leverdatum: ${fmtIsoDate(o.deliveryDate)}`,
203
+ ` Type: ${o.deliveryType}`,
204
+ ` Karren: ${o.cartCount ?? "-"} | Planten: ${o.plantCount ?? "-"}`,
205
+ ` Klant: ${o.customer.name}` +
206
+ (o.customer.email ? ` <${o.customer.email}>` : ""),
207
+ ` Afleverlocatie: ${addr}`,
208
+ ` Ophaallocatie: ${(o.pickupLocations ?? []).join(", ") || "Onbekend"}`,
209
+ items ? ` Artikelen:\n${items}` : ` Artikelen: -`,
210
+ o.notes ? ` Opmerkingen: ${o.notes}` : "",
211
+ ]
212
+ .filter(Boolean)
213
+ .join("\n");
214
+ })
215
+ .join("\n\n");
216
+ const text = `Aanmelden karren — ${input.carrierName}\n` +
217
+ `Ophaalmoment: ${fmtDate(input.pickupDate)} ${input.pickupTime}\n` +
218
+ `Aanmelddeadline: ${fmtDateTime(input.signupDeadline)}\n\n` +
219
+ textOrders +
220
+ `\n\nTotaal: ${totalCarts} karren, ${totalPlants} planten.`;
221
+ return { subject, html, text, totalCarts, totalPlants };
222
+ }
223
+ function escape(s) {
224
+ return String(s)
225
+ .replace(/&/g, "&amp;")
226
+ .replace(/</g, "&lt;")
227
+ .replace(/>/g, "&gt;")
228
+ .replace(/"/g, "&quot;");
229
+ }