karrio-fedex 2025.5rc1__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 (42) hide show
  1. karrio/mappers/fedex/__init__.py +3 -0
  2. karrio/mappers/fedex/mapper.py +89 -0
  3. karrio/mappers/fedex/proxy.py +144 -0
  4. karrio/mappers/fedex/settings.py +24 -0
  5. karrio/plugins/fedex/__init__.py +23 -0
  6. karrio/providers/fedex/__init__.py +24 -0
  7. karrio/providers/fedex/document.py +88 -0
  8. karrio/providers/fedex/error.py +66 -0
  9. karrio/providers/fedex/pickup/__init__.py +9 -0
  10. karrio/providers/fedex/pickup/cancel.py +79 -0
  11. karrio/providers/fedex/pickup/create.py +148 -0
  12. karrio/providers/fedex/pickup/update.py +162 -0
  13. karrio/providers/fedex/rate.py +357 -0
  14. karrio/providers/fedex/shipment/__init__.py +9 -0
  15. karrio/providers/fedex/shipment/cancel.py +46 -0
  16. karrio/providers/fedex/shipment/create.py +748 -0
  17. karrio/providers/fedex/tracking.py +154 -0
  18. karrio/providers/fedex/units.py +501 -0
  19. karrio/providers/fedex/utils.py +199 -0
  20. karrio/schemas/fedex/__init__.py +0 -0
  21. karrio/schemas/fedex/cancel_pickup_request.py +31 -0
  22. karrio/schemas/fedex/cancel_pickup_response.py +24 -0
  23. karrio/schemas/fedex/cancel_request.py +17 -0
  24. karrio/schemas/fedex/cancel_response.py +25 -0
  25. karrio/schemas/fedex/error_response.py +16 -0
  26. karrio/schemas/fedex/paperless_request.py +30 -0
  27. karrio/schemas/fedex/paperless_response.py +21 -0
  28. karrio/schemas/fedex/pickup_request.py +106 -0
  29. karrio/schemas/fedex/pickup_response.py +25 -0
  30. karrio/schemas/fedex/rating_request.py +478 -0
  31. karrio/schemas/fedex/rating_responses.py +208 -0
  32. karrio/schemas/fedex/shipping_request.py +731 -0
  33. karrio/schemas/fedex/shipping_responses.py +584 -0
  34. karrio/schemas/fedex/tracking_document_request.py +30 -0
  35. karrio/schemas/fedex/tracking_document_response.py +30 -0
  36. karrio/schemas/fedex/tracking_request.py +23 -0
  37. karrio/schemas/fedex/tracking_response.py +350 -0
  38. karrio_fedex-2025.5rc1.dist-info/METADATA +45 -0
  39. karrio_fedex-2025.5rc1.dist-info/RECORD +42 -0
  40. karrio_fedex-2025.5rc1.dist-info/WHEEL +5 -0
  41. karrio_fedex-2025.5rc1.dist-info/entry_points.txt +2 -0
  42. karrio_fedex-2025.5rc1.dist-info/top_level.txt +3 -0
