@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.
Files changed (34) hide show
  1. package/dist-lib/components/klanten/BewerkKlant.d.ts.map +1 -1
  2. package/dist-lib/components/klanten/BewerkKlant.js +2 -1
  3. package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts.map +1 -1
  4. package/dist-lib/components/klanten/DeliveryLocationsTab.js +67 -8
  5. package/dist-lib/components/klanten/ImportKlanten.d.ts.map +1 -1
  6. package/dist-lib/components/klanten/ImportKlanten.js +2 -1
  7. package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
  8. package/dist-lib/components/klanten/KlantenOverzicht.js +61 -18
  9. package/dist-lib/components/klanten/LocalDiscountFormDialog.d.ts.map +1 -1
  10. package/dist-lib/components/klanten/LocalDiscountFormDialog.js +2 -1
  11. package/dist-lib/components/klanten/LocalDiscountsTab.d.ts.map +1 -1
  12. package/dist-lib/components/klanten/LocalDiscountsTab.js +2 -1
  13. package/dist-lib/components/klanten/LogisticsStrategyCard.d.ts.map +1 -1
  14. package/dist-lib/components/klanten/LogisticsStrategyCard.js +2 -1
  15. package/dist-lib/components/klanten/NieuweKlant.d.ts.map +1 -1
  16. package/dist-lib/components/klanten/NieuweKlant.js +2 -1
  17. package/dist-lib/components/klanten/OvernemenTwinfieldKlant.d.ts.map +1 -1
  18. package/dist-lib/components/klanten/OvernemenTwinfieldKlant.js +2 -1
  19. package/dist-lib/components/klanten/RouteError.d.ts +6 -0
  20. package/dist-lib/components/klanten/RouteError.d.ts.map +1 -0
  21. package/dist-lib/components/klanten/RouteError.js +13 -0
  22. package/dist-lib/lib/customers.functions.d.ts.map +1 -1
  23. package/dist-lib/lib/customers.functions.js +39 -35
  24. package/dist-lib/lib/errors.d.ts +7 -0
  25. package/dist-lib/lib/errors.d.ts.map +1 -0
  26. package/dist-lib/lib/errors.js +36 -0
  27. package/dist-lib/lib/external-customers.functions.d.ts +1 -1
  28. package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
  29. package/dist-lib/lib/external-customers.functions.js +28 -12
  30. package/dist-lib/lib/persisted-state.d.ts +7 -0
  31. package/dist-lib/lib/persisted-state.d.ts.map +1 -0
  32. package/dist-lib/lib/persisted-state.js +92 -0
  33. package/dist-lib/lib/price-groups-local.functions.d.ts +3 -3
  34. 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
- let q = supabase
67
- .from("customers")
68
- .select("*")
69
- .eq("source", "local")
70
- .order("company_name");
71
- if (data.customer_class)
72
- q = q.eq("customer_class", data.customer_class);
73
- if (data.country)
74
- q = q.eq("country", data.country);
75
- if (data.is_active === "active")
76
- q = q.eq("is_active", true);
77
- if (data.is_active === "inactive")
78
- q = q.eq("is_active", false);
79
- if (data.search) {
80
- const s = `%${data.search}%`;
81
- q = q.or(`company_name.ilike.${s},contact_person.ilike.${s},email.ilike.${s}`);
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
- throw new Error(`Klant aanmaken mislukt: ${msg}`);
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.message);
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.message);
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.message);
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.message);
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[];