@flowselections/floriday-klanten-module 1.0.14 → 1.0.16
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/assets/floriday-logo.png.asset.json +11 -0
- package/dist-lib/components/CustomerAvatar.js +1 -1
- package/dist-lib/components/klanten/BackLink.d.ts +12 -0
- package/dist-lib/components/klanten/BackLink.d.ts.map +1 -0
- package/dist-lib/components/klanten/BackLink.js +12 -0
- package/dist-lib/components/klanten/FloridayAssortmentTab.d.ts +4 -0
- package/dist-lib/components/klanten/FloridayAssortmentTab.d.ts.map +1 -0
- package/dist-lib/components/klanten/FloridayAssortmentTab.js +80 -0
- package/dist-lib/components/klanten/FloridayDeeplinkButton.d.ts +8 -0
- package/dist-lib/components/klanten/FloridayDeeplinkButton.d.ts.map +1 -0
- package/dist-lib/components/klanten/FloridayDeeplinkButton.js +9 -0
- package/dist-lib/components/klanten/GroupedKlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/GroupedKlantDetail.js +19 -5
- package/dist-lib/components/klanten/ImportKlanten.d.ts.map +1 -1
- package/dist-lib/components/klanten/ImportKlanten.js +46 -13
- package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantDetail.js +9 -3
- package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +6 -3
- package/dist-lib/components/klanten/NetwerkPagina.d.ts.map +1 -1
- package/dist-lib/components/klanten/NetwerkPagina.js +42 -10
- package/dist-lib/components/klanten/NieuweKlant.d.ts.map +1 -1
- package/dist-lib/components/klanten/NieuweKlant.js +23 -8
- package/dist-lib/components/klanten/PriceGroupBadges.d.ts +7 -0
- package/dist-lib/components/klanten/PriceGroupBadges.d.ts.map +1 -0
- package/dist-lib/components/klanten/PriceGroupBadges.js +13 -0
- package/dist-lib/components/klanten/SyncStatusChip.d.ts +4 -0
- package/dist-lib/components/klanten/SyncStatusChip.d.ts.map +1 -0
- package/dist-lib/components/klanten/SyncStatusChip.js +49 -0
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.d.ts +8 -0
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.js +75 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.d.ts +4 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.js +269 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.d.ts +2 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.js +133 -0
- package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.d.ts +8 -0
- package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.js +18 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +6 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +762 -3
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +762 -3
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.js +4 -3
- package/dist-lib/integrations/supabase/client.server.d.ts +762 -3
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +789 -8
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/accounting/twinfield/client.server.d.ts +21 -0
- package/dist-lib/lib/accounting/twinfield/client.server.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/client.server.js +171 -0
- package/dist-lib/lib/accounting/twinfield/mapping.d.ts +46 -0
- package/dist-lib/lib/accounting/twinfield/mapping.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/mapping.js +109 -0
- package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts +36318 -0
- package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/worker.functions.js +48 -0
- package/dist-lib/lib/accounting/twinfield/worker.server.d.ts +12 -0
- package/dist-lib/lib/accounting/twinfield/worker.server.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/worker.server.js +124 -0
- package/dist-lib/lib/accounting.functions.d.ts +2286 -9
- package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
- package/dist-lib/lib/customer-analytics.functions.d.ts +762 -3
- package/dist-lib/lib/customer-analytics.functions.d.ts.map +1 -1
- package/dist-lib/lib/customer-schemas.d.ts +48 -46
- package/dist-lib/lib/customer-schemas.d.ts.map +1 -1
- package/dist-lib/lib/customer-schemas.js +46 -3
- package/dist-lib/lib/customers.functions.d.ts +18288 -129
- package/dist-lib/lib/customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/customers.functions.js +146 -10
- package/dist-lib/lib/delivery-locations.functions.d.ts +3048 -12
- package/dist-lib/lib/delivery-locations.functions.d.ts.map +1 -1
- package/dist-lib/lib/external-customers.functions.d.ts +3048 -12
- package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-assortment.functions.d.ts +12127 -0
- package/dist-lib/lib/floriday-assortment.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-assortment.functions.js +213 -0
- package/dist-lib/lib/floriday-discounts.functions.d.ts +1800 -282
- package/dist-lib/lib/floriday-discounts.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-trade-items.functions.d.ts +12111 -0
- package/dist-lib/lib/floriday-trade-items.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-trade-items.functions.js +107 -0
- package/dist-lib/lib/local-discounts.functions.d.ts +5336 -23
- package/dist-lib/lib/local-discounts.functions.d.ts.map +1 -1
- package/dist-lib/lib/overdue-invoices.functions.d.ts +762 -3
- package/dist-lib/lib/overdue-invoices.functions.d.ts.map +1 -1
- package/dist-lib/lib/price-groups-local.functions.d.ts +84841 -0
- package/dist-lib/lib/price-groups-local.functions.d.ts.map +1 -0
- package/dist-lib/lib/price-groups-local.functions.js +148 -0
- package/dist-lib/lib/price-groups.functions.d.ts +12120 -0
- package/dist-lib/lib/price-groups.functions.d.ts.map +1 -0
- package/dist-lib/lib/price-groups.functions.js +115 -0
- package/dist-lib/lib/pricing.d.ts +24 -0
- package/dist-lib/lib/pricing.d.ts.map +1 -0
- package/dist-lib/lib/pricing.js +60 -0
- package/package.json +6 -3
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface TwinfieldConnection {
|
|
2
|
+
id: string;
|
|
3
|
+
source: "connections" | "credentials";
|
|
4
|
+
client_id: string | null;
|
|
5
|
+
client_secret: string | null;
|
|
6
|
+
refresh_token: string | null;
|
|
7
|
+
access_token: string | null;
|
|
8
|
+
token_expires_at: string | null;
|
|
9
|
+
cluster_uri: string | null;
|
|
10
|
+
company_code: string | null;
|
|
11
|
+
office_code: string | null;
|
|
12
|
+
}
|
|
13
|
+
export declare function loadActiveConnection(preferredId?: string): Promise<TwinfieldConnection | null>;
|
|
14
|
+
export declare function ensureAccessToken(conn: TwinfieldConnection): Promise<string>;
|
|
15
|
+
export declare function ensureCluster(conn: TwinfieldConnection, accessToken: string): Promise<string>;
|
|
16
|
+
export declare function sendProcessXml(conn: TwinfieldConnection, dimensionXml: string): Promise<{
|
|
17
|
+
ok: boolean;
|
|
18
|
+
message: string;
|
|
19
|
+
raw: string;
|
|
20
|
+
}>;
|
|
21
|
+
//# sourceMappingURL=client.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.server.d.ts","sourceRoot":"","sources":["../../../../src/lib/accounting/twinfield/client.server.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,aAAa,GAAG,aAAa,CAAC;IACtC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAKD,wBAAsB,oBAAoB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CA0DpG;AA8BD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6BlF;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAenG;AAED,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,CAyC5I"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { supabaseAdmin } from "../../../integrations/supabase/client.server";
|
|
2
|
+
import { buildProcessXmlEnvelope } from "./mapping";
|
|
3
|
+
const TOKEN_URL = "https://login.twinfield.com/auth/authentication/connect/token";
|
|
4
|
+
const VALIDATE_URL = "https://login.twinfield.com/auth/authentication/connect/accesstokenvalidation";
|
|
5
|
+
export async function loadActiveConnection(preferredId) {
|
|
6
|
+
// Voorkeur: expliciete connection uit twinfield_connections
|
|
7
|
+
if (preferredId) {
|
|
8
|
+
const { data } = await supabaseAdmin
|
|
9
|
+
.from("twinfield_connections")
|
|
10
|
+
.select("id, client_id, client_secret, refresh_token, access_token, token_expires_at, cluster_uri, company_code, office_code, is_active")
|
|
11
|
+
.eq("id", preferredId)
|
|
12
|
+
.maybeSingle();
|
|
13
|
+
if (data && data.is_active) {
|
|
14
|
+
return {
|
|
15
|
+
id: data.id,
|
|
16
|
+
source: "connections",
|
|
17
|
+
client_id: data.client_id,
|
|
18
|
+
client_secret: data.client_secret,
|
|
19
|
+
refresh_token: data.refresh_token,
|
|
20
|
+
access_token: data.access_token,
|
|
21
|
+
token_expires_at: data.token_expires_at,
|
|
22
|
+
cluster_uri: data.cluster_uri,
|
|
23
|
+
company_code: data.company_code,
|
|
24
|
+
office_code: data.office_code,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const { data: conn } = await supabaseAdmin
|
|
29
|
+
.from("twinfield_connections")
|
|
30
|
+
.select("id, client_id, client_secret, refresh_token, access_token, token_expires_at, cluster_uri, company_code, office_code")
|
|
31
|
+
.eq("is_active", true)
|
|
32
|
+
.order("connected_at", { ascending: false, nullsFirst: false })
|
|
33
|
+
.limit(1)
|
|
34
|
+
.maybeSingle();
|
|
35
|
+
if (conn) {
|
|
36
|
+
return { ...conn, source: "connections" };
|
|
37
|
+
}
|
|
38
|
+
const { data: cred } = await supabaseAdmin
|
|
39
|
+
.from("twinfield_credentials")
|
|
40
|
+
.select("id, client_id, client_secret, refresh_token, cluster, administration_code")
|
|
41
|
+
.not("refresh_token", "is", null)
|
|
42
|
+
.order("updated_at", { ascending: false })
|
|
43
|
+
.limit(1)
|
|
44
|
+
.maybeSingle();
|
|
45
|
+
if (cred) {
|
|
46
|
+
return {
|
|
47
|
+
id: cred.id,
|
|
48
|
+
source: "credentials",
|
|
49
|
+
client_id: cred.client_id,
|
|
50
|
+
client_secret: cred.client_secret,
|
|
51
|
+
refresh_token: cred.refresh_token,
|
|
52
|
+
access_token: null,
|
|
53
|
+
token_expires_at: null,
|
|
54
|
+
cluster_uri: cred.cluster,
|
|
55
|
+
company_code: cred.administration_code,
|
|
56
|
+
office_code: cred.administration_code,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
async function persistNewToken(conn, token) {
|
|
62
|
+
const expiresAt = new Date(Date.now() + (token.expires_in - 30) * 1000).toISOString();
|
|
63
|
+
const table = conn.source === "connections" ? "twinfield_connections" : "twinfield_credentials";
|
|
64
|
+
const patch = { updated_at: new Date().toISOString() };
|
|
65
|
+
if (conn.source === "connections") {
|
|
66
|
+
patch.access_token = token.access_token;
|
|
67
|
+
patch.token_expires_at = expiresAt;
|
|
68
|
+
if (token.refresh_token)
|
|
69
|
+
patch.refresh_token = token.refresh_token;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// credentials-tabel heeft geen access_token kolom; alleen refresh bijwerken
|
|
73
|
+
if (token.refresh_token)
|
|
74
|
+
patch.refresh_token = token.refresh_token;
|
|
75
|
+
}
|
|
76
|
+
await supabaseAdmin.from(table).update(patch).eq("id", conn.id);
|
|
77
|
+
await supabaseAdmin
|
|
78
|
+
.from("twinfield_token_refresh_log")
|
|
79
|
+
.insert({ connection_id: conn.id, success: true })
|
|
80
|
+
.then(() => { }, () => { });
|
|
81
|
+
conn.access_token = token.access_token;
|
|
82
|
+
conn.token_expires_at = expiresAt;
|
|
83
|
+
if (token.refresh_token)
|
|
84
|
+
conn.refresh_token = token.refresh_token;
|
|
85
|
+
}
|
|
86
|
+
export async function ensureAccessToken(conn) {
|
|
87
|
+
if (conn.access_token &&
|
|
88
|
+
conn.token_expires_at &&
|
|
89
|
+
new Date(conn.token_expires_at).getTime() > Date.now() + 60000) {
|
|
90
|
+
return conn.access_token;
|
|
91
|
+
}
|
|
92
|
+
if (!conn.refresh_token || !conn.client_id || !conn.client_secret) {
|
|
93
|
+
throw new Error("Twinfield-koppeling is niet volledig geconfigureerd (client_id/secret/refresh_token ontbreekt).");
|
|
94
|
+
}
|
|
95
|
+
const body = new URLSearchParams({
|
|
96
|
+
grant_type: "refresh_token",
|
|
97
|
+
client_id: conn.client_id,
|
|
98
|
+
client_secret: conn.client_secret,
|
|
99
|
+
refresh_token: conn.refresh_token,
|
|
100
|
+
});
|
|
101
|
+
const res = await fetch(TOKEN_URL, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
104
|
+
body,
|
|
105
|
+
});
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const text = await res.text().catch(() => "");
|
|
108
|
+
throw new Error(`Twinfield token refresh mislukt (${res.status}): ${text.slice(0, 200)}`);
|
|
109
|
+
}
|
|
110
|
+
const json = (await res.json());
|
|
111
|
+
await persistNewToken(conn, json);
|
|
112
|
+
return json.access_token;
|
|
113
|
+
}
|
|
114
|
+
export async function ensureCluster(conn, accessToken) {
|
|
115
|
+
if (conn.cluster_uri && conn.cluster_uri.startsWith("http"))
|
|
116
|
+
return conn.cluster_uri.replace(/\/+$/, "");
|
|
117
|
+
const res = await fetch(`${VALIDATE_URL}?token=${encodeURIComponent(accessToken)}`);
|
|
118
|
+
if (!res.ok)
|
|
119
|
+
throw new Error(`Twinfield cluster-lookup faalde (${res.status})`);
|
|
120
|
+
const json = (await res.json());
|
|
121
|
+
const cluster = json.twf_cluster || json.cluster;
|
|
122
|
+
if (!cluster)
|
|
123
|
+
throw new Error("Twinfield cluster-URL niet gevonden in validation-response");
|
|
124
|
+
if (conn.source === "connections") {
|
|
125
|
+
await supabaseAdmin
|
|
126
|
+
.from("twinfield_connections")
|
|
127
|
+
.update({ cluster_uri: cluster, updated_at: new Date().toISOString() })
|
|
128
|
+
.eq("id", conn.id);
|
|
129
|
+
}
|
|
130
|
+
conn.cluster_uri = cluster;
|
|
131
|
+
return cluster.replace(/\/+$/, "");
|
|
132
|
+
}
|
|
133
|
+
export async function sendProcessXml(conn, dimensionXml) {
|
|
134
|
+
const companyCode = conn.office_code || conn.company_code;
|
|
135
|
+
if (!companyCode)
|
|
136
|
+
throw new Error("Twinfield office/company code ontbreekt in de koppeling.");
|
|
137
|
+
let token = await ensureAccessToken(conn);
|
|
138
|
+
let cluster = await ensureCluster(conn, token);
|
|
139
|
+
const url = `${cluster}/webservices/processxml.asmx`;
|
|
140
|
+
const doCall = async () => {
|
|
141
|
+
const envelope = buildProcessXmlEnvelope(token, companyCode, dimensionXml);
|
|
142
|
+
return fetch(url, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: {
|
|
145
|
+
"Content-Type": "text/xml; charset=utf-8",
|
|
146
|
+
SOAPAction: "http://www.twinfield.com/ProcessXmlDocument",
|
|
147
|
+
},
|
|
148
|
+
body: envelope,
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
let res = await doCall();
|
|
152
|
+
if (res.status === 401) {
|
|
153
|
+
// Forceer refresh
|
|
154
|
+
conn.token_expires_at = null;
|
|
155
|
+
conn.access_token = null;
|
|
156
|
+
token = await ensureAccessToken(conn);
|
|
157
|
+
cluster = await ensureCluster(conn, token);
|
|
158
|
+
res = await doCall();
|
|
159
|
+
}
|
|
160
|
+
const text = await res.text();
|
|
161
|
+
if (!res.ok) {
|
|
162
|
+
throw new Error(`Twinfield SOAP-fout ${res.status}: ${text.slice(0, 400)}`);
|
|
163
|
+
}
|
|
164
|
+
// Zeer eenvoudige result-parse: zoek result="1" of msg="1" in de body
|
|
165
|
+
const okMatch = /result="1"/.test(text);
|
|
166
|
+
const msgMatch = /<message[^>]*>([^<]+)<\/message>/i.exec(text);
|
|
167
|
+
if (!okMatch && /msg="1"/.test(text)) {
|
|
168
|
+
throw new Error(`Twinfield weigerde de klant: ${msgMatch?.[1] ?? "onbekende fout"}`);
|
|
169
|
+
}
|
|
170
|
+
return { ok: true, message: msgMatch?.[1] ?? "OK", raw: text };
|
|
171
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface TwinfieldCustomerInput {
|
|
2
|
+
id: string;
|
|
3
|
+
twinfield_code: string;
|
|
4
|
+
company_name: string;
|
|
5
|
+
contact_person?: string | null;
|
|
6
|
+
email?: string | null;
|
|
7
|
+
billing_email?: string | null;
|
|
8
|
+
phone?: string | null;
|
|
9
|
+
fax?: string | null;
|
|
10
|
+
website?: string | null;
|
|
11
|
+
street?: string | null;
|
|
12
|
+
house_number?: string | null;
|
|
13
|
+
postal_code?: string | null;
|
|
14
|
+
city?: string | null;
|
|
15
|
+
country?: string | null;
|
|
16
|
+
address_line1?: string | null;
|
|
17
|
+
address_line2?: string | null;
|
|
18
|
+
vat_number?: string | null;
|
|
19
|
+
coc_number?: string | null;
|
|
20
|
+
payment_term_days?: number | null;
|
|
21
|
+
discount_days?: number | null;
|
|
22
|
+
discount_percentage?: number | null;
|
|
23
|
+
bank_iban?: string | null;
|
|
24
|
+
bank_bic?: string | null;
|
|
25
|
+
bank_account_holder?: string | null;
|
|
26
|
+
bank_account_number?: string | null;
|
|
27
|
+
bank_name?: string | null;
|
|
28
|
+
bank_address_line1?: string | null;
|
|
29
|
+
bank_house_number?: string | null;
|
|
30
|
+
bank_postal_code?: string | null;
|
|
31
|
+
bank_city?: string | null;
|
|
32
|
+
bank_country?: string | null;
|
|
33
|
+
language?: string | null;
|
|
34
|
+
is_active?: boolean | null;
|
|
35
|
+
}
|
|
36
|
+
export interface MappedCustomer {
|
|
37
|
+
xml: string;
|
|
38
|
+
hash: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Bouw Twinfield DEB-dimension XML voor processxml.asmx.
|
|
42
|
+
* Zie: https://developers.twinfield.com/documentation → Master data → Customers
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildCustomerDimensionXml(c: TwinfieldCustomerInput, office: string): MappedCustomer;
|
|
45
|
+
export declare function buildProcessXmlEnvelope(accessToken: string, companyCode: string, dimensionXml: string): string;
|
|
46
|
+
//# sourceMappingURL=mapping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../../src/lib/accounting/twinfield/mapping.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,CAAC,EAAE,sBAAsB,EACzB,MAAM,EAAE,MAAM,GACb,cAAc,CAyEhB;AAED,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,MAAM,CAeR"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
function xmlEscape(v) {
|
|
3
|
+
if (v === null || v === undefined)
|
|
4
|
+
return "";
|
|
5
|
+
return String(v)
|
|
6
|
+
.replace(/&/g, "&")
|
|
7
|
+
.replace(/</g, "<")
|
|
8
|
+
.replace(/>/g, ">")
|
|
9
|
+
.replace(/"/g, """)
|
|
10
|
+
.replace(/'/g, "'");
|
|
11
|
+
}
|
|
12
|
+
function el(tag, value, attrs) {
|
|
13
|
+
const v = value === null || value === undefined ? "" : String(value).trim();
|
|
14
|
+
if (!v)
|
|
15
|
+
return "";
|
|
16
|
+
const a = attrs
|
|
17
|
+
? " " + Object.entries(attrs).map(([k, vv]) => `${k}="${xmlEscape(vv)}"`).join(" ")
|
|
18
|
+
: "";
|
|
19
|
+
return `<${tag}${a}>${xmlEscape(v)}</${tag}>`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Bouw Twinfield DEB-dimension XML voor processxml.asmx.
|
|
23
|
+
* Zie: https://developers.twinfield.com/documentation → Master data → Customers
|
|
24
|
+
*/
|
|
25
|
+
export function buildCustomerDimensionXml(c, office) {
|
|
26
|
+
const streetLine = [c.street, c.house_number].filter(Boolean).join(" ") || c.address_line1 || "";
|
|
27
|
+
const address = [
|
|
28
|
+
`<address type="invoice" default="true">`,
|
|
29
|
+
el("field", "name"),
|
|
30
|
+
el("name", c.company_name),
|
|
31
|
+
el("contact", c.contact_person),
|
|
32
|
+
el("country", c.country),
|
|
33
|
+
el("city", c.city),
|
|
34
|
+
el("postcode", c.postal_code),
|
|
35
|
+
el("telephone", c.phone),
|
|
36
|
+
el("fax", c.fax),
|
|
37
|
+
el("email", c.billing_email || c.email),
|
|
38
|
+
el("field1", streetLine),
|
|
39
|
+
el("field2", c.address_line2),
|
|
40
|
+
"</address>",
|
|
41
|
+
].filter(Boolean).join("");
|
|
42
|
+
const bank = c.bank_iban
|
|
43
|
+
? [
|
|
44
|
+
`<bank default="true">`,
|
|
45
|
+
el("ascription", c.bank_account_holder || c.company_name),
|
|
46
|
+
el("accountnumber", c.bank_account_number),
|
|
47
|
+
el("iban", c.bank_iban),
|
|
48
|
+
el("bicode", c.bank_bic),
|
|
49
|
+
el("bankname", c.bank_name),
|
|
50
|
+
el("city", c.bank_city),
|
|
51
|
+
el("country", c.bank_country),
|
|
52
|
+
el("postcode", c.bank_postal_code),
|
|
53
|
+
el("address", [c.bank_address_line1, c.bank_house_number].filter(Boolean).join(" ")),
|
|
54
|
+
"</bank>",
|
|
55
|
+
].filter(Boolean).join("")
|
|
56
|
+
: "";
|
|
57
|
+
const financials = [
|
|
58
|
+
"<financials>",
|
|
59
|
+
el("duedays", c.payment_term_days ?? undefined),
|
|
60
|
+
el("discountdays", c.discount_days ?? undefined),
|
|
61
|
+
el("discountpercentage", c.discount_percentage ?? undefined),
|
|
62
|
+
el("ebilling", "true"),
|
|
63
|
+
el("ebillmail", c.billing_email || c.email),
|
|
64
|
+
el("vatcode", c.country && c.country.toUpperCase() !== "NL" ? "VH" : "VN"),
|
|
65
|
+
el("substitutionlevel", "0"),
|
|
66
|
+
"</financials>",
|
|
67
|
+
].filter(Boolean).join("");
|
|
68
|
+
const dimension = [
|
|
69
|
+
`<dimension status="active">`,
|
|
70
|
+
el("code", c.twinfield_code),
|
|
71
|
+
el("office", office),
|
|
72
|
+
el("type", "DEB"),
|
|
73
|
+
el("name", c.company_name),
|
|
74
|
+
el("shortname", c.company_name.slice(0, 25)),
|
|
75
|
+
el("website", c.website),
|
|
76
|
+
el("coc", c.coc_number),
|
|
77
|
+
el("vatnumber", c.vat_number),
|
|
78
|
+
el("inuse", "true"),
|
|
79
|
+
el("behaviour", "normal"),
|
|
80
|
+
el("touched", "0"),
|
|
81
|
+
el("beginperiod", "0"),
|
|
82
|
+
el("beginyear", "0"),
|
|
83
|
+
el("endperiod", "0"),
|
|
84
|
+
el("endyear", "0"),
|
|
85
|
+
el("editdimensionname", "true"),
|
|
86
|
+
financials,
|
|
87
|
+
`<addresses>${address}</addresses>`,
|
|
88
|
+
bank ? `<banks>${bank}</banks>` : "",
|
|
89
|
+
"</dimension>",
|
|
90
|
+
].filter(Boolean).join("");
|
|
91
|
+
const hash = createHash("sha1").update(dimension).digest("hex");
|
|
92
|
+
return { xml: dimension, hash };
|
|
93
|
+
}
|
|
94
|
+
export function buildProcessXmlEnvelope(accessToken, companyCode, dimensionXml) {
|
|
95
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
96
|
+
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
|
97
|
+
<soap:Header>
|
|
98
|
+
<Header xmlns="http://www.twinfield.com/">
|
|
99
|
+
<AccessToken>${xmlEscape(accessToken)}</AccessToken>
|
|
100
|
+
<CompanyCode>${xmlEscape(companyCode)}</CompanyCode>
|
|
101
|
+
</Header>
|
|
102
|
+
</soap:Header>
|
|
103
|
+
<soap:Body>
|
|
104
|
+
<ProcessXmlDocument xmlns="http://www.twinfield.com/">
|
|
105
|
+
<xmlRequest>${dimensionXml}</xmlRequest>
|
|
106
|
+
</ProcessXmlDocument>
|
|
107
|
+
</soap:Body>
|
|
108
|
+
</soap:Envelope>`;
|
|
109
|
+
}
|