@flowselections/floriday-klanten-module 1.0.36 → 1.0.37

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 (67) hide show
  1. package/dist-lib/components/klanten/BulkStickerUploadDialog.d.ts.map +1 -1
  2. package/dist-lib/components/klanten/BulkStickerUploadDialog.js +30 -8
  3. package/dist-lib/components/klanten/CustomerAssortmentTab.d.ts.map +1 -1
  4. package/dist-lib/components/klanten/CustomerAssortmentTab.js +48 -10
  5. package/dist-lib/integrations/supabase/auth-middleware.d.ts +129 -0
  6. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  7. package/dist-lib/integrations/supabase/client.d.ts +129 -0
  8. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  9. package/dist-lib/integrations/supabase/client.server.d.ts +129 -0
  10. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  11. package/dist-lib/integrations/supabase/types.d.ts +129 -0
  12. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  13. package/dist-lib/lib/accounting/twinfield/client.server.d.ts +7 -8
  14. package/dist-lib/lib/accounting/twinfield/client.server.d.ts.map +1 -1
  15. package/dist-lib/lib/accounting/twinfield/client.server.js +147 -78
  16. package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts +387 -0
  17. package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts.map +1 -1
  18. package/dist-lib/lib/accounting/twinfield/worker.server.d.ts.map +1 -1
  19. package/dist-lib/lib/accounting/twinfield/worker.server.js +17 -0
  20. package/dist-lib/lib/accounting.functions.d.ts +387 -0
  21. package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
  22. package/dist-lib/lib/customer-analytics.functions.d.ts +129 -0
  23. package/dist-lib/lib/customer-analytics.functions.d.ts.map +1 -1
  24. package/dist-lib/lib/customer-assortment.functions.d.ts +132 -3
  25. package/dist-lib/lib/customer-assortment.functions.d.ts.map +1 -1
  26. package/dist-lib/lib/customer-assortment.functions.js +12 -0
  27. package/dist-lib/lib/customer-notes.functions.d.ts +258 -0
  28. package/dist-lib/lib/customer-notes.functions.d.ts.map +1 -1
  29. package/dist-lib/lib/customer-revenue.functions.d.ts +129 -0
  30. package/dist-lib/lib/customer-revenue.functions.d.ts.map +1 -1
  31. package/dist-lib/lib/customer-stickers.functions.d.ts +1165 -0
  32. package/dist-lib/lib/customer-stickers.functions.d.ts.map +1 -1
  33. package/dist-lib/lib/customer-stickers.functions.js +122 -75
  34. package/dist-lib/lib/customers.functions.d.ts +1161 -0
  35. package/dist-lib/lib/customers.functions.d.ts.map +1 -1
  36. package/dist-lib/lib/delivery-locations.functions.d.ts +645 -0
  37. package/dist-lib/lib/delivery-locations.functions.d.ts.map +1 -1
  38. package/dist-lib/lib/external-customers.functions.d.ts +516 -0
  39. package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
  40. package/dist-lib/lib/floriday-assortment.functions.d.ts +129 -0
  41. package/dist-lib/lib/floriday-assortment.functions.d.ts.map +1 -1
  42. package/dist-lib/lib/floriday-discounts.functions.d.ts +258 -0
  43. package/dist-lib/lib/floriday-discounts.functions.d.ts.map +1 -1
  44. package/dist-lib/lib/floriday-price-import.functions.d.ts +258 -0
  45. package/dist-lib/lib/floriday-price-import.functions.d.ts.map +1 -1
  46. package/dist-lib/lib/floriday-sticker-import.functions.d.ts +27323 -0
  47. package/dist-lib/lib/floriday-sticker-import.functions.d.ts.map +1 -0
  48. package/dist-lib/lib/floriday-sticker-import.functions.js +307 -0
  49. package/dist-lib/lib/floriday-trade-items.functions.d.ts +129 -0
  50. package/dist-lib/lib/floriday-trade-items.functions.d.ts.map +1 -1
  51. package/dist-lib/lib/local-discounts.functions.d.ts +903 -0
  52. package/dist-lib/lib/local-discounts.functions.d.ts.map +1 -1
  53. package/dist-lib/lib/logistics-events.functions.d.ts +129 -0
  54. package/dist-lib/lib/logistics-events.functions.d.ts.map +1 -1
  55. package/dist-lib/lib/logistics-strategy.functions.d.ts +516 -0
  56. package/dist-lib/lib/logistics-strategy.functions.d.ts.map +1 -1
  57. package/dist-lib/lib/overdue-invoices.functions.d.ts +129 -0
  58. package/dist-lib/lib/overdue-invoices.functions.d.ts.map +1 -1
  59. package/dist-lib/lib/price-groups-local.functions.d.ts +1677 -0
  60. package/dist-lib/lib/price-groups-local.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/price-groups.functions.d.ts +129 -0
  62. package/dist-lib/lib/price-groups.functions.d.ts.map +1 -1
  63. package/dist-lib/lib/twinfield-adopt.functions.d.ts +258 -0
  64. package/dist-lib/lib/twinfield-adopt.functions.d.ts.map +1 -1
  65. package/dist-lib/lib/twinfield-ledger.functions.d.ts +129 -0
  66. package/dist-lib/lib/twinfield-ledger.functions.d.ts.map +1 -1
  67. package/package.json +2 -2
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Sinds Twinfield-authenticatie 2.0 staan client-id, secret en refresh-token
3
- * NIET meer in enige projectdatabase. `twinfield_connections` bevat alleen de
4
- * koppeling zelf; een access token wordt per aanroep vers opgehaald bij de
5
- * Central Auth-module en nooit gecached of opgeslagen.
2
+ * Alle Twinfield-communicatie loopt via de Twinfield-authenticatiemodule.
3
+ * Deze module praat nooit rechtstreeks met Twinfield of Central Auth en gebruikt
4
+ * nooit een vast cluster-adres of vaste administratiecode: die komen per aanroep
5
+ * uit het service-token-antwoord van de authenticatiemodule.
6
6
  */
