karrio-server-core 2025.5rc31__py3-none-any.whl → 2026.1.1__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/core/authentication.py +38 -20
- karrio/server/core/config.py +31 -0
- karrio/server/core/datatypes.py +30 -4
- karrio/server/core/dataunits.py +26 -7
- karrio/server/core/exceptions.py +287 -17
- karrio/server/core/filters.py +14 -0
- karrio/server/core/gateway.py +284 -11
- karrio/server/core/logging.py +403 -0
- karrio/server/core/middleware.py +104 -2
- karrio/server/core/models/base.py +34 -1
- karrio/server/core/oauth_validators.py +2 -3
- karrio/server/core/permissions.py +1 -2
- karrio/server/core/serializers.py +154 -7
- karrio/server/core/signals.py +22 -28
- karrio/server/core/telemetry.py +573 -0
- karrio/server/core/tests/__init__.py +27 -0
- karrio/server/core/{tests.py → tests/base.py} +6 -7
- karrio/server/core/tests/test_exception_level.py +159 -0
- karrio/server/core/tests/test_resource_token.py +593 -0
- karrio/server/core/utils.py +688 -38
- karrio/server/core/validators.py +144 -222
- karrio/server/core/views/oauth.py +13 -12
- karrio/server/core/views/references.py +2 -2
- karrio/server/iam/apps.py +1 -4
- karrio/server/iam/migrations/0002_setup_carrier_permission_groups.py +103 -0
- karrio/server/iam/migrations/0003_remove_permission_groups.py +91 -0
- karrio/server/iam/permissions.py +7 -134
- karrio/server/iam/serializers.py +9 -3
- karrio/server/iam/signals.py +2 -4
- karrio/server/providers/admin.py +1 -1
- karrio/server/providers/migrations/0085_populate_dhl_parcel_de_oauth_credentials.py +82 -0
- karrio/server/providers/migrations/0086_rename_dhl_parcel_de_customer_number_to_billing_number.py +71 -0
- karrio/server/providers/migrations/0087_alter_carrier_capabilities.py +38 -0
- karrio/server/providers/migrations/0088_servicelevel_surcharges.py +24 -0
- karrio/server/providers/migrations/0089_servicelevel_cost_max_volume.py +31 -0
- karrio/server/providers/migrations/0090_ratesheet_surcharges_servicelevel_zone_surcharge_ids.py +47 -0
- karrio/server/providers/migrations/0091_migrate_legacy_zones_surcharges.py +154 -0
- karrio/server/providers/models/carrier.py +101 -29
- karrio/server/providers/models/service.py +182 -125
- karrio/server/providers/models/sheet.py +342 -198
- karrio/server/providers/serializers/base.py +263 -2
- karrio/server/providers/signals.py +2 -4
- karrio/server/providers/templates/providers/oauth_callback.html +105 -0
- karrio/server/providers/tests/__init__.py +5 -0
- karrio/server/providers/tests/test_connections.py +895 -0
- karrio/server/providers/views/carriers.py +1 -3
- karrio/server/providers/views/connections.py +322 -2
- karrio/server/serializers/abstract.py +112 -21
- karrio/server/tracing/utils.py +5 -8
- karrio/server/user/models.py +36 -34
- karrio/server/user/serializers.py +1 -0
- {karrio_server_core-2025.5rc31.dist-info → karrio_server_core-2026.1.1.dist-info}/METADATA +2 -2
- {karrio_server_core-2025.5rc31.dist-info → karrio_server_core-2026.1.1.dist-info}/RECORD +55 -38
- karrio/server/providers/tests.py +0 -3
- {karrio_server_core-2025.5rc31.dist-info → karrio_server_core-2026.1.1.dist-info}/WHEEL +0 -0
- {karrio_server_core-2025.5rc31.dist-info → karrio_server_core-2026.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Generated migration to remove permission groups
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def remove_permission_groups(apps, schema_editor):
|
|
7
|
+
"""
|
|
8
|
+
Remove all permission groups that were created by setup_groups().
|
|
9
|
+
|
|
10
|
+
This migration removes the following groups:
|
|
11
|
+
- manage_apps
|
|
12
|
+
- manage_carriers (deprecated)
|
|
13
|
+
- read_carriers
|
|
14
|
+
- write_carriers
|
|
15
|
+
- manage_orders
|
|
16
|
+
- manage_team
|
|
17
|
+
- manage_org_owner
|
|
18
|
+
- manage_webhooks
|
|
19
|
+
- manage_data
|
|
20
|
+
- manage_shipments
|
|
21
|
+
- manage_system
|
|
22
|
+
"""
|
|
23
|
+
Group = apps.get_model("user", "Group")
|
|
24
|
+
ContextPermission = apps.get_model("iam", "ContextPermission")
|
|
25
|
+
|
|
26
|
+
# List of groups to remove
|
|
27
|
+
groups_to_remove = [
|
|
28
|
+
"manage_apps",
|
|
29
|
+
"manage_carriers",
|
|
30
|
+
"read_carriers",
|
|
31
|
+
"write_carriers",
|
|
32
|
+
"manage_orders",
|
|
33
|
+
"manage_team",
|
|
34
|
+
"manage_org_owner",
|
|
35
|
+
"manage_webhooks",
|
|
36
|
+
"manage_data",
|
|
37
|
+
"manage_shipments",
|
|
38
|
+
"manage_system",
|
|
39
|
+
"manage_pickups",
|
|
40
|
+
"manage_trackers",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
# First, remove the groups from all ContextPermissions
|
|
44
|
+
for group_name in groups_to_remove:
|
|
45
|
+
group = Group.objects.filter(name=group_name).first()
|
|
46
|
+
if group:
|
|
47
|
+
# Remove this group from all context permissions
|
|
48
|
+
for ctx_perm in ContextPermission.objects.filter(groups=group):
|
|
49
|
+
ctx_perm.groups.remove(group)
|
|
50
|
+
|
|
51
|
+
# Then delete the groups themselves
|
|
52
|
+
Group.objects.filter(name__in=groups_to_remove).delete()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def reverse_migration(apps, schema_editor):
|
|
56
|
+
"""
|
|
57
|
+
Reverse migration - recreate groups (without permissions, which were set dynamically).
|
|
58
|
+
Note: This won't restore the full permission setup, only creates empty groups.
|
|
59
|
+
"""
|
|
60
|
+
Group = apps.get_model("user", "Group")
|
|
61
|
+
|
|
62
|
+
groups_to_create = [
|
|
63
|
+
"manage_apps",
|
|
64
|
+
"manage_carriers",
|
|
65
|
+
"read_carriers",
|
|
66
|
+
"write_carriers",
|
|
67
|
+
"manage_orders",
|
|
68
|
+
"manage_team",
|
|
69
|
+
"manage_org_owner",
|
|
70
|
+
"manage_webhooks",
|
|
71
|
+
"manage_data",
|
|
72
|
+
"manage_shipments",
|
|
73
|
+
"manage_system",
|
|
74
|
+
"manage_pickups",
|
|
75
|
+
"manage_trackers",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
for group_name in groups_to_create:
|
|
79
|
+
Group.objects.get_or_create(name=group_name)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Migration(migrations.Migration):
|
|
83
|
+
|
|
84
|
+
dependencies = [
|
|
85
|
+
("iam", "0002_setup_carrier_permission_groups"),
|
|
86
|
+
("user", "0004_group"),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
operations = [
|
|
90
|
+
migrations.RunPython(remove_permission_groups, reverse_migration),
|
|
91
|
+
]
|
karrio/server/iam/permissions.py
CHANGED
|
@@ -1,134 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import karrio.server.user.models as users
|
|
9
|
-
import karrio.server.iam.serializers as serializers
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
User = get_user_model()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@utils.skip_on_loadata
|
|
16
|
-
@utils.async_wrapper
|
|
17
|
-
@utils.tenant_aware
|
|
18
|
-
def setup_groups(**_):
|
|
19
|
-
"""This function create all standard group permissions if they don't exsist."""
|
|
20
|
-
print("> setting up permissions")
|
|
21
|
-
|
|
22
|
-
# manage_apps
|
|
23
|
-
setup_group(
|
|
24
|
-
serializers.PermissionGroup.manage_apps.name,
|
|
25
|
-
permissions=Permission.objects.filter(content_type__app_label="apps"),
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
# manage_carriers
|
|
29
|
-
setup_group(
|
|
30
|
-
serializers.PermissionGroup.manage_carriers.name,
|
|
31
|
-
permissions=[
|
|
32
|
-
*Permission.objects.filter(content_type__app_label="providers"),
|
|
33
|
-
*Permission.objects.filter(
|
|
34
|
-
models.Q(content_type__app_label="orgs")
|
|
35
|
-
& models.Q(name__icontains="carrier")
|
|
36
|
-
),
|
|
37
|
-
],
|
|
38
|
-
override=True,
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
# manage_orders
|
|
42
|
-
setup_group(
|
|
43
|
-
serializers.PermissionGroup.manage_orders.name,
|
|
44
|
-
permissions=Permission.objects.filter(content_type__app_label="orders"),
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
# manage_team
|
|
48
|
-
setup_group(
|
|
49
|
-
serializers.PermissionGroup.manage_team.name,
|
|
50
|
-
permissions=(
|
|
51
|
-
Permission.objects.filter(
|
|
52
|
-
content_type__app_label="orgs", name__icontains="organization"
|
|
53
|
-
).exclude(name__icontains="owner")
|
|
54
|
-
),
|
|
55
|
-
override=True,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
# manage_org_owner
|
|
59
|
-
setup_group(
|
|
60
|
-
serializers.PermissionGroup.manage_org_owner.name,
|
|
61
|
-
permissions=Permission.objects.filter(
|
|
62
|
-
content_type__model="OrganizationOwner".lower()
|
|
63
|
-
),
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
# manage_webhooks
|
|
67
|
-
setup_group(
|
|
68
|
-
serializers.PermissionGroup.manage_webhooks.name,
|
|
69
|
-
permissions=Permission.objects.filter(content_type__model="Webhook".lower()),
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
# manage_data
|
|
73
|
-
setup_group(
|
|
74
|
-
serializers.PermissionGroup.manage_data.name,
|
|
75
|
-
permissions=[
|
|
76
|
-
*Permission.objects.filter(
|
|
77
|
-
content_type__app_label__in=["data", "graph", "documents"]
|
|
78
|
-
),
|
|
79
|
-
*Permission.objects.filter(
|
|
80
|
-
content_type__app_label="audit", name__icontains="view"
|
|
81
|
-
),
|
|
82
|
-
*Permission.objects.filter(
|
|
83
|
-
content_type__app_label="rest_framework_tracking",
|
|
84
|
-
name__icontains="view",
|
|
85
|
-
),
|
|
86
|
-
],
|
|
87
|
-
override=True,
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
# manage_shipments
|
|
91
|
-
setup_group(
|
|
92
|
-
serializers.PermissionGroup.manage_shipments.name,
|
|
93
|
-
permissions=[
|
|
94
|
-
*Permission.objects.filter(content_type__app_label="manager"),
|
|
95
|
-
*Permission.objects.filter(
|
|
96
|
-
models.Q(content_type__app_label="orgs")
|
|
97
|
-
& (
|
|
98
|
-
models.Q(name__icontains="address")
|
|
99
|
-
| models.Q(name__icontains="parcel")
|
|
100
|
-
| models.Q(name__icontains="commodity")
|
|
101
|
-
| models.Q(name__icontains="customs")
|
|
102
|
-
| models.Q(name__icontains="pickup")
|
|
103
|
-
| models.Q(name__icontains="tracker")
|
|
104
|
-
| models.Q(name__icontains="shipment")
|
|
105
|
-
)
|
|
106
|
-
),
|
|
107
|
-
],
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
# manage_system
|
|
111
|
-
setup_group(
|
|
112
|
-
serializers.PermissionGroup.manage_system.name,
|
|
113
|
-
permissions=Permission.objects.filter(
|
|
114
|
-
content_type__app_label__in=[
|
|
115
|
-
"admin",
|
|
116
|
-
"user",
|
|
117
|
-
"pricing",
|
|
118
|
-
"providers",
|
|
119
|
-
"audit",
|
|
120
|
-
"database",
|
|
121
|
-
"rest_framework_tracking",
|
|
122
|
-
]
|
|
123
|
-
),
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def setup_group(
|
|
128
|
-
name: str, permissions: typing.List[Permission], override: bool = False
|
|
129
|
-
):
|
|
130
|
-
group, created = users.Group.objects.get_or_create(name=name)
|
|
131
|
-
|
|
132
|
-
if created or override:
|
|
133
|
-
group.permissions.set(permissions)
|
|
134
|
-
group.save()
|
|
1
|
+
# This module previously contained permission group setup logic.
|
|
2
|
+
# The setup_groups() function has been removed to:
|
|
3
|
+
# 1. Fix Django warning about database access during app initialization
|
|
4
|
+
# 2. Prepare for a better RBAC implementation in the future
|
|
5
|
+
#
|
|
6
|
+
# The PermissionGroup enum and ROLES_GROUPS mapping are preserved in
|
|
7
|
+
# karrio.server.iam.serializers for organization role management.
|
karrio/server/iam/serializers.py
CHANGED
|
@@ -9,7 +9,9 @@ class PermissionGroup(lib.StrEnum):
|
|
|
9
9
|
manage_orders = "manage_orders"
|
|
10
10
|
manage_data = "manage_data"
|
|
11
11
|
manage_pickups = "manage_pickups"
|
|
12
|
-
manage_carriers = "manage_carriers"
|
|
12
|
+
manage_carriers = "manage_carriers" # Deprecated: use read_carriers + write_carriers
|
|
13
|
+
read_carriers = "read_carriers"
|
|
14
|
+
write_carriers = "write_carriers"
|
|
13
15
|
manage_trackers = "manage_trackers"
|
|
14
16
|
manage_webhooks = "manage_webhooks"
|
|
15
17
|
manage_shipments = "manage_shipments"
|
|
@@ -22,7 +24,8 @@ ROLES_GROUPS: typing.Dict[str, typing.List[str]] = {
|
|
|
22
24
|
PermissionGroup.manage_org_owner.value,
|
|
23
25
|
PermissionGroup.manage_team.value,
|
|
24
26
|
PermissionGroup.manage_apps.value,
|
|
25
|
-
PermissionGroup.
|
|
27
|
+
PermissionGroup.read_carriers.value,
|
|
28
|
+
PermissionGroup.write_carriers.value,
|
|
26
29
|
PermissionGroup.manage_webhooks.value,
|
|
27
30
|
PermissionGroup.manage_data.value,
|
|
28
31
|
PermissionGroup.manage_orders.value,
|
|
@@ -33,10 +36,12 @@ ROLES_GROUPS: typing.Dict[str, typing.List[str]] = {
|
|
|
33
36
|
"admin": [
|
|
34
37
|
PermissionGroup.manage_team.value,
|
|
35
38
|
PermissionGroup.manage_apps.value,
|
|
36
|
-
PermissionGroup.
|
|
39
|
+
PermissionGroup.read_carriers.value,
|
|
40
|
+
PermissionGroup.write_carriers.value,
|
|
37
41
|
],
|
|
38
42
|
"developer": [
|
|
39
43
|
PermissionGroup.manage_webhooks.value,
|
|
44
|
+
PermissionGroup.read_carriers.value,
|
|
40
45
|
],
|
|
41
46
|
"member": [
|
|
42
47
|
PermissionGroup.manage_data.value,
|
|
@@ -44,5 +49,6 @@ ROLES_GROUPS: typing.Dict[str, typing.List[str]] = {
|
|
|
44
49
|
PermissionGroup.manage_pickups.value,
|
|
45
50
|
PermissionGroup.manage_trackers.value,
|
|
46
51
|
PermissionGroup.manage_shipments.value,
|
|
52
|
+
PermissionGroup.read_carriers.value,
|
|
47
53
|
],
|
|
48
54
|
}
|
karrio/server/iam/signals.py
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from django.db.models import signals
|
|
3
2
|
|
|
3
|
+
from karrio.server.core.logging import logger
|
|
4
4
|
import karrio.server.core.utils as utils
|
|
5
5
|
import karrio.server.user.models as user
|
|
6
6
|
import karrio.server.iam.models as models
|
|
7
7
|
|
|
8
|
-
logger = logging.getLogger(__name__)
|
|
9
|
-
|
|
10
8
|
|
|
11
9
|
def register_all():
|
|
12
10
|
signals.post_delete.connect(context_object_deleted, sender=user.Token)
|
|
13
11
|
|
|
14
|
-
logger.info("karrio.iam
|
|
12
|
+
logger.info("Signal registration complete", module="karrio.iam")
|
|
15
13
|
|
|
16
14
|
|
|
17
15
|
@utils.disable_for_loaddata
|
karrio/server/providers/admin.py
CHANGED
|
@@ -354,7 +354,7 @@ class LabelTemplateAdmin(admin.ModelAdmin):
|
|
|
354
354
|
return False
|
|
355
355
|
|
|
356
356
|
|
|
357
|
-
@utils.skip_on_commands()
|
|
357
|
+
@utils.skip_on_commands(["loaddata", "migrate", "makemigrations", "shell"])
|
|
358
358
|
def register_carrier_admins():
|
|
359
359
|
for carrier_name, display_name in ref.REFERENCES["carriers"].items():
|
|
360
360
|
proxy = providers.create_carrier_proxy(carrier_name, display_name)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Generated by Django migration to populate missing required credentials for DHL Parcel DE
|
|
2
|
+
# This migration ensures existing dhl_parcel_de connections have all required fields
|
|
3
|
+
# (username, password, client_id, client_secret) to prevent initialization errors
|
|
4
|
+
# after the settings schema was updated.
|
|
5
|
+
|
|
6
|
+
from django.db import migrations
|
|
7
|
+
from django.utils import timezone
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Required fields for dhl_parcel_de that have no default values
|
|
11
|
+
REQUIRED_FIELDS = ["username", "password", "client_id", "client_secret"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def populate_dhl_parcel_de_required_credentials(apps, schema_editor):
|
|
15
|
+
"""
|
|
16
|
+
Find all dhl_parcel_de carrier connections that are missing required fields
|
|
17
|
+
and populate them with placeholder timestamp values.
|
|
18
|
+
"""
|
|
19
|
+
Carrier = apps.get_model("providers", "Carrier")
|
|
20
|
+
db_alias = schema_editor.connection.alias
|
|
21
|
+
|
|
22
|
+
# Find all dhl_parcel_de carriers
|
|
23
|
+
dhl_parcel_de_carriers = Carrier.objects.using(db_alias).filter(
|
|
24
|
+
carrier_code="dhl_parcel_de"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
timestamp = timezone.now().strftime("%Y%m%d%H%M%S")
|
|
28
|
+
|
|
29
|
+
for carrier in dhl_parcel_de_carriers:
|
|
30
|
+
credentials = carrier.credentials or {}
|
|
31
|
+
updated = False
|
|
32
|
+
|
|
33
|
+
# Check and populate all missing required fields
|
|
34
|
+
for field in REQUIRED_FIELDS:
|
|
35
|
+
if not credentials.get(field):
|
|
36
|
+
credentials[field] = f"PLACEHOLDER_{timestamp}"
|
|
37
|
+
updated = True
|
|
38
|
+
|
|
39
|
+
if updated:
|
|
40
|
+
carrier.credentials = credentials
|
|
41
|
+
carrier.save(using=db_alias, update_fields=["credentials"])
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def reverse_dhl_parcel_de_required_credentials(apps, schema_editor):
|
|
45
|
+
"""
|
|
46
|
+
Remove placeholder credentials (for rollback).
|
|
47
|
+
Only removes credentials that were set by this migration (contain PLACEHOLDER_).
|
|
48
|
+
"""
|
|
49
|
+
Carrier = apps.get_model("providers", "Carrier")
|
|
50
|
+
db_alias = schema_editor.connection.alias
|
|
51
|
+
|
|
52
|
+
dhl_parcel_de_carriers = Carrier.objects.using(db_alias).filter(
|
|
53
|
+
carrier_code="dhl_parcel_de"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
for carrier in dhl_parcel_de_carriers:
|
|
57
|
+
credentials = carrier.credentials or {}
|
|
58
|
+
updated = False
|
|
59
|
+
|
|
60
|
+
# Only remove if it's a placeholder value
|
|
61
|
+
for field in REQUIRED_FIELDS:
|
|
62
|
+
if credentials.get(field, "").startswith("PLACEHOLDER_"):
|
|
63
|
+
del credentials[field]
|
|
64
|
+
updated = True
|
|
65
|
+
|
|
66
|
+
if updated:
|
|
67
|
+
carrier.credentials = credentials
|
|
68
|
+
carrier.save(using=db_alias, update_fields=["credentials"])
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Migration(migrations.Migration):
|
|
72
|
+
|
|
73
|
+
dependencies = [
|
|
74
|
+
("providers", "0084_alter_servicelevel_currency"),
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
operations = [
|
|
78
|
+
migrations.RunPython(
|
|
79
|
+
populate_dhl_parcel_de_required_credentials,
|
|
80
|
+
reverse_dhl_parcel_de_required_credentials,
|
|
81
|
+
),
|
|
82
|
+
]
|
karrio/server/providers/migrations/0086_rename_dhl_parcel_de_customer_number_to_billing_number.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Generated by Django migration to rename customer_number to billing_number
|
|
2
|
+
# for DHL Parcel DE connections. This keeps existing configuration values
|
|
3
|
+
# when the field was renamed in the settings schema.
|
|
4
|
+
|
|
5
|
+
from django.db import migrations
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def rename_customer_number_to_billing_number(apps, schema_editor):
|
|
9
|
+
"""
|
|
10
|
+
Find all dhl_parcel_de carrier connections that have customer_number
|
|
11
|
+
and rename it to billing_number to preserve the configuration value.
|
|
12
|
+
"""
|
|
13
|
+
Carrier = apps.get_model("providers", "Carrier")
|
|
14
|
+
db_alias = schema_editor.connection.alias
|
|
15
|
+
|
|
16
|
+
# Find all dhl_parcel_de carriers
|
|
17
|
+
dhl_parcel_de_carriers = Carrier.objects.using(db_alias).filter(
|
|
18
|
+
carrier_code="dhl_parcel_de"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
for carrier in dhl_parcel_de_carriers:
|
|
22
|
+
credentials = carrier.credentials or {}
|
|
23
|
+
|
|
24
|
+
# Check if customer_number exists and billing_number doesn't
|
|
25
|
+
if credentials.get("customer_number") and not credentials.get("billing_number"):
|
|
26
|
+
# Copy customer_number value to billing_number
|
|
27
|
+
credentials["billing_number"] = credentials["customer_number"]
|
|
28
|
+
# Remove old customer_number field
|
|
29
|
+
del credentials["customer_number"]
|
|
30
|
+
|
|
31
|
+
carrier.credentials = credentials
|
|
32
|
+
carrier.save(using=db_alias, update_fields=["credentials"])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def rename_billing_number_to_customer_number(apps, schema_editor):
|
|
36
|
+
"""
|
|
37
|
+
Reverse migration: rename billing_number back to customer_number.
|
|
38
|
+
"""
|
|
39
|
+
Carrier = apps.get_model("providers", "Carrier")
|
|
40
|
+
db_alias = schema_editor.connection.alias
|
|
41
|
+
|
|
42
|
+
dhl_parcel_de_carriers = Carrier.objects.using(db_alias).filter(
|
|
43
|
+
carrier_code="dhl_parcel_de"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
for carrier in dhl_parcel_de_carriers:
|
|
47
|
+
credentials = carrier.credentials or {}
|
|
48
|
+
|
|
49
|
+
# Check if billing_number exists and customer_number doesn't
|
|
50
|
+
if credentials.get("billing_number") and not credentials.get("customer_number"):
|
|
51
|
+
# Copy billing_number value back to customer_number
|
|
52
|
+
credentials["customer_number"] = credentials["billing_number"]
|
|
53
|
+
# Remove the billing_number field
|
|
54
|
+
del credentials["billing_number"]
|
|
55
|
+
|
|
56
|
+
carrier.credentials = credentials
|
|
57
|
+
carrier.save(using=db_alias, update_fields=["credentials"])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Migration(migrations.Migration):
|
|
61
|
+
|
|
62
|
+
dependencies = [
|
|
63
|
+
("providers", "0085_populate_dhl_parcel_de_oauth_credentials"),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
operations = [
|
|
67
|
+
migrations.RunPython(
|
|
68
|
+
rename_customer_number_to_billing_number,
|
|
69
|
+
rename_billing_number_to_customer_number,
|
|
70
|
+
),
|
|
71
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Generated by Django 5.2.9 on 2025-12-03 05:47
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import karrio.server.core.fields
|
|
5
|
+
import karrio.server.core.models
|
|
6
|
+
from django.db import migrations
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
("providers", "0086_rename_dhl_parcel_de_customer_number_to_billing_number"),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.AlterField(
|
|
17
|
+
model_name="carrier",
|
|
18
|
+
name="capabilities",
|
|
19
|
+
field=karrio.server.core.fields.MultiChoiceField(
|
|
20
|
+
choices=[
|
|
21
|
+
("pickup", "pickup"),
|
|
22
|
+
("rating", "rating"),
|
|
23
|
+
("shipping", "shipping"),
|
|
24
|
+
("tracking", "tracking"),
|
|
25
|
+
("paperless", "paperless"),
|
|
26
|
+
("manifest", "manifest"),
|
|
27
|
+
("duties", "duties"),
|
|
28
|
+
("insurance", "insurance"),
|
|
29
|
+
("webhook", "webhook"),
|
|
30
|
+
("oauth", "oauth"),
|
|
31
|
+
],
|
|
32
|
+
default=functools.partial(
|
|
33
|
+
karrio.server.core.models._identity, *(), **{"value": []}
|
|
34
|
+
),
|
|
35
|
+
help_text="Select the capabilities of the carrier that you want to enable",
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Generated by Django - Add surcharges field to ServiceLevel
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import karrio.server.core.models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('providers', '0087_alter_carrier_capabilities'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AddField(
|
|
15
|
+
model_name='servicelevel',
|
|
16
|
+
name='surcharges',
|
|
17
|
+
field=models.JSONField(
|
|
18
|
+
blank=True,
|
|
19
|
+
default=karrio.server.core.models.field_default([]),
|
|
20
|
+
help_text='Service-level surcharges (fuel, handling, residential, etc.)',
|
|
21
|
+
null=True,
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Generated by Django - Add cost and max_volume fields to ServiceLevel
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('providers', '0088_servicelevel_surcharges'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name='servicelevel',
|
|
15
|
+
name='cost',
|
|
16
|
+
field=models.FloatField(
|
|
17
|
+
blank=True,
|
|
18
|
+
null=True,
|
|
19
|
+
help_text='Base COGS (Cost of Goods Sold) - internal cost tracking',
|
|
20
|
+
),
|
|
21
|
+
),
|
|
22
|
+
migrations.AddField(
|
|
23
|
+
model_name='servicelevel',
|
|
24
|
+
name='max_volume',
|
|
25
|
+
field=models.FloatField(
|
|
26
|
+
blank=True,
|
|
27
|
+
null=True,
|
|
28
|
+
help_text='Maximum volume in liters for volumetric weight calculation',
|
|
29
|
+
),
|
|
30
|
+
),
|
|
31
|
+
]
|
karrio/server/providers/migrations/0090_ratesheet_surcharges_servicelevel_zone_surcharge_ids.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Generated by Django - Add surcharges to RateSheet and zone_ids/surcharge_ids to ServiceLevel
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import karrio.server.core.models as core
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('providers', '0089_servicelevel_cost_max_volume'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
# Add surcharges field to RateSheet (shared surcharge definitions)
|
|
15
|
+
migrations.AddField(
|
|
16
|
+
model_name='ratesheet',
|
|
17
|
+
name='surcharges',
|
|
18
|
+
field=models.JSONField(
|
|
19
|
+
blank=True,
|
|
20
|
+
null=True,
|
|
21
|
+
default=core.field_default([]),
|
|
22
|
+
help_text="Shared surcharge definitions: [{'id': 'surch_1', 'name': 'Fuel', 'amount': 8.5, 'surcharge_type': 'percentage'}]",
|
|
23
|
+
),
|
|
24
|
+
),
|
|
25
|
+
# Add zone_ids to ServiceLevel (references shared zones)
|
|
26
|
+
migrations.AddField(
|
|
27
|
+
model_name='servicelevel',
|
|
28
|
+
name='zone_ids',
|
|
29
|
+
field=models.JSONField(
|
|
30
|
+
blank=True,
|
|
31
|
+
null=True,
|
|
32
|
+
default=core.field_default([]),
|
|
33
|
+
help_text="List of zone IDs this service applies to: ['zone_1', 'zone_2']",
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
# Add surcharge_ids to ServiceLevel (references shared surcharges)
|
|
37
|
+
migrations.AddField(
|
|
38
|
+
model_name='servicelevel',
|
|
39
|
+
name='surcharge_ids',
|
|
40
|
+
field=models.JSONField(
|
|
41
|
+
blank=True,
|
|
42
|
+
null=True,
|
|
43
|
+
default=core.field_default([]),
|
|
44
|
+
help_text="List of surcharge IDs to apply: ['surch_fuel', 'surch_residential']",
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
]
|