karrio-server-data 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 (43) hide show
  1. karrio/server/data/__init__.py +0 -0
  2. karrio/server/data/admin.py +1 -0
  3. karrio/server/data/apps.py +13 -0
  4. karrio/server/data/filters.py +43 -0
  5. karrio/server/data/migrations/0001_initial.py +62 -0
  6. karrio/server/data/migrations/0002_alter_batchoperation_resource_type_and_more.py +28 -0
  7. karrio/server/data/migrations/0003_datatemplate_metadata_alter_batchoperation_resources.py +36 -0
  8. karrio/server/data/migrations/__init__.py +0 -0
  9. karrio/server/data/models.py +97 -0
  10. karrio/server/data/resources/__init__.py +53 -0
  11. karrio/server/data/resources/orders.py +523 -0
  12. karrio/server/data/resources/shipments.py +473 -0
  13. karrio/server/data/resources/trackers.py +212 -0
  14. karrio/server/data/serializers/__init__.py +26 -0
  15. karrio/server/data/serializers/base.py +107 -0
  16. karrio/server/data/serializers/batch.py +9 -0
  17. karrio/server/data/serializers/batch_orders.py +99 -0
  18. karrio/server/data/serializers/batch_shipments.py +102 -0
  19. karrio/server/data/serializers/batch_trackers.py +131 -0
  20. karrio/server/data/serializers/data.py +109 -0
  21. karrio/server/data/signals.py +52 -0
  22. karrio/server/data/tests.py +3 -0
  23. karrio/server/data/urls.py +13 -0
  24. karrio/server/data/views/__init__.py +0 -0
  25. karrio/server/data/views/batch.py +72 -0
  26. karrio/server/data/views/batch_order.py +40 -0
  27. karrio/server/data/views/batch_shipment.py +40 -0
  28. karrio/server/data/views/batch_tracking.py +40 -0
  29. karrio/server/data/views/data.py +171 -0
  30. karrio/server/events/task_definitions/__init__.py +1 -0
  31. karrio/server/events/task_definitions/data/__init__.py +136 -0
  32. karrio/server/events/task_definitions/data/batch.py +130 -0
  33. karrio/server/events/task_definitions/data/shipments.py +51 -0
  34. karrio/server/graph/schemas/__init__.py +1 -0
  35. karrio/server/graph/schemas/data/__init__.py +51 -0
  36. karrio/server/graph/schemas/data/inputs.py +39 -0
  37. karrio/server/graph/schemas/data/mutations.py +53 -0
  38. karrio/server/graph/schemas/data/types.py +78 -0
  39. karrio/server/settings/data.py +15 -0
  40. karrio_server_data-2025.5rc1.dist-info/METADATA +18 -0
  41. karrio_server_data-2025.5rc1.dist-info/RECORD +43 -0
  42. karrio_server_data-2025.5rc1.dist-info/WHEEL +5 -0
  43. karrio_server_data-2025.5rc1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,26 @@