7
7
  export interface TwinfieldConnection {
8
8
  id: string;
@@ -21,13 +21,12 @@ export declare class TwinfieldError extends Error {
21
21
  export interface TwinfieldToken {
22
22
  accessToken: string;
23
23
  clusterUri: string;
24
- companyCode: string | null;
24
+ companyCode: string;
25
25
  }
26
26
  export declare function loadActiveConnection(preferredId?: string): Promise<TwinfieldConnection | null>;
27
27
  /**
28
- * Haalt een vers access token op bij Central Auth. Nooit cachen of opslaan.
29
- * Bij een 404/401 wordt eenmalig de andere omgeving geprobeerd; slaagt die,
30
- * dan wordt de opgeslagen omgeving gecorrigeerd.
28
+ * Volledige tokentraject via de authenticatiemodule: statuscontrole, daarna
29
+ * het service-token. Nooit cachen of opslaan.
31
30
  */
32
31
  export declare function fetchTwinfieldToken(conn: TwinfieldConnection): Promise<TwinfieldToken>;
33
32
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"client.server.d.ts","sourceRoot":"","sources":["../../../../src/lib/accounting/twinfield/client.server.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,qDAAqD;AACrD,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;AAEvF,qBAAa,cAAe,SAAQ,KAAK;IACvC,IAAI,EAAE,oBAAoB,CAAC;gBACf,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM;CAKxD;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAaD,wBAAsB,oBAAoB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAyBpG;AAoFD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CAqC5F;AAeD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAiCpF;AAYD,wBAAsB,cAAc,CAAC,IAAI,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CA0D5I"}
1
+ {"version":3,"file":"client.server.d.ts","sourceRoot":"","sources":["../../../../src/lib/accounting/twinfield/client.server.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,qDAAqD;AACrD,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;AAEvF,qBAAa,cAAe,SAAQ,KAAK;IACvC,IAAI,EAAE,oBAAoB,CAAC;gBACf,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM;CAKxD;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAaD,wBAAsB,oBAAoB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAyBpG;AAwOD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CAK5F;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAiCpF;AAYD,wBAAsB,cAAc,CAAC,IAAI,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAwD5I"}
@@ -1,3 +1,4 @@
1
+ import { getRequestHeader } from "@tanstack/react-start/server";
1
2
  import { supabaseAdmin } from "../../../integrations/supabase/client.server";
2
3
  import { buildProcessXmlEnvelope } from "./mapping";
3
4
  export class TwinfieldError extends Error {
@@ -41,103 +42,173 @@ export async function loadActiveConnection(preferredId) {
41
42
  }
42
43
  return null;
43
44
  }
44
- /** Eén tokenaanvraag bij Central Auth voor een specifieke omgeving. */
45
- async function requestCentralAuthToken(baseUrl, secret, accountKey, environment) {
45
+ /**
46
+ * Het bearer-token van de lopende aanvraag, indien aanwezig. In een cron-run
47
+ * (geen request-context) geeft dit `null` terug.
48
+ */
49
+ function getCallerBearerToken() {
50
+ try {
51
+ const header = getRequestHeader("authorization");
52
+ if (!header)
53
+ return null;
54
+ const m = /^Bearer\s+(.+)$/i.exec(header.trim());
55
+ return m ? m[1] : null;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ /**
62
+ * Fail closed: adres van de authenticatiemodule uit een omgevingsvariabele,
63
+ * anders uit de eigen instellingentabel. Ontbreekt beide → duidelijke fout.
64
+ */
65
+ async function resolveAuthModuleUrl() {
66
+ const fromEnv = process.env.TWINFIELD_AUTH_URL;
67
+ if (fromEnv && fromEnv.trim())
68
+ return fromEnv.trim().replace(/\/+$/, "");
69
+ const { data } = await supabaseAdmin
70
+ .from("app_settings")
71
+ .select("value")
72
+ .eq("key", "twinfield_auth_url")
73
+ .maybeSingle();
74
+ const fromSettings = data?.value;
75
+ if (fromSettings && fromSettings.trim())
76
+ return fromSettings.trim().replace(/\/+$/, "");
77
+ // Terugval op het al geconfigureerde adres van de gedeelde authenticatie-
78
+ // module (CENTRAL_AUTH_URL is dezelfde dienst).
79
+ const legacy = process.env.CENTRAL_AUTH_URL;
80
+ if (legacy && legacy.trim())
81
+ return legacy.trim().replace(/\/+$/, "");
82
+ throw new TwinfieldError("config", "Het adres van de Twinfield-authenticatiemodule is niet ingesteld (omgevingsvariabele TWINFIELD_AUTH_URL of instelling 'twinfield_auth_url').");
83
+ }
84
+ /** Machine-sleutelwoord uit de gedeelde database. Alleen server-side, nooit loggen. */
85
+ async function getCronSecret() {
86
+ const { data, error } = await supabaseAdmin
87
+ .from("twinfield_cron_auth")
88
+ .select("secret")
89
+ .eq("id", 1)
90
+ .maybeSingle();
91
+ const secret = data?.secret;
92
+ if (error || !secret) {
93
+ throw new TwinfieldError("config", "Het machine-sleutelwoord voor de Twinfield-authenticatiemodule ontbreekt (twinfield_cron_auth, id = 1).");
94
+ }
95
+ return secret;
96
+ }
97
+ /**
98
+ * Statuscontrole bij de authenticatiemodule. Leesaanvraag met de
99
+ * koppelings-id als parameter in het adres. Auth: bearer-token van de lopende
100
+ * aanvraag indien aanwezig, anders het machine-sleutelwoord.
101
+ */
102
+ async function checkAuthModuleStatus(baseUrl, conn, cronSecret) {
103
+ const bearer = getCallerBearerToken();
104
+ const headers = bearer
105
+ ? { Authorization: `Bearer ${bearer}` }
106
+ : { "x-cron-secret": cronSecret };
107
+ const url = `${baseUrl}/api/public/twinfield/status?connection_id=${encodeURIComponent(conn.id)}`;
46
108
  let res;
47
109
  try {
48
- res = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/token/twinfield`, {
110
+ res = await fetch(url, { headers });
111
+ }
112
+ catch (e) {
113
+ throw new TwinfieldError("temporary", `De Twinfield-authenticatiemodule is nu niet bereikbaar: ${e instanceof Error ? e.message : String(e)}`);
114
+ }
115
+ const text = await res.text().catch(() => "");
116
+ if (!res.ok) {
117
+ if (res.status === 502) {
118
+ throw new TwinfieldError("temporary", "De Twinfield-authenticatiemodule is tijdelijk niet bereikbaar (502). Er volgt automatisch een nieuwe poging.");
119
+ }
120
+ throw new TwinfieldError("config", `De statuscontrole van de Twinfield-authenticatiemodule gaf een fout (${res.status}).`);
121
+ }
122
+ let json;
123
+ try {
124
+ json = JSON.parse(text);
125
+ }
126
+ catch {
127
+ throw new TwinfieldError("temporary", "De authenticatiemodule gaf een onleesbaar statusantwoord.");
128
+ }
129
+ const lower = text.toLowerCase();
130
+ if (lower.includes("ambiguous") || lower.includes("niet eenduidig")) {
131
+ throw new TwinfieldError("config", "De Twinfield-authenticatiemodule kon de administratie niet eenduidig bepalen. Controleer de koppeling in de authenticatiemodule.");
132
+ }
133
+ const status = String(json.status ?? json.state ?? "").toLowerCase();
134
+ const connected = json.connected === true ||
135
+ status === "connected" ||
136
+ status === "ok" ||
137
+ status === "active";
138
+ if (!connected) {
139
+ throw new TwinfieldError("config", "Er is geen actieve Twinfield-koppeling in de authenticatiemodule. De klant blijft op wachtstatus staan.");
140
+ }
141
+ const connectionId = json.connection_id ??
142
+ json.connectionId ??
143
+ json.connection?.id ??
144
+ null;
145
+ return { connected: true, connectionId };
146
+ }
147
+ /**
148
+ * Haalt een vers service-token op bij de authenticatiemodule. Dit eindpunt
149
+ * accepteert uitsluitend het machine-sleutelwoord — nooit een inlogtoken.
150
+ * Geeft access_token, cluster_url en administration_code terug; zonder
151
+ * administratiecode stopt de push met een duidelijke melding.
152
+ */
153
+ async function requestServiceToken(baseUrl, cronSecret, connectionId) {
154
+ let res;
155
+ try {
156
+ res = await fetch(`${baseUrl}/api/public/twinfield/service-token`, {
49
157
  method: "POST",
50
158
  headers: {
51
- Authorization: `Bearer ${secret}`,
159
+ "x-cron-secret": cronSecret,
52
160
  "Content-Type": "application/json",
53
161
  },
54
- // LET OP: de sleutel moet account_key in snake_case zijn. Met accountKey
55
- // negeert Central Auth het filter en antwoordt met ambiguous_account.
56
- body: JSON.stringify({ account_key: accountKey, environment }),
162
+ body: JSON.stringify({ connection_id: connectionId }),
57
163
  });
58
164
  }
59
165
  catch (e) {
60
- throw new TwinfieldError("temporary", `Central Auth is nu niet bereikbaar: ${e instanceof Error ? e.message : String(e)}`);
166
+ throw new TwinfieldError("temporary", `De Twinfield-authenticatiemodule is nu niet bereikbaar: ${e instanceof Error ? e.message : String(e)}`);
61
167
  }
62
168
  const text = await res.text().catch(() => "");
63
- if (!res.ok)
64
- return { ok: false, status: res.status, body: text };
169
+ const lower = text.toLowerCase();
170
+ if (res.status === 401 || res.status === 403) {
171
+ if (lower.includes("reauthorize") || lower.includes("herautoris")) {
172
+ throw new TwinfieldError("reauthorize", "De Twinfield-autorisatie is verlopen of ingetrokken. Autoriseer Twinfield opnieuw in de authenticatiemodule; de klant blijft op wachtstatus staan.");
173
+ }
174
+ throw new TwinfieldError("config", "De Twinfield-authenticatiemodule weigerde het machine-sleutelwoord. Controleer twinfield_cron_auth.");
175
+ }
176
+ if (res.status === 502) {
177
+ throw new TwinfieldError("temporary", "De achterliggende Twinfield-dienst was even niet bereikbaar (502). Er volgt automatisch een nieuwe poging.");
178
+ }
179
+ if (lower.includes("ambiguous") || lower.includes("niet eenduidig")) {
180
+ throw new TwinfieldError("config", "De Twinfield-authenticatiemodule kon de administratie niet eenduidig bepalen. Controleer de koppeling in de authenticatiemodule.");
181
+ }
182
+ if (!res.ok) {
183
+ throw new TwinfieldError("temporary", `De Twinfield-authenticatiemodule gaf een fout (${res.status}).`);
184
+ }
65
185
  let json;
66
186
  try {
67
187
  json = JSON.parse(text);
68
188
  }
69
189
  catch {
70
- throw new TwinfieldError("temporary", "Central Auth gaf een onleesbaar antwoord terug.");
190
+ throw new TwinfieldError("temporary", "De authenticatiemodule gaf een onleesbaar tokenantwoord.");
71
191
  }
72
- const accessToken = (json.accessToken ?? json.access_token);
73
- if (!accessToken)
74
- return { ok: false, status: res.status, body: text };
75
- return {
76
- ok: true,
77
- token: {
78
- accessToken,
79
- clusterUri: String(json.clusterUri ?? json.cluster_uri ?? "").replace(/\/+$/, ""),
80
- companyCode: (json.companyCode ?? json.company_code ?? null),
81
- },
82
- };
83
- }
84
- function classifyCentralAuthFailure(status, body) {
85
- const lower = body.toLowerCase();
86
- if (lower.includes("ambiguous_account")) {
87
- return new TwinfieldError("config", "Central Auth kon het account niet eenduidig bepalen (ambiguous_account). Controleer de koppeling in de authenticatie-module.");
192
+ const accessToken = (json.access_token ?? json.accessToken);
193
+ const clusterUri = String(json.cluster_url ?? json.clusterUrl ?? "").replace(/\/+$/, "");
194
+ const companyCode = (json.administration_code ?? json.administrationCode ?? null);
195
+ if (!accessToken || !clusterUri) {
196
+ throw new TwinfieldError("temporary", "De authenticatiemodule gaf een onvolledig service-token terug.");
88
197
  }
89
- if (status === 404 || lower.includes("not_found")) {
90
- return new TwinfieldError("reauthorize", "Central Auth kent dit Twinfield-account of deze omgeving niet (meer). Autoriseer Twinfield opnieuw.");
91
- }
92
- if (status === 401 ||
93
- status === 403 ||
94
- /expired|revoked|invalid_grant/.test(lower)) {
95
- return new TwinfieldError("reauthorize", "De Twinfield-autorisatie is verlopen of ingetrokken. Autoriseer Twinfield opnieuw.");
198
+ if (!companyCode) {
199
+ throw new TwinfieldError("config", "De authenticatiemodule gaf geen administratiecode terug. De push is gestopt; er wordt niet teruggevallen op een opgeslagen code.");
96
200
  }
97
- return new TwinfieldError("temporary", `Central Auth gaf een fout (${status}): ${body.slice(0, 200)}`);
201
+ return { accessToken, clusterUri, companyCode };
98
202
  }
99
203
  /**
100
- * Haalt een vers access token op bij Central Auth. Nooit cachen of opslaan.
101
- * Bij een 404/401 wordt eenmalig de andere omgeving geprobeerd; slaagt die,
102
- * dan wordt de opgeslagen omgeving gecorrigeerd.
204
+ * Volledige tokentraject via de authenticatiemodule: statuscontrole, daarna
205
+ * het service-token. Nooit cachen of opslaan.
103
206
  */
104
207
  export async function fetchTwinfieldToken(conn) {
105
- const baseUrl = process.env.CENTRAL_AUTH_URL;
106
- const secret = process.env.CENTRAL_AUTH_SECRET;
107
- if (!baseUrl || !secret) {
108
- throw new TwinfieldError("config", "Central Auth niet geconfigureerd: stel CENTRAL_AUTH_URL en CENTRAL_AUTH_SECRET in voor dit project.");
109
- }
110
- const accountKey = (conn.account_key ?? "").trim().toLowerCase();
111
- if (!accountKey) {
112
- throw new TwinfieldError("reauthorize", "De Twinfield-koppeling heeft geen account_key. Autoriseer Twinfield opnieuw in de authenticatie-module.");
113
- }
114
- const environment = (conn.environment ?? "live").trim().toLowerCase();
115
- const first = await requestCentralAuthToken(baseUrl, secret, accountKey, environment);
116
- if (first.ok)
117
- return withFallbackCluster(conn, first.token);
118
- // Mogelijke omgeving-mismatch: probeer eenmalig de andere omgeving.
119
- if (first.status === 404 || first.status === 401) {
120
- const other = environment === "sandbox" ? "live" : "sandbox";
121
- const retry = await requestCentralAuthToken(baseUrl, secret, accountKey, other);
122
- if (retry.ok) {
123
- await supabaseAdmin
124
- .from("twinfield_connections")
125
- .update({ environment: other, updated_at: new Date().toISOString() })
126
- .eq("id", conn.id);
127
- conn.environment = other;
128
- return withFallbackCluster(conn, retry.token);
129
- }
130
- }
131
- throw classifyCentralAuthFailure(first.status, first.body);
132
- }
133
- function withFallbackCluster(conn, token) {
134
- if (!token.clusterUri && conn.cluster_uri) {
135
- token.clusterUri = conn.cluster_uri.replace(/\/+$/, "");
136
- }
137
- if (!token.clusterUri) {
138
- throw new TwinfieldError("config", "Er is geen Twinfield cluster-URL bekend, niet uit Central Auth en niet uit de koppeling.");
139
- }
140
- return token;
208
+ const baseUrl = await resolveAuthModuleUrl();
209
+ const cronSecret = await getCronSecret();
210
+ const status = await checkAuthModuleStatus(baseUrl, conn, cronSecret);
211
+ return requestServiceToken(baseUrl, cronSecret, status.connectionId ?? conn.id);
141
212
  }
142
213
  /**
143
214
  * Leest het antwoord van processxml.asmx strikt uit. Twinfield geeft bij succes
@@ -190,10 +261,8 @@ function decodeXml(v) {
190
261
  }
191
262
  export async function sendProcessXml(conn, dimensionXml) {
192
263
  let token = await fetchTwinfieldToken(conn);
193
- const companyCode = conn.office_code || conn.company_code || token.companyCode;
194
- if (!companyCode) {
195
- throw new TwinfieldError("config", "Twinfield office/company code ontbreekt in de koppeling.");
196
- }
264
+ // De administratiecode komt uitsluitend uit het service-token-antwoord.
265
+ const companyCode = token.companyCode;
197
266
  const doCall = async () => {
198
267
  const envelope = buildProcessXmlEnvelope(token.accessToken, companyCode, dimensionXml);
199
268
  return fetch(`${token.clusterUri}/webservices/processxml.asmx`, {