karrio-canadapost 2025.5__py3-none-any.whl

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 (41) hide show
  1. karrio/mappers/canadapost/__init__.py +3 -0
  2. karrio/mappers/canadapost/mapper.py +88 -0
  3. karrio/mappers/canadapost/proxy.py +379 -0
  4. karrio/mappers/canadapost/settings.py +23 -0
  5. karrio/plugins/canadapost/__init__.py +23 -0
  6. karrio/providers/canadapost/__init__.py +25 -0
  7. karrio/providers/canadapost/error.py +42 -0
  8. karrio/providers/canadapost/manifest.py +127 -0
  9. karrio/providers/canadapost/pickup/__init__.py +3 -0
  10. karrio/providers/canadapost/pickup/cancel.py +33 -0
  11. karrio/providers/canadapost/pickup/create.py +217 -0
  12. karrio/providers/canadapost/pickup/update.py +55 -0
  13. karrio/providers/canadapost/rate.py +194 -0
  14. karrio/providers/canadapost/shipment/__init__.py +8 -0
  15. karrio/providers/canadapost/shipment/cancel.py +53 -0
  16. karrio/providers/canadapost/shipment/create.py +340 -0
  17. karrio/providers/canadapost/tracking.py +75 -0
  18. karrio/providers/canadapost/units.py +285 -0
  19. karrio/providers/canadapost/utils.py +92 -0
  20. karrio/schemas/canadapost/__init__.py +0 -0
  21. karrio/schemas/canadapost/authreturn.py +3389 -0
  22. karrio/schemas/canadapost/common.py +2037 -0
  23. karrio/schemas/canadapost/customerinfo.py +2307 -0
  24. karrio/schemas/canadapost/discovery.py +3016 -0
  25. karrio/schemas/canadapost/manifest.py +3704 -0
  26. karrio/schemas/canadapost/merchantregistration.py +1498 -0
  27. karrio/schemas/canadapost/messages.py +1431 -0
  28. karrio/schemas/canadapost/ncshipment.py +7231 -0
  29. karrio/schemas/canadapost/openreturn.py +2438 -0
  30. karrio/schemas/canadapost/pickup.py +1407 -0
  31. karrio/schemas/canadapost/pickuprequest.py +6794 -0
  32. karrio/schemas/canadapost/postoffice.py +2240 -0
  33. karrio/schemas/canadapost/rating.py +5308 -0
  34. karrio/schemas/canadapost/serviceinfo.py +1505 -0
  35. karrio/schemas/canadapost/shipment.py +9982 -0
  36. karrio/schemas/canadapost/track.py +3100 -0
  37. karrio_canadapost-2025.5.dist-info/METADATA +44 -0
  38. karrio_canadapost-2025.5.dist-info/RECORD +41 -0
  39. karrio_canadapost-2025.5.dist-info/WHEEL +5 -0
  40. karrio_canadapost-2025.5.dist-info/entry_points.txt +2 -0
  41. karrio_canadapost-2025.5.dist-info/top_level.txt +3 -0