@@ -0,0 +1,748 @@
1
+ import karrio.schemas.fedex.shipping_request as fedex
2
+ import karrio.schemas.fedex.shipping_responses as shipping
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.fedex.error as provider_error
9
+ import karrio.providers.fedex.utils as provider_utils
10
+ import karrio.providers.fedex.units as provider_units
11
+
12
+
13
+ def parse_shipment_response(
14
+ _response: lib.Deserializable[typing.List[dict]],
15
+ settings: provider_utils.Settings,
16
+ ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
17
+ response = _response.deserialize()
18
+
19
+ messages = provider_error.parse_error_response(response, settings)
20
+ shipment = lib.identity(
21
+ _extract_details(
22
+ response["output"]["transactionShipments"][0],
23
+ settings,
24
+ ctx=_response.ctx,
25
+ )
26
+ if response.get("errors") is None
27
+ and response.get("output") is not None
28
+ and response.get("output").get("transactionShipments") is not None
29
+ else None
30
+ )
31
+
32
+ return shipment, messages
33
+
34
+
35
+ def _extract_details(
36
+ data: dict,
37
+ settings: provider_utils.Settings,
38
+ ctx: dict = {},
39
+ ) -> models.ShipmentDetails:
40
+ # fmt: off
41
+ shipment = lib.to_object(shipping.TransactionShipmentType, data)
42
+ service = provider_units.ShippingService.map(shipment.serviceType)
43
+ pieceDocuments: typing.List[shipping.PackageDocuments] = sum(
44
+ [_.packageDocuments for _ in shipment.pieceResponses],
45
+ start=[],
46
+ )
47
+
48
+ tracking_number = shipment.masterTrackingNumber
49
+ invoices = [_ for _ in shipment.shipmentDocuments if "INVOICE" in _.contentType]
50
+ labels = [_ for _ in pieceDocuments if "LABEL" in _.contentType]
51
+
52
+ invoice_type = invoices[0].docType if len(invoices) > 0 else "PDF"
53
+ invoice = lib.identity(
54
+ lib.bundle_base64(
55
+ [_.encodedLabel or lib.request(url=_.url, decoder=lib.encode_base64) for _ in invoices],
56
+ invoice_type,
57
+ )
58
+ if len(invoices) > 0
59
+ else None
60
+ )
61
+
62
+ label_type = labels[0].docType if len(labels) > 0 else "PDF"
63
+ label = lib.identity(
64
+ lib.bundle_base64(
65
+ [_.encodedLabel or lib.request(url=_.url, decoder=lib.encode_base64) for _ in labels],
66
+ label_type,
67
+ )
68
+ if len(labels) > 0
69
+ else None
70
+ )
71
+ # fmt: on
72
+
73
+ return models.ShipmentDetails(
74
+ carrier_id=settings.carrier_id,
75
+ carrier_name=settings.carrier_name,
76
+ tracking_number=tracking_number,
77
+ shipment_identifier=tracking_number,
78
+ label_type=label_type,
79
+ docs=models.Documents(label=label, invoice=invoice),
80
+ meta=dict(
81
+ service_name=service.name_or_key,
82
+ carrier_tracking_link=settings.tracking_url.format(tracking_number),
83
+ trackingIdType=shipment.pieceResponses[0].trackingIdType,
84
+ serviceCategory=shipment.pieceResponses[0].serviceCategory,
85
+ fedex_carrier_code=lib.failsafe(
86
+ lambda: shipment.completedShipmentDetail.carrierCode
87
+ ),
88
+ ),
89
+ )
90
+
91
+
92
+ def shipment_request(
93
+ payload: models.ShipmentRequest,
94
+ settings: provider_utils.Settings,
95
+ ) -> lib.Serializable:
96
+ shipper = lib.to_address(payload.shipper)
97
+ recipient = lib.to_address(payload.recipient)
98
+ service = provider_units.ShippingService.map(payload.service).value_or_key
99
+ options = lib.to_shipping_options(
100
+ payload.options,
101
+ initializer=provider_units.shipping_options_initializer,
102
+ )
103
+ packages = lib.to_packages(
104
+ payload.parcels,
105
+ required=["weight"],
106
+ options=options,
107
+ presets=provider_units.PackagePresets,
108
+ shipping_options_initializer=provider_units.shipping_options_initializer,
109
+ )
110
+
111
+ default_currency = lib.identity(
112
+ options.currency.state
113
+ or settings.default_currency
114
+ or units.CountryCurrency.map(payload.shipper.country_code).value
115
+ or "USD"
116
+ )
117
+ weight_unit, dim_unit = lib.identity(
118
+ provider_units.COUNTRY_PREFERED_UNITS.get(payload.shipper.country_code)
119
+ or packages.compatible_units
120
+ )
121
+ customs = lib.to_customs_info(
122
+ payload.customs,
123
+ shipper=payload.shipper,
124
+ recipient=payload.recipient,
125
+ weight_unit=weight_unit.value,
126
+ )
127
+ payment = payload.payment or models.Payment(
128
+ paid_by="sender", account_number=settings.account_number
129
+ )
130
+ shipment_date = lib.to_date(options.shipment_date.state or datetime.datetime.now())
131
+ label_type, label_format = lib.identity(
132
+ provider_units.LabelType.map(payload.label_type or "PDF_4x6").value
133
+ )
134
+ return_address = lib.to_address(payload.return_address)
135
+ billing_address = lib.to_address(
136
+ payload.billing_address
137
+ or dict(
138
+ sender=payload.shipper,
139
+ recipient=payload.recipient,
140
+ third_party=payload.billing_address,
141
+ )[payment.paid_by]
142
+ )
143
+ duty_billing_address = lib.to_address(
144
+ customs.duty_billing_address
145
+ or dict(
146
+ sender=payload.shipper,
147
+ recipient=payload.recipient,
148
+ third_party=customs.duty_billing_address or billing_address or shipper,
149
+ ).get(customs.duty.paid_by)
150
+ )
151
+ package_options = lambda _options: [
152
+ option
153
+ for _, option in _options.items()
154
+ if option.state is not False and option.code in provider_units.PACKAGE_OPTIONS
155
+ ]
156
+ shipment_options = lambda _options: [
157
+ option
158
+ for _, option in _options.items()
159
+ if option.state is not False and option.code in provider_units.SHIPMENT_OPTIONS
160
+ ]
161
+ hub_id = lib.identity(
162
+ lib.text(options.fedex_smart_post_hub_id.state)
163
+ or lib.text(settings.connection_config.smart_post_hub_id.state)
164
+ )
165
+ request_types = lib.identity(
166
+ settings.connection_config.rate_request_types.state
167
+ if any(settings.connection_config.rate_request_types.state or [])
168
+ else ["LIST", "ACCOUNT", *([] if "currency" not in options else ["PREFERRED"])]
169
+ )
170
+
171
+ requests = fedex.ShippingRequestType(
172
+ mergeLabelDocOption=None,
173
+ requestedShipment=fedex.RequestedShipmentType(
174
+ shipDatestamp=lib.fdate(shipment_date, "%Y-%m-%d"),
175
+ totalDeclaredValue=lib.identity(
176
+ fedex.TotalDeclaredValueType(
177
+ amount=lib.to_money(options.declared_value.state),
178
+ currency=options.currency.state or default_currency,
179
+ )
180
+ if options.declared_value.state
181
+ else None
182
+ ),
183
+ shipper=fedex.ShipperType(
184
+ address=fedex.AddressType(
185
+ streetLines=shipper.address_lines,
186
+ city=shipper.city,
187
+ stateOrProvinceCode=provider_utils.state_code(shipper),
188
+ postalCode=shipper.postal_code,
189
+ countryCode=shipper.country_code,
190
+ residential=shipper.residential,
191
+ ),
192
+ contact=fedex.ResponsiblePartyContactType(
193
+ personName=lib.text(shipper.contact, max=35),
194
+ emailAddress=shipper.email,
195
+ phoneNumber=lib.text(
196
+ shipper.phone_number or "000-000-0000",
197
+ max=15,
198
+ trim=True,
199
+ ),
200
+ phoneExtension=None,
201
+ companyName=lib.text(shipper.company_name, max=35),
202
+ faxNumber=None,
203
+ ),
204
+ tins=lib.identity(
205
+ fedex.TinType(number=shipper.tax_id) if shipper.has_tax_info else []
206
+ ),
207
+ deliveryInstructions=options.shipper_instructions.state,
208
+ ),
209
+ soldTo=None,
210
+ recipients=[
211
+ fedex.ShipperType(
212
+ address=fedex.AddressType(
213
+ streetLines=recipient.address_lines,
214
+ city=recipient.city,
215
+ stateOrProvinceCode=provider_utils.state_code(recipient),
216
+ postalCode=recipient.postal_code,
217
+ countryCode=recipient.country_code,
218
+ residential=recipient.residential,
219
+ ),
220
+ contact=fedex.ResponsiblePartyContactType(
221
+ personName=lib.text(recipient.person_name, max=35),
222
+ emailAddress=recipient.email,
223
+ phoneNumber=lib.text(
224
+ recipient.phone_number
225
+ or shipper.phone_number
226
+ or "000-000-0000",
227
+ max=15,
228
+ trim=True,
229
+ ),
230
+ phoneExtension=None,
231
+ companyName=lib.text(recipient.company_name, max=35),
232
+ faxNumber=None,
233
+ ),
234
+ tins=(
235
+ fedex.TinType(number=recipient.tax_id)
236
+ if recipient.has_tax_info
237
+ else []
238
+ ),
239
+ deliveryInstructions=options.recipient_instructions.state,
240
+ )
241
+ ],
242
+ recipientLocationNumber=None,
243
+ pickupType="DROPOFF_AT_FEDEX_LOCATION",
244
+ serviceType=service,
245
+ packagingType=lib.identity(
246
+ provider_units.PackagingType.map(
247
+ packages.package_type or "your_packaging"
248
+ ).value
249
+ ),
250
+ totalWeight=packages.weight.LB,
251
+ origin=lib.identity(
252
+ fedex.OriginType(
253
+ address=fedex.AddressType(
254
+ streetLines=return_address.address_lines,
255
+ city=return_address.city,
256
+ stateOrProvinceCode=provider_utils.state_code(return_address),
257
+ postalCode=return_address.postal_code,
258
+ countryCode=return_address.country_code,
259
+ residential=return_address.residential,
260
+ ),
261
+ contact=fedex.ResponsiblePartyContactType(
262
+ personName=lib.text(return_address.contact, max=35),
263
+ emailAddress=return_address.email,
264
+ phoneNumber=lib.text(
265
+ return_address.phone_number or "000-000-0000",
266
+ max=15,
267
+ trim=True,
268
+ ),
269
+ phoneExtension=None,
270
+ companyName=lib.text(return_address.company_name, max=35),
271
+ faxNumber=None,
272
+ ),
273
+ )
274
+ if payload.return_address is not None
275
+ else None
276
+ ),
277
+ shippingChargesPayment=fedex.ShippingChargesPaymentType(
278
+ paymentType=provider_units.PaymentType.map(
279
+ payment.paid_by
280
+ ).value_or_key,
281
+ payor=fedex.PayorType(
282
+ responsibleParty=fedex.ResponsiblePartyType(
283
+ address=lib.identity(
284
+ fedex.AddressType(
285
+ streetLines=billing_address.address_lines,
286
+ city=billing_address.city,
287
+ stateOrProvinceCode=provider_utils.state_code(
288
+ billing_address
289
+ ),
290
+ postalCode=billing_address.postal_code,
291
+ countryCode=billing_address.country_code,
292
+ residential=billing_address.residential,
293
+ )
294
+ if billing_address.address is not None
295
+ else None
296
+ ),
297
+ contact=lib.identity(
298
+ fedex.ResponsiblePartyContactType(
299
+ personName=lib.text(billing_address.contact, max=35),
300
+ emailAddress=billing_address.email,
301
+ phoneNumber=lib.text(
302
+ billing_address.phone_number
303
+ or shipper.phone_number
304
+ or "000-000-0000",
305
+ max=15,
306
+ trim=True,
307
+ ),
308
+ phoneExtension=None,
309
+ companyName=lib.text(
310
+ billing_address.company_name, max=35
311
+ ),
312
+ faxNumber=None,
313
+ )
314
+ if billing_address.address is not None
315
+ else None
316
+ ),
317
+ accountNumber=lib.identity(
318
+ fedex.AccountNumberType(value=payment.account_number)
319
+ if payment.paid_by != "sender" and payment.account_number
320
+ else None
321
+ ),
322
+ tins=lib.identity(
323
+ fedex.TinType(number=billing_address.tax_id)
324
+ if billing_address.has_tax_info
325
+ else []
326
+ ),
327
+ )
328
+ ),
329
+ ),
330
+ shipmentSpecialServices=lib.identity(
331
+ fedex.ShipmentSpecialServicesType(
332
+ specialServiceTypes=lib.identity(
333
+ [option.code for option in shipment_options(packages.options)]
334
+ if shipment_options(packages.options)
335
+ else None
336
+ ),
337
+ etdDetail=lib.identity(
338
+ fedex.EtdDetailType(
339
+ attributes=lib.identity(
340
+ None
341
+ if options.doc_files.state
342
+ or options.doc_references.state
343
+ else ["POST_SHIPMENT_UPLOAD_REQUESTED"]
344
+ ),
345
+ attachedDocuments=lib.identity(
346
+ [
347
+ fedex.AttachedDocumentType(
348
+ documentType=(
349
+ provider_units.UploadDocumentType.map(
350
+ doc["doc_name"]
351
+ ).value
352
+ or "COMMERCIAL_INVOICE"
353
+ ),
354
+ documentReference=(
355
+ payload.reference
356
+ or getattr(payload, "id", None),
357
+ ),
358
+ description=None,
359
+ documentId=None,
360
+ )
361
+ for doc in options.doc_files.state
362
+ ]
363
+ if (options.doc_files.state or [])
364
+ else []
365
+ ),
366
+ requestedDocumentTypes=["COMMERCIAL_INVOICE"],
367
+ )
368
+ if options.fedex_electronic_trade_documents.state
369
+ else None
370
+ ),
371
+ returnShipmentDetail=None,
372
+ deliveryOnInvoiceAcceptanceDetail=None,
373
+ internationalTrafficInArmsRegulationsDetail=None,
374
+ pendingShipmentDetail=None,
375
+ holdAtLocationDetail=None,
376
+ shipmentCODDetail=lib.identity(
377
+ fedex.ShipmentCODDetailType(
378
+ addTransportationChargesDetail=None,
379
+ codRecipient=None,
380
+ remitToName=None,
381
+ codCollectionType="CASH",
382
+ financialInstitutionContactAndAddress=None,
383
+ codCollectionAmount=fedex.TotalDeclaredValueType(
384
+ amount=lib.to_money(options.cash_on_delivery.state),
385
+ currency=lib.identity(
386
+ options.currency.state or default_currency
387
+ ),
388
+ ),
389
+ returnReferenceIndicatorType=None,
390
+ shipmentCodAmount=None,
391
+ )
392
+ if options.cash_on_delivery.state
393
+ else None
394
+ ),
395
+ shipmentDryIceDetail=None,
396
+ internationalControlledExportDetail=None,
397
+ homeDeliveryPremiumDetail=None,
398
+ )
399
+ if any(shipment_options(packages.options))
400
+ else None
401
+ ),
402
+ emailNotificationDetail=lib.identity(
403
+ fedex.RequestedShipmentEmailNotificationDetailType(
404
+ aggregationType="PER_SHIPMENT",
405
+ emailNotificationRecipients=[
406
+ fedex.EmailNotificationRecipientType(
407
+ name=recipient.person_name,
408
+ emailNotificationRecipientType="RECIPIENT",
409
+ emailAddress=lib.identity(
410
+ options.email_notification_to.state or recipient.email
411
+ ),
412
+ notificationFormatType="HTML",
413
+ notificationType="EMAIL",
414
+ notificationEventType=[
415
+ "ON_DELIVERY",
416
+ "ON_EXCEPTION",
417
+ "ON_SHIPMENT",
418
+ ],
419
+ )
420
+ ],
421
+ personalMessage=None,
422
+ )
423
+ if options.email_notification.state
424
+ or any([options.email_notification_to.state, recipient.email])
425
+ else None
426
+ ),
427
+ expressFreightDetail=None,
428
+ variableHandlingChargeDetail=None,
429
+ customsClearanceDetail=lib.identity(
430
+ fedex.CustomsClearanceDetailType(
431
+ regulatoryControls=None,
432
+ brokers=[],
433
+ commercialInvoice=fedex.CommercialInvoiceType(
434
+ originatorName=lib.text(
435
+ shipper.company_name or shipper.contact, max=35
436
+ ),
437
+ comments=None,
438
+ customerReferences=(
439
+ [
440
+ fedex.CustomerReferenceType(
441
+ customerReferenceType="INVOICE_NUMBER",
442
+ value=customs.invoice,
443
+ )
444
+ ]
445
+ if customs.invoice is not None
446
+ else None
447
+ ),
448
+ taxesOrMiscellaneousCharge=None,
449
+ taxesOrMiscellaneousChargeType=None,
450
+ freightCharge=None,
451
+ packingCosts=None,
452
+ handlingCosts=None,
453
+ declarationStatement=None,
454
+ termsOfSale=provider_units.Incoterm.map(
455
+ customs.incoterm or "DDU"
456
+ ).value,
457
+ specialInstructions=None,
458
+ shipmentPurpose=provider_units.PurposeType.map(
459
+ customs.content_type or "other"
460
+ ).value,
461
+ emailNotificationDetail=None,
462
+ ),
463
+ freightOnValue=None,
464
+ dutiesPayment=fedex.DutiesPaymentType(
465
+ paymentType=provider_units.PaymentType.map(
466
+ customs.duty.paid_by
467
+ ).value,
468
+ payor=lib.identity(
469
+ fedex.PayorType(
470
+ responsibleParty=fedex.ResponsiblePartyType(
471
+ address=lib.identity(
472
+ fedex.AddressType(
473
+ streetLines=duty_billing_address.address_lines,
474
+ city=duty_billing_address.city,
475
+ stateOrProvinceCode=provider_utils.state_code(
476
+ duty_billing_address
477
+ ),
478
+ postalCode=duty_billing_address.postal_code,
479
+ countryCode=duty_billing_address.country_code,
480
+ residential=duty_billing_address.residential,
481
+ )
482
+ if duty_billing_address.address
483
+ and customs.duty.account_number
484
+ else None
485
+ ),
486
+ contact=lib.identity(
487
+ fedex.ResponsiblePartyContactType(
488
+ personName=lib.text(
489
+ duty_billing_address.contact, max=35
490
+ ),
491
+ emailAddress=duty_billing_address.email,
492
+ phoneNumber=lib.text(
493
+ duty_billing_address.phone_number
494
+ or shipper.phone_number
495
+ or "000-000-0000",
496
+ max=15,
497
+ trim=True,
498
+ ),
499
+ phoneExtension=None,
500
+ companyName=lib.text(
501
+ duty_billing_address.company_name,
502
+ max=35,
503
+ ),
504
+ faxNumber=None,
505
+ )
506
+ if duty_billing_address.address
507
+ and customs.duty.account_number
508
+ else None
509
+ ),
510
+ accountNumber=lib.identity(
511
+ fedex.AccountNumberType(
512
+ value=payment.account_number
513
+ )
514
+ if customs.duty.paid_by != "sender"
515
+ and customs.duty.account_number
516
+ else None
517
+ ),
518
+ tins=lib.identity(
519
+ fedex.TinType(
520
+ number=duty_billing_address.tax_id,
521
+ tinType="FEDERAL",
522
+ )
523
+ if duty_billing_address.has_tax_info
524
+ else []
525
+ ),
526
+ )
527
+ )
528
+ if duty_billing_address.address
529
+ else None
530
+ ),
531
+ ),
532
+ commodities=[
533
+ fedex.CommodityType(
534
+ unitPrice=lib.identity(
535
+ fedex.TotalDeclaredValueType(
536
+ amount=lib.to_money(item.value_amount),
537
+ currency=lib.identity(
538
+ item.value_currency
539
+ or packages.options.currency.state
540
+ or default_currency
541
+ ),
542
+ )
543
+ if item.value_amount
544
+ else None
545
+ ),
546
+ additionalMeasures=[],
547
+ numberOfPieces=item.quantity,
548
+ quantity=item.quantity,
549
+ quantityUnits="PCS",
550
+ customsValue=fedex.CustomsValueType(
551
+ amount=lib.identity(
552
+ lib.to_money(item.value_amount * item.quantity)
553
+ if item.value_amount is not None
554
+ else 0.0
555
+ ),
556
+ currency=lib.identity(
557
+ item.value_currency
558
+ or packages.options.currency.state
559
+ or default_currency
560
+ ),
561
+ ),
562
+ countryOfManufacture=(
563
+ item.origin_country or shipper.country_code
564
+ ),
565
+ cIMarksAndNumbers=None,
566
+ harmonizedCode=item.hs_code,
567
+ description=lib.text(
568
+ item.description or item.title or "N/A", max=35
569
+ ),
570
+ name=lib.text(item.title, max=35),
571
+ weight=fedex.WeightType(
572
+ units=weight_unit.value,
573
+ value=item.weight,
574
+ ),
575
+ exportLicenseNumber=None,
576
+ exportLicenseExpirationDate=None,
577
+ partNumber=item.sku,
578
+ purpose=None,
579
+ usmcaDetail=None,
580
+ )
581
+ for item in customs.commodities
582
+ ],
583
+ isDocumentOnly=packages.is_document,
584
+ recipientCustomsId=None,
585
+ customsOption=None,
586
+ importerOfRecord=None,
587
+ generatedDocumentLocale=None,
588
+ exportDetail=None,
589
+ totalCustomsValue=lib.identity(
590
+ fedex.TotalDeclaredValueType(
591
+ amount=lib.to_money(packages.options.declared_value.state),
592
+ currency=lib.identity(
593
+ packages.options.currency.state or default_currency
594
+ ),
595
+ )
596
+ if lib.to_money(packages.options.declared_value.state)
597
+ is not None
598
+ else None
599
+ ),
600
+ partiesToTransactionAreRelated=None,
601
+ declarationStatementDetail=None,
602
+ insuranceCharge=fedex.TotalDeclaredValueType(
603
+ amount=packages.options.insurance.state or 0.0,
604
+ currency=lib.identity(
605
+ packages.options.currency.state or default_currency
606
+ ),
607
+ ),
608
+ )
609
+ if payload.customs is not None
610
+ else None
611
+ ),
612
+ smartPostInfoDetail=lib.identity(
613
+ fedex.SmartPostInfoDetailType(
614
+ ancillaryEndorsement=None,
615
+ hubId=hub_id,
616
+ indicia=(
617
+ lib.text(options.fedex_smart_post_allowed_indicia.state)
618
+ or "PARCEL_SELECT"
619
+ ),
620
+ specialServices=None,
621
+ )
622
+ if hub_id and service == "SMART_POST"
623
+ else None
624
+ ),
625
+ blockInsightVisibility=False,
626
+ labelSpecification=fedex.LabelSpecificationType(
627
+ labelFormatType="COMMON2D",
628
+ labelOrder="SHIPPING_LABEL_FIRST",
629
+ customerSpecifiedDetail=None,
630
+ printedLabelOrigin=None,
631
+ labelStockType=label_format,
632
+ labelRotation=None,
633
+ imageType=label_type,
634
+ labelPrintingOrientation=None,
635
+ returnedDispositionDetail=None,
636
+ ),
637
+ shippingDocumentSpecification=lib.identity(
638
+ fedex.ShippingDocumentSpecificationType(
639
+ generalAgencyAgreementDetail=None,
640
+ returnInstructionsDetail=None,
641
+ op900Detail=None,
642
+ usmcaCertificationOfOriginDetail=None,
643
+ usmcaCommercialInvoiceCertificationOfOriginDetail=None,
644
+ shippingDocumentTypes=["COMMERCIAL_INVOICE"],
645
+ certificateOfOrigin=None,
646
+ commercialInvoiceDetail=fedex.CertificateOfOriginType(
647
+ customerImageUsages=[],
648
+ documentFormat=fedex.DocumentFormatType(
649
+ provideInstructions=None,
650
+ optionsRequested=None,
651
+ stockType="PAPER_LETTER",
652
+ dispositions=[],
653
+ locale=None,
654
+ docType="PDF",
655
+ ),
656
+ ),
657
+ )
658
+ if (
659
+ customs.commercial_invoice is True
660
+ and not packages.options.fedex_electronic_trade_documents.state
661
+ )
662
+ else None
663
+ ),
664
+ rateRequestType=request_types,
665
+ preferredCurrency=packages.options.currency.state,
666
+ totalPackageCount=len(packages),
667
+ masterTrackingId=None,
668
+ requestedPackageLineItems=[
669
+ fedex.RequestedPackageLineItemType(
670
+ sequenceNumber=None,
671
+ subPackagingType="OTHER",
672
+ customerReferences=[],
673
+ declaredValue=fedex.TotalDeclaredValueType(
674
+ amount=lib.identity(
675
+ lib.to_money(package.total_value)
676
+ or lib.to_money(packages.options.declared_value.state)
677
+ or 0.0
678
+ ),
679
+ currency=lib.identity(
680
+ packages.options.currency.state or default_currency
681
+ ),
682
+ ),
683
+ weight=fedex.WeightType(
684
+ units=package.weight.unit,
685
+ value=package.weight.value,
686
+ ),
687
+ dimensions=lib.identity(
688
+ fedex.DimensionsType(
689
+ length=package.length.value,
690
+ width=package.width.value,
691
+ height=package.height.value,
692
+ units=dim_unit.value,
693
+ )
694
+ if (
695
+ # only set dimensions if the packaging type is set to your_packaging
696
+ package.has_dimensions
697
+ and provider_units.PackagingType.map(
698
+ package.packaging_type or "your_packaging"
699
+ ).value
700
+ == provider_units.PackagingType.your_packaging.value
701
+ )
702
+ else None
703
+ ),
704
+ groupPackageCount=1,
705
+ itemDescriptionForClearance=None,
706
+ contentRecord=[],
707
+ itemDescription=package.parcel.description,
708
+ variableHandlingChargeDetail=None,
709
+ packageSpecialServices=fedex.PackageSpecialServicesType(
710
+ specialServiceTypes=[
711
+ option.code for option in package_options(package.options)
712
+ ],
713
+ priorityAlertDetail=None,
714
+ signatureOptionType=lib.identity(
715
+ provider_units.SignatureOptionType.map(
716
+ package.options.fedex_signature_option.state
717
+ ).value
718
+ or "SERVICE_DEFAULT"
719
+ ),
720
+ signatureOptionDetail=None,
721
+ alcoholDetail=None,
722
+ dangerousGoodsDetail=None,
723
+ packageCODDetail=None,
724
+ pieceCountVerificationBoxCount=None,
725
+ batteryDetails=[],
726
+ dryIceWeight=None,
727
+ ),
728
+ trackingNumber=None,
729
+ )
730
+ for package in packages
731
+ ],
732
+ ),
733
+ labelResponseOptions="LABEL",
734
+ accountNumber=fedex.AccountNumberType(value=settings.account_number),
735
+ shipAction="CONFIRM",
736
+ processingOptionType=None,
737
+ oneLabelAtATime=False,
738
+ )
739
+
740
+ return lib.Serializable(
741
+ requests,
742
+ lib.to_dict,
743
+ dict(
744
+ shipment_date=shipment_date,
745
+ label_type=label_type,
746
+ label_format=label_format,
747
+ ),
748
+ )