@flowselections/mailbox-orders 1.0.34 → 1.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +89 -10
- package/dist-lib/integrations/supabase/types.d.ts +110 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.d.ts +30 -0
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.js +125 -82
- package/dist-lib/lib/verkoop-order.d.ts +14 -0
- package/dist-lib/lib/verkoop-order.d.ts.map +1 -0
- package/dist-lib/lib/verkoop-order.js +24 -0
- package/package.json +1 -1
|
@@ -156,6 +156,7 @@ export declare const approveProposal: import("@tanstack/start-client-core").Opti
|
|
|
156
156
|
crates_per_layer: number;
|
|
157
157
|
layers_per_cart: number;
|
|
158
158
|
} | null | undefined;
|
|
159
|
+
needs_sticker?: boolean | undefined;
|
|
159
160
|
}[];
|
|
160
161
|
fieldValues?: {
|
|
161
162
|
field_key: string;
|
|
@@ -164,12 +165,27 @@ export declare const approveProposal: import("@tanstack/start-client-core").Opti
|
|
|
164
165
|
customerReference?: string | null | undefined;
|
|
165
166
|
momentKey?: string | null | undefined;
|
|
166
167
|
acceptMissingPrice?: boolean | undefined;
|
|
168
|
+
lateSignup?: {
|
|
169
|
+
reason: string;
|
|
170
|
+
} | null | undefined;
|
|
167
171
|
}, Promise<{
|
|
172
|
+
ok: false;
|
|
173
|
+
reason: import("./verkoop-order.js").VerkoopRefusalKind;
|
|
174
|
+
message: string;
|
|
175
|
+
nextDeliveryDate: any;
|
|
176
|
+
orderId?: undefined;
|
|
177
|
+
orderNumber?: undefined;
|
|
178
|
+
alreadyApproved?: undefined;
|
|
179
|
+
shortages?: undefined;
|
|
180
|
+
} | {
|
|
168
181
|
ok: true;
|
|
169
182
|
orderId: string;
|
|
170
183
|
orderNumber: string;
|
|
171
184
|
alreadyApproved: boolean;
|
|
172
185
|
shortages: any[];
|
|
186
|
+
reason?: undefined;
|
|
187
|
+
message?: undefined;
|
|
188
|
+
nextDeliveryDate?: undefined;
|
|
173
189
|
}>>;
|
|
174
190
|
export declare const rejectProposal: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
175
191
|
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
@@ -476,4 +492,18 @@ export declare const countOutdatedProposals: import("@tanstack/start-client-core
|
|
|
476
492
|
}, undefined, undefined, undefined>], undefined, Promise<{
|
|
477
493
|
count: number;
|
|
478
494
|
}>>;
|
|
495
|
+
export declare const getStickerDefaults: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
496
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
497
|
+
userId: string;
|
|
498
|
+
claims: import("@supabase/auth-js").User;
|
|
499
|
+
}, undefined, undefined, undefined>], (input: unknown) => {
|
|
500
|
+
customerId: string | null;
|
|
501
|
+
productIds: string[];
|
|
502
|
+
}, Promise<{
|
|
503
|
+
available: false;
|
|
504
|
+
defaults: Record<string, boolean>;
|
|
505
|
+
} | {
|
|
506
|
+
available: true;
|
|
507
|
+
defaults: Record<string, boolean>;
|
|
508
|
+
}>>;
|
|
479
509
|
//# 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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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { classifyVerkoopRefusal, existingOrderOrFilter, defaultNeedsSticker } from "./verkoop-order.js";
|
|
1
2
|
// Server functions voor de Mailbox-bestellingen module
|
|
2
3
|
import { createServerFn } from "@tanstack/react-start";
|
|
3
4
|
import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware.js";
|
|
@@ -267,6 +268,7 @@ const LineEdit = z.object({
|
|
|
267
268
|
})
|
|
268
269
|
.nullable()
|
|
269
270
|
.optional(),
|
|
271
|
+
needs_sticker: z.boolean().optional(),
|
|
270
272
|
});
|
|
271
273
|
const FieldValueInput = z.object({
|
|
272
274
|
field_key: z.string().min(1).max(64),
|
|
@@ -285,6 +287,7 @@ export const approveProposal = createServerFn({ method: "POST" })
|
|
|
285
287
|
customerReference: z.string().max(120).nullable().optional(),
|
|
286
288
|
momentKey: z.string().max(200).nullable().optional(),
|
|
287
289
|
acceptMissingPrice: z.boolean().optional(),
|
|
290
|
+
lateSignup: z.object({ reason: z.string().min(3).max(500) }).nullable().optional(),
|
|
288
291
|
}).parse(input))
|
|
289
292
|
.handler(async ({ data, context }) => {
|
|
290
293
|
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
@@ -305,42 +308,36 @@ export const approveProposal = createServerFn({ method: "POST" })
|
|
|
305
308
|
throw new Error(`Ladingdrager "${loadCarrierLabel(lcList, loadCarrierType) ?? "onbekend"}" is uitgeschakeld in de Rule Engine — bestelling niet aangemaakt.`);
|
|
306
309
|
}
|
|
307
310
|
customFields["load_carrier"] = loadCarrierType;
|
|
308
|
-
// ───
|
|
309
|
-
//
|
|
311
|
+
// ─── Prijs en referentie ────────────────────────────────────────────────
|
|
312
|
+
// Verkoop accepteert geen regel zonder prijs; dus hier al blokkeren.
|
|
313
|
+
if (data.lines.some((l) => l.unit_price === null || l.unit_price === undefined || l.unit_price <= 0)) {
|
|
314
|
+
throw new Error("Niet alle regels hebben een prijs. Vul bij elke regel een prijs per stuk in.");
|
|
315
|
+
}
|
|
316
|
+
const reference = (data.customerReference ?? "").trim();
|
|
317
|
+
if (reference)
|
|
318
|
+
customFields["customer_reference"] = reference;
|
|
319
|
+
else
|
|
320
|
+
delete customFields["customer_reference"];
|
|
310
321
|
const carrierId = typeof customFields["carrier"] === "string" ? customFields["carrier"] : null;
|
|
311
322
|
const deliveryLocationId = typeof customFields["delivery_location"] === "string" ? customFields["delivery_location"] : null;
|
|
312
323
|
if (!carrierId)
|
|
313
324
|
throw new Error("Kies een vervoerder.");
|
|
314
325
|
if (!deliveryLocationId)
|
|
315
326
|
throw new Error("Kies een afleverlocatie.");
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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}.` : ""));
|
|
327
|
+
if (!data.customerId)
|
|
328
|
+
throw new Error("Selecteer een bestaande klant.");
|
|
329
|
+
if (data.lines.some((line) => !line.product_id || !line.quantity || line.quantity <= 0)) {
|
|
330
|
+
throw new Error("Iedere bestelregel moet aan een bestaand product gekoppeld zijn.");
|
|
330
331
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
332
|
+
// ─── Dubbele orders voorkomen: zoek op BEIDE sleutels ───────────────────
|
|
333
|
+
const existing = await findExistingOrderForProposal(supabaseAdmin, data.proposalId);
|
|
334
|
+
if (existing) {
|
|
335
|
+
await markProposalApproved(supabaseAdmin, data.proposalId, context.userId, existing, data.notes, reference, data.fieldValues ?? []);
|
|
336
|
+
return {
|
|
337
|
+
ok: true, orderId: existing.id, orderNumber: existing.order_number,
|
|
338
|
+
alreadyApproved: true, shortages: [],
|
|
339
|
+
};
|
|
338
340
|
}
|
|
339
|
-
const reference = (data.customerReference ?? "").trim();
|
|
340
|
-
if (reference)
|
|
341
|
-
customFields["customer_reference"] = reference;
|
|
342
|
-
else
|
|
343
|
-
delete customFields["customer_reference"];
|
|
344
341
|
// ─── Voorraadcontrole door de Logistics Engine (nooit zelf beslissen) ────
|
|
345
342
|
const checkLines = data.lines
|
|
346
343
|
.filter((l) => l.product_id)
|
|
@@ -361,79 +358,99 @@ export const approveProposal = createServerFn({ method: "POST" })
|
|
|
361
358
|
.map((s) => `${s.product ?? s.productId}: besteld ${s.wanted}, beschikbaar ${s.available}, tekort ${s.missing}`)
|
|
362
359
|
.join("\n");
|
|
363
360
|
const blockText = `Geblokkeerd - onvoldoende voorraad\n${detail}`;
|
|
364
|
-
await supabaseAdmin
|
|
365
|
-
.from("mailbox_order_proposals")
|
|
366
|
-
.update({ notes: blockText })
|
|
367
|
-
.eq("id", data.proposalId);
|
|
361
|
+
await supabaseAdmin.from("mailbox_order_proposals").update({ notes: blockText }).eq("id", data.proposalId);
|
|
368
362
|
const { data: blockedProp } = await supabaseAdmin
|
|
369
|
-
.from("mailbox_order_proposals")
|
|
370
|
-
.select("message_id").eq("id", data.proposalId).maybeSingle();
|
|
363
|
+
.from("mailbox_order_proposals").select("message_id").eq("id", data.proposalId).maybeSingle();
|
|
371
364
|
if (blockedProp?.message_id) {
|
|
372
|
-
await supabaseAdmin
|
|
373
|
-
.from("mailbox_messages")
|
|
374
|
-
.update({ status: "blocked_insufficient_stock" })
|
|
375
|
-
.eq("id", blockedProp.message_id);
|
|
365
|
+
await supabaseAdmin.from("mailbox_messages").update({ status: "blocked_insufficient_stock" }).eq("id", blockedProp.message_id);
|
|
376
366
|
}
|
|
377
367
|
throw new Error(blockText);
|
|
378
368
|
}
|
|
379
369
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
370
|
+
// ─── "Toch aanmaken" na verstreken aanmeldtijd ──────────────────────────
|
|
371
|
+
const lateReason = (data.lateSignup?.reason ?? "").trim();
|
|
372
|
+
const acceptLate = lateReason.length > 0;
|
|
373
|
+
const claims = context.claims ?? {};
|
|
374
|
+
const acceptedBy = acceptLate
|
|
375
|
+
? String(claims.email ?? claims.user_metadata?.full_name ?? context.userId)
|
|
376
|
+
: null;
|
|
377
|
+
const notesParts = [data.notes ?? ""].filter((s) => s.trim());
|
|
378
|
+
if (acceptLate) {
|
|
379
|
+
notesParts.push(`Toch aangemaakt na verstreken aanmeldtijd door ${acceptedBy} op ${new Date().toISOString()} — reden: ${lateReason}`);
|
|
384
380
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
381
|
+
// ─── Order aanmaken via Verkoop (één centrale route) ────────────────────
|
|
382
|
+
const { data: vk, error: vkErr } = await context.supabase.rpc("verkoop_create_order", {
|
|
383
|
+
p_source: "mailbox",
|
|
384
|
+
p_source_id: data.proposalId,
|
|
388
385
|
p_customer_id: data.customerId,
|
|
389
386
|
p_delivery_date: data.deliveryDate,
|
|
390
|
-
|
|
391
|
-
|
|
387
|
+
p_delivery_location_id: deliveryLocationId,
|
|
388
|
+
p_carrier_id: carrierId,
|
|
392
389
|
p_lines: data.lines.map((line) => ({
|
|
393
390
|
product_id: line.product_id,
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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,
|
|
391
|
+
quantity_plants: Math.round(line.quantity ?? 0),
|
|
392
|
+
unit_price: line.unit_price,
|
|
393
|
+
needs_sticker: line.needs_sticker === true,
|
|
400
394
|
})),
|
|
401
|
-
|
|
395
|
+
p_customer_reference: reference || null,
|
|
396
|
+
p_moment_key: data.momentKey ?? null,
|
|
397
|
+
p_accept_late_signup: acceptLate,
|
|
398
|
+
p_accepted_by: acceptedBy,
|
|
399
|
+
p_own_transport: false,
|
|
400
|
+
p_notes: notesParts.join("\n") || null,
|
|
402
401
|
});
|
|
403
|
-
if (
|
|
404
|
-
throw new Error(
|
|
405
|
-
const
|
|
406
|
-
if (
|
|
407
|
-
|
|
402
|
+
if (vkErr)
|
|
403
|
+
throw new Error(`Bestelling aanmaken mislukt: ${vkErr.message}`);
|
|
404
|
+
const res = (vk ?? {});
|
|
405
|
+
if (res.ok !== true || !res.order_id) {
|
|
406
|
+
// Nooit stil mislukken: het voorstel blijft open met een duidelijke reden.
|
|
407
|
+
return {
|
|
408
|
+
ok: false,
|
|
409
|
+
reason: classifyVerkoopRefusal(res.reason),
|
|
410
|
+
message: String(res.reason ?? "Verkoop heeft de bestelling geweigerd zonder reden."),
|
|
411
|
+
nextDeliveryDate: typeof res.next_delivery_date === "string" ? res.next_delivery_date : null,
|
|
412
|
+
};
|
|
408
413
|
}
|
|
409
|
-
|
|
410
|
-
const deliveryLocationRef = typeof customFields["delivery_location"] === "string" ? customFields["delivery_location"] : null;
|
|
411
|
-
await context.supabase.rpc("logistics_emit_event", {
|
|
412
|
-
p_source_module: "mailbox",
|
|
413
|
-
p_event_type: "order.received",
|
|
414
|
-
p_order_ref: result.orderNumber,
|
|
415
|
-
p_payload: {
|
|
416
|
-
checkinAt: data.deliveryDate,
|
|
417
|
-
pickupAt: pv.chosen?.departureAt ?? data.deliveryDate,
|
|
418
|
-
momentKey: pv.chosen?.momentKey ?? null,
|
|
419
|
-
customerRef: data.customerId,
|
|
420
|
-
deliveryLocationRef,
|
|
421
|
-
bufferRef: null,
|
|
422
|
-
loadCarrierType,
|
|
423
|
-
loadCarriers: [loadCarrierType],
|
|
424
|
-
ruleVersion: null,
|
|
425
|
-
},
|
|
426
|
-
p_shadow: true,
|
|
427
|
-
p_idempotency_key: `mailbox:${data.proposalId}:order.received:v1`,
|
|
428
|
-
});
|
|
414
|
+
await markProposalApproved(supabaseAdmin, data.proposalId, context.userId, { id: res.order_id, order_number: res.order_number }, notesParts.join("\n") || null, reference, data.fieldValues ?? []);
|
|
429
415
|
return {
|
|
430
416
|
ok: true,
|
|
431
|
-
orderId:
|
|
432
|
-
orderNumber:
|
|
433
|
-
alreadyApproved:
|
|
417
|
+
orderId: res.order_id,
|
|
418
|
+
orderNumber: res.order_number,
|
|
419
|
+
alreadyApproved: res.already_existed === true,
|
|
434
420
|
shortages,
|
|
435
421
|
};
|
|
436
422
|
});
|
|
423
|
+
/** Zoekt een bestaande order voor dit voorstel op beide sleutels (oud en nieuw). */
|
|
424
|
+
async function findExistingOrderForProposal(admin, proposalId) {
|
|
425
|
+
const { data, error } = await admin
|
|
426
|
+
.from("orders")
|
|
427
|
+
.select("id, order_number")
|
|
428
|
+
.is("deleted_at", null)
|
|
429
|
+
.or(existingOrderOrFilter(proposalId))
|
|
430
|
+
.limit(1);
|
|
431
|
+
if (error)
|
|
432
|
+
throw new Error(`Controle op bestaande order mislukt: ${error.message}`);
|
|
433
|
+
const row = Array.isArray(data) ? data[0] : null;
|
|
434
|
+
return row?.id ? row : null;
|
|
435
|
+
}
|
|
436
|
+
async function markProposalApproved(admin, proposalId, userId, order, notes, reference, fieldValues) {
|
|
437
|
+
for (const fv of fieldValues) {
|
|
438
|
+
await admin.from("mailbox_proposal_field_values").upsert({ proposal_id: proposalId, field_key: fv.field_key, value: fv.value, ai_filled: false, needs_review: false }, { onConflict: "proposal_id,field_key" });
|
|
439
|
+
}
|
|
440
|
+
const { data: prop } = await admin
|
|
441
|
+
.from("mailbox_order_proposals")
|
|
442
|
+
.update({
|
|
443
|
+
status: "approved", reviewed_by: userId, reviewed_at: new Date().toISOString(),
|
|
444
|
+
created_order_id: order.id, order_number: order.order_number, notes,
|
|
445
|
+
customer_reference: reference || null,
|
|
446
|
+
})
|
|
447
|
+
.eq("id", proposalId)
|
|
448
|
+
.select("message_id")
|
|
449
|
+
.maybeSingle();
|
|
450
|
+
if (prop?.message_id) {
|
|
451
|
+
await admin.from("mailbox_messages").update({ status: "converted" }).eq("id", prop.message_id);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
437
454
|
export const rejectProposal = createServerFn({ method: "POST" })
|
|
438
455
|
.middleware([requireSupabaseAuth])
|
|
439
456
|
.inputValidator((input) => z.object({ proposalId: z.string().uuid(), reason: z.string().max(500).optional() }).parse(input))
|
|
@@ -1214,3 +1231,29 @@ export const countOutdatedProposals = createServerFn({ method: "GET" })
|
|
|
1214
1231
|
const { countOutdatedAgentProposals } = await import("./mailbox-ingest.server.js");
|
|
1215
1232
|
return { count: await countOutdatedAgentProposals(supabaseAdmin) };
|
|
1216
1233
|
});
|
|
1234
|
+
// ─── Stickervinkje: beschikbaarheid + standaardwaarden ─────────────────────
|
|
1235
|
+
// Verborgen als de kolom order_items.needs_sticker (Verkoop) ontbreekt.
|
|
1236
|
+
export const getStickerDefaults = createServerFn({ method: "POST" })
|
|
1237
|
+
.middleware([requireSupabaseAuth])
|
|
1238
|
+
.inputValidator((input) => z.object({
|
|
1239
|
+
customerId: z.string().uuid().nullable(),
|
|
1240
|
+
productIds: z.array(z.string().uuid()).max(200),
|
|
1241
|
+
}).parse(input))
|
|
1242
|
+
.handler(async ({ data }) => {
|
|
1243
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
1244
|
+
const probe = await supabaseAdmin.from("order_items").select("needs_sticker").limit(1);
|
|
1245
|
+
if (probe.error)
|
|
1246
|
+
return { available: false, defaults: {} };
|
|
1247
|
+
const defaults = {};
|
|
1248
|
+
if (!data.customerId || data.productIds.length === 0)
|
|
1249
|
+
return { available: true, defaults };
|
|
1250
|
+
const { data: rows } = await supabaseAdmin
|
|
1251
|
+
.from("customer_product_stickers")
|
|
1252
|
+
.select("customer_key, trade_item_key")
|
|
1253
|
+
.eq("customer_key", data.customerId)
|
|
1254
|
+
.in("trade_item_key", data.productIds);
|
|
1255
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
1256
|
+
for (const id of data.productIds)
|
|
1257
|
+
defaults[id] = defaultNeedsSticker(list, data.customerId, id);
|
|
1258
|
+
return { available: true, defaults };
|
|
1259
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Hulpfuncties rond de Verkoop-route (verkoop_create_order). Puur, zonder IO. */
|
|
2
|
+
export type VerkoopRefusalKind = "departed" | "late_signup" | "other";
|
|
3
|
+
/** Herkent op basis van de Nederlandse reden van Verkoop welk soort weigering het is. */
|
|
4
|
+
export declare function classifyVerkoopRefusal(reason: unknown): VerkoopRefusalKind;
|
|
5
|
+
/** PostgREST or-filter: een order hoort bij dit voorstel via de oude óf de nieuwe sleutel. */
|
|
6
|
+
export declare function existingOrderOrFilter(proposalId: string): string;
|
|
7
|
+
/** "Toch aanmaken" mag alleen met een reden, en nooit als de vervoerder al vertrokken is. */
|
|
8
|
+
export declare function canOverrideLateSignup(kind: VerkoopRefusalKind, reason: string | null | undefined): boolean;
|
|
9
|
+
/** Standaardwaarde stickervinkje: aan als er een sticker bestaat voor deze klant + dit product. */
|
|
10
|
+
export declare function defaultNeedsSticker(stickers: {
|
|
11
|
+
customer_key: string | null;
|
|
12
|
+
trade_item_key: string | null;
|
|
13
|
+
}[], customerId: string | null, productId: string | null): boolean;
|
|
14
|
+
//# sourceMappingURL=verkoop-order.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verkoop-order.d.ts","sourceRoot":"","sources":["../../src/lib/verkoop-order.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAElF,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,aAAa,GAAG,OAAO,CAAC;AAEtE,yFAAyF;AACzF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,GAAG,kBAAkB,CAK1E;AAED,8FAA8F;AAC9F,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,6FAA6F;AAC7F,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE1G;AAED,mGAAmG;AACnG,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,EAC1E,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAGT"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Hulpfuncties rond de Verkoop-route (verkoop_create_order). Puur, zonder IO. */
|
|
2
|
+
/** Herkent op basis van de Nederlandse reden van Verkoop welk soort weigering het is. */
|
|
3
|
+
export function classifyVerkoopRefusal(reason) {
|
|
4
|
+
const r = String(reason ?? "").toLowerCase();
|
|
5
|
+
if (r.includes("vertrokken"))
|
|
6
|
+
return "departed";
|
|
7
|
+
if (r.includes("aanmeld") || r.includes("verstreken") || r.includes("niet meer"))
|
|
8
|
+
return "late_signup";
|
|
9
|
+
return "other";
|
|
10
|
+
}
|
|
11
|
+
/** PostgREST or-filter: een order hoort bij dit voorstel via de oude óf de nieuwe sleutel. */
|
|
12
|
+
export function existingOrderOrFilter(proposalId) {
|
|
13
|
+
return `custom_fields->>proposal_id.eq.${proposalId},custom_fields->>mailbox_proposal_id.eq.${proposalId}`;
|
|
14
|
+
}
|
|
15
|
+
/** "Toch aanmaken" mag alleen met een reden, en nooit als de vervoerder al vertrokken is. */
|
|
16
|
+
export function canOverrideLateSignup(kind, reason) {
|
|
17
|
+
return kind === "late_signup" && (reason ?? "").trim().length >= 3;
|
|
18
|
+
}
|
|
19
|
+
/** Standaardwaarde stickervinkje: aan als er een sticker bestaat voor deze klant + dit product. */
|
|
20
|
+
export function defaultNeedsSticker(stickers, customerId, productId) {
|
|
21
|
+
if (!customerId || !productId)
|
|
22
|
+
return false;
|
|
23
|
+
return stickers.some((s) => s.customer_key === customerId && s.trade_item_key === productId);
|
|
24
|
+
}
|