karrio-server-manager 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.
- karrio/server/manager/__init__.py +1 -0
- karrio/server/manager/admin.py +1 -0
- karrio/server/manager/apps.py +13 -0
- karrio/server/manager/migrations/0001_initial.py +1358 -0
- karrio/server/manager/migrations/0002_auto_20201127_0721.py +61 -0
- karrio/server/manager/migrations/0003_auto_20201230_0820.py +34 -0
- karrio/server/manager/migrations/0004_auto_20210125_2125.py +18 -0
- karrio/server/manager/migrations/0005_auto_20210216_0758.py +27 -0
- karrio/server/manager/migrations/0006_auto_20210307_0438.py +24 -0
- karrio/server/manager/migrations/0006_auto_20210308_0302.py +53 -0
- karrio/server/manager/migrations/0007_merge_20210311_1428.py +14 -0
- karrio/server/manager/migrations/0008_remove_shipment_doc_images.py +17 -0
- karrio/server/manager/migrations/0009_auto_20210326_1425.py +28 -0
- karrio/server/manager/migrations/0010_auto_20210403_1404.py +28 -0
- karrio/server/manager/migrations/0011_auto_20210426_1924.py +48 -0
- karrio/server/manager/migrations/0012_auto_20210427_1319.py +24 -0
- karrio/server/manager/migrations/0013_customs_invoice_date.py +18 -0
- karrio/server/manager/migrations/0014_auto_20210515_0928.py +24 -0
- karrio/server/manager/migrations/0015_auto_20210601_0340.py +182 -0
- karrio/server/manager/migrations/0016_shipment_archived.py +18 -0
- karrio/server/manager/migrations/0017_auto_20210629_1650.py +22 -0
- karrio/server/manager/migrations/0018_auto_20210705_1049.py +23 -0
- karrio/server/manager/migrations/0019_auto_20210722_1131.py +43 -0
- karrio/server/manager/migrations/0020_tracking_messages.py +20 -0
- karrio/server/manager/migrations/0021_tracking_estimated_delivery.py +18 -0
- karrio/server/manager/migrations/0022_auto_20211122_2100.py +53 -0
- karrio/server/manager/migrations/0023_auto_20211227_2141.py +118 -0
- karrio/server/manager/migrations/0024_alter_parcel_items.py +18 -0
- karrio/server/manager/migrations/0025_auto_20220113_1158.py +25 -0
- karrio/server/manager/migrations/0026_parcel_reference_number.py +18 -0
- karrio/server/manager/migrations/0027_custom_migration_2021_1.py +47 -0
- karrio/server/manager/migrations/0028_auto_20220303_1153.py +39 -0
- karrio/server/manager/migrations/0029_auto_20220303_1249.py +55 -0
- karrio/server/manager/migrations/0030_alter_shipment_status.py +44 -0
- karrio/server/manager/migrations/0031_shipment_invoice.py +34 -0
- karrio/server/manager/migrations/0032_custom_migration_2022_3.py +26 -0
- karrio/server/manager/migrations/0033_auto_20220504_1335.py +57 -0
- karrio/server/manager/migrations/0034_commodity_hs_code.py +18 -0
- karrio/server/manager/migrations/0035_parcel_options.py +26 -0
- karrio/server/manager/migrations/0036_alter_tracking_shipment.py +24 -0
- karrio/server/manager/migrations/0037_auto_20220710_1350.py +28 -0
- karrio/server/manager/migrations/0038_alter_tracking_status.py +18 -0
- karrio/server/manager/migrations/0039_documentuploadrecord.py +43 -0
- karrio/server/manager/migrations/0040_parcel_freight_class.py +18 -0
- karrio/server/manager/migrations/0041_alter_commodity_options_alter_parcel_options.py +29 -0
- karrio/server/manager/migrations/0042_remove_shipment_shipment_tracking_number_idx_and_more.py +658 -0
- karrio/server/manager/migrations/0043_customs_duty_billing_address_and_more.py +62 -0
- karrio/server/manager/migrations/0044_address_address_line1_temp_and_more.py +326 -0
- karrio/server/manager/migrations/0045_alter_customs_duty_billing_address_and_more.py +45 -0
- karrio/server/manager/migrations/0046_auto_20230114_0930.py +78 -0
- karrio/server/manager/migrations/0047_remove_shipment_shipment_tracking_number_idx_and_more.py +595 -0
- karrio/server/manager/migrations/0048_commodity_title_alter_commodity_description_and_more.py +53 -0
- karrio/server/manager/migrations/0049_auto_20230318_0708.py +39 -0
- karrio/server/manager/migrations/0050_address_street_number_tracking_account_number_and_more.py +60 -0
- karrio/server/manager/migrations/0051_auto_20230330_0556.py +56 -0
- karrio/server/manager/migrations/0052_auto_20230520_0811.py +35 -0
- karrio/server/manager/migrations/0053_alter_commodity_weight_unit_alter_parcel_weight_unit.py +32 -0
- karrio/server/manager/migrations/0054_alter_address_company_name_alter_address_person_name.py +22 -0
- karrio/server/manager/migrations/0055_alter_tracking_status.py +32 -0
- karrio/server/manager/migrations/0056_tracking_delivery_image_tracking_signature_image.py +22 -0
- karrio/server/manager/migrations/0057_alter_customs_invoice_date.py +18 -0
- karrio/server/manager/migrations/0058_manifest_shipment_manifest.py +124 -0
- karrio/server/manager/migrations/0059_shipment_return_address.py +24 -0
- karrio/server/manager/migrations/0060_pickup_meta_alter_address_country_code_and_more.py +527 -0
- karrio/server/manager/migrations/0061_alter_customs_incoterm.py +37 -0
- karrio/server/manager/migrations/0062_alter_tracking_status.py +35 -0
- karrio/server/manager/migrations/__init__.py +0 -0
- karrio/server/manager/models.py +984 -0
- karrio/server/manager/router.py +3 -0
- karrio/server/manager/serializers/__init__.py +50 -0
- karrio/server/manager/serializers/address.py +82 -0
- karrio/server/manager/serializers/commodity.py +51 -0
- karrio/server/manager/serializers/customs.py +84 -0
- karrio/server/manager/serializers/document.py +113 -0
- karrio/server/manager/serializers/manifest.py +85 -0
- karrio/server/manager/serializers/parcel.py +84 -0
- karrio/server/manager/serializers/pickup.py +285 -0
- karrio/server/manager/serializers/rate.py +19 -0
- karrio/server/manager/serializers/shipment.py +869 -0
- karrio/server/manager/serializers/tracking.py +250 -0
- karrio/server/manager/signals.py +70 -0
- karrio/server/manager/tests/__init__.py +10 -0
- karrio/server/manager/tests/test_addresses.py +110 -0
- karrio/server/manager/tests/test_custom_infos.py +97 -0
- karrio/server/manager/tests/test_parcels.py +104 -0
- karrio/server/manager/tests/test_pickups.py +345 -0
- karrio/server/manager/tests/test_shipments.py +833 -0
- karrio/server/manager/tests/test_trackers.py +215 -0
- karrio/server/manager/urls.py +10 -0
- karrio/server/manager/views/__init__.py +9 -0
- karrio/server/manager/views/addresses.py +154 -0
- karrio/server/manager/views/customs.py +159 -0
- karrio/server/manager/views/documents.py +131 -0
- karrio/server/manager/views/manifests.py +160 -0
- karrio/server/manager/views/parcels.py +155 -0
- karrio/server/manager/views/pickups.py +182 -0
- karrio/server/manager/views/shipments.py +335 -0
- karrio/server/manager/views/trackers.py +364 -0
- karrio_server_manager-2025.5rc1.dist-info/METADATA +28 -0
- karrio_server_manager-2025.5rc1.dist-info/RECORD +102 -0
- karrio_server_manager-2025.5rc1.dist-info/WHEEL +5 -0
- karrio_server_manager-2025.5rc1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
from karrio.server.serializers import *
|
2
|
+
from karrio.server.core.serializers import *
|
3
|
+
from karrio.server.manager.serializers.address import (
|
4
|
+
AddressSerializer,
|
5
|
+
can_mutate_address,
|
6
|
+
)
|
7
|
+
from karrio.server.manager.serializers.parcel import (
|
8
|
+
ParcelSerializer,
|
9
|
+
can_mutate_parcel,
|
10
|
+
)
|
11
|
+
from karrio.server.manager.serializers.customs import (
|
12
|
+
CustomsSerializer,
|
13
|
+
can_mutate_customs,
|
14
|
+
)
|
15
|
+
from karrio.server.manager.serializers.commodity import (
|
16
|
+
CommoditySerializer,
|
17
|
+
can_mutate_commodity,
|
18
|
+
)
|
19
|
+
from karrio.server.manager.serializers.rate import RateSerializer
|
20
|
+
from karrio.server.manager.serializers.tracking import (
|
21
|
+
TrackingSerializer,
|
22
|
+
TrackerUpdateData,
|
23
|
+
update_shipment_tracker,
|
24
|
+
can_mutate_tracker,
|
25
|
+
)
|
26
|
+
from karrio.server.manager.serializers.shipment import (
|
27
|
+
ShipmentRateData,
|
28
|
+
ShipmentSerializer,
|
29
|
+
ShipmentUpdateData,
|
30
|
+
ShipmentPurchaseData,
|
31
|
+
ShipmentPurchaseSerializer,
|
32
|
+
ShipmentCancelSerializer,
|
33
|
+
create_shipment_tracker,
|
34
|
+
reset_related_shipment_rates,
|
35
|
+
can_mutate_shipment,
|
36
|
+
buy_shipment_label,
|
37
|
+
fetch_shipment_rates,
|
38
|
+
)
|
39
|
+
from karrio.server.manager.serializers.pickup import (
|
40
|
+
PickupData,
|
41
|
+
PickupUpdateData,
|
42
|
+
PickupCancelData,
|
43
|
+
)
|
44
|
+
from karrio.server.manager.serializers.document import (
|
45
|
+
DocumentUploadSerializer,
|
46
|
+
can_upload_shipment_document,
|
47
|
+
)
|
48
|
+
from karrio.server.manager.serializers.manifest import (
|
49
|
+
ManifestSerializer,
|
50
|
+
)
|
@@ -0,0 +1,82 @@
|
|
1
|
+
from rest_framework import status
|
2
|
+
|
3
|
+
from karrio.server.core.exceptions import APIException
|
4
|
+
from karrio.server.serializers import owned_model_serializer
|
5
|
+
from karrio.core import utils
|
6
|
+
from karrio.server.core import gateway
|
7
|
+
from karrio.server.core.serializers import AddressData, ShipmentStatus
|
8
|
+
from karrio.server.manager import models
|
9
|
+
|
10
|
+
|
11
|
+
@owned_model_serializer
|
12
|
+
class AddressSerializer(AddressData):
|
13
|
+
def validate(self, data):
|
14
|
+
validated_data = super().validate(data)
|
15
|
+
should_validate = validated_data.get("validate_location") is True or (
|
16
|
+
self.instance is not None and self.instance.validate_location
|
17
|
+
)
|
18
|
+
|
19
|
+
if should_validate:
|
20
|
+
address = {
|
21
|
+
**(
|
22
|
+
AddressData(self.instance).data if self.instance is not None else {}
|
23
|
+
),
|
24
|
+
**validated_data,
|
25
|
+
}
|
26
|
+
validation = gateway.Address.validate(address)
|
27
|
+
validated_data.update(dict(validation=utils.DP.to_dict(validation)))
|
28
|
+
|
29
|
+
return validated_data
|
30
|
+
|
31
|
+
def create(self, validated_data: dict, **kwargs) -> models.Address:
|
32
|
+
return models.Address.objects.create(**validated_data)
|
33
|
+
|
34
|
+
def update(
|
35
|
+
self, instance: models.Address, validated_data: dict, **kwargs
|
36
|
+
) -> models.Address:
|
37
|
+
changes = []
|
38
|
+
for key, val in validated_data.items():
|
39
|
+
if getattr(instance, key) != val:
|
40
|
+
changes.append(key)
|
41
|
+
setattr(instance, key, val)
|
42
|
+
|
43
|
+
instance.save(update_fields=changes)
|
44
|
+
return instance
|
45
|
+
|
46
|
+
|
47
|
+
def can_mutate_address(
|
48
|
+
address: models.Address, update: bool = False, delete: bool = False
|
49
|
+
):
|
50
|
+
shipment = address.shipment
|
51
|
+
order = address.order
|
52
|
+
|
53
|
+
if shipment is None and order is None:
|
54
|
+
return
|
55
|
+
|
56
|
+
if update and shipment and shipment.status != ShipmentStatus.draft.value:
|
57
|
+
raise APIException(
|
58
|
+
f"Operation not permitted. The related shipment is '{shipment.status}'.",
|
59
|
+
status_code=status.HTTP_409_CONFLICT,
|
60
|
+
code="state_error",
|
61
|
+
)
|
62
|
+
|
63
|
+
if delete and shipment is not None:
|
64
|
+
raise APIException(
|
65
|
+
"This address is linked to a shipment and cannot be removed",
|
66
|
+
status_code=status.HTTP_409_CONFLICT,
|
67
|
+
code="state_error",
|
68
|
+
)
|
69
|
+
|
70
|
+
if update and order and order.status != "unfulfilled":
|
71
|
+
raise APIException(
|
72
|
+
f"Operation not permitted. The related order is '{order.status}'.",
|
73
|
+
status_code=status.HTTP_409_CONFLICT,
|
74
|
+
code="state_error",
|
75
|
+
)
|
76
|
+
|
77
|
+
if delete and order is not None:
|
78
|
+
raise APIException(
|
79
|
+
"This address is linked to an order and cannot be removed",
|
80
|
+
status_code=status.HTTP_409_CONFLICT,
|
81
|
+
code="state_error",
|
82
|
+
)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
from rest_framework import status
|
2
|
+
|
3
|
+
from karrio.server.core.exceptions import APIException
|
4
|
+
from karrio.server.core.serializers import CommodityData, ShipmentStatus
|
5
|
+
from karrio.server.serializers import owned_model_serializer
|
6
|
+
import karrio.server.manager.models as models
|
7
|
+
|
8
|
+
|
9
|
+
@owned_model_serializer
|
10
|
+
class CommoditySerializer(CommodityData):
|
11
|
+
object_type = None
|
12
|
+
|
13
|
+
def create(self, validated_data: dict, **kwargs) -> models.Commodity:
|
14
|
+
return models.Commodity.objects.create(**validated_data)
|
15
|
+
|
16
|
+
def update(
|
17
|
+
self, instance: models.Commodity, validated_data: dict, **kwargs
|
18
|
+
) -> models.Commodity:
|
19
|
+
changes = []
|
20
|
+
|
21
|
+
for key, val in validated_data.items():
|
22
|
+
if getattr(instance, key) != val:
|
23
|
+
changes.append(key)
|
24
|
+
setattr(instance, key, val)
|
25
|
+
|
26
|
+
instance.save()
|
27
|
+
return instance
|
28
|
+
|
29
|
+
|
30
|
+
def can_mutate_commodity(
|
31
|
+
commodity: models.Commodity, update: bool = False, delete: bool = False, **kwargs
|
32
|
+
):
|
33
|
+
shipment = commodity.shipment
|
34
|
+
order = commodity.order
|
35
|
+
|
36
|
+
if shipment is None and order is None:
|
37
|
+
return
|
38
|
+
|
39
|
+
if update and shipment and shipment.status != ShipmentStatus.draft.value:
|
40
|
+
raise APIException(
|
41
|
+
f"Operation not permitted. The related shipment is '{shipment.status}'.",
|
42
|
+
status_code=status.HTTP_409_CONFLICT,
|
43
|
+
code="state_error",
|
44
|
+
)
|
45
|
+
|
46
|
+
if delete and order and len(order.line_items.all()) == 1:
|
47
|
+
raise APIException(
|
48
|
+
f"Operation not permitted. The related order needs at least one line_item.",
|
49
|
+
status_code=status.HTTP_409_CONFLICT,
|
50
|
+
code="state_error",
|
51
|
+
)
|
@@ -0,0 +1,84 @@
|
|
1
|
+
from django.db import transaction
|
2
|
+
from rest_framework import status
|
3
|
+
|
4
|
+
import karrio.server.manager.models as models
|
5
|
+
import karrio.server.serializers as serializers
|
6
|
+
import karrio.server.core.exceptions as exceptions
|
7
|
+
from karrio.server.core.serializers import CustomsData, ShipmentStatus
|
8
|
+
from karrio.server.manager.serializers.address import AddressSerializer
|
9
|
+
from karrio.server.manager.serializers.commodity import CommoditySerializer
|
10
|
+
|
11
|
+
|
12
|
+
@serializers.owned_model_serializer
|
13
|
+
class CustomsSerializer(CustomsData):
|
14
|
+
def __init__(self, instance: models.Customs = None, **kwargs):
|
15
|
+
data = kwargs.get("data") or {}
|
16
|
+
|
17
|
+
if ("commodities" in data) and (instance is not None):
|
18
|
+
context = getattr(self, "__context", None) or kwargs.get("context")
|
19
|
+
serializers.save_many_to_many_data(
|
20
|
+
"commodities",
|
21
|
+
CommoditySerializer,
|
22
|
+
instance,
|
23
|
+
payload=data,
|
24
|
+
context=context,
|
25
|
+
partial=True,
|
26
|
+
)
|
27
|
+
|
28
|
+
super().__init__(instance, **kwargs)
|
29
|
+
|
30
|
+
@transaction.atomic
|
31
|
+
def create(self, validated_data: dict, context: dict, **kwargs) -> models.Customs:
|
32
|
+
instance = models.Customs.objects.create(
|
33
|
+
**{
|
34
|
+
**{
|
35
|
+
key: value
|
36
|
+
for key, value in validated_data.items()
|
37
|
+
if key in models.Customs.DIRECT_PROPS
|
38
|
+
},
|
39
|
+
"duty_billing_address": serializers.save_one_to_one_data(
|
40
|
+
"duty_billing_address",
|
41
|
+
AddressSerializer,
|
42
|
+
payload=validated_data,
|
43
|
+
context=context,
|
44
|
+
),
|
45
|
+
}
|
46
|
+
)
|
47
|
+
|
48
|
+
serializers.save_many_to_many_data(
|
49
|
+
"commodities",
|
50
|
+
CommoditySerializer,
|
51
|
+
instance,
|
52
|
+
payload=validated_data,
|
53
|
+
context=context,
|
54
|
+
)
|
55
|
+
|
56
|
+
return instance
|
57
|
+
|
58
|
+
@transaction.atomic
|
59
|
+
def update(
|
60
|
+
self, instance: models.Customs, validated_data: dict, **kwargs
|
61
|
+
) -> models.Customs:
|
62
|
+
data = serializers.process_dictionaries_mutations(
|
63
|
+
["options"], validated_data, instance
|
64
|
+
)
|
65
|
+
changes = []
|
66
|
+
|
67
|
+
for key, val in data.items():
|
68
|
+
if key in models.Customs.DIRECT_PROPS and getattr(instance, key) != val:
|
69
|
+
changes.append(key)
|
70
|
+
setattr(instance, key, val)
|
71
|
+
|
72
|
+
instance.save(update_fields=changes)
|
73
|
+
return instance
|
74
|
+
|
75
|
+
|
76
|
+
def can_mutate_customs(customs: models.Customs, **kwargs):
|
77
|
+
shipment = customs.shipment
|
78
|
+
|
79
|
+
if shipment is not None and shipment.status != ShipmentStatus.create.value:
|
80
|
+
raise exceptions.APIException(
|
81
|
+
f"Operation not permitted. The related shipment is '{shipment.status}'.",
|
82
|
+
status_code=status.HTTP_409_CONFLICT,
|
83
|
+
code="state_error",
|
84
|
+
)
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import rest_framework.status as status
|
2
|
+
|
3
|
+
import karrio.lib as lib
|
4
|
+
import karrio.server.serializers as serialiazers
|
5
|
+
import karrio.server.core.exceptions as exceptions
|
6
|
+
import karrio.server.core.serializers as core
|
7
|
+
import karrio.server.core.gateway as gateway
|
8
|
+
import karrio.server.manager.models as models
|
9
|
+
|
10
|
+
|
11
|
+
@serialiazers.owned_model_serializer
|
12
|
+
class DocumentUploadSerializer(core.DocumentUploadData):
|
13
|
+
def create(
|
14
|
+
self,
|
15
|
+
validated_data: dict,
|
16
|
+
context: serialiazers.Context,
|
17
|
+
**kwargs,
|
18
|
+
) -> models.DocumentUploadRecord:
|
19
|
+
shipment = validated_data.get("shipment")
|
20
|
+
carrier = validated_data.get("carrier") or getattr(
|
21
|
+
shipment, "selected_rate_carrier", None
|
22
|
+
)
|
23
|
+
tracking_number = getattr(shipment, "tracking_number", None)
|
24
|
+
reference = validated_data.get("reference") or tracking_number
|
25
|
+
|
26
|
+
payload = core.DocumentUploadData(validated_data).data
|
27
|
+
options = ({
|
28
|
+
"origin_country_code": shipment.shipper.country_code,
|
29
|
+
"origin_postal_code": shipment.shipper.postal_code,
|
30
|
+
"destination_country_code": shipment.recipient.country_code,
|
31
|
+
"destination_postal_code": shipment.recipient.postal_code,
|
32
|
+
**(payload.get("options") or {})
|
33
|
+
} if shipment else payload.get("options"))
|
34
|
+
|
35
|
+
response = gateway.Documents.upload(
|
36
|
+
{
|
37
|
+
**payload,
|
38
|
+
"options": options,
|
39
|
+
"reference": reference,
|
40
|
+
"tracking_number": tracking_number,
|
41
|
+
},
|
42
|
+
carrier=carrier,
|
43
|
+
context=context,
|
44
|
+
)
|
45
|
+
|
46
|
+
upload_record = models.DocumentUploadRecord.objects.create(
|
47
|
+
documents=lib.to_dict(response.documents),
|
48
|
+
messages=lib.to_dict(response.messages),
|
49
|
+
options=response.options,
|
50
|
+
meta=response.meta,
|
51
|
+
shipment=shipment,
|
52
|
+
upload_carrier=carrier,
|
53
|
+
created_by=context.user,
|
54
|
+
)
|
55
|
+
|
56
|
+
return upload_record
|
57
|
+
|
58
|
+
def update(
|
59
|
+
self,
|
60
|
+
instance: models.DocumentUploadRecord,
|
61
|
+
validated_data: dict,
|
62
|
+
context: serialiazers.Context,
|
63
|
+
**kwargs,
|
64
|
+
) -> models.DocumentUploadRecord:
|
65
|
+
changes = []
|
66
|
+
|
67
|
+
response = gateway.Documents.upload(
|
68
|
+
{
|
69
|
+
"reference": getattr(instance.shipment, "tracking_number", None),
|
70
|
+
**core.DocumentUploadData(validated_data).data,
|
71
|
+
},
|
72
|
+
carrier=instance.upload_carrier,
|
73
|
+
context=context,
|
74
|
+
)
|
75
|
+
|
76
|
+
if any(response.documents):
|
77
|
+
changes.append("documents")
|
78
|
+
instance.documents = [*instance.documents, *lib.to_dict(response.documents)]
|
79
|
+
|
80
|
+
if any(response.messages):
|
81
|
+
changes.append("messages")
|
82
|
+
instance.messages = lib.to_dict(response.messages)
|
83
|
+
|
84
|
+
instance.save(update_fields=changes)
|
85
|
+
|
86
|
+
return instance
|
87
|
+
|
88
|
+
|
89
|
+
def can_upload_shipment_document(shipment: models.Shipment):
|
90
|
+
carrier = getattr(shipment, "selected_rate_carrier", None)
|
91
|
+
capabilities = getattr(carrier, "capabilities", [])
|
92
|
+
|
93
|
+
if shipment is None:
|
94
|
+
raise exceptions.APIException(
|
95
|
+
detail=f"No purchased shipment found for trade document upload.",
|
96
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
97
|
+
)
|
98
|
+
|
99
|
+
if shipment.status not in [
|
100
|
+
core.ShipmentStatus.shipped.value,
|
101
|
+
core.ShipmentStatus.purchased.value,
|
102
|
+
core.ShipmentStatus.in_transit.value,
|
103
|
+
]:
|
104
|
+
raise exceptions.APIException(
|
105
|
+
detail=f"The trade document upload is not enabled for shipment status: '{shipment.status}'.",
|
106
|
+
status_code=status.HTTP_409_CONFLICT,
|
107
|
+
)
|
108
|
+
|
109
|
+
if "paperless" not in capabilities:
|
110
|
+
raise exceptions.APIException(
|
111
|
+
detail=f"trade document upload is not supported by carrier: '{carrier.carrier_id}'",
|
112
|
+
status_code=status.HTTP_406_NOT_ACCEPTABLE,
|
113
|
+
)
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import typing
|
2
|
+
|
3
|
+
import karrio.server.core.gateway as gateway
|
4
|
+
import karrio.server.manager.models as models
|
5
|
+
import karrio.server.core.serializers as core
|
6
|
+
import karrio.server.serializers as serializers
|
7
|
+
import karrio.server.manager.serializers as manager
|
8
|
+
|
9
|
+
DEFAULT_CARRIER_FILTER: typing.Any = dict(active=True, capability="manifest")
|
10
|
+
|
11
|
+
|
12
|
+
@serializers.owned_model_serializer
|
13
|
+
class ManifestSerializer(core.ManifestData):
|
14
|
+
def create(
|
15
|
+
self, validated_data: dict, context: serializers.Context, **kwargs
|
16
|
+
) -> models.Manifest:
|
17
|
+
data = validated_data.copy()
|
18
|
+
shipment_ids = list(set(data.pop("shipment_ids")))
|
19
|
+
carrier_name = data["carrier_name"]
|
20
|
+
carrier = gateway.Carriers.first(
|
21
|
+
context=context,
|
22
|
+
carrier_name=carrier_name,
|
23
|
+
**{"raise_not_found": True, **DEFAULT_CARRIER_FILTER},
|
24
|
+
)
|
25
|
+
|
26
|
+
shipments = models.Shipment.access_by(context).filter(
|
27
|
+
id__in=shipment_ids,
|
28
|
+
manifest__isnull=True,
|
29
|
+
selected_rate_carrier__carrier_code=carrier_name,
|
30
|
+
)
|
31
|
+
shipment_identifiers = [_.shipment_identifier for _ in shipments]
|
32
|
+
|
33
|
+
if (
|
34
|
+
len(shipment_identifiers) > len(shipment_ids)
|
35
|
+
or len(shipment_identifiers) == 0
|
36
|
+
):
|
37
|
+
raise serializers.ValidationError(
|
38
|
+
{
|
39
|
+
"shipment_ids": (
|
40
|
+
"One or more shipment ids are invalid or not found. "
|
41
|
+
"Please make sure that the shipments referenced exist and have been purchased with the same carrier."
|
42
|
+
)
|
43
|
+
}
|
44
|
+
)
|
45
|
+
|
46
|
+
response = gateway.Manifests.create(
|
47
|
+
payload=core.ManifestRequest.map(
|
48
|
+
data={
|
49
|
+
**data,
|
50
|
+
"shipment_identifiers": shipment_identifiers,
|
51
|
+
"options": {
|
52
|
+
**data.get("options", {}),
|
53
|
+
"shipments": core.Shipment(shipments, many=True).data,
|
54
|
+
},
|
55
|
+
}
|
56
|
+
).data,
|
57
|
+
carrier=carrier,
|
58
|
+
)
|
59
|
+
|
60
|
+
payload = {
|
61
|
+
key: value
|
62
|
+
for key, value in core.Manifest(response.manifest).data.items()
|
63
|
+
if key in models.Manifest.DIRECT_PROPS
|
64
|
+
}
|
65
|
+
address = serializers.save_one_to_one_data(
|
66
|
+
"address",
|
67
|
+
manager.AddressSerializer,
|
68
|
+
payload=validated_data,
|
69
|
+
context=context,
|
70
|
+
)
|
71
|
+
|
72
|
+
manifest = models.Manifest.objects.create(
|
73
|
+
**{
|
74
|
+
**payload,
|
75
|
+
"address": address,
|
76
|
+
"created_by": context.user,
|
77
|
+
"manifest_carrier": carrier,
|
78
|
+
"options": data.get("options", {}),
|
79
|
+
"test_mode": response.manifest.test_mode,
|
80
|
+
"manifest": response.manifest.doc.manifest,
|
81
|
+
}
|
82
|
+
)
|
83
|
+
manifest.shipments.set(shipments)
|
84
|
+
|
85
|
+
return manifest
|
@@ -0,0 +1,84 @@
|
|
1
|
+
from rest_framework import status
|
2
|
+
|
3
|
+
from karrio.server.core.exceptions import APIException
|
4
|
+
from karrio.server.core.serializers import ParcelData, ShipmentStatus
|
5
|
+
from karrio.server.serializers import (
|
6
|
+
owned_model_serializer,
|
7
|
+
save_many_to_many_data,
|
8
|
+
process_dictionaries_mutations,
|
9
|
+
)
|
10
|
+
|
11
|
+
from karrio.server.manager.serializers.commodity import CommoditySerializer
|
12
|
+
import karrio.server.manager.models as models
|
13
|
+
|
14
|
+
|
15
|
+
@owned_model_serializer
|
16
|
+
class ParcelSerializer(ParcelData):
|
17
|
+
object_type = None
|
18
|
+
|
19
|
+
def __init__(self, instance: models.Address = None, **kwargs):
|
20
|
+
data = kwargs.get("data") or {}
|
21
|
+
|
22
|
+
if ("items" in data) and (instance is not None):
|
23
|
+
context = getattr(self, "__context", None) or kwargs.get("context")
|
24
|
+
save_many_to_many_data(
|
25
|
+
"items",
|
26
|
+
CommoditySerializer,
|
27
|
+
instance,
|
28
|
+
payload=data,
|
29
|
+
context=context,
|
30
|
+
)
|
31
|
+
|
32
|
+
super().__init__(instance, **kwargs)
|
33
|
+
|
34
|
+
def create(self, validated_data: dict, context: dict, **kwargs) -> models.Parcel:
|
35
|
+
instance = models.Parcel.objects.create(
|
36
|
+
**{key: value for key, value in validated_data.items() if key != "items"}
|
37
|
+
)
|
38
|
+
|
39
|
+
save_many_to_many_data(
|
40
|
+
"items",
|
41
|
+
CommoditySerializer,
|
42
|
+
instance,
|
43
|
+
payload=validated_data,
|
44
|
+
context=context,
|
45
|
+
)
|
46
|
+
|
47
|
+
return instance
|
48
|
+
|
49
|
+
def update(
|
50
|
+
self, instance: models.Parcel, validated_data: dict, **kwargs
|
51
|
+
) -> models.Parcel:
|
52
|
+
data = process_dictionaries_mutations(["options"], validated_data, instance)
|
53
|
+
changes = []
|
54
|
+
|
55
|
+
for key, val in data.items():
|
56
|
+
if getattr(instance, key) != val and key != "items":
|
57
|
+
changes.append(key)
|
58
|
+
setattr(instance, key, val)
|
59
|
+
|
60
|
+
instance.save(update_fields=changes)
|
61
|
+
return instance
|
62
|
+
|
63
|
+
|
64
|
+
def can_mutate_parcel(
|
65
|
+
parcel: models.Parcel, update: bool = False, delete: bool = False, **kwargs
|
66
|
+
):
|
67
|
+
shipment = parcel.shipment
|
68
|
+
|
69
|
+
if shipment is None:
|
70
|
+
return
|
71
|
+
|
72
|
+
if update and shipment.status != ShipmentStatus.draft.value:
|
73
|
+
raise APIException(
|
74
|
+
f"Operation not permitted. The related shipment is '{shipment.status}'.",
|
75
|
+
status_code=status.HTTP_409_CONFLICT,
|
76
|
+
code="state_error",
|
77
|
+
)
|
78
|
+
|
79
|
+
if delete and len(shipment.parcels.all()) == 1:
|
80
|
+
raise APIException(
|
81
|
+
f"Operation not permitted. The related shipment needs at least one parcel.",
|
82
|
+
status_code=status.HTTP_409_CONFLICT,
|
83
|
+
code="state_error",
|
84
|
+
)
|