@shopickup/adapters-foxpost 0.0.1

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 (47) hide show
  1. package/README.md +48 -0
  2. package/dist/capabilities/index.d.ts +9 -0
  3. package/dist/capabilities/index.d.ts.map +1 -0
  4. package/dist/capabilities/index.js +8 -0
  5. package/dist/capabilities/label.d.ts +27 -0
  6. package/dist/capabilities/label.d.ts.map +1 -0
  7. package/dist/capabilities/label.js +370 -0
  8. package/dist/capabilities/parcels.d.ts +21 -0
  9. package/dist/capabilities/parcels.d.ts.map +1 -0
  10. package/dist/capabilities/parcels.js +233 -0
  11. package/dist/capabilities/pickup-points.d.ts +38 -0
  12. package/dist/capabilities/pickup-points.d.ts.map +1 -0
  13. package/dist/capabilities/pickup-points.js +225 -0
  14. package/dist/capabilities/track.d.ts +16 -0
  15. package/dist/capabilities/track.d.ts.map +1 -0
  16. package/dist/capabilities/track.js +99 -0
  17. package/dist/client/index.d.ts +17 -0
  18. package/dist/client/index.d.ts.map +1 -0
  19. package/dist/client/index.js +30 -0
  20. package/dist/errors.d.ts +34 -0
  21. package/dist/errors.d.ts.map +1 -0
  22. package/dist/errors.js +165 -0
  23. package/dist/index.d.ts +119 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +151 -0
  26. package/dist/mappers/index.d.ts +108 -0
  27. package/dist/mappers/index.d.ts.map +1 -0
  28. package/dist/mappers/index.js +270 -0
  29. package/dist/mappers/trackStatus.d.ts +58 -0
  30. package/dist/mappers/trackStatus.d.ts.map +1 -0
  31. package/dist/mappers/trackStatus.js +290 -0
  32. package/dist/types/generated.d.ts +177 -0
  33. package/dist/types/generated.d.ts.map +1 -0
  34. package/dist/types/generated.js +9 -0
  35. package/dist/types/index.d.ts +7 -0
  36. package/dist/types/index.d.ts.map +1 -0
  37. package/dist/types/index.js +6 -0
  38. package/dist/utils/httpUtils.d.ts +18 -0
  39. package/dist/utils/httpUtils.d.ts.map +1 -0
  40. package/dist/utils/httpUtils.js +33 -0
  41. package/dist/utils/resolveBaseUrl.d.ts +23 -0
  42. package/dist/utils/resolveBaseUrl.d.ts.map +1 -0
  43. package/dist/utils/resolveBaseUrl.js +19 -0
  44. package/dist/validation.d.ts +1723 -0
  45. package/dist/validation.d.ts.map +1 -0
  46. package/dist/validation.js +799 -0
  47. package/package.json +68 -0
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Mappers for Foxpost adapter
3
+ * Converts between canonical Shopickup types and Foxpost API types
4
+ */
5
+ import { mapFoxpostStatusCode } from './trackStatus.js';
6
+ const FOXPOST_SIZES = ["xs", "s", "m", "l", "xl"];
7
+ /**
8
+ * Map canonical Address (from Parcel.sender or Parcel.recipient) to Foxpost address format
9
+ */
10
+ export function mapAddressToFoxpost(addr) {
11
+ return {
12
+ name: addr.name.substring(0, 150), // Foxpost max length
13
+ phone: addr.phone || "",
14
+ email: addr.email || "",
15
+ city: addr.city?.substring(0, 25),
16
+ zip: addr.postalCode,
17
+ address: addr.street?.substring(0, 150),
18
+ country: addr.country || "HU", // Default to Hungary
19
+ };
20
+ }
21
+ /**
22
+ * Determine parcel size based on dimensions or weight
23
+ * Foxpost sizes: xs, s, m, l, xl
24
+ */
25
+ export function determineFoxpostSize(parcel) {
26
+ // If no dimensions, default to 's' (small)
27
+ if (!parcel.package.dimensionsCm) {
28
+ return "s";
29
+ }
30
+ const { length, width, height } = parcel.package.dimensionsCm;
31
+ const volume = length * width * height;
32
+ // Very rough heuristic based on volume
33
+ let candidate = "xs";
34
+ if (volume < 5000) {
35
+ candidate = "xs";
36
+ }
37
+ else if (volume < 15000) {
38
+ candidate = "s";
39
+ }
40
+ else if (volume < 50000) {
41
+ candidate = "m";
42
+ }
43
+ else if (volume < 100000) {
44
+ candidate = "l";
45
+ }
46
+ else {
47
+ candidate = "xl";
48
+ }
49
+ return candidate;
50
+ }
51
+ /**
52
+ * Map canonical Parcel to Foxpost CreateParcelRequest (strongly-typed)
53
+ * Returns typed FoxCreateParcelRequestItem with lenient validation support
54
+ *
55
+ * Handles both HOME delivery (full address) and PICKUP_POINT delivery (APM/locker)
56
+ */
57
+ export function mapParcelToFoxpostRequest(parcel, options = {}) {
58
+ const delivery = parcel.recipient.delivery;
59
+ const isHomeDelivery = delivery.method === 'HOME';
60
+ const recipientAddr = isHomeDelivery
61
+ ? delivery.address
62
+ : undefined;
63
+ const recipient = {
64
+ name: parcel.recipient.contact.name.substring(0, 150),
65
+ phone: parcel.recipient.contact.phone || "",
66
+ email: parcel.recipient.contact.email || "",
67
+ city: recipientAddr?.city?.substring(0, 25),
68
+ zip: recipientAddr?.postalCode,
69
+ address: recipientAddr?.street?.substring(0, 150),
70
+ country: recipientAddr?.country || "HU",
71
+ };
72
+ // COD amount from parcel if present, default to 0
73
+ const codAmount = parcel.cod?.amount.amount ?? 0;
74
+ const baseRequest = {
75
+ recipientName: recipient.name,
76
+ recipientPhone: recipient.phone,
77
+ recipientEmail: recipient.email,
78
+ size: determineFoxpostSize(parcel)?.toUpperCase() || 'S',
79
+ cod: codAmount,
80
+ // Optional fields
81
+ refCode: parcel.references?.customerReference
82
+ ?.substring(0, 30)
83
+ .concat(`-${parcel.id.substring(0, 10)}`),
84
+ comment: parcel.handling?.fragile ? 'FRAGILE' : undefined,
85
+ fragile: parcel.handling?.fragile || false,
86
+ };
87
+ // HOME delivery includes address fields
88
+ if (isHomeDelivery) {
89
+ return {
90
+ ...baseRequest,
91
+ recipientCity: recipient.city,
92
+ recipientZip: recipient.zip,
93
+ recipientAddress: recipient.address,
94
+ recipientCountry: recipient.country,
95
+ };
96
+ }
97
+ // PICKUP_POINT delivery adds destination (locker/APM code)
98
+ if (delivery.method === 'PICKUP_POINT') {
99
+ const pickupPoint = delivery.pickupPoint;
100
+ return {
101
+ ...baseRequest,
102
+ destination: pickupPoint?.id || '',
103
+ };
104
+ }
105
+ return baseRequest;
106
+ }
107
+ /**
108
+ * Map canonical Parcel to Foxpost CreateParcelRequest
109
+ * Parcel contains complete shipping details (sender, recipient, weight, service, etc.)
110
+ *
111
+ * Handles both HOME delivery (full address) and PICKUP_POINT delivery (APM/locker)
112
+ */
113
+ export function mapParcelToFoxpost(parcel, options = {}) {
114
+ const delivery = parcel.recipient.delivery;
115
+ const isHomeDelivery = delivery.method === 'HOME';
116
+ const recipientAddr = isHomeDelivery
117
+ ? delivery.address
118
+ : undefined;
119
+ const recipient = {
120
+ name: parcel.recipient.contact.name.substring(0, 150),
121
+ phone: parcel.recipient.contact.phone || "",
122
+ email: parcel.recipient.contact.email || "",
123
+ city: recipientAddr?.city?.substring(0, 25),
124
+ zip: recipientAddr?.postalCode,
125
+ address: recipientAddr?.street?.substring(0, 150),
126
+ country: recipientAddr?.country || "HU",
127
+ };
128
+ const foxpostRequest = {
129
+ recipientName: recipient.name,
130
+ recipientPhone: recipient.phone,
131
+ recipientEmail: recipient.email,
132
+ // HOME delivery includes address fields; APM leaves them undefined
133
+ ...(isHomeDelivery && {
134
+ recipientCity: recipient.city,
135
+ recipientZip: recipient.zip,
136
+ recipientAddress: recipient.address,
137
+ recipientCountry: recipient.country,
138
+ }),
139
+ size: determineFoxpostSize(parcel),
140
+ // Optional fields
141
+ refCode: parcel.references?.customerReference
142
+ ?.substring(0, 30)
143
+ .concat(`-${parcel.id.substring(0, 10)}`),
144
+ comment: parcel.handling?.fragile ? 'FRAGILE' : undefined,
145
+ fragile: parcel.handling?.fragile || false,
146
+ };
147
+ // For PICKUP_POINT delivery, add destination (locker/APM code)
148
+ if (!isHomeDelivery && delivery.method === 'PICKUP_POINT') {
149
+ const pickupPoint = delivery.pickupPoint;
150
+ if (pickupPoint?.id) {
151
+ foxpostRequest.destination = pickupPoint.id;
152
+ }
153
+ }
154
+ return foxpostRequest;
155
+ }
156
+ /**
157
+ * Map Foxpost tracking status code to canonical TrackingStatus
158
+ * Uses comprehensive status mapping from trackStatus.ts
159
+ * Unknown codes default to PENDING.
160
+ */
161
+ export function mapFoxpostStatusToCanonical(foxpostStatus) {
162
+ const mapping = mapFoxpostStatusCode(foxpostStatus);
163
+ return mapping.canonical; // Safe cast: trackStatus map returns valid canonical values
164
+ }
165
+ /**
166
+ * Map Foxpost TrackDTO to canonical TrackingEvent
167
+ *
168
+ * Normalizes the Foxpost status code to a canonical TrackingStatus while preserving
169
+ * the original carrier-specific code in `carrierStatusCode` for debugging and carrier-specific logic.
170
+ */
171
+ export function mapFoxpostTrackToCanonical(track) {
172
+ return {
173
+ timestamp: new Date(track.statusDate || new Date()),
174
+ status: mapFoxpostStatusToCanonical(track.status || "PENDING"),
175
+ carrierStatusCode: track.status || undefined,
176
+ description: track.longName || track.status || "Unknown status",
177
+ raw: track,
178
+ };
179
+ }
180
+ /**
181
+ * Map Foxpost TraceDTO (from new /api/tracking/{barcode} endpoint) to canonical TrackingEvent
182
+ * TraceDTO is returned in reverse chronological order (latest first) from the API
183
+ *
184
+ * Normalizes the Foxpost status code to a canonical TrackingStatus while preserving
185
+ * the original carrier-specific code in `carrierStatusCode`.
186
+ *
187
+ * Includes both English and Hungarian human-readable descriptions from the status map.
188
+ *
189
+ * Accepts both string and Date types for statusDate to support both raw API responses
190
+ * and validated Zod-parsed responses (which transform to Date).
191
+ *
192
+ * Example mapping:
193
+ * - Foxpost "CREATE" -> canonical "PENDING" (carrierStatusCode: "CREATE", description: "Order created")
194
+ * - Foxpost "HDINTRANSIT" -> canonical "OUT_FOR_DELIVERY" (carrierStatusCode: "HDINTRANSIT", description: "Out for home delivery")
195
+ * - Foxpost "RECEIVE" -> canonical "DELIVERED" (carrierStatusCode: "RECEIVE", description: "Delivered to recipient")
196
+ */
197
+ export function mapFoxpostTraceToCanonical(trace) {
198
+ const statusDate = typeof trace.statusDate === 'string'
199
+ ? new Date(trace.statusDate)
200
+ : trace.statusDate;
201
+ const statusCode = trace.status || "PENDING";
202
+ const statusMapping = mapFoxpostStatusCode(statusCode);
203
+ // Prefer mapped human description; fall back to API data if available
204
+ const humanDescription = statusMapping.human_en
205
+ || (trace.longName || trace.shortName || null)
206
+ || `Foxpost: ${statusCode}`;
207
+ const humanDescriptionHu = statusMapping.human_hu || null;
208
+ return {
209
+ timestamp: statusDate || new Date(),
210
+ status: mapFoxpostStatusToCanonical(statusCode),
211
+ carrierStatusCode: statusCode || undefined,
212
+ description: humanDescription,
213
+ descriptionLocalLanguage: humanDescriptionHu || undefined,
214
+ raw: trace,
215
+ };
216
+ }
217
+ /**
218
+ * Map canonical Parcel to Foxpost carrier-specific parcel (HD or APM)
219
+ * Discriminates based on Delivery type in the parcel
220
+ *
221
+ * @param parcel - Canonical parcel with full shipping details
222
+ * @param options - Additional mapping options (COD, comment, etc.)
223
+ * @returns FoxpostParcelHD | FoxpostParcelAPM with type discriminator set
224
+ */
225
+ export function mapParcelToFoxpostCarrierType(parcel, options = {}) {
226
+ const recipient = parcel.recipient;
227
+ const delivery = recipient.delivery;
228
+ // Determine parcel size
229
+ const size = (determineFoxpostSize(parcel) || 's').toUpperCase();
230
+ // COD amount from parcel.cod or options override
231
+ const codAmount = options.cod ?? parcel.cod?.amount.amount ?? 0;
232
+ // Fragile from options or parcel.handling
233
+ const isFragile = options.fragile ?? parcel.handling?.fragile ?? false;
234
+ // If delivery is PICKUP_POINT, create APM parcel
235
+ if (delivery.method === 'PICKUP_POINT') {
236
+ const apmParcel = {
237
+ type: 'APM',
238
+ cod: codAmount,
239
+ comment: options.comment,
240
+ destination: delivery.pickupPoint.id,
241
+ recipientEmail: recipient.contact.email || '',
242
+ recipientName: recipient.contact.name.substring(0, 150),
243
+ recipientPhone: recipient.contact.phone || '',
244
+ refCode: parcel.references?.customerReference?.substring(0, 30),
245
+ size,
246
+ };
247
+ return apmParcel;
248
+ }
249
+ // Otherwise (HOME delivery), create HD parcel
250
+ const homeDelivery = delivery;
251
+ const recipientAddr = homeDelivery.address;
252
+ const hdParcel = {
253
+ type: 'HD',
254
+ cod: codAmount,
255
+ comment: options.comment,
256
+ deliveryNote: options.deliveryNote || homeDelivery.instructions,
257
+ fragile: isFragile,
258
+ label: false, // Default: Foxpost prints label for B2B, not for C2C
259
+ recipientAddress: `${recipientAddr.street || ''}, ${recipientAddr.postalCode || ''} ${recipientAddr.city || ''}`.trim(),
260
+ recipientCity: recipientAddr.city,
261
+ recipientCountry: recipientAddr.country,
262
+ recipientEmail: recipient.contact.email || '',
263
+ recipientName: recipient.contact.name.substring(0, 150),
264
+ recipientPhone: recipient.contact.phone || '',
265
+ recipientZip: recipientAddr.postalCode,
266
+ refCode: parcel.references?.customerReference?.substring(0, 30),
267
+ size,
268
+ };
269
+ return hdParcel;
270
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Foxpost Tracking Status Map
3
+ *
4
+ * Comprehensive mapping of Foxpost status codes to:
5
+ * 1. Canonical tracking status (for normalized processing)
6
+ * 2. Human-readable descriptions in English and Hungarian
7
+ *
8
+ * The canonical status is used for generic processing, filtering, and integrator logic,
9
+ * while the human descriptions provide detailed information about the shipment state.
10
+ *
11
+ * Source: Foxpost OpenAPI tracking documentation and operational codes
12
+ */
13
+ import type { TrackingStatus } from "@shopickup/core";
14
+ export interface FoxpostStatusMapping {
15
+ /** Canonical status across all carriers */
16
+ canonical: TrackingStatus;
17
+ /** Human-readable description in English */
18
+ human_en?: string;
19
+ /** Human-readable description in Hungarian */
20
+ human_hu?: string;
21
+ /** Category hint for interpretation */
22
+ type?: 'locker' | 'courier' | 'facility' | 'technical';
23
+ }
24
+ /**
25
+ * Complete Foxpost status code to canonical mapping
26
+ *
27
+ * Key characteristics:
28
+ * - All Foxpost codes are covered (37 codes)
29
+ * - Unknown codes default to "UNKNOWN" with fallback description
30
+ * - Each code has English and Hungarian descriptions from Foxpost docs
31
+ * - Technical codes (CREATE, SLOTCHANGE, etc.) are marked with type: 'technical'
32
+ */
33
+ export declare const FOXPOST_STATUS_MAP: Record<string, FoxpostStatusMapping>;
34
+ /**
35
+ * Map Foxpost status code to canonical and human descriptions
36
+ *
37
+ * @param code Foxpost status code (e.g., "OPERIN", "HDINTRANSIT")
38
+ * @returns Mapping with canonical status and human descriptions (EN + HU)
39
+ *
40
+ * Unknown codes default to PENDING canonical status with fallback description.
41
+ *
42
+ * @example
43
+ * const mapping = mapFoxpostStatusCode("OPERIN");
44
+ * // { canonical: "IN_TRANSIT", human_en: "Arrived at locker", human_hu: "Automatában megérkezett" }
45
+ *
46
+ * const unknown = mapFoxpostStatusCode("FOOBAR");
47
+ * // { canonical: "PENDING", human_en: "Foxpost: FOOBAR", human_hu: undefined }
48
+ */
49
+ export declare function mapFoxpostStatusCode(code: string): FoxpostStatusMapping;
50
+ /**
51
+ * Get human-readable description in specified language
52
+ *
53
+ * @param code Foxpost status code
54
+ * @param language "en" for English, "hu" for Hungarian
55
+ * @returns Human-readable description
56
+ */
57
+ export declare function getFoxpostStatusDescription(code: string, language?: "en" | "hu"): string;
58
+ //# sourceMappingURL=trackStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trackStatus.d.ts","sourceRoot":"","sources":["../../src/mappers/trackStatus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACnC,2CAA2C;IAC3C,SAAS,EAAE,cAAc,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;CACxD;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CA0OnE,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,CAcvE;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,IAAI,GAAG,IAAW,GAC3B,MAAM,CAQR"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Foxpost Tracking Status Map
3
+ *
4
+ * Comprehensive mapping of Foxpost status codes to:
5
+ * 1. Canonical tracking status (for normalized processing)
6
+ * 2. Human-readable descriptions in English and Hungarian
7
+ *
8
+ * The canonical status is used for generic processing, filtering, and integrator logic,
9
+ * while the human descriptions provide detailed information about the shipment state.
10
+ *
11
+ * Source: Foxpost OpenAPI tracking documentation and operational codes
12
+ */
13
+ /**
14
+ * Complete Foxpost status code to canonical mapping
15
+ *
16
+ * Key characteristics:
17
+ * - All Foxpost codes are covered (37 codes)
18
+ * - Unknown codes default to "UNKNOWN" with fallback description
19
+ * - Each code has English and Hungarian descriptions from Foxpost docs
20
+ * - Technical codes (CREATE, SLOTCHANGE, etc.) are marked with type: 'technical'
21
+ */
22
+ export const FOXPOST_STATUS_MAP = {
23
+ // === Locker/APM Operations ===
24
+ CREATE: {
25
+ canonical: "PENDING",
26
+ human_en: "Order created",
27
+ human_hu: "Rendelés létrehozva",
28
+ type: "locker",
29
+ },
30
+ OPERIN: {
31
+ canonical: "IN_TRANSIT",
32
+ human_en: "Arrived at locker",
33
+ human_hu: "Automatában megérkezett",
34
+ type: "locker",
35
+ },
36
+ OPEROUT: {
37
+ canonical: "IN_TRANSIT",
38
+ human_en: "Removed from locker / Out for delivery",
39
+ human_hu: "Automatából kivéve / Kiszállítás",
40
+ type: "locker",
41
+ },
42
+ RECEIVE: {
43
+ canonical: "DELIVERED",
44
+ human_en: "Delivered to recipient",
45
+ human_hu: "Átvéve",
46
+ type: "locker",
47
+ },
48
+ // === Return Operations ===
49
+ RETURN: {
50
+ canonical: "RETURNED",
51
+ human_en: "Returned to sender",
52
+ human_hu: "Visszaküldésre került",
53
+ type: "facility",
54
+ },
55
+ REDIRECT: {
56
+ canonical: "IN_TRANSIT",
57
+ human_en: "Redirected to new destination",
58
+ human_hu: "Átirányítva új célhelyre",
59
+ type: "facility",
60
+ },
61
+ BACKTOSENDER: {
62
+ canonical: "RETURNED",
63
+ human_en: "Returned to sender",
64
+ human_hu: "Szállító felé visszaküldve",
65
+ type: "facility",
66
+ },
67
+ RESENT: {
68
+ canonical: "IN_TRANSIT",
69
+ human_en: "Resent to new destination",
70
+ human_hu: "Újra küldve új célhelyre",
71
+ type: "facility",
72
+ },
73
+ // === Facility Sorting Operations ===
74
+ SORTIN: {
75
+ canonical: "IN_TRANSIT",
76
+ human_en: "Arrived at sorting facility",
77
+ human_hu: "Rendezőközpontba megérkezett",
78
+ type: "facility",
79
+ },
80
+ SORTOUT: {
81
+ canonical: "IN_TRANSIT",
82
+ human_en: "Left sorting facility",
83
+ human_hu: "Rendezőközpontból elküldve",
84
+ type: "facility",
85
+ },
86
+ MPSIN: {
87
+ canonical: "IN_TRANSIT",
88
+ human_en: "Arrived at parcel hub",
89
+ human_hu: "Csomagközpontba megérkezett",
90
+ type: "facility",
91
+ },
92
+ C2CIN: {
93
+ canonical: "IN_TRANSIT",
94
+ human_en: "Arrived at customer collection point",
95
+ human_hu: "Ügyfél felvevőpontba megérkezett",
96
+ type: "facility",
97
+ },
98
+ C2BIN: {
99
+ canonical: "IN_TRANSIT",
100
+ human_en: "Arrived at business collection point",
101
+ human_hu: "Üzleti felvevőpontba megérkezett",
102
+ type: "facility",
103
+ },
104
+ INWAREHOUSE: {
105
+ canonical: "IN_TRANSIT",
106
+ human_en: "In warehouse",
107
+ human_hu: "Raktárban van",
108
+ type: "facility",
109
+ },
110
+ // === Home Delivery Operations ===
111
+ HDSENT: {
112
+ canonical: "OUT_FOR_DELIVERY",
113
+ human_en: "Home delivery sent",
114
+ human_hu: "Házhozszállítás küldve",
115
+ type: "courier",
116
+ },
117
+ HDINTRANSIT: {
118
+ canonical: "OUT_FOR_DELIVERY",
119
+ human_en: "Out for home delivery",
120
+ human_hu: "Házhoz szállítás alatt",
121
+ type: "courier",
122
+ },
123
+ HDDEPO: {
124
+ canonical: "IN_TRANSIT",
125
+ human_en: "At home delivery depot",
126
+ human_hu: "Kiszállítási depoban",
127
+ type: "facility",
128
+ },
129
+ HDCOURIER: {
130
+ canonical: "OUT_FOR_DELIVERY",
131
+ human_en: "With courier for delivery",
132
+ human_hu: "Futárnál szállításra",
133
+ type: "courier",
134
+ },
135
+ HDHUBIN: {
136
+ canonical: "IN_TRANSIT",
137
+ human_en: "Arrived at delivery hub",
138
+ human_hu: "Szállítási csomópontra megérkezett",
139
+ type: "facility",
140
+ },
141
+ HDHUBOUT: {
142
+ canonical: "OUT_FOR_DELIVERY",
143
+ human_en: "Left delivery hub",
144
+ human_hu: "Szállítási csomópontból elküldve",
145
+ type: "facility",
146
+ },
147
+ HDRECEIVE: {
148
+ canonical: "DELIVERED",
149
+ human_en: "Delivered by home delivery",
150
+ human_hu: "Házhoz szállítva",
151
+ type: "courier",
152
+ },
153
+ HDRETURN: {
154
+ canonical: "RETURNED",
155
+ human_en: "Returned from home delivery",
156
+ human_hu: "Házhoz szállítás visszatérült",
157
+ type: "courier",
158
+ },
159
+ // === Exception States ===
160
+ OVERTIMEOUT: {
161
+ canonical: "EXCEPTION",
162
+ human_en: "Overtime out (delivery exceeded time limit)",
163
+ human_hu: "Túlóra lejárt",
164
+ type: "technical",
165
+ },
166
+ OVERTIMED: {
167
+ canonical: "EXCEPTION",
168
+ human_en: "Overtime (delivery delayed)",
169
+ human_hu: "Túlóra (késedelem)",
170
+ type: "technical",
171
+ },
172
+ HDUNDELIVERABLE: {
173
+ canonical: "EXCEPTION",
174
+ human_en: "Undeliverable (home delivery failed)",
175
+ human_hu: "Nem szállítható (házhoz szállítás sikertelen)",
176
+ type: "courier",
177
+ },
178
+ MISSORT: {
179
+ canonical: "EXCEPTION",
180
+ human_en: "Missorted - rerouted",
181
+ human_hu: "Hibásan rendezett - átirányított",
182
+ type: "technical",
183
+ },
184
+ EMPTYSLOT: {
185
+ canonical: "EXCEPTION",
186
+ human_en: "No locker slot available",
187
+ human_hu: "Nincs szabad automatahely",
188
+ type: "locker",
189
+ },
190
+ BACKLOGINFULL: {
191
+ canonical: "EXCEPTION",
192
+ human_en: "Backlog - facility at capacity",
193
+ human_hu: "Feldolgozási várakozási sor teljes",
194
+ type: "facility",
195
+ },
196
+ BACKLOGINFAIL: {
197
+ canonical: "EXCEPTION",
198
+ human_en: "Backlog failed - retry needed",
199
+ human_hu: "Feldolgozási sor sikertelen",
200
+ type: "technical",
201
+ },
202
+ // === Collection/Handoff Operations ===
203
+ COLLECTSENT: {
204
+ canonical: "IN_TRANSIT",
205
+ human_en: "Collect shipment sent",
206
+ human_hu: "Gyűjtőszállítmány küldve",
207
+ type: "facility",
208
+ },
209
+ COLLECTED: {
210
+ canonical: "DELIVERED",
211
+ human_en: "Collected from sender",
212
+ human_hu: "Feladótól összeszedve",
213
+ type: "facility",
214
+ },
215
+ // === Slot/Redirect Operations ===
216
+ SLOTCHANGE: {
217
+ canonical: "IN_TRANSIT",
218
+ human_en: "Locker slot changed",
219
+ human_hu: "Automatahely módosult",
220
+ type: "technical",
221
+ },
222
+ WBXREDIRECT: {
223
+ canonical: "IN_TRANSIT",
224
+ human_en: "Redirected via WBX",
225
+ human_hu: "WBX-en keresztül átirányított",
226
+ type: "facility",
227
+ },
228
+ PREREDIRECT: {
229
+ canonical: "IN_TRANSIT",
230
+ human_en: "Pre-redirect (staged for redirection)",
231
+ human_hu: "Előátirányítás (átirányításra előkészítve)",
232
+ type: "technical",
233
+ },
234
+ // === Final Delivery State ===
235
+ RETURNED: {
236
+ canonical: "DELIVERED",
237
+ human_en: "Returned (delivered back to sender)",
238
+ human_hu: "Visszaküldve (feladónak szállítva)",
239
+ type: "facility",
240
+ },
241
+ // === Preparation/Technical ===
242
+ PREPAREDFORPD: {
243
+ canonical: "IN_TRANSIT",
244
+ human_en: "Prepared for home delivery",
245
+ human_hu: "Házhoz szállításra előkészítve",
246
+ type: "technical",
247
+ },
248
+ };
249
+ /**
250
+ * Map Foxpost status code to canonical and human descriptions
251
+ *
252
+ * @param code Foxpost status code (e.g., "OPERIN", "HDINTRANSIT")
253
+ * @returns Mapping with canonical status and human descriptions (EN + HU)
254
+ *
255
+ * Unknown codes default to PENDING canonical status with fallback description.
256
+ *
257
+ * @example
258
+ * const mapping = mapFoxpostStatusCode("OPERIN");
259
+ * // { canonical: "IN_TRANSIT", human_en: "Arrived at locker", human_hu: "Automatában megérkezett" }
260
+ *
261
+ * const unknown = mapFoxpostStatusCode("FOOBAR");
262
+ * // { canonical: "PENDING", human_en: "Foxpost: FOOBAR", human_hu: undefined }
263
+ */
264
+ export function mapFoxpostStatusCode(code) {
265
+ const mapping = FOXPOST_STATUS_MAP[code];
266
+ if (mapping) {
267
+ return mapping;
268
+ }
269
+ // Fallback for unknown codes: map to PENDING with fallback description
270
+ return {
271
+ canonical: "PENDING",
272
+ human_en: `Foxpost: ${code}`,
273
+ human_hu: undefined,
274
+ type: "technical",
275
+ };
276
+ }
277
+ /**
278
+ * Get human-readable description in specified language
279
+ *
280
+ * @param code Foxpost status code
281
+ * @param language "en" for English, "hu" for Hungarian
282
+ * @returns Human-readable description
283
+ */
284
+ export function getFoxpostStatusDescription(code, language = "en") {
285
+ const mapping = mapFoxpostStatusCode(code);
286
+ if (language === "hu") {
287
+ return mapping.human_hu ?? mapping.human_en ?? `Foxpost: ${code}`;
288
+ }
289
+ return mapping.human_en ?? `Foxpost: ${code}`;
290
+ }