@@ -0,0 +1,340 @@
1
+ import karrio.schemas.canadapost.shipment as canadapost
2
+ import uuid
3
+ import typing
4
+ import datetime
5
+ import karrio.lib as lib
6
+ import karrio.core.units as units
7
+ import karrio.core.models as models
8
+ import karrio.providers.canadapost.error as provider_error
9
+ import karrio.providers.canadapost.units as provider_units
10
+ import karrio.providers.canadapost.utils as provider_utils
11
+
12
+
13
+ def parse_shipment_response(
14
+ _responses: lib.Deserializable[typing.Tuple[lib.Element, str]],
15
+ settings: provider_utils.Settings,
16
+ ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
17
+ responses = _responses.deserialize()
18
+
19
+ shipment_details = [
20
+ (
21
+ f"{_}",
22
+ (
23
+ _extract_shipment(response, settings, _responses.ctx)
24
+ if len(lib.find_element("shipment-id", response[0])) > 0
25
+ else None
26
+ ),
27
+ )
28
+ for _, response in enumerate(responses, start=1)
29
+ ]
30
+
31
+ shipment = lib.to_multi_piece_shipment(shipment_details)
32
+ messages: typing.List[models.Message] = sum(
33
+ [provider_error.parse_error_response(_, settings) for _, __ in responses],
34
+ start=[],
35
+ )
36
+
37
+ return shipment, messages
38
+
39
+
40
+ def _extract_shipment(
41
+ _response: typing.Tuple[lib.Element, str],
42
+ settings: provider_utils.Settings,
43
+ ctx: dict,
44
+ ) -> models.ShipmentDetails:
45
+ response, label = _response
46
+ info = lib.to_object(canadapost.ShipmentInfoType, response)
47
+
48
+ base_amount = lib.failsafe(lambda: info.shipment_price.base_amount)
49
+ service_code = lib.failsafe(lambda: info.shipment_price.service_code)
50
+ service = provider_units.ServiceType.map(service_code)
51
+ adjustments = lib.failsafe(lambda: info.shipment_price.adjustments.adjustment) or []
52
+ priced_options = lib.failsafe(lambda: info.shipment_price.priced_options.priced_option) or []
53
+ charges = lib.failsafe(lambda: [
54
+ ("Base charge", info.shipment_price.base_amount),
55
+ ("GST", info.shipment_price.gst_amount),
56
+ ("PST", info.shipment_price.pst_amount),
57
+ ("HST", info.shipment_price.hst_amount),
58
+ *((f"Option {o.option_code}", o.option_price) for o in priced_options),
59
+ *((a.adjustment_code, a.adjustment_amount) for a in adjustments),
60
+ ]) or []
61
+
62
+ return models.ShipmentDetails(
63
+ carrier_name=settings.carrier_name,
64
+ carrier_id=settings.carrier_id,
65
+ tracking_number=info.tracking_pin,
66
+ shipment_identifier=info.shipment_id,
67
+ docs=models.Documents(label=label),
68
+ label_type=ctx["label_type"],
69
+ selected_rate=lib.identity(
70
+ models.RateDetails(
71
+ carrier_id=settings.carrier_id,
72
+ carrier_name=settings.carrier_name,
73
+ service=service.name_or_key,
74
+ total_charge=lib.to_money(base_amount),
75
+ currency="CAD",
76
+ extra_charges=lib.identity([
77
+ models.ChargeDetails(name=name, amount=lib.to_money(amount), currency="CAD")
78
+ for name, amount in charges
79
+ if amount and lib.to_money(amount) != 0
80
+ ]),
81
+ meta=dict(
82
+ service_name=(service.name or service_code),
83
+ ),
84
+ )
85
+ if base_amount is not None else None
86
+ ),
87
+ meta=lib.to_dict(
88
+ dict(
89
+ carrier_tracking_link=settings.tracking_url.format(info.tracking_pin),
90
+ customer_request_ids=ctx["customer_request_ids"],
91
+ manifest_required=ctx["manifest_required"],
92
+ group_id=ctx["group_id"],
93
+ )
94
+ ),
95
+ )
96
+
97
+
98
+ def shipment_request(
99
+ payload: models.ShipmentRequest,
100
+ settings: provider_utils.Settings,
101
+ ) -> lib.Serializable:
102
+ shipper = lib.to_address(payload.shipper)
103
+ recipient = lib.to_address(payload.recipient)
104
+ service = provider_units.ServiceType.map(payload.service).value_or_key
105
+ options = lib.to_shipping_options(
106
+ payload.options,
107
+ is_international=(
108
+ recipient.country_code is not None and recipient.country_code != "CA"
109
+ ),
110
+ initializer=provider_units.shipping_options_initializer,
111
+ )
112
+ packages = lib.to_packages(
113
+ payload.parcels,
114
+ provider_units.PackagePresets,
115
+ required=["weight"],
116
+ options=options,
117
+ package_option_type=provider_units.ShippingOption,
118
+ shipping_options_initializer=provider_units.shipping_options_initializer,
119
+ )
120
+
121
+ customs = lib.to_customs_info(payload.customs, weight_unit=units.WeightUnit.KG.name)
122
+ label_encoding, label_format = provider_units.LabelType.map(
123
+ payload.label_type or "PDF_4x6"
124
+ ).value
125
+ group_id = lib.fdate(datetime.datetime.now(), "%Y%m%d") + "-" + settings.carrier_id
126
+ customer_request_ids = [f"{str(uuid.uuid4().hex)}" for _ in range(len(packages))]
127
+ submit_shipment = lib.identity(
128
+ # set to true if canadapost_submit_shipment is true
129
+ options.canadapost_submit_shipment.state
130
+ # default to true if transmit_shipment_by_default is true
131
+ or (
132
+ settings.connection_config.transmit_shipment_by_default.state
133
+ and options.canadapost_submit_shipment.state is not False
134
+ )
135
+ )
136
+
137
+ requests = [
138
+ canadapost.ShipmentType(
139
+ customer_request_id=customer_request_ids[index],
140
+ groupIdOrTransmitShipment=canadapost.groupIdOrTransmitShipment(),
141
+ quickship_label_requested=None,
142
+ cpc_pickup_indicator=None,
143
+ requested_shipping_point=provider_utils.format_ca_postal_code(
144
+ shipper.postal_code
145
+ ),
146
+ shipping_point_id=None,
147
+ expected_mailing_date=options.shipment_date.state,
148
+ provide_pricing_info=True,
149
+ provide_receipt_info=None,
150
+ delivery_spec=canadapost.DeliverySpecType(
151
+ service_code=service,
152
+ sender=canadapost.SenderType(
153
+ name=shipper.person_name,
154
+ company=(shipper.company_name or "Not Applicable"),
155
+ contact_phone=(shipper.phone_number or "000 000 0000"),
156
+ address_details=canadapost.AddressDetailsType(
157
+ city=shipper.city,
158
+ prov_state=shipper.state_code,
159
+ country_code=shipper.country_code,
160
+ postal_zip_code=provider_utils.format_ca_postal_code(
161
+ shipper.postal_code
162
+ ),
163
+ address_line_1=shipper.street,
164
+ address_line_2=lib.text(shipper.address_line2),
165
+ ),
166
+ ),
167
+ destination=canadapost.DestinationType(
168
+ name=recipient.person_name,
169
+ company=recipient.company_name,
170
+ additional_address_info=None,
171
+ client_voice_number=recipient.phone_number or "000 000 0000",
172
+ address_details=canadapost.DestinationAddressDetailsType(
173
+ city=recipient.city,
174
+ prov_state=recipient.state_code,
175
+ country_code=recipient.country_code,
176
+ postal_zip_code=provider_utils.format_ca_postal_code(
177
+ recipient.postal_code
178
+ ),
179
+ address_line_1=recipient.street,
180
+ address_line_2=lib.text(recipient.address_line2),
181
+ ),
182
+ ),
183
+ parcel_characteristics=canadapost.ParcelCharacteristicsType(
184
+ weight=package.weight.map(provider_units.MeasurementOptions).KG,
185
+ dimensions=canadapost.dimensionsType(
186
+ length=package.length.map(provider_units.MeasurementOptions).CM,
187
+ width=package.width.map(provider_units.MeasurementOptions).CM,
188
+ height=package.height.map(provider_units.MeasurementOptions).CM,
189
+ ),
190
+ unpackaged=None,
191
+ mailing_tube=None,
192
+ ),
193
+ options=(
194
+ canadapost.optionsType(
195
+ option=[
196
+ canadapost.OptionType(
197
+ option_code=option.code,
198
+ option_amount=lib.to_money(option.state),
199
+ option_qualifier_1=None,
200
+ option_qualifier_2=None,
201
+ )
202
+ for _, option in package.options.items()
203
+ if option.state is not False
204
+ ]
205
+ )
206
+ if any(
207
+ [
208
+ option
209
+ for _, option in package.options.items()
210
+ if option.state is not False
211
+ ]
212
+ )
213
+ else None
214
+ ),
215
+ notification=(
216
+ canadapost.NotificationType(
217
+ email=(
218
+ package.options.email_notification_to.state
219
+ or recipient.email
220
+ ),
221
+ on_shipment=True,
222
+ on_exception=True,
223
+ on_delivery=True,
224
+ )
225
+ if package.options.email_notification.state
226
+ and any(
227
+ [package.options.email_notification_to.state, recipient.email]
228
+ )
229
+ else None
230
+ ),
231
+ print_preferences=canadapost.PrintPreferencesType(
232
+ output_format=label_format,
233
+ encoding=label_encoding,
234
+ ),
235
+ preferences=canadapost.PreferencesType(
236
+ service_code=None,
237
+ show_packing_instructions=False,
238
+ show_postage_rate=True,
239
+ show_insured_value=True,
240
+ ),
241
+ customs=(
242
+ canadapost.CustomsType(
243
+ currency=(options.currency.state or units.Currency.CAD.name),
244
+ conversion_from_cad=None,
245
+ reason_for_export="OTH",
246
+ other_reason=customs.content_type,
247
+ duties_and_taxes_prepaid=customs.duty.account_number,
248
+ certificate_number=customs.options.certificate_number.state,
249
+ licence_number=lib.text(
250
+ customs.options.license_number.state, max=10
251
+ ),
252
+ invoice_number=lib.text(customs.invoice, max=10),
253
+ sku_list=(
254
+ (
255
+ canadapost.sku_listType(
256
+ item=[
257
+ canadapost.SkuType(
258
+ customs_number_of_units=item.quantity,
259
+ customs_description=lib.text(
260
+ item.title
261
+ or item.description
262
+ or item.sku
263
+ or "N/B",
264
+ max=35,
265
+ ),
266
+ sku=item.sku or "0000",
267
+ hs_tariff_code=item.hs_code,
268
+ unit_weight=(item.weight or 1),
269
+ customs_value_per_unit=item.value_amount,
270
+ customs_unit_of_measure=None,
271
+ country_of_origin=(
272
+ item.origin_country
273
+ or shipper.country_code
274
+ ),
275
+ province_of_origin=shipper.state_code
276
+ or "N/B",
277
+ )
278
+ for item in customs.commodities
279
+ ]
280
+ )
281
+ )
282
+ if any(customs.commodities or [])
283
+ else None
284
+ ),
285
+ )
286
+ if payload.customs is not None
287
+ else None
288
+ ),
289
+ references=canadapost.ReferencesType(
290
+ cost_centre=(
291
+ options.canadapost_cost_center.state
292
+ or settings.connection_config.cost_center.state
293
+ or payload.reference
294
+ ),
295
+ customer_ref_1=payload.reference,
296
+ customer_ref_2=None,
297
+ ),
298
+ settlement_info=canadapost.SettlementInfoType(
299
+ paid_by_customer=getattr(
300
+ payload.payment, "account_number", settings.customer_number
301
+ ),
302
+ contract_id=settings.contract_id,
303
+ cif_shipment=None,
304
+ intended_method_of_payment=provider_units.PaymentType.map(
305
+ getattr(payload.payment, "paid_by", None)
306
+ ).value,
307
+ promo_code=None,
308
+ ),
309
+ ),
310
+ return_spec=None,
311
+ pre_authorized_payment=None,
312
+ create_qr_code=None,
313
+ )
314
+ for index, package in enumerate(packages)
315
+ ]
316
+
317
+ return lib.Serializable(
318
+ requests,
319
+ lambda __: [
320
+ lib.to_xml(
321
+ request,
322
+ name_="shipment",
323
+ namespacedef_='xmlns="http://www.canadapost.ca/ws/shipment-v8"',
324
+ ).replace(
325
+ "<groupIdOrTransmitShipment/>",
326
+ (
327
+ "<transmit-shipment/>"
328
+ if submit_shipment
329
+ else f"<group-id>{group_id}</group-id>"
330
+ ),
331
+ )
332
+ for request in __
333
+ ],
334
+ dict(
335
+ label_type=label_encoding,
336
+ manifest_required=(not submit_shipment),
337
+ customer_request_ids=customer_request_ids,
338
+ group_id=(None if submit_shipment else group_id),
339
+ ),
340
+ )
@@ -0,0 +1,75 @@
1
+ import karrio.schemas.canadapost.track as capost
2
+ import typing
3
+ import karrio.lib as lib
4
+ import karrio.core.models as models
5
+ import karrio.providers.canadapost.error as error
6
+ import karrio.providers.canadapost.units as provider_units
7
+ import karrio.providers.canadapost.utils as provider_utils
8
+
9
+
10
+ def parse_tracking_response(
11
+ _response: lib.Deserializable[lib.Element],
12
+ settings: provider_utils.Settings,
13
+ ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
14
+ response = _response.deserialize()
15
+ details = lib.find_element("tracking-detail", response)
16
+ tracking_details: typing.List[models.TrackingDetails] = [
17
+ _extract_tracking(node, settings)
18
+ for node in details
19
+ if len(lib.find_element("occurrence", node)) > 0
20
+ ]
21
+
22
+ return tracking_details, error.parse_error_response(response, settings)
23
+
24
+
25
+ def _extract_tracking(
26
+ detail_node: lib.Element,
27
+ settings: provider_utils.Settings,
28
+ ) -> models.TrackingDetails:
29
+ details = lib.to_object(capost.tracking_detail, detail_node)
30
+ events: typing.List[capost.occurrenceType] = details.significant_events.occurrence
31
+ last_event = events[0]
32
+ estimated_delivery = lib.fdate(
33
+ details.changed_expected_date or details.expected_delivery_date,
34
+ "%Y-%m-%d",
35
+ )
36
+ status = next(
37
+ (
38
+ status.name
39
+ for status in list(provider_units.TrackingStatus)
40
+ if last_event.event_identifier in status.value
41
+ ),
42
+ provider_units.TrackingStatus.in_transit.name,
43
+ )
44
+
45
+ return models.TrackingDetails(
46
+ carrier_name=settings.carrier_name,
47
+ carrier_id=settings.carrier_id,
48
+ tracking_number=details.pin,
49
+ estimated_delivery=estimated_delivery,
50
+ delivered=status == "delivered",
51
+ status=status,
52
+ events=[
53
+ models.TrackingEvent(
54
+ date=lib.fdate(event.event_date, "%Y-%m-%d"),
55
+ time=lib.flocaltime(event.event_time, "%H:%M:%S"),
56
+ code=event.event_identifier,
57
+ location=lib.join(
58
+ event.event_site, event.event_province, join=True, separator=", "
59
+ ),
60
+ description=event.event_description,
61
+ )
62
+ for event in events
63
+ ],
64
+ info=models.TrackingInfo(
65
+ carrier_tracking_link=settings.tracking_url.format(details.pin),
66
+ shipment_destination_postal_code=details.destination_postal_id,
67
+ shipment_delivery_date=estimated_delivery,
68
+ shipment_service=details.service_name,
69
+ signed_by=last_event.signatory_name,
70
+ ),
71
+ )
72
+
73
+
74
+ def tracking_request(payload: models.TrackingRequest, _) -> lib.Serializable:
75
+ return lib.Serializable(payload.tracking_numbers)
@@ -0,0 +1,285 @@
1
+ import karrio.lib as lib
2
+
3
+ PRESET_DEFAULTS = dict(
4
+ dimension_unit="CM",
5
+ weight_unit="KG",
6
+ )
7
+
8
+ MeasurementOptions = lib.units.MeasurementOptionsType(
9
+ quant=0.1,
10
+ min_kg=0.01,
11
+ min_in=0.01,
12
+ )
13
+
14
+
15
+ class PackagePresets(lib.Enum):
16
+ """
17
+ Note that dimensions are in CM and weight in KG
18
+ """
19
+
20
+ canadapost_mailing_box = lib.units.PackagePreset(
21
+ **dict(width=10.2, height=15.2, length=1.0), **PRESET_DEFAULTS
22
+ )
23
+ canadapost_extra_small_mailing_box = lib.units.PackagePreset(
24
+ **dict(width=14.0, height=14.0, length=14.0), **PRESET_DEFAULTS
25
+ )
26
+ canadapost_small_mailing_box = lib.units.PackagePreset(
27
+ **dict(width=28.6, height=22.9, length=6.4), **PRESET_DEFAULTS
28
+ )
29
+ canadapost_medium_mailing_box = lib.units.PackagePreset(
30
+ **dict(width=31.0, height=23.5, length=13.3), **PRESET_DEFAULTS
31
+ )
32
+ canadapost_large_mailing_box = lib.units.PackagePreset(
33
+ **dict(width=38.1, height=30.5, length=9.5), **PRESET_DEFAULTS
34
+ )
35
+ canadapost_extra_large_mailing_box = lib.units.PackagePreset(
36
+ **dict(width=40.0, height=30.5, length=21.6), **PRESET_DEFAULTS
37
+ )
38
+ canadapost_corrugated_small_box = lib.units.PackagePreset(
39
+ **dict(width=42.0, height=32.0, length=32.0), **PRESET_DEFAULTS
40
+ )
41
+ canadapost_corrugated_medium_box = lib.units.PackagePreset(
42
+ **dict(width=46.0, height=38.0, length=32.0), **PRESET_DEFAULTS
43
+ )
44
+ canadapost_corrugated_large_box = lib.units.PackagePreset(
45
+ **dict(width=46.0, height=46.0, length=40.6), **PRESET_DEFAULTS
46
+ )
47
+ canadapost_xexpresspost_certified_envelope = lib.units.PackagePreset(
48
+ **dict(width=26.0, height=15.9, weight=0.5, length=1.5), **PRESET_DEFAULTS
49
+ )
50
+ canadapost_xexpresspost_national_large_envelope = lib.units.PackagePreset(
51
+ **dict(width=40.0, height=29.2, weight=1.36, length=1.5), **PRESET_DEFAULTS
52
+ )
53
+ canadapost_xexpresspost_regional_small_envelope = lib.units.PackagePreset(
54
+ **dict(width=26.0, height=15.9, weight=0.5, length=1.5), **PRESET_DEFAULTS
55
+ )
56
+ canadapost_xexpresspost_regional_large_envelope = lib.units.PackagePreset(
57
+ **dict(width=40.0, height=29.2, weight=1.36, length=1.5), **PRESET_DEFAULTS
58
+ )
59
+
60
+
61
+ class LabelType(lib.Enum):
62
+ PDF_4x6 = ("PDF", "4x6")
63
+ PDF_8_5x11 = ("PDF", "8.5x11")
64
+ ZPL_4x6 = ("ZPL", "4x6")
65
+
66
+ """ Unified Label type mapping """
67
+ PDF = PDF_4x6
68
+ ZPL = ZPL_4x6
69
+
70
+
71
+ class PaymentType(lib.StrEnum):
72
+ account = "Account"
73
+ card = "CreditCard"
74
+ supplier_account = "SupplierAccount"
75
+
76
+ sender = account
77
+ recipient = account
78
+ third_party = supplier_account
79
+ credit_card = card
80
+
81
+
82
+ class ConnectionConfig(lib.Enum):
83
+ cost_center = lib.OptionEnum("cost_center")
84
+ label_type = lib.OptionEnum("label_type", LabelType)
85
+ shipping_options = lib.OptionEnum("shipping_options", list)
86
+ shipping_services = lib.OptionEnum("shipping_services", list)
87
+ transmit_shipment_by_default = lib.OptionEnum("transmit_shipment_by_default", bool)
88
+
89
+
90
+ class ServiceType(lib.Enum):
91
+ canadapost_regular_parcel = "DOM.RP"
92
+ canadapost_expedited_parcel = "DOM.EP"
93
+ canadapost_xpresspost = "DOM.XP"
94
+ canadapost_xpresspost_certified = "DOM.XP.CERT"
95
+ canadapost_priority = "DOM.PC"
96
+ canadapost_library_books = "DOM.LIB"
97
+ canadapost_expedited_parcel_usa = "USA.EP"
98
+ canadapost_priority_worldwide_envelope_usa = "USA.PW.ENV"
99
+ canadapost_priority_worldwide_pak_usa = "USA.PW.PAK"
100
+ canadapost_priority_worldwide_parcel_usa = "USA.PW.PARCEL"
101
+ canadapost_small_packet_usa_air = "USA.SP.AIR"
102
+ canadapost_tracked_packet_usa = "USA.TP"
103
+ canadapost_tracked_packet_usa_lvm = "USA.TP.LVM"
104
+ canadapost_xpresspost_usa = "USA.XP"
105
+ canadapost_xpresspost_international = "INT.XP"
106
+ canadapost_international_parcel_air = "INT.IP.AIR"
107
+ canadapost_international_parcel_surface = "INT.IP.SURF"
108
+ canadapost_priority_worldwide_envelope_intl = "INT.PW.ENV"
109
+ canadapost_priority_worldwide_pak_intl = "INT.PW.PAK"
110
+ canadapost_priority_worldwide_parcel_intl = "INT.PW.PARCEL"
111
+ canadapost_small_packet_international_air = "INT.SP.AIR"
112
+ canadapost_small_packet_international_surface = "INT.SP.SURF"
113
+ canadapost_tracked_packet_international = "INT.TP"
114
+
115
+
116
+ class ShippingOption(lib.Enum):
117
+ canadapost_signature = lib.OptionEnum("SO", bool)
118
+ canadapost_coverage = lib.OptionEnum("COV", float)
119
+ canadapost_collect_on_delivery = lib.OptionEnum("COD", float)
120
+ canadapost_proof_of_age_required_18 = lib.OptionEnum("PA18", bool)
121
+ canadapost_proof_of_age_required_19 = lib.OptionEnum("PA19", bool)
122
+ canadapost_card_for_pickup = lib.OptionEnum("HFP", bool)
123
+ canadapost_do_not_safe_drop = lib.OptionEnum("DNS", bool)
124
+ canadapost_leave_at_door = lib.OptionEnum("LAD", bool)
125
+ canadapost_deliver_to_post_office = lib.OptionEnum("D2PO", bool)
126
+ canadapost_return_at_senders_expense = lib.OptionEnum("RASE", bool)
127
+ canadapost_return_to_sender = lib.OptionEnum("RTS", bool)
128
+ canadapost_abandon = lib.OptionEnum("ABAN", bool)
129
+
130
+ """ Custom Option """
131
+ canadapost_cost_center = lib.OptionEnum("cost-centre")
132
+ canadapost_submit_shipment = lib.OptionEnum("transmit-shipment", bool)
133
+
134
+ """ Unified Option type mapping """
135
+ insurance = canadapost_coverage
136
+ cash_on_delivery = canadapost_collect_on_delivery
137
+ signature_confirmation = canadapost_signature
138
+
139
+
140
+ def shipping_options_initializer(
141
+ options: dict,
142
+ package_options: lib.units.ShippingOptions = None,
143
+ is_international: bool = False,
144
+ ) -> lib.units.ShippingOptions:
145
+ _options = options.copy()
146
+
147
+ # Apply default non delivery options for if international.
148
+ no_international_option_specified: bool = not any(
149
+ key in _options for key in INTERNATIONAL_NON_DELIVERY_OPTION
150
+ )
151
+
152
+ if is_international and no_international_option_specified:
153
+ _options.update(
154
+ {ShippingOption.canadapost_return_at_senders_expense.name: True}
155
+ )
156
+
157
+ # Apply package options if specified.
158
+ if package_options is not None:
159
+ _options.update(package_options.content)
160
+
161
+ # Define carrier option filter.
162
+ def items_filter(key: str) -> bool:
163
+ return key in ShippingOption and key not in CUSTOM_OPTIONS # type:ignore
164
+
165
+ return lib.units.ShippingOptions(
166
+ _options, ShippingOption, items_filter=items_filter
167
+ )
168
+
169
+
170
+ class TrackingStatus(lib.Enum):
171
+ """Carrier tracking status mapping"""
172
+
173
+ delivered = [
174
+ "1408",
175
+ "1409",
176
+ "1421",
177
+ "1422",
178
+ "1423",
179
+ "1424",
180
+ "1425",
181
+ "1426",
182
+ "1427",
183
+ "1428",
184
+ "1429",
185
+ "1430",
186
+ "1431",
187
+ "1432",
188
+ "1433",
189
+ "1434",
190
+ "1441",
191
+ "1442",
192
+ "1496",
193
+ "1497",
194
+ "1498",
195
+ "1499",
196
+ ]
197
+ in_transit = [""]
198
+ on_hold = [
199
+ "117",
200
+ "120",
201
+ "121",
202
+ "125",
203
+ "127",
204
+ "810",
205
+ "1411",
206
+ "1414",
207
+ "1443",
208
+ "1484",
209
+ "1487",
210
+ "1494",
211
+ "2411",
212
+ "2414",
213
+ "4700",
214
+ ]
215
+ ready_for_pickup = [
216
+ "118",
217
+ "156",
218
+ "1407",
219
+ "1410",
220
+ "1435",
221
+ "1436",
222
+ "1437",
223
+ "1438",
224
+ "1479",
225
+ "1488",
226
+ "1701",
227
+ "2410",
228
+ ]
229
+ delivery_failed = [
230
+ "150",
231
+ "154",
232
+ "167",
233
+ "168",
234
+ "169",
235
+ "167",
236
+ "168",
237
+ "169",
238
+ "179",
239
+ "181",
240
+ "182",
241
+ "183",
242
+ "184",
243
+ "190",
244
+ "1100",
245
+ "1415",
246
+ "1416",
247
+ "1417",
248
+ "1418",
249
+ "1419",
250
+ "1420",
251
+ "1450",
252
+ "1481",
253
+ "1482",
254
+ "1483",
255
+ "1491",
256
+ "1492",
257
+ "1493",
258
+ "2600",
259
+ "2802",
260
+ "3001",
261
+ "4650",
262
+ ]
263
+ delivery_delayed = [
264
+ "159",
265
+ "160",
266
+ "161",
267
+ "162",
268
+ "163",
269
+ "172",
270
+ "173",
271
+ "2412",
272
+ ]
273
+ out_for_delivery = ["174", "500"]
274
+
275
+
276
+ INTERNATIONAL_NON_DELIVERY_OPTION = [
277
+ ShippingOption.canadapost_return_at_senders_expense.name,
278
+ ShippingOption.canadapost_return_to_sender.name,
279
+ ShippingOption.canadapost_abandon.name,
280
+ ]
281
+
282
+ CUSTOM_OPTIONS = [
283
+ ShippingOption.canadapost_cost_center.name,
284
+ ShippingOption.canadapost_submit_shipment.name,
285
+ ]