@flowselections/floriday-klanten-module 1.0.27 → 1.0.29
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/klanten/BewerkKlant.d.ts.map +1 -1
- package/dist-lib/components/klanten/BewerkKlant.js +2 -1
- package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts.map +1 -1
- package/dist-lib/components/klanten/DeliveryLocationsTab.js +67 -8
- package/dist-lib/components/klanten/ImportKlanten.d.ts.map +1 -1
- package/dist-lib/components/klanten/ImportKlanten.js +2 -1
- package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +61 -18
- package/dist-lib/components/klanten/LocalDiscountFormDialog.d.ts.map +1 -1
- package/dist-lib/components/klanten/LocalDiscountFormDialog.js +2 -1
- package/dist-lib/components/klanten/LocalDiscountsTab.d.ts.map +1 -1
- package/dist-lib/components/klanten/LocalDiscountsTab.js +2 -1
- package/dist-lib/components/klanten/LogisticsStrategyCard.d.ts.map +1 -1
- package/dist-lib/components/klanten/LogisticsStrategyCard.js +2 -1
- package/dist-lib/components/klanten/NieuweKlant.d.ts.map +1 -1
- package/dist-lib/components/klanten/NieuweKlant.js +2 -1
- package/dist-lib/components/klanten/OvernemenTwinfieldKlant.d.ts.map +1 -1
- package/dist-lib/components/klanten/OvernemenTwinfieldKlant.js +2 -1
- package/dist-lib/components/klanten/RouteError.d.ts +6 -0
- package/dist-lib/components/klanten/RouteError.d.ts.map +1 -0
- package/dist-lib/components/klanten/RouteError.js +13 -0
- package/dist-lib/lib/customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/customers.functions.js +39 -35
- package/dist-lib/lib/errors.d.ts +7 -0
- package/dist-lib/lib/errors.d.ts.map +1 -0
- package/dist-lib/lib/errors.js +36 -0
- package/dist-lib/lib/external-customers.functions.d.ts +1 -1
- package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/external-customers.functions.js +28 -12
- package/dist-lib/lib/persisted-state.d.ts +7 -0
- package/dist-lib/lib/persisted-state.d.ts.map +1 -0
- package/dist-lib/lib/persisted-state.js +92 -0
- package/dist-lib/lib/price-groups-local.functions.d.ts +3 -3
- package/package.json +2 -2
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
|
|
3
4
|
import { customerCreateSchema, customerUpdateSchema, listFiltersSchema, customerImportSchema, } from "./customer-schemas";
|
|
4
5
|
import { buildPriceGroupsMap } from "./price-groups.functions";
|
|
5
6
|
import { buildCustomerInsertRow } from "./customer-insert";
|
|
7
|
+
import { friendlyError } from "./errors";
|
|
6
8
|
function checkTwinfieldReady(draft) {
|
|
7
9
|
const missing = [];
|
|
8
10
|
if (!draft.company_name?.trim())
|
|
@@ -42,7 +44,7 @@ async function getActiveTwinfieldConnection(supabase) {
|
|
|
42
44
|
}
|
|
43
45
|
export const precheckTwinfieldPush = createServerFn({ method: "POST" })
|
|
44
46
|
.middleware([requireSupabaseAuth])
|
|
45
|
-
.inputValidator((input) => input)
|
|
47
|
+
.inputValidator((input) => z.object({ draft: z.record(z.string(), z.any()).default({}) }).parse(input ?? { draft: {} }))
|
|
46
48
|
.handler(async ({ data, context }) => {
|
|
47
49
|
const conn = await getActiveTwinfieldConnection(context.supabase);
|
|
48
50
|
if (!conn) {
|
|
@@ -63,36 +65,38 @@ export const listCustomers = createServerFn({ method: "POST" })
|
|
|
63
65
|
// Floriday-klanten worden niet meer naar `customers` gespiegeld — die
|
|
64
66
|
// worden direct uit floriday_customers_cache gelezen via
|
|
65
67
|
// listFloridayCustomers(). Deze functie levert dus alleen lokale klanten.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const { data: rows, error } = await q.limit(500);
|
|
84
|
-
if (error) {
|
|
85
|
-
// Vertaal technische Postgres-fouten naar begrijpelijke meldingen.
|
|
86
|
-
const msg = error.message ?? "";
|
|
87
|
-
if (/duplicate key/i.test(msg)) {
|
|
88
|
-
throw new Error("Deze klant bestaat al (dubbele waarde in een uniek veld).");
|
|
89
|
-
}
|
|
90
|
-
if (/relation .* does not exist|function .* does not exist/i.test(msg)) {
|
|
91
|
-
throw new Error("De klant kon niet worden opgeslagen door een configuratieprobleem in de database. Neem contact op met de beheerder.");
|
|
68
|
+
const build = () => {
|
|
69
|
+
let q = supabase
|
|
70
|
+
.from("customers")
|
|
71
|
+
.select("*")
|
|
72
|
+
.eq("source", "local")
|
|
73
|
+
.order("company_name");
|
|
74
|
+
if (data.customer_class)
|
|
75
|
+
q = q.eq("customer_class", data.customer_class);
|
|
76
|
+
if (data.country)
|
|
77
|
+
q = q.eq("country", data.country);
|
|
78
|
+
if (data.is_active === "active")
|
|
79
|
+
q = q.eq("is_active", true);
|
|
80
|
+
if (data.is_active === "inactive")
|
|
81
|
+
q = q.eq("is_active", false);
|
|
82
|
+
if (data.search) {
|
|
83
|
+
const s = `%${data.search}%`;
|
|
84
|
+
q = q.or(`company_name.ilike.${s},contact_person.ilike.${s},email.ilike.${s}`);
|
|
92
85
|
}
|
|
93
|
-
|
|
86
|
+
return q;
|
|
87
|
+
};
|
|
88
|
+
// Geen maximum: lees in blokken van 1000 door tot het volledige
|
|
89
|
+
// klantenbestand binnen is (Postgres levert nooit meer per aanvraag).
|
|
90
|
+
const list = [];
|
|
91
|
+
for (let offset = 0;; offset += 1000) {
|
|
92
|
+
const { data: rows, error } = await build().range(offset, offset + 999);
|
|
93
|
+
if (error)
|
|
94
|
+
throw new Error(friendlyError(error, "Klanten laden mislukt."));
|
|
95
|
+
const chunk = rows ?? [];
|
|
96
|
+
list.push(...chunk);
|
|
97
|
+
if (chunk.length < 1000)
|
|
98
|
+
break;
|
|
94
99
|
}
|
|
95
|
-
const list = rows ?? [];
|
|
96
100
|
const orgIds = list
|
|
97
101
|
.map((r) => r.floriday_organization_id)
|
|
98
102
|
.filter((v) => typeof v === "string" && !!v);
|
|
@@ -107,7 +111,7 @@ export const listCustomers = createServerFn({ method: "POST" })
|
|
|
107
111
|
});
|
|
108
112
|
export const getCustomer = createServerFn({ method: "POST" })
|
|
109
113
|
.middleware([requireSupabaseAuth])
|
|
110
|
-
.inputValidator((input) => input)
|
|
114
|
+
.inputValidator((input) => z.object({ id: z.string().uuid() }).parse(input))
|
|
111
115
|
.handler(async ({ data, context }) => {
|
|
112
116
|
const { supabase } = context;
|
|
113
117
|
const [{ data: customer, error: cErr }, { data: audit }, { data: contacts }, { data: deliveryLocations }, { data: tradeSettings }, { data: assortment },] = await Promise.all([
|
|
@@ -141,7 +145,7 @@ export const getCustomer = createServerFn({ method: "POST" })
|
|
|
141
145
|
.limit(500),
|
|
142
146
|
]);
|
|
143
147
|
if (cErr)
|
|
144
|
-
throw new Error(cErr.
|
|
148
|
+
throw new Error(friendlyError(cErr, "Klant laden mislukt."));
|
|
145
149
|
if (!customer)
|
|
146
150
|
throw new Error("Klant niet gevonden");
|
|
147
151
|
const { data: orders } = await supabase
|
|
@@ -238,7 +242,7 @@ export const createCustomer = createServerFn({ method: "POST" })
|
|
|
238
242
|
if (/customers_customer_number_key/i.test(error.message ?? "")) {
|
|
239
243
|
throw new Error("Dit klantnummer is al in gebruik door een andere klant.");
|
|
240
244
|
}
|
|
241
|
-
throw new Error(error.
|
|
245
|
+
throw new Error(friendlyError(error, "Klant aanmaken mislukt."));
|
|
242
246
|
}
|
|
243
247
|
// De DB-trigger `customers_enqueue_twinfield_push` plaatst automatisch een
|
|
244
248
|
// rij in twinfield_push_queue zodra er een actieve koppeling is; we hoeven
|
|
@@ -296,7 +300,7 @@ export const updateCustomer = createServerFn({ method: "POST" })
|
|
|
296
300
|
if (/customers_customer_number_key/i.test(error.message ?? "")) {
|
|
297
301
|
throw new Error("Dit klantnummer is al in gebruik door een andere klant.");
|
|
298
302
|
}
|
|
299
|
-
throw new Error(error.
|
|
303
|
+
throw new Error(friendlyError(error, "Wijzigingen opslaan mislukt."));
|
|
300
304
|
}
|
|
301
305
|
// De DB-trigger heeft de wijziging al in twinfield_push_queue gezet wanneer
|
|
302
306
|
// er een Twinfield-relevant veld is gewijzigd. Die regel verwerken we direct,
|
|
@@ -306,14 +310,14 @@ export const updateCustomer = createServerFn({ method: "POST" })
|
|
|
306
310
|
});
|
|
307
311
|
export const deleteCustomer = createServerFn({ method: "POST" })
|
|
308
312
|
.middleware([requireSupabaseAuth])
|
|
309
|
-
.inputValidator((input) => input)
|
|
313
|
+
.inputValidator((input) => z.object({ id: z.string().uuid() }).parse(input))
|
|
310
314
|
.handler(async ({ data, context }) => {
|
|
311
315
|
const { error } = await context.supabase
|
|
312
316
|
.from("customers")
|
|
313
317
|
.delete()
|
|
314
318
|
.eq("id", data.id);
|
|
315
319
|
if (error)
|
|
316
|
-
throw new Error(error.
|
|
320
|
+
throw new Error(friendlyError(error, "Klant verwijderen mislukt."));
|
|
317
321
|
return { ok: true };
|
|
318
322
|
});
|
|
319
323
|
export const listFloridayNetwork = createServerFn({ method: "POST" })
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zet technische database-/servermeldingen om naar begrijpelijke Nederlandse
|
|
3
|
+
* tekst. Onbekende fouten krijgen een nette algemene melding, zodat interne
|
|
4
|
+
* kolom- en constraintnamen nooit in de UI belanden.
|
|
5
|
+
*/
|
|
6
|
+
export declare function friendlyError(err: unknown, fallback?: string): string;
|
|
7
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,SAA2C,GAAG,MAAM,CA0BvG"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zet technische database-/servermeldingen om naar begrijpelijke Nederlandse
|
|
3
|
+
* tekst. Onbekende fouten krijgen een nette algemene melding, zodat interne
|
|
4
|
+
* kolom- en constraintnamen nooit in de UI belanden.
|
|
5
|
+
*/
|
|
6
|
+
export function friendlyError(err, fallback = "Er ging iets mis. Probeer het opnieuw.") {
|
|
7
|
+
const raw = err instanceof Error ? err.message : typeof err === "string" ? err : "";
|
|
8
|
+
if (!raw)
|
|
9
|
+
return fallback;
|
|
10
|
+
// Meldingen die we zelf al in het Nederlands schrijven, tonen we ongewijzigd.
|
|
11
|
+
if (/[a-z]/i.test(raw) && !looksTechnical(raw))
|
|
12
|
+
return raw;
|
|
13
|
+
if (/duplicate key|unique constraint/i.test(raw)) {
|
|
14
|
+
return "Deze gegevens bestaan al. Controleer of het record niet dubbel is.";
|
|
15
|
+
}
|
|
16
|
+
if (/violates foreign key/i.test(raw)) {
|
|
17
|
+
return "Deze actie kan niet worden uitgevoerd omdat er nog gekoppelde gegevens zijn.";
|
|
18
|
+
}
|
|
19
|
+
if (/violates not-null|null value in column/i.test(raw)) {
|
|
20
|
+
return "Er ontbreken verplichte gegevens. Vul alle verplichte velden in.";
|
|
21
|
+
}
|
|
22
|
+
if (/permission denied|row-level security|not authorized|unauthorized/i.test(raw)) {
|
|
23
|
+
return "Je hebt geen rechten voor deze actie.";
|
|
24
|
+
}
|
|
25
|
+
if (/could not find the .* column|relation .* does not exist|function .* does not exist|schema cache/i.test(raw)) {
|
|
26
|
+
return "Er is een configuratieprobleem in de database. Neem contact op met de beheerder.";
|
|
27
|
+
}
|
|
28
|
+
if (/failed to fetch|networkerror|timeout/i.test(raw)) {
|
|
29
|
+
return "Geen verbinding met de server. Controleer je internetverbinding en probeer opnieuw.";
|
|
30
|
+
}
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
function looksTechnical(msg) {
|
|
34
|
+
return (/duplicate key|constraint|column|relation |schema cache|row-level security|permission denied|violates|pgrst|jwt|syntax error|null value/i
|
|
35
|
+
.test(msg));
|
|
36
|
+
}
|
|
@@ -39680,8 +39680,8 @@ export declare const listExternalCustomers: import("@tanstack/start-client-core"
|
|
|
39680
39680
|
}, undefined, undefined, undefined>], (input: unknown) => {
|
|
39681
39681
|
search: string;
|
|
39682
39682
|
status: "active" | "all";
|
|
39683
|
-
limit: number;
|
|
39684
39683
|
providers?: string[] | undefined;
|
|
39684
|
+
limit?: number | undefined;
|
|
39685
39685
|
}, Promise<{
|
|
39686
39686
|
customers: GroupedCustomer[];
|
|
39687
39687
|
providers: ExternalProvider[];
|