1
+ from karrio.server.serializers import *
2
+ from karrio.server.core.serializers import *
3
+ from karrio.server.events.serializers.base import *
4
+ from karrio.server.orders.serializers import OrderData
5
+ from karrio.server.orders.serializers.order import OrderSerializer
6
+ from karrio.server.events.serializers.event import EventSerializer
7
+ from karrio.server.manager.serializers import (
8
+ ShipmentSerializer,
9
+ ShipmentData,
10
+ TrackingSerializer,
11
+ TrackingData,
12
+ )
13
+ from karrio.server.data.serializers.base import (
14
+ ImportData,
15
+ BatchObject,
16
+ ResourceType,
17
+ ResourceStatus,
18
+ BatchOperation,
19
+ BatchOperationData,
20
+ BatchOperationStatus,
21
+ OPERATION_STATUS,
22
+ RESOURCE_TYPE,
23
+ )
24
+ from karrio.server.data.serializers.batch_orders import BatchOrderData
25
+ from karrio.server.data.serializers.batch_trackers import BatchTrackerData
26
+ from karrio.server.data.serializers.batch_shipments import BatchShipmentData
@@ -0,0 +1,107 @@
1
+ import karrio.lib as lib
2
+ import rest_framework.fields as fields
3
+ import karrio.server.serializers as serializers
4
+
5
+
6
+ class ResourceType(lib.StrEnum):
7
+ order = "orders"
8
+ shipment = "shipments"
9
+ trackers = "trackers"
10
+ billing = "billing"
11
+
12
+ @classmethod
13
+ def get_default_mapping(cls, resource_type: str) -> dict:
14
+ from karrio.server.data import resources
15
+
16
+ if resource_type == "orders":
17
+ return resources.orders.DEFAULT_HEADERS
18
+ if resource_type == "shipments":
19
+ return resources.shipments.DEFAULT_HEADERS
20
+ if resource_type == "trackers":
21
+ return resources.trackers.DEFAULT_HEADERS
22
+
23
+ return {}
24
+
25
+ @classmethod
26
+ def get_model(cls, resource_type: str) -> dict:
27
+ if resource_type == "orders":
28
+ from karrio.server.orders.models import Order
29
+
30
+ return Order
31
+ if resource_type == "shipments":
32
+ from karrio.server.manager.models import Shipment
33
+
34
+ return Shipment
35
+ if resource_type == "trackers":
36
+ from karrio.server.manager.models import Tracking
37
+
38
+ return Tracking
39
+
40
+ return None
41
+
42
+ @classmethod
43
+ def get_serialiazer(cls, resource_type: str) -> dict:
44
+ if resource_type == "orders":
45
+ from karrio.server.data.serializers import BatchOrderData
46
+
47
+ return BatchOrderData
48
+ if resource_type == "shipments":
49
+ from karrio.server.data.serializers import BatchShipmentData
50
+
51
+ return BatchShipmentData
52
+ if resource_type == "trackers":
53
+ from karrio.server.data.serializers import BatchTrackerData
54
+
55
+ return BatchTrackerData
56
+
57
+ return None
58
+
59
+
60
+ class ResourceStatus(lib.StrEnum):
61
+ queued = "queued"
62
+ created = "created"
63
+ has_errors = "has_errors"
64
+ incomplete = "incomplete"
65
+ processed = "processed"
66
+
67
+
68
+ class BatchOperationStatus(lib.StrEnum):
69
+ queued = "queued"
70
+ running = "running"
71
+ failed = "failed"
72
+ completed = "completed"
73
+ completed_with_errors = "completed_with_errors"
74
+
75
+
76
+ RESOURCE_TYPE = [(c.value, c.value) for c in list(ResourceType)]
77
+ OPERATION_STATUS = [(c.value, c.value) for c in list(BatchOperationStatus)]
78
+
79
+
80
+ class ImportData(serializers.Serializer):
81
+ resource_type = fields.ChoiceField(required=True, choices=RESOURCE_TYPE)
82
+ data_template = fields.CharField(required=False)
83
+ data_file = fields.FileField(required=True)
84
+
85
+
86
+ class BatchObject(serializers.EntitySerializer):
87
+ status = fields.ChoiceField(
88
+ choices=OPERATION_STATUS,
89
+ help_text="The batch operation resource status",
90
+ )
91
+ errors = serializers.PlainDictField(
92
+ required=False,
93
+ allow_null=True,
94
+ help_text="Resource processing errors",
95
+ )
96
+
97
+
98
+ class BatchOperationData(serializers.Serializer):
99
+ status = fields.ChoiceField(choices=OPERATION_STATUS)
100
+ resource_type = fields.ChoiceField(required=True, choices=RESOURCE_TYPE)
101
+ resources = BatchObject(many=True)
102
+
103
+
104
+ class BatchOperation(serializers.EntitySerializer, BatchOperationData):
105
+ created_at = fields.DateTimeField()
106
+ updated_at = fields.DateTimeField()
107
+ test_mode = fields.BooleanField(required=True)
@@ -0,0 +1,9 @@
1
+ import karrio.server.serializers as serializers
2
+ import karrio.server.data.models as models
3
+
4
+
5
+ @serializers.owned_model_serializer
6
+ class BatchOperationModelSerializer(serializers.ModelSerializer):
7
+ class Meta:
8
+ model = models.BatchOperation
9
+ exclude = ["created_at", "updated_at", "created_by"]
@@ -0,0 +1,99 @@
1
+ from django.db import transaction
2
+
3
+ import karrio.lib as lib
4
+ import karrio.server.conf as conf
5
+ import karrio.server.orders.models as orders
6
+ import karrio.server.serializers as serializers
7
+ import karrio.server.data.serializers.base as base
8
+ import karrio.server.core.exceptions as exceptions
9
+ import karrio.server.orders.serializers as order_serializers
10
+
11
+
12
+ @serializers.owned_model_serializer
13
+ class BatchOrderData(serializers.Serializer):
14
+ orders = order_serializers.OrderData(
15
+ many=True,
16
+ allow_empty=False,
17
+ help_text="The list of orders to process.",
18
+ )
19
+
20
+ @transaction.atomic
21
+ def create(self, validated_data: dict, context: serializers.Context, **kwargs):
22
+ import karrio.server.events.tasks as tasks
23
+ import karrio.server.data.serializers.batch as batch
24
+
25
+ operation_data = dict(resource_type="orders", test_mode=context.test_mode)
26
+ operation = (
27
+ batch.BatchOperationModelSerializer.map(
28
+ data=operation_data, context=context
29
+ )
30
+ .save()
31
+ .instance
32
+ )
33
+
34
+ sid = transaction.savepoint()
35
+ resources = BatchOrderData.save_resources(
36
+ context=context,
37
+ data=validated_data,
38
+ batch_id=operation.id,
39
+ format_errors=False,
40
+ )
41
+ errors = [r["errors"] for r in resources if r.get("errors") is not None]
42
+ transaction.savepoint_rollback(sid)
43
+
44
+ if any(errors):
45
+ raise exceptions.APIExceptions(errors, code="invalid_data")
46
+
47
+ tasks.save_batch_resources(
48
+ operation.id,
49
+ data=validated_data,
50
+ schema=conf.settings.schema,
51
+ ctx=dict(
52
+ test_mode=context.test_mode,
53
+ org_id=getattr(context.org, "id", None),
54
+ user_id=getattr(context.user, "id", None),
55
+ ),
56
+ )
57
+
58
+ return operation
59
+
60
+ @staticmethod
61
+ def save_resources(
62
+ data: dict,
63
+ batch_id: str,
64
+ context: serializers.Context,
65
+ format_errors: bool = True,
66
+ ):
67
+ orders_data = data["orders"]
68
+ resources = []
69
+
70
+ for index, order_data in enumerate(orders_data):
71
+ try:
72
+ queryset = orders.Order.access_by(context).filter(
73
+ order_id=order_data["order_id"], source=order_data["source"]
74
+ )
75
+ order = (
76
+ queryset.first()
77
+ if queryset.exists()
78
+ else (
79
+ order_serializers.OrderSerializer.map(
80
+ data=order_data, context=context
81
+ )
82
+ .save()
83
+ .instance
84
+ )
85
+ )
86
+ resources.append(
87
+ dict(id=order.id, status=base.ResourceStatus.queued.value)
88
+ )
89
+ except Exception as e:
90
+ setattr(e, "index", index)
91
+ resources.append(
92
+ dict(
93
+ id=index,
94
+ status=base.ResourceStatus.has_errors.value,
95
+ errors=(lib.to_dict(e) if format_errors else e),
96
+ )
97
+ )
98
+
99
+ return resources
@@ -0,0 +1,102 @@
1
+ from django.db import transaction
2
+
3
+ import karrio.lib as lib
4
+ import karrio.server.conf as conf
5
+ import karrio.server.manager.models as manager
6
+ import karrio.server.serializers as serializers
7
+ import karrio.server.core.exceptions as exceptions
8
+ import karrio.server.data.serializers.base as base
9
+ import karrio.server.manager.serializers as manager_serializers
10
+
11
+
12
+ class ShipmentDataReference(manager_serializers.ShipmentData):
13
+ id = serializers.CharField(
14
+ help_text="The shipment id.",
15
+ required=False,
16
+ )
17
+
18
+
19
+ @serializers.owned_model_serializer
20
+ class BatchShipmentData(serializers.Serializer):
21
+ shipments = ShipmentDataReference(
22
+ many=True,
23
+ allow_empty=False,
24
+ help_text="The list of shipments to process.",
25
+ )
26
+
27
+ @transaction.atomic
28
+ def create(self, validated_data: dict, context: serializers.Context, **kwargs):
29
+ import karrio.server.events.tasks as tasks
30
+ import karrio.server.data.serializers.batch as batch
31
+
32
+ operation_data = dict(resource_type="shipments", test_mode=context.test_mode)
33
+ operation = (
34
+ batch.BatchOperationModelSerializer.map(
35
+ data=operation_data, context=context
36
+ )
37
+ .save()
38
+ .instance
39
+ )
40
+
41
+ sid = transaction.savepoint()
42
+ resources = BatchShipmentData.save_resources(
43
+ context=context,
44
+ data=validated_data,
45
+ batch_id=operation.id,
46
+ format_errors=False,
47
+ )
48
+ errors = [r["errors"] for r in resources if r.get("errors") is not None]
49
+ transaction.savepoint_rollback(sid)
50
+
51
+ if any(errors):
52
+ raise exceptions.APIExceptions(errors, code="invalid_data")
53
+
54
+ tasks.save_batch_resources(
55
+ operation.id,
56
+ data=validated_data,
57
+ schema=conf.settings.schema,
58
+ ctx=dict(
59
+ test_mode=context.test_mode,
60
+ org_id=getattr(context.org, "id", None),
61
+ user_id=getattr(context.user, "id", None),
62
+ ),
63
+ )
64
+
65
+ return operation
66
+
67
+ @staticmethod
68
+ def save_resources(
69
+ data: dict,
70
+ batch_id: str,
71
+ context: serializers.Context,
72
+ format_errors: bool = True,
73
+ ):
74
+ shipments_data = data["shipments"]
75
+ resources = []
76
+
77
+ for index, shipment_data in enumerate(shipments_data):
78
+ try:
79
+ shipment = (
80
+ manager.Shipment.access_by(context).get(id=shipment_data["id"])
81
+ if shipment_data.get("id") is not None
82
+ else (
83
+ manager_serializers.ShipmentSerializer.map(
84
+ data=shipment_data, context=context
85
+ )
86
+ .save(fetch_rates=False)
87
+ .instance
88
+ )
89
+ )
90
+ resources.append(
91
+ dict(id=shipment.id, status=base.ResourceStatus.queued.value)
92
+ )
93
+ except Exception as e:
94
+ resources.append(
95
+ dict(
96
+ id=index,
97
+ status=base.ResourceStatus.has_errors.value,
98
+ errors=(lib.to_dict(e) if format_errors else e),
99
+ )
100
+ )
101
+
102
+ return resources
@@ -0,0 +1,131 @@
1
+ from django.db import transaction
2
+
3
+ import karrio.lib as lib
4
+ import karrio.server.conf as conf
5
+ import karrio.server.core.utils as utils
6
+ import karrio.server.core.gateway as gateway
7
+ import karrio.server.core.exceptions as exceptions
8
+ import karrio.server.serializers as serializers
9
+ import karrio.server.manager.models as models
10
+ import karrio.server.manager.serializers as manager
11
+ import karrio.server.data.serializers.base as base
12
+
13
+
14
+ @serializers.owned_model_serializer
15
+ class BatchTrackerData(serializers.Serializer):
16
+ trackers = manager.TrackingData(
17
+ many=True,
18
+ allow_empty=False,
19
+ help_text="The list of tracking info to process.",
20
+ )
21
+
22
+ @transaction.atomic
23
+ def create(self, validated_data: dict, context: serializers.Context, **kwargs):
24
+ import karrio.server.events.tasks as tasks
25
+ import karrio.server.data.serializers.batch as batch
26
+
27
+ operation_data = dict(resource_type="trackers", test_mode=context.test_mode)
28
+ operation = (
29
+ batch.BatchOperationModelSerializer
30
+ .map(data=operation_data, context=context)
31
+ .save()
32
+ .instance
33
+ )
34
+
35
+ sid = transaction.savepoint()
36
+ resources = BatchTrackerData.save_resources(
37
+ context=context,
38
+ data=validated_data,
39
+ batch_id=operation.id,
40
+ format_errors=False,
41
+ )
42
+ errors = [r['errors'] for r in resources if r.get('errors') is not None]
43
+ transaction.savepoint_rollback(sid)
44
+
45
+ if any(errors):
46
+ raise exceptions.APIExceptions(errors, code="invalid_data")
47
+
48
+ tasks.save_batch_resources(
49
+ operation.id,
50
+ data=validated_data,
51
+ schema=conf.settings.schema,
52
+ ctx=dict(
53
+ test_mode=context.test_mode,
54
+ org_id=getattr(context.org, "id", None),
55
+ user_id=getattr(context.user, "id", None),
56
+ ),
57
+ )
58
+
59
+ return operation
60
+
61
+ @staticmethod
62
+ def save_resources(data: dict, batch_id: str, context: serializers.Context, format_errors: bool = True):
63
+ meta = dict(batch_id=batch_id)
64
+ trackers_data = data['trackers']
65
+ carrier_names = set([t['carrier_name'] for t in trackers_data])
66
+ carriers = {
67
+ carrier_name: utils.failsafe(lambda: gateway.Carriers.first(
68
+ context=context,
69
+ capability='tracking',
70
+ carrier_name=carrier_name,
71
+ raise_not_found=False,
72
+ ))
73
+ for carrier_name in carrier_names
74
+ }
75
+ resources = []
76
+ trackers = []
77
+
78
+ for index, tracker_data in enumerate(trackers_data):
79
+ try:
80
+ carrier_name = tracker_data['carrier_name']
81
+ carrier = carriers[carrier_name]
82
+
83
+ if carrier is None:
84
+ raise exceptions.APIException(
85
+ f"No carrier connection found for '{tracker_data['carrier_name']}'",
86
+ code="invalid_carrier",
87
+ )
88
+
89
+ _tracker = (
90
+ models.Tracking.access_by(context)
91
+ .filter(tracking_number=tracker_data["tracking_number"])
92
+ .first()
93
+ )
94
+ _exists = getattr(_tracker, "carrier_name", None) == carrier_name
95
+ tracker = (
96
+ _tracker if _exists
97
+ else models.Tracking(
98
+ meta=meta,
99
+ status="unknown",
100
+ test_mode=context.test_mode,
101
+ created_by_id=context.user.id,
102
+ tracking_carrier_id=carrier.id,
103
+ tracking_number=tracker_data["tracking_number"],
104
+ events = utils.default_tracking_event(
105
+ description="Awaiting update from carrier...",
106
+ code="UNKNOWN",
107
+ ),
108
+ )
109
+ )
110
+
111
+ if _exists is False:
112
+ trackers.append(tracker)
113
+
114
+ resources.append(dict(
115
+ id=tracker.id,
116
+ status=(base.ResourceStatus.processed.value
117
+ if _exists else base.ResourceStatus.queued.value)
118
+ ))
119
+ except Exception as e:
120
+ setattr(e, "index", index)
121
+ resources.append(dict(
122
+ id=index,
123
+ status=base.ResourceStatus.has_errors.value,
124
+ errors=(lib.to_dict(e) if format_errors else e),
125
+ ))
126
+
127
+ if any(trackers):
128
+ models.Tracking.objects.bulk_create(trackers)
129
+ serializers.bulk_link_org(trackers, context)
130
+
131
+ return resources
@@ -0,0 +1,109 @@
1
+ import tablib
2
+ import logging
3
+ from django.db import transaction
4
+
5
+ from karrio.server.conf import settings
6
+ import karrio.server.core.exceptions as exceptions
7
+ import karrio.server.data.models as models
8
+ import karrio.server.data.resources as resources
9
+ import karrio.server.data.serializers as serializers
10
+ import karrio.server.data.serializers.batch as batch
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ @serializers.owned_model_serializer
16
+ class DataTemplateModelSerializer(serializers.ModelSerializer):
17
+ class Meta:
18
+ model = models.DataTemplate
19
+ exclude = ["created_at", "updated_at", "created_by", "org"]
20
+
21
+
22
+ @serializers.owned_model_serializer
23
+ class ImportDataSerializer(serializers.ImportData):
24
+ @transaction.atomic
25
+ def create(
26
+ self, validated_data: dict, context: serializers.Context, **kwargs
27
+ ) -> models.BatchOperation:
28
+ import karrio.server.events.tasks as tasks
29
+
30
+ resource_type = validated_data["resource_type"]
31
+ data_field = validated_data["data_file"]
32
+ template = (
33
+ models.DataTemplate.access_by(context).first(
34
+ slug=validated_data.get("data_template")
35
+ )
36
+ if "data_template" in validated_data
37
+ else None
38
+ )
39
+ data_fields: dict = getattr(
40
+ template,
41
+ "data_fields",
42
+ serializers.ResourceType.get_default_mapping(resource_type),
43
+ )
44
+ resource = resources.get_import_resource(
45
+ resource_type=resource_type,
46
+ data_fields=data_fields,
47
+ params=validated_data,
48
+ context=context,
49
+ )
50
+ dataset = tablib.Dataset().load(
51
+ data_field.read().decode(),
52
+ headers=data_fields.values(),
53
+ )
54
+
55
+ # dry run data import to validate file content.
56
+ validation = resource.import_data(dataset, dry_run=True)
57
+ check_dataset_validation_errors(validation)
58
+
59
+ operation = (
60
+ batch.BatchOperationModelSerializer.map(
61
+ data=dict(resource_type=resource_type, test_mode=context.test_mode),
62
+ context=context,
63
+ )
64
+ .save()
65
+ .instance
66
+ )
67
+
68
+ tasks.queue_batch_import(
69
+ operation.id,
70
+ data=dict(
71
+ dataset=dataset,
72
+ import_data=validated_data,
73
+ ),
74
+ ctx=dict(
75
+ org_id=getattr(context.org, "id", None),
76
+ user_id=getattr(context.user, "id", None),
77
+ test_mode=context.test_mode,
78
+ ),
79
+ schema=settings.schema,
80
+ )
81
+
82
+ return operation
83
+
84
+
85
+ def check_dataset_validation_errors(validation):
86
+ if any(validation.base_errors):
87
+ raise exceptions.APIExceptions(
88
+ validation.base_errors,
89
+ code="invalid_data",
90
+ )
91
+
92
+ errors = []
93
+ flattened_row_errors = sum(
94
+ [
95
+ [(i, e.error) for e in row.errors]
96
+ for i, row in enumerate(validation.rows)
97
+ ],
98
+ []
99
+ )
100
+
101
+ for index, error in flattened_row_errors:
102
+ setattr(error, "index", index)
103
+ errors.append(error)
104
+
105
+ if any(errors):
106
+ raise exceptions.APIExceptions(
107
+ errors,
108
+ code="invalid_data",
109
+ )
@@ -0,0 +1,52 @@
1
+ import logging
2
+ from django.db.models import signals
3
+
4
+ from karrio.server.conf import settings
5
+ import karrio.server.core.utils as utils
6
+ import karrio.server.data.models as models
7
+ import karrio.server.events.tasks as tasks
8
+ import karrio.server.data.serializers as serializers
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def register_all():
14
+ signals.post_save.connect(batch_operation_updated, sender=models.BatchOperation)
15
+
16
+ logger.info("karrio.data signals registered...")
17
+
18
+
19
+ @utils.disable_for_loaddata
20
+ def batch_operation_updated(sender, instance, *args, **kwargs):
21
+ """Process batch related events
22
+ - Create event on batch creation and completion
23
+ - Notifiy webhook subscribers of batch operation updates
24
+ """
25
+ changes = kwargs.get("update_fields") or []
26
+ post_create = "created_at" in changes
27
+
28
+ if post_create:
29
+ event = serializers.EventTypes.batch_queued.value
30
+ elif instance.status == serializers.BatchOperationStatus.running.value:
31
+ event = serializers.EventTypes.batch_running.value
32
+ # Run post creation processing for batch_operation resources
33
+ tasks.process_batch_resources(instance.id, schema=settings.schema)
34
+ elif instance.status == serializers.BatchOperationStatus.completed.value:
35
+ event = serializers.EventTypes.batch_completed.value
36
+ else:
37
+ return
38
+
39
+ data = serializers.BatchOperation(instance).data
40
+ event_at = instance.updated_at
41
+ context = dict(
42
+ user_id=utils.failsafe(lambda: instance.created_by.id),
43
+ test_mode=instance.test_mode,
44
+ org_id=utils.failsafe(
45
+ lambda: instance.org.first().id if hasattr(instance, "org") else None
46
+ ),
47
+ )
48
+
49
+ if settings.MULTI_ORGANIZATIONS and context["org_id"] is None:
50
+ return
51
+
52
+ tasks.notify_webhooks(event, data, event_at, context, schema=settings.schema)
@@ -0,0 +1,3 @@
1
+ from django.test import TestCase
2
+
3
+ # Create your tests here.
@@ -0,0 +1,13 @@
1
+ """
2
+ karrio server data module urls
3
+ """
4
+ from django.urls import include, path
5
+
6
+ app_name = "karrio.server.data"
7
+ urlpatterns = [
8
+ path("v1/", include("karrio.server.data.views.data")),
9
+ path("v1/", include("karrio.server.data.views.batch")),
10
+ path("v1/", include("karrio.server.data.views.batch_order")),
11
+ path("v1/", include("karrio.server.data.views.batch_shipment")),
12
+ path("v1/", include("karrio.server.data.views.batch_tracking")),
13
+ ]
File without changes