PluginProbe
PostNL for WooCommerce / trunk
PostNL for WooCommerce vtrunk
5.9.12 5.9.11 5.9.10 5.9.9 5.9.8 5.9.7 5.9.6 trunk 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 3.1.4 3.1.5 3.1.6 3.1.7 4.0.0 4.0.1 4.0.2 4.3.2 4.3.3 4.4.0 4.4.1 All 72 releases
woo-postnl / src / Helper / Product_Mapper / V4_Mapper.php

V4_Mapper.php in PostNL for WooCommerce trunk, at src/Helper/Product_Mapper/V4_Mapper.php

430 lines 17.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class V4_Mapper file.
4 *
5 * @package PostNLWooCommerce\Helper\Product_Mapper
6 */
7
8 namespace PostNLWooCommerce\Helper\Product_Mapper;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit;
12 }
13
14 /**
15 * Pure mapper: V1 combination → V4 ShipmentType + Services shape, or Legacy-only marker.
16 *
17 * Input combination shape:
18 * array(
19 * 'origin' => 'NL|BE',
20 * 'destination' => 'NL|BE|EU|ROW',
21 * 'flow' => 'delivery_day|pickup_points',
22 * 'options' => array(),
23 * 'legacy_product_code' => optional string,
24 * )
25 *
26 * Runtime outcomes: has_v4_equivalent = true (42 rows) or false (47 rows).
27 * needs_confirmation rows behave as Legacy-only at runtime until promoted to v4_mapped.
28 *
29 * Not-yet-available codes are always Legacy-only; see NOT_YET_AVAILABLE_CODES.
30 * EU/ROW parcels (4907/4909) map to V4 with an InternationalShipmentData bundle
31 * (see SDK_SERVICES_BUNDLE_GAP). EU/ROW packet/mailbox products and international
32 * pickup stay Legacy-only.
33 *
34 * Source: PostNL Product Overview documentation.
35 */
36 class V4_Mapper {
37
38 const NOT_YET_AVAILABLE_CODES = array(
39 '1175',
40 '3571',
41 '3574',
42 '4936',
43 '4960',
44 '4961',
45 '4962',
46 '4963',
47 '4965',
48 '4983',
49 );
50
51 const REASON_NOT_YET_AVAILABLE = 'not_yet_available_in_v4';
52 const REASON_NEEDS_CONFIRMATION = 'needs_confirmation';
53 const REASON_UNKNOWN_COMBINATION = 'unknown_combination';
54 const REASON_PRODUCT_CODE_MISMATCH = 'product_code_mismatch';
55
56 // The V4 Services DTO has no `bundle` property; the international service bundle
57 // lives on InternationalShipmentData->bundle instead, which is where EU/ROW parcels carry it.
58 const SDK_SERVICES_BUNDLE_GAP = 'sdk_v4_services_dto_missing_bundle_property';
59
60 /**
61 * Returns true if a confirmed V4 equivalent exists for the given combination.
62 *
63 * @param array $combination See class PHPDoc.
64 * @return bool
65 */
66 public static function has_v4_equivalent( array $combination ): bool {
67 return static::map( $combination )['has_v4_equivalent'];
68 }
69
70 /**
71 * Maps a combination to its V4 result or a Legacy-only marker.
72 *
73 * @param array $combination See class PHPDoc.
74 * @return array
75 */
76 public static function map( array $combination ): array {
77 $origin = $combination['origin'] ?? '';
78 $destination = $combination['destination'] ?? '';
79 $flow = $combination['flow'] ?? '';
80 $options = $combination['options'] ?? array();
81 $input_code = $combination['legacy_product_code'] ?? null;
82
83 $matrix = static::matrix();
84 $key = static::options_key( $options );
85
86 $row = $matrix[ $origin ][ $destination ][ $flow ][ $key ] ?? null;
87
88 if ( null === $row ) {
89 return array(
90 'has_v4_equivalent' => false,
91 'legacy_product_code' => $input_code,
92 'source_row_id' => 0,
93 'legacy_only_reason' => self::REASON_UNKNOWN_COMBINATION,
94 );
95 }
96
97 if ( null !== $input_code && $row['legacy_product_code'] !== $input_code ) {
98 return array(
99 'has_v4_equivalent' => false,
100 'legacy_product_code' => $input_code,
101 'source_row_id' => 0,
102 'legacy_only_reason' => self::REASON_PRODUCT_CODE_MISMATCH,
103 );
104 }
105
106 // Not-yet-available codes never map to V4, even if a future matrix edit says otherwise.
107 if ( in_array( $row['legacy_product_code'], self::NOT_YET_AVAILABLE_CODES, true ) ) {
108 return static::legacy_result( $row['source_row_id'], $row['legacy_product_code'], self::REASON_NOT_YET_AVAILABLE );
109 }
110
111 return $row;
112 }
113
114 /**
115 * Produces a canonical, order-independent lookup key from an options array.
116 *
117 * @param string[] $options Options flags.
118 * @return string
119 */
120 private static function options_key( array $options ): string {
121 if ( empty( $options ) ) {
122 return '(base)';
123 }
124 sort( $options );
125 return implode( '+', $options );
126 }
127
128 /**
129 * Builds a V4-mapped result entry.
130 *
131 * @param int $row_id CSV row_id for traceability.
132 * @param string $code Legacy product code.
133 * @param string $shipment_type V4 ShipmentType value ('parcel' or 'letterbox').
134 * @param array $services V4 Services flags.
135 * @param array $delivery_location V4 DeliveryLocation data (pickup flows only).
136 * @param array $international_data V4 InternationalShipmentData hints.
137 * @return array
138 */
139 private static function v4_result(
140 int $row_id,
141 string $code,
142 string $shipment_type,
143 array $services = array(),
144 array $delivery_location = array(),
145 array $international_data = array()
146 ): array {
147 return array(
148 'has_v4_equivalent' => true,
149 'legacy_product_code' => $code,
150 'source_row_id' => $row_id,
151 'shipmentType' => $shipment_type,
152 'services' => $services,
153 'deliveryLocation' => $delivery_location,
154 'internationalShipmentData' => $international_data,
155 );
156 }
157
158 /**
159 * Builds a Legacy-only result entry.
160 *
161 * @param int $row_id CSV row_id for traceability.
162 * @param string $code Legacy product code.
163 * @param string $reason One of the REASON_* constants.
164 * @return array
165 */
166 private static function legacy_result( int $row_id, string $code, string $reason ): array {
167 return array(
168 'has_v4_equivalent' => false,
169 'legacy_product_code' => $code,
170 'source_row_id' => $row_id,
171 'legacy_only_reason' => $reason,
172 );
173 }
174
175 /**
176 * 89-row combination matrix indexed for O(1) lookup.
177 *
178 * @return array
179 */
180 private static function matrix(): array {
181 static $cache = null;
182
183 if ( null !== $cache ) {
184 return $cache;
185 }
186
187 $nc = self::REASON_NEEDS_CONFIRMATION;
188 $nya = self::REASON_NOT_YET_AVAILABLE;
189 $pickup = array( 'pickupLocationId' => '<from_selected_location>' );
190
191 // International service bundles carried on InternationalShipmentData->bundle (4907/4909).
192 // The V4 request carries only this flat bundle enum for international insurance —
193 // there is no declared value anywhere on the block, whereas V1 sent an Amounts entry
194 // (AmountType 02, currency, order subtotal) with every insured EU shipment. Whether the
195 // bundles are flat-coverage tiers or still expect a declared amount is an open question
196 // with PostNL, tracked as Q14 in docs/postnl-v4-migration/flip-checklist.md and a
197 // pre-flip gate for the label flow.
198 $track_trace = array( 'bundle' => 'track_trace' );
199 $insured = array( 'bundle' => 'insured' );
200 $insured_plus = array( 'bundle' => 'insured_plus' );
201
202 $cache = array(
203 'NL' => array(
204 'NL' => array(
205 'delivery_day' => array(
206 '(base)' => self::v4_result( 1, '3085', 'parcel' ),
207 'delivery_code_at_door+insured_shipping' => self::v4_result(
208 2,
209 '3085',
210 'parcel',
211 array(
212 'deliveryConfirmation' => 'deliverycode',
213 'insuredValue' => '<order_total>',
214 )
215 ),
216 'only_home_address' => self::v4_result( 3, '3385', 'parcel', array( 'statedAddressOnly' => true ) ),
217 'return_no_answer' => self::v4_result( 4, '3090', 'parcel', array( 'returnWhenNotHome' => true ) ),
218 'signature_on_delivery' => self::v4_result( 5, '3189', 'parcel', array( 'deliveryConfirmation' => 'signature' ) ),
219 'only_home_address+return_no_answer' => self::v4_result(
220 6,
221 '3390',
222 'parcel',
223 array(
224 'returnWhenNotHome' => true,
225 'statedAddressOnly' => true,
226 )
227 ),
228 'insured_shipping+return_no_answer+signature_on_delivery' => self::v4_result(
229 7,
230 '3094',
231 'parcel',
232 array(
233 'deliveryConfirmation' => 'signature',
234 'insuredValue' => '<order_total>',
235 'returnWhenNotHome' => true,
236 )
237 ),
238 'only_home_address+signature_on_delivery' => self::v4_result(
239 8,
240 '3089',
241 'parcel',
242 array(
243 'deliveryConfirmation' => 'signature',
244 'statedAddressOnly' => true,
245 )
246 ),
247 'insured_shipping+signature_on_delivery' => self::v4_result(
248 9,
249 '3087',
250 'parcel',
251 array(
252 'deliveryConfirmation' => 'signature',
253 'insuredValue' => '<order_total>',
254 )
255 ),
256 'return_no_answer+signature_on_delivery' => self::v4_result(
257 10,
258 '3389',
259 'parcel',
260 array(
261 'deliveryConfirmation' => 'signature',
262 'returnWhenNotHome' => true,
263 )
264 ),
265 'only_home_address+return_no_answer+signature_on_delivery' => self::v4_result(
266 11,
267 '3096',
268 'parcel',
269 array(
270 'deliveryConfirmation' => 'signature',
271 'returnWhenNotHome' => true,
272 'statedAddressOnly' => true,
273 )
274 ),
275 'letterbox' => self::v4_result( 12, '2928', 'letterbox' ),
276 // letterbox_48 (2948, 48h Letterbox): V4 shape unconfirmed against the portal — keep on Legacy.
277 'letterbox_48' => self::legacy_result( 89, '2948', $nc ),
278 'id_check' => self::legacy_result( 13, '3438', $nc ),
279 'id_check+signature_on_delivery' => self::legacy_result( 14, '3438', $nc ),
280 'id_check+only_home_address' => self::legacy_result( 15, '3438', $nc ),
281 'id_check+only_home_address+signature_on_delivery' => self::legacy_result( 16, '3438', $nc ),
282 'id_check+insured_shipping' => self::legacy_result( 17, '3443', $nc ),
283 'id_check+insured_shipping+signature_on_delivery' => self::legacy_result( 18, '3443', $nc ),
284 'id_check+insured_shipping+only_home_address' => self::legacy_result( 19, '3443', $nc ),
285 'id_check+insured_shipping+only_home_address+signature_on_delivery' => self::legacy_result( 20, '3443', $nc ),
286 ),
287 'pickup_points' => array(
288 '(base)' => self::v4_result( 21, '3533', 'parcel', array(), $pickup ),
289 'insured_shipping' => self::v4_result( 22, '3534', 'parcel', array( 'insuredValue' => '<order_total>' ), $pickup ),
290 'id_check' => self::legacy_result( 23, '3571', $nya ),
291 'id_check+insured_shipping' => self::legacy_result( 24, '3581', $nc ),
292 ),
293 ),
294 'BE' => array(
295 'delivery_day' => array(
296 '(base)' => self::v4_result( 25, '4946', 'parcel' ),
297 'only_home_address' => self::v4_result( 26, '4941', 'parcel', array( 'statedAddressOnly' => true ) ),
298 'signature_on_delivery' => self::v4_result( 27, '4912', 'parcel', array( 'deliveryConfirmation' => 'signature' ) ),
299 'insured_shipping' => self::v4_result( 28, '4914', 'parcel', array( 'insuredValue' => '<order_total>' ) ),
300 'insured_shipping+track_and_trace' => self::legacy_result( 29, '4914', $nc ),
301 'insured_shipping+signature_on_delivery' => self::legacy_result( 30, '4914', $nc ),
302 'insured_shipping+only_home_address' => self::legacy_result( 31, '4914', $nc ),
303 'insured_shipping+only_home_address+signature_on_delivery' => self::legacy_result( 32, '4914', $nc ),
304 'insured_shipping+signature_on_delivery+track_and_trace' => self::legacy_result( 33, '4914', $nc ),
305 'insured_shipping+only_home_address+track_and_trace' => self::legacy_result( 34, '4914', $nc ),
306 'insured_shipping+only_home_address+signature_on_delivery+track_and_trace' => self::legacy_result( 35, '4914', $nc ),
307 'mailboxpacket' => self::legacy_result( 36, '6440', $nc ),
308 'mailboxpacket+track_and_trace' => self::legacy_result( 37, '6972', $nc ),
309 'packets' => self::legacy_result( 38, '6405', $nc ),
310 'packets+track_and_trace' => self::legacy_result( 39, '6350', $nc ),
311 'insured_shipping+packets+track_and_trace' => self::legacy_result( 40, '6906', $nc ),
312 ),
313 'pickup_points' => array(
314 '(base)' => self::legacy_result( 41, '4936', $nya ),
315 ),
316 ),
317 'EU' => array(
318 'delivery_day' => array(
319 '(base)' => self::v4_result( 42, '4907', 'parcel', array(), array(), $track_trace ),
320 'track_and_trace' => self::v4_result( 43, '4907', 'parcel', array(), array(), $track_trace ),
321 'insured_shipping+track_and_trace' => self::v4_result( 44, '4907', 'parcel', array(), array(), $insured ),
322 'insured_plus+track_and_trace' => self::v4_result( 45, '4907', 'parcel', array(), array(), $insured_plus ),
323 'mailboxpacket' => self::legacy_result( 46, '6440', $nc ),
324 'mailboxpacket+track_and_trace' => self::legacy_result( 47, '6972', $nc ),
325 'packets' => self::legacy_result( 48, '6405', $nc ),
326 'packets+track_and_trace' => self::legacy_result( 49, '6350', $nc ),
327 'insured_shipping+packets+track_and_trace' => self::legacy_result( 50, '6906', $nc ),
328 ),
329 'pickup_points' => array(
330 '(base)' => self::legacy_result( 51, '4907', $nc ),
331 ),
332 ),
333 'ROW' => array(
334 'delivery_day' => array(
335 '(base)' => self::v4_result( 52, '4909', 'parcel', array(), array(), $track_trace ),
336 'track_and_trace' => self::v4_result( 53, '4909', 'parcel', array(), array(), $track_trace ),
337 'insured_plus+track_and_trace' => self::v4_result( 54, '4909', 'parcel', array(), array(), $insured_plus ),
338 'mailboxpacket' => self::legacy_result( 55, '6440', $nc ),
339 'mailboxpacket+track_and_trace' => self::legacy_result( 56, '6972', $nc ),
340 'packets' => self::legacy_result( 57, '6405', $nc ),
341 'packets+track_and_trace' => self::legacy_result( 58, '6350', $nc ),
342 'insured_shipping+packets+track_and_trace' => self::legacy_result( 59, '6906', $nc ),
343 ),
344 'pickup_points' => array(
345 '(base)' => self::legacy_result( 60, '4909', $nc ),
346 ),
347 ),
348 ),
349 'BE' => array(
350 'BE' => array(
351 'delivery_day' => array(
352 '(base)' => self::legacy_result( 61, '4961', $nya ),
353 'only_home_address' => self::legacy_result( 62, '4960', $nya ),
354 'signature_on_delivery' => self::legacy_result( 63, '4963', $nya ),
355 'only_home_address+signature_on_delivery' => self::legacy_result( 64, '4962', $nya ),
356 'insured_shipping+only_home_address' => self::legacy_result( 65, '4965', $nya ),
357 ),
358 'pickup_points' => array(
359 '(base)' => self::v4_result( 66, '4880', 'parcel', array(), $pickup ),
360 'insured_shipping' => self::v4_result( 67, '4878', 'parcel', array( 'insuredValue' => '<order_total>' ), $pickup ),
361 ),
362 ),
363 'NL' => array(
364 'delivery_day' => array(
365 '(base)' => self::v4_result( 68, '4890', 'parcel' ),
366 'signature_on_delivery' => self::v4_result( 69, '4891', 'parcel', array( 'deliveryConfirmation' => 'signature' ) ),
367 'only_home_address' => self::v4_result( 70, '4893', 'parcel', array( 'statedAddressOnly' => true ) ),
368 'only_home_address+signature_on_delivery' => self::v4_result(
369 71,
370 '4894',
371 'parcel',
372 array(
373 'deliveryConfirmation' => 'signature',
374 'statedAddressOnly' => true,
375 )
376 ),
377 'id_check+only_home_address+signature_on_delivery' => self::legacy_result( 72, '4895', $nc ),
378 'only_home_address+return_no_answer+signature_on_delivery' => self::v4_result(
379 73,
380 '4896',
381 'parcel',
382 array(
383 'deliveryConfirmation' => 'signature',
384 'returnWhenNotHome' => true,
385 'statedAddressOnly' => true,
386 )
387 ),
388 'insured_shipping+only_home_address+signature_on_delivery' => self::v4_result(
389 74,
390 '4897',
391 'parcel',
392 array(
393 'deliveryConfirmation' => 'signature',
394 'insuredValue' => '<order_total>',
395 'statedAddressOnly' => true,
396 )
397 ),
398 ),
399 'pickup_points' => array(
400 'signature_on_delivery' => self::v4_result( 75, '4898', 'parcel', array( 'deliveryConfirmation' => 'signature' ), $pickup ),
401 '(base)' => self::v4_result( 76, '4898', 'parcel', array(), $pickup ),
402 ),
403 ),
404 'EU' => array(
405 'delivery_day' => array(
406 '(base)' => self::v4_result( 77, '4907', 'parcel', array(), array(), $track_trace ),
407 'track_and_trace' => self::v4_result( 78, '4907', 'parcel', array(), array(), $track_trace ),
408 'insured_shipping+track_and_trace' => self::v4_result( 79, '4907', 'parcel', array(), array(), $insured ),
409 'insured_plus+track_and_trace' => self::v4_result( 80, '4907', 'parcel', array(), array(), $insured_plus ),
410 'mailboxpacket' => self::legacy_result( 81, '6440', $nc ),
411 'mailboxpacket+track_and_trace' => self::legacy_result( 82, '6972', $nc ),
412 'packets' => self::legacy_result( 83, '6405', $nc ),
413 'packets+track_and_trace' => self::legacy_result( 84, '6350', $nc ),
414 'insured_shipping+packets+track_and_trace' => self::legacy_result( 85, '6906', $nc ),
415 ),
416 ),
417 'ROW' => array(
418 'delivery_day' => array(
419 '(base)' => self::v4_result( 86, '4909', 'parcel', array(), array(), $track_trace ),
420 'track_and_trace' => self::v4_result( 87, '4909', 'parcel', array(), array(), $track_trace ),
421 'insured_plus+track_and_trace' => self::v4_result( 88, '4909', 'parcel', array(), array(), $insured_plus ),
422 ),
423 ),
424 ),
425 );
426
427 return $cache;
428 }
429 }
430