karrio-server-graph 2026.1__py3-none-any.whl → 2026.1.3__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/graph/migrations/0003_remove_template_customs.py +15 -0
- karrio/server/graph/models.py +2 -5
- karrio/server/graph/schemas/base/__init__.py +68 -48
- karrio/server/graph/schemas/base/inputs.py +286 -71
- karrio/server/graph/schemas/base/mutations.py +365 -84
- karrio/server/graph/schemas/base/types.py +940 -248
- karrio/server/graph/serializers.py +25 -59
- karrio/server/graph/tests/base.py +33 -6
- karrio/server/graph/tests/test_carrier_connections.py +33 -4
- karrio/server/graph/tests/test_pickups.py +333 -0
- karrio/server/graph/tests/test_rate_sheets.py +0 -1
- karrio/server/graph/tests/test_templates.py +328 -510
- karrio/server/graph/utils.py +68 -1
- {karrio_server_graph-2026.1.dist-info → karrio_server_graph-2026.1.3.dist-info}/METADATA +1 -1
- {karrio_server_graph-2026.1.dist-info → karrio_server_graph-2026.1.3.dist-info}/RECORD +17 -15
- {karrio_server_graph-2026.1.dist-info → karrio_server_graph-2026.1.3.dist-info}/WHEEL +1 -1
- {karrio_server_graph-2026.1.dist-info → karrio_server_graph-2026.1.3.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
import datetime
|
|
3
3
|
import strawberry
|
|
4
|
+
from itertools import groupby
|
|
5
|
+
from operator import itemgetter
|
|
4
6
|
import django.db.models as models
|
|
5
7
|
import django.db.models.functions as functions
|
|
6
8
|
from django.conf import settings
|
|
@@ -173,6 +175,180 @@ class WorkspaceConfigType:
|
|
|
173
175
|
|
|
174
176
|
# endregion
|
|
175
177
|
|
|
178
|
+
# ─────────────────────────────────────────────────────────────────
|
|
179
|
+
# Printing Options - Labels (format uses default_label_type above)
|
|
180
|
+
# ─────────────────────────────────────────────────────────────────
|
|
181
|
+
# region
|
|
182
|
+
|
|
183
|
+
@strawberry.field
|
|
184
|
+
def print_label_size(
|
|
185
|
+
self: auth.WorkspaceConfig,
|
|
186
|
+
) -> typing.Optional[utils.LabelSizeEnum]:
|
|
187
|
+
return self.config.get("print_label_size")
|
|
188
|
+
|
|
189
|
+
@strawberry.field
|
|
190
|
+
def print_label_show_options(
|
|
191
|
+
self: auth.WorkspaceConfig,
|
|
192
|
+
) -> typing.Optional[bool]:
|
|
193
|
+
return self.config.get("print_label_show_options")
|
|
194
|
+
|
|
195
|
+
# endregion
|
|
196
|
+
|
|
197
|
+
# ─────────────────────────────────────────────────────────────────
|
|
198
|
+
# Printing Options - Return Labels
|
|
199
|
+
# ─────────────────────────────────────────────────────────────────
|
|
200
|
+
# region
|
|
201
|
+
|
|
202
|
+
@strawberry.field
|
|
203
|
+
def print_return_label_size(
|
|
204
|
+
self: auth.WorkspaceConfig,
|
|
205
|
+
) -> typing.Optional[utils.LabelSizeEnum]:
|
|
206
|
+
return self.config.get("print_return_label_size")
|
|
207
|
+
|
|
208
|
+
@strawberry.field
|
|
209
|
+
def print_return_label_show_options(
|
|
210
|
+
self: auth.WorkspaceConfig,
|
|
211
|
+
) -> typing.Optional[bool]:
|
|
212
|
+
return self.config.get("print_return_label_show_options")
|
|
213
|
+
|
|
214
|
+
# endregion
|
|
215
|
+
|
|
216
|
+
# ─────────────────────────────────────────────────────────────────
|
|
217
|
+
# Printing Options - Customs Documents
|
|
218
|
+
# ─────────────────────────────────────────────────────────────────
|
|
219
|
+
# region
|
|
220
|
+
|
|
221
|
+
@strawberry.field
|
|
222
|
+
def print_customs_size(
|
|
223
|
+
self: auth.WorkspaceConfig,
|
|
224
|
+
) -> typing.Optional[utils.LabelSizeEnum]:
|
|
225
|
+
return self.config.get("print_customs_size")
|
|
226
|
+
|
|
227
|
+
@strawberry.field
|
|
228
|
+
def print_customs_show_options(
|
|
229
|
+
self: auth.WorkspaceConfig,
|
|
230
|
+
) -> typing.Optional[bool]:
|
|
231
|
+
return self.config.get("print_customs_show_options")
|
|
232
|
+
|
|
233
|
+
@strawberry.field
|
|
234
|
+
def print_customs_with_label(
|
|
235
|
+
self: auth.WorkspaceConfig,
|
|
236
|
+
) -> typing.Optional[bool]:
|
|
237
|
+
return self.config.get("print_customs_with_label")
|
|
238
|
+
|
|
239
|
+
@strawberry.field
|
|
240
|
+
def print_customs_copies(
|
|
241
|
+
self: auth.WorkspaceConfig,
|
|
242
|
+
) -> typing.Optional[int]:
|
|
243
|
+
return self.config.get("print_customs_copies")
|
|
244
|
+
|
|
245
|
+
# endregion
|
|
246
|
+
|
|
247
|
+
# ─────────────────────────────────────────────────────────────────
|
|
248
|
+
# Shipping Defaults - Settings
|
|
249
|
+
# ─────────────────────────────────────────────────────────────────
|
|
250
|
+
# region
|
|
251
|
+
|
|
252
|
+
@strawberry.field
|
|
253
|
+
def default_parcel_weight(
|
|
254
|
+
self: auth.WorkspaceConfig,
|
|
255
|
+
) -> typing.Optional[float]:
|
|
256
|
+
return self.config.get("default_parcel_weight")
|
|
257
|
+
|
|
258
|
+
@strawberry.field
|
|
259
|
+
def default_shipping_service(
|
|
260
|
+
self: auth.WorkspaceConfig,
|
|
261
|
+
) -> typing.Optional[str]:
|
|
262
|
+
return self.config.get("default_shipping_service")
|
|
263
|
+
|
|
264
|
+
@strawberry.field
|
|
265
|
+
def default_shipping_carrier(
|
|
266
|
+
self: auth.WorkspaceConfig,
|
|
267
|
+
) -> typing.Optional[str]:
|
|
268
|
+
return self.config.get("default_shipping_carrier")
|
|
269
|
+
|
|
270
|
+
@strawberry.field
|
|
271
|
+
def default_export_reason(
|
|
272
|
+
self: auth.WorkspaceConfig,
|
|
273
|
+
) -> typing.Optional[utils.ExportReasonEnum]:
|
|
274
|
+
return self.config.get("default_export_reason")
|
|
275
|
+
|
|
276
|
+
@strawberry.field
|
|
277
|
+
def default_delivery_instructions(
|
|
278
|
+
self: auth.WorkspaceConfig,
|
|
279
|
+
) -> typing.Optional[str]:
|
|
280
|
+
return self.config.get("default_delivery_instructions")
|
|
281
|
+
|
|
282
|
+
# endregion
|
|
283
|
+
|
|
284
|
+
# ─────────────────────────────────────────────────────────────────
|
|
285
|
+
# Shipping Defaults - Label Options
|
|
286
|
+
# ─────────────────────────────────────────────────────────────────
|
|
287
|
+
# region
|
|
288
|
+
|
|
289
|
+
@strawberry.field
|
|
290
|
+
def label_show_postage_paid_logo(
|
|
291
|
+
self: auth.WorkspaceConfig,
|
|
292
|
+
) -> typing.Optional[bool]:
|
|
293
|
+
return self.config.get("label_show_postage_paid_logo")
|
|
294
|
+
|
|
295
|
+
@strawberry.field
|
|
296
|
+
def label_show_qr_code(
|
|
297
|
+
self: auth.WorkspaceConfig,
|
|
298
|
+
) -> typing.Optional[bool]:
|
|
299
|
+
return self.config.get("label_show_qr_code")
|
|
300
|
+
|
|
301
|
+
@strawberry.field
|
|
302
|
+
def customs_use_order_as_invoice(
|
|
303
|
+
self: auth.WorkspaceConfig,
|
|
304
|
+
) -> typing.Optional[bool]:
|
|
305
|
+
return self.config.get("customs_use_order_as_invoice")
|
|
306
|
+
|
|
307
|
+
# endregion
|
|
308
|
+
|
|
309
|
+
# ─────────────────────────────────────────────────────────────────
|
|
310
|
+
# Shipping Defaults - Recommendations Preferences
|
|
311
|
+
# ─────────────────────────────────────────────────────────────────
|
|
312
|
+
# region
|
|
313
|
+
|
|
314
|
+
@strawberry.field
|
|
315
|
+
def pref_first_mile(
|
|
316
|
+
self: auth.WorkspaceConfig,
|
|
317
|
+
) -> typing.Optional[typing.List[utils.FirstMileEnum]]:
|
|
318
|
+
return self.config.get("pref_first_mile")
|
|
319
|
+
|
|
320
|
+
@strawberry.field
|
|
321
|
+
def pref_last_mile(
|
|
322
|
+
self: auth.WorkspaceConfig,
|
|
323
|
+
) -> typing.Optional[typing.List[utils.LastMileEnum]]:
|
|
324
|
+
return self.config.get("pref_last_mile")
|
|
325
|
+
|
|
326
|
+
@strawberry.field
|
|
327
|
+
def pref_form_factor(
|
|
328
|
+
self: auth.WorkspaceConfig,
|
|
329
|
+
) -> typing.Optional[typing.List[utils.FormFactorEnum]]:
|
|
330
|
+
return self.config.get("pref_form_factor")
|
|
331
|
+
|
|
332
|
+
@strawberry.field
|
|
333
|
+
def pref_age_check(
|
|
334
|
+
self: auth.WorkspaceConfig,
|
|
335
|
+
) -> typing.Optional[utils.AgeCheckEnum]:
|
|
336
|
+
return self.config.get("pref_age_check")
|
|
337
|
+
|
|
338
|
+
@strawberry.field
|
|
339
|
+
def pref_signature_required(
|
|
340
|
+
self: auth.WorkspaceConfig,
|
|
341
|
+
) -> typing.Optional[bool]:
|
|
342
|
+
return self.config.get("pref_signature_required")
|
|
343
|
+
|
|
344
|
+
@strawberry.field
|
|
345
|
+
def pref_max_lead_time_days(
|
|
346
|
+
self: auth.WorkspaceConfig,
|
|
347
|
+
) -> typing.Optional[int]:
|
|
348
|
+
return self.config.get("pref_max_lead_time_days")
|
|
349
|
+
|
|
350
|
+
# endregion
|
|
351
|
+
|
|
176
352
|
@staticmethod
|
|
177
353
|
@utils.authentication_required
|
|
178
354
|
def resolve(info) -> typing.Optional["WorkspaceConfigType"]:
|
|
@@ -238,8 +414,14 @@ class SystemUsageType:
|
|
|
238
414
|
.annotate(count=models.Count("id"))
|
|
239
415
|
.order_by("-date")
|
|
240
416
|
)
|
|
241
|
-
|
|
242
|
-
|
|
417
|
+
# Calculate order volumes from JSONField (line_items is embedded JSON)
|
|
418
|
+
_compute_total = lambda items: sum(
|
|
419
|
+
float(i.get("value_amount") or 0) * float(i.get("quantity") or 1)
|
|
420
|
+
for i in (items or [])
|
|
421
|
+
)
|
|
422
|
+
orders_with_totals = [
|
|
423
|
+
(o["date"], _compute_total(o.get("line_items")))
|
|
424
|
+
for o in order_filters.OrderFilters(
|
|
243
425
|
dict(
|
|
244
426
|
created_before=_filter["date_before"],
|
|
245
427
|
created_after=_filter["date_after"],
|
|
@@ -249,15 +431,13 @@ class SystemUsageType:
|
|
|
249
431
|
),
|
|
250
432
|
)
|
|
251
433
|
.qs.annotate(date=functions.TruncDay("created_at"))
|
|
252
|
-
.values("date")
|
|
253
|
-
.annotate(
|
|
254
|
-
count=models.Sum(
|
|
255
|
-
models.F("line_items__value_amount")
|
|
256
|
-
* models.F("line_items__quantity")
|
|
257
|
-
)
|
|
258
|
-
)
|
|
434
|
+
.values("date", "line_items")
|
|
259
435
|
.order_by("-date")
|
|
260
|
-
|
|
436
|
+
]
|
|
437
|
+
order_volumes = [
|
|
438
|
+
{"date": date, "count": sum(t for _, t in items)}
|
|
439
|
+
for date, items in groupby(orders_with_totals, key=itemgetter(0))
|
|
440
|
+
]
|
|
261
441
|
shipment_count = (
|
|
262
442
|
filters.ShipmentFilters(
|
|
263
443
|
dict(
|
|
@@ -371,12 +551,19 @@ class SystemUsageType:
|
|
|
371
551
|
|
|
372
552
|
@strawberry.type
|
|
373
553
|
class MetafieldType:
|
|
374
|
-
object_type: str
|
|
375
554
|
id: str
|
|
376
555
|
key: str
|
|
377
556
|
is_required: bool
|
|
378
557
|
type: utils.MetafieldTypeEnum
|
|
379
558
|
value: typing.Optional[utils.JSON] = None
|
|
559
|
+
object_id: typing.Optional[str] = None
|
|
560
|
+
|
|
561
|
+
@strawberry.field
|
|
562
|
+
def object_type(self: core.Metafield) -> str:
|
|
563
|
+
"""Return the model name of the attached object, or 'metafield' if not attached."""
|
|
564
|
+
if self.content_type:
|
|
565
|
+
return self.content_type.model
|
|
566
|
+
return "metafield"
|
|
380
567
|
|
|
381
568
|
@strawberry.field
|
|
382
569
|
def parsed_value(self: core.Metafield) -> typing.Optional[utils.JSON]:
|
|
@@ -394,6 +581,8 @@ class MetafieldType:
|
|
|
394
581
|
info,
|
|
395
582
|
filter: typing.Optional[inputs.MetafieldFilter] = strawberry.UNSET,
|
|
396
583
|
) -> utils.Connection["MetafieldType"]:
|
|
584
|
+
from django.contrib.contenttypes.models import ContentType
|
|
585
|
+
|
|
397
586
|
_filter = filter if not utils.is_unset(filter) else inputs.MetafieldFilter()
|
|
398
587
|
queryset = core.Metafield.access_by(info.context.request)
|
|
399
588
|
|
|
@@ -404,6 +593,14 @@ class MetafieldType:
|
|
|
404
593
|
queryset = queryset.filter(type=_filter.type)
|
|
405
594
|
if not utils.is_unset(_filter.is_required):
|
|
406
595
|
queryset = queryset.filter(is_required=_filter.is_required)
|
|
596
|
+
if not utils.is_unset(_filter.object_type):
|
|
597
|
+
ct = ContentType.objects.filter(model=_filter.object_type).first()
|
|
598
|
+
if ct:
|
|
599
|
+
queryset = queryset.filter(content_type=ct)
|
|
600
|
+
else:
|
|
601
|
+
queryset = queryset.none()
|
|
602
|
+
if not utils.is_unset(_filter.object_id):
|
|
603
|
+
queryset = queryset.filter(object_id=_filter.object_id)
|
|
407
604
|
|
|
408
605
|
return utils.paginated_connection(queryset, **_filter.pagination())
|
|
409
606
|
|
|
@@ -452,12 +649,11 @@ class LogType:
|
|
|
452
649
|
if User.objects.filter(
|
|
453
650
|
id=info.context.request.user.id, is_staff=False
|
|
454
651
|
).exists():
|
|
455
|
-
# exclude system
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
queryset = queryset.exclude(meta__carrier_account_id__in=system_carriers)
|
|
652
|
+
# exclude system connection records if user is not staff
|
|
653
|
+
system_connection_ids = list(
|
|
654
|
+
providers.SystemConnection.objects.values_list("id", flat=True)
|
|
655
|
+
)
|
|
656
|
+
queryset = queryset.exclude(meta__carrier_account_id__in=system_connection_ids)
|
|
461
657
|
|
|
462
658
|
return queryset
|
|
463
659
|
|
|
@@ -534,6 +730,7 @@ class TokenType:
|
|
|
534
730
|
|
|
535
731
|
@strawberry.field
|
|
536
732
|
def permissions(self: auth.Token, info) -> typing.Optional[typing.List[str]]:
|
|
733
|
+
# self is a Token model instance, permissions is a @property on the model
|
|
537
734
|
return self.permissions
|
|
538
735
|
|
|
539
736
|
@staticmethod
|
|
@@ -555,6 +752,7 @@ class APIKeyType:
|
|
|
555
752
|
|
|
556
753
|
@strawberry.field
|
|
557
754
|
def permissions(self: auth.Token, info) -> typing.Optional[typing.List[str]]:
|
|
755
|
+
# self is a Token model instance, permissions is a @property on the model
|
|
558
756
|
return self.permissions
|
|
559
757
|
|
|
560
758
|
@staticmethod
|
|
@@ -643,29 +841,194 @@ class RateType:
|
|
|
643
841
|
|
|
644
842
|
@strawberry.type
|
|
645
843
|
class CommodityType:
|
|
646
|
-
id: str
|
|
647
|
-
object_type: str
|
|
648
|
-
weight: float
|
|
649
|
-
quantity: int
|
|
650
|
-
metadata: utils.JSON
|
|
651
|
-
sku: typing.Optional[str]
|
|
652
|
-
title: typing.Optional[str]
|
|
653
|
-
hs_code: typing.Optional[str]
|
|
654
|
-
description: typing.Optional[str]
|
|
655
|
-
value_amount: typing.Optional[float]
|
|
656
|
-
weight_unit: typing.Optional[utils.WeightUnitEnum]
|
|
657
|
-
origin_country: typing.Optional[utils.CountryCodeEnum]
|
|
658
|
-
value_currency: typing.Optional[utils.CurrencyCodeEnum]
|
|
659
|
-
created_at: typing.Optional[datetime.datetime]
|
|
660
|
-
updated_at: typing.Optional[datetime.datetime]
|
|
661
|
-
created_by: typing.Optional[UserType]
|
|
844
|
+
id: typing.Optional[str] = None
|
|
845
|
+
object_type: typing.Optional[str] = None
|
|
846
|
+
weight: typing.Optional[float] = None
|
|
847
|
+
quantity: typing.Optional[int] = None
|
|
848
|
+
metadata: typing.Optional[utils.JSON] = None
|
|
849
|
+
sku: typing.Optional[str] = None
|
|
850
|
+
title: typing.Optional[str] = None
|
|
851
|
+
hs_code: typing.Optional[str] = None
|
|
852
|
+
description: typing.Optional[str] = None
|
|
853
|
+
value_amount: typing.Optional[float] = None
|
|
854
|
+
weight_unit: typing.Optional[utils.WeightUnitEnum] = None
|
|
855
|
+
origin_country: typing.Optional[utils.CountryCodeEnum] = None
|
|
856
|
+
value_currency: typing.Optional[utils.CurrencyCodeEnum] = None
|
|
857
|
+
created_at: typing.Optional[datetime.datetime] = None
|
|
858
|
+
updated_at: typing.Optional[datetime.datetime] = None
|
|
859
|
+
created_by: typing.Optional[UserType] = None
|
|
662
860
|
parent_id: typing.Optional[str] = None
|
|
663
861
|
parent: typing.Optional["CommodityType"] = None
|
|
664
862
|
unfulfilled_quantity: typing.Optional[int] = None
|
|
665
863
|
|
|
864
|
+
@staticmethod
|
|
865
|
+
def parse(item: dict) -> typing.Optional["CommodityType"]:
|
|
866
|
+
if not item:
|
|
867
|
+
return None
|
|
868
|
+
return CommodityType(
|
|
869
|
+
**{
|
|
870
|
+
"object_type": item.get("object_type", "commodity"),
|
|
871
|
+
"weight": item.get("weight") or 0,
|
|
872
|
+
"quantity": item.get("quantity") or 1,
|
|
873
|
+
"metadata": item.get("metadata") or {},
|
|
874
|
+
**{k: v for k, v in item.items() if k in CommodityType.__annotations__},
|
|
875
|
+
}
|
|
876
|
+
)
|
|
877
|
+
|
|
666
878
|
|
|
667
879
|
@strawberry.type
|
|
668
880
|
class AddressType:
|
|
881
|
+
id: typing.Optional[str] = None
|
|
882
|
+
object_type: typing.Optional[str] = None
|
|
883
|
+
postal_code: typing.Optional[str] = None
|
|
884
|
+
city: typing.Optional[str] = None
|
|
885
|
+
federal_tax_id: typing.Optional[str] = None
|
|
886
|
+
state_tax_id: typing.Optional[str] = None
|
|
887
|
+
person_name: typing.Optional[str] = None
|
|
888
|
+
company_name: typing.Optional[str] = None
|
|
889
|
+
country_code: typing.Optional[utils.CountryCodeEnum] = None
|
|
890
|
+
email: typing.Optional[str] = None
|
|
891
|
+
phone_number: typing.Optional[str] = None
|
|
892
|
+
state_code: typing.Optional[str] = None
|
|
893
|
+
residential: typing.Optional[bool] = None
|
|
894
|
+
street_number: typing.Optional[str] = None
|
|
895
|
+
address_line1: typing.Optional[str] = None
|
|
896
|
+
address_line2: typing.Optional[str] = None
|
|
897
|
+
created_at: typing.Optional[datetime.datetime] = None
|
|
898
|
+
updated_at: typing.Optional[datetime.datetime] = None
|
|
899
|
+
created_by: typing.Optional[UserType] = None
|
|
900
|
+
validate_location: typing.Optional[bool] = None
|
|
901
|
+
validation: typing.Optional[utils.JSON] = None
|
|
902
|
+
|
|
903
|
+
@staticmethod
|
|
904
|
+
def parse(address: dict) -> typing.Optional["AddressType"]:
|
|
905
|
+
if not address:
|
|
906
|
+
return None
|
|
907
|
+
return AddressType(
|
|
908
|
+
**{
|
|
909
|
+
"object_type": address.get("object_type", "address"),
|
|
910
|
+
**{k: v for k, v in address.items() if k in AddressType.__annotations__},
|
|
911
|
+
}
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
@strawberry.type
|
|
916
|
+
class ParcelType:
|
|
917
|
+
id: typing.Optional[str] = None
|
|
918
|
+
object_type: typing.Optional[str] = None
|
|
919
|
+
weight: typing.Optional[float] = None
|
|
920
|
+
width: typing.Optional[float] = None
|
|
921
|
+
height: typing.Optional[float] = None
|
|
922
|
+
length: typing.Optional[float] = None
|
|
923
|
+
packaging_type: typing.Optional[str] = None
|
|
924
|
+
package_preset: typing.Optional[str] = None
|
|
925
|
+
description: typing.Optional[str] = None
|
|
926
|
+
content: typing.Optional[str] = None
|
|
927
|
+
is_document: typing.Optional[bool] = None
|
|
928
|
+
weight_unit: typing.Optional[utils.WeightUnitEnum] = None
|
|
929
|
+
dimension_unit: typing.Optional[utils.DimensionUnitEnum] = None
|
|
930
|
+
freight_class: typing.Optional[str] = None
|
|
931
|
+
reference_number: typing.Optional[str] = None
|
|
932
|
+
created_at: typing.Optional[datetime.datetime] = None
|
|
933
|
+
updated_at: typing.Optional[datetime.datetime] = None
|
|
934
|
+
created_by: typing.Optional[UserType] = None
|
|
935
|
+
items: typing.Optional[typing.List[CommodityType]] = None
|
|
936
|
+
|
|
937
|
+
@staticmethod
|
|
938
|
+
def parse(parcel: dict) -> typing.Optional["ParcelType"]:
|
|
939
|
+
if not parcel:
|
|
940
|
+
return None
|
|
941
|
+
return ParcelType(
|
|
942
|
+
**{
|
|
943
|
+
"object_type": parcel.get("object_type", "parcel"),
|
|
944
|
+
**{k: v for k, v in parcel.items() if k in ParcelType.__annotations__ and k != "items"},
|
|
945
|
+
"items": [CommodityType.parse(i) for i in (parcel.get("items") or [])],
|
|
946
|
+
}
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
@strawberry.type
|
|
951
|
+
class DutyType:
|
|
952
|
+
paid_by: typing.Optional[utils.PaidByEnum] = None
|
|
953
|
+
currency: typing.Optional[utils.CurrencyCodeEnum] = None
|
|
954
|
+
account_number: typing.Optional[str] = None
|
|
955
|
+
declared_value: typing.Optional[float] = None
|
|
956
|
+
bill_to: typing.Optional[AddressType] = None
|
|
957
|
+
|
|
958
|
+
@staticmethod
|
|
959
|
+
def parse(duty: dict) -> typing.Optional["DutyType"]:
|
|
960
|
+
if not duty:
|
|
961
|
+
return None
|
|
962
|
+
return DutyType(
|
|
963
|
+
paid_by=duty.get("paid_by"),
|
|
964
|
+
currency=duty.get("currency"),
|
|
965
|
+
account_number=duty.get("account_number"),
|
|
966
|
+
declared_value=duty.get("declared_value"),
|
|
967
|
+
bill_to=AddressType.parse(duty.get("bill_to")),
|
|
968
|
+
)
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
@strawberry.type
|
|
972
|
+
class CustomsType:
|
|
973
|
+
"""Customs type for embedded JSON customs data on shipments.
|
|
974
|
+
|
|
975
|
+
This is a pure data type that parses customs JSON data, not tied to a database model.
|
|
976
|
+
"""
|
|
977
|
+
certify: typing.Optional[bool] = None
|
|
978
|
+
commercial_invoice: typing.Optional[bool] = None
|
|
979
|
+
content_type: typing.Optional[utils.CustomsContentTypeEnum] = None
|
|
980
|
+
content_description: typing.Optional[str] = None
|
|
981
|
+
incoterm: typing.Optional[utils.IncotermCodeEnum] = None
|
|
982
|
+
invoice: typing.Optional[str] = None
|
|
983
|
+
invoice_date: typing.Optional[str] = None
|
|
984
|
+
signer: typing.Optional[str] = None
|
|
985
|
+
options: typing.Optional[utils.JSON] = None
|
|
986
|
+
# Private fields for parsed nested objects
|
|
987
|
+
_duty: strawberry.Private[typing.Optional[DutyType]] = None
|
|
988
|
+
_duty_billing_address: strawberry.Private[typing.Optional[AddressType]] = None
|
|
989
|
+
_commodities: strawberry.Private[typing.Optional[typing.List[CommodityType]]] = None
|
|
990
|
+
|
|
991
|
+
@strawberry.field
|
|
992
|
+
def duty(self) -> typing.Optional[DutyType]:
|
|
993
|
+
return self._duty
|
|
994
|
+
|
|
995
|
+
@strawberry.field
|
|
996
|
+
def duty_billing_address(self) -> typing.Optional[AddressType]:
|
|
997
|
+
return self._duty_billing_address
|
|
998
|
+
|
|
999
|
+
@strawberry.field
|
|
1000
|
+
def commodities(self) -> typing.Optional[typing.List[CommodityType]]:
|
|
1001
|
+
return self._commodities
|
|
1002
|
+
|
|
1003
|
+
@staticmethod
|
|
1004
|
+
def parse(customs: dict) -> typing.Optional["CustomsType"]:
|
|
1005
|
+
if not customs:
|
|
1006
|
+
return None
|
|
1007
|
+
return CustomsType(
|
|
1008
|
+
certify=customs.get("certify"),
|
|
1009
|
+
commercial_invoice=customs.get("commercial_invoice"),
|
|
1010
|
+
content_type=customs.get("content_type"),
|
|
1011
|
+
content_description=customs.get("content_description"),
|
|
1012
|
+
incoterm=customs.get("incoterm"),
|
|
1013
|
+
invoice=customs.get("invoice"),
|
|
1014
|
+
invoice_date=customs.get("invoice_date"),
|
|
1015
|
+
signer=customs.get("signer"),
|
|
1016
|
+
options=customs.get("options"),
|
|
1017
|
+
_duty=DutyType.parse(customs.get("duty")),
|
|
1018
|
+
_duty_billing_address=AddressType.parse(customs.get("duty_billing_address")),
|
|
1019
|
+
_commodities=[CommodityType.parse(c) for c in (customs.get("commodities") or [])],
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
@strawberry.type
|
|
1024
|
+
class AddressTemplateType:
|
|
1025
|
+
"""Address template type for reusable address templates.
|
|
1026
|
+
|
|
1027
|
+
Uses the Address model directly with meta.label for template metadata,
|
|
1028
|
+
following the PRD pattern for direct model templates.
|
|
1029
|
+
All fields are resolved from the Django model's properties.
|
|
1030
|
+
"""
|
|
1031
|
+
|
|
669
1032
|
id: str
|
|
670
1033
|
object_type: str
|
|
671
1034
|
postal_code: typing.Optional[str]
|
|
@@ -674,7 +1037,7 @@ class AddressType:
|
|
|
674
1037
|
state_tax_id: typing.Optional[str]
|
|
675
1038
|
person_name: typing.Optional[str]
|
|
676
1039
|
company_name: typing.Optional[str]
|
|
677
|
-
country_code: utils.CountryCodeEnum
|
|
1040
|
+
country_code: typing.Optional[utils.CountryCodeEnum]
|
|
678
1041
|
email: typing.Optional[str]
|
|
679
1042
|
phone_number: typing.Optional[str]
|
|
680
1043
|
state_code: typing.Optional[str]
|
|
@@ -686,11 +1049,81 @@ class AddressType:
|
|
|
686
1049
|
updated_at: typing.Optional[datetime.datetime]
|
|
687
1050
|
created_by: typing.Optional[UserType]
|
|
688
1051
|
validate_location: typing.Optional[bool]
|
|
689
|
-
validation: typing.Optional[utils.JSON]
|
|
1052
|
+
validation: typing.Optional[utils.JSON]
|
|
1053
|
+
meta: typing.Optional[utils.JSON] = None
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
# Standalone resolver functions for saved addresses (AddressTemplateType)
|
|
1057
|
+
@utils.authentication_required
|
|
1058
|
+
def resolve_addresses(
|
|
1059
|
+
info,
|
|
1060
|
+
filter: typing.Optional[inputs.AddressFilter] = strawberry.UNSET,
|
|
1061
|
+
) -> utils.Connection[AddressTemplateType]:
|
|
1062
|
+
"""Resolver for listing saved addresses."""
|
|
1063
|
+
_filter = inputs.AddressFilter() if utils.is_unset(filter) else filter
|
|
1064
|
+
_search = _filter.to_dict()
|
|
1065
|
+
_query = models.Q(meta__label__isnull=False) & ~models.Q(meta__label="")
|
|
1066
|
+
|
|
1067
|
+
if any(_search.get("label") or ""):
|
|
1068
|
+
_value = _search.get("label")
|
|
1069
|
+
_query = _query & models.Q(meta__label__icontains=_value)
|
|
1070
|
+
|
|
1071
|
+
if any(_search.get("address") or ""):
|
|
1072
|
+
_value = _search.get("address")
|
|
1073
|
+
_query = _query & (
|
|
1074
|
+
models.Q(address_line1__icontains=_value)
|
|
1075
|
+
| models.Q(address_line2__icontains=_value)
|
|
1076
|
+
| models.Q(postal_code__icontains=_value)
|
|
1077
|
+
| models.Q(person_name__icontains=_value)
|
|
1078
|
+
| models.Q(company_name__icontains=_value)
|
|
1079
|
+
| models.Q(country_code__icontains=_value)
|
|
1080
|
+
| models.Q(city__icontains=_value)
|
|
1081
|
+
| models.Q(email__icontains=_value)
|
|
1082
|
+
| models.Q(phone_number__icontains=_value)
|
|
1083
|
+
)
|
|
1084
|
+
|
|
1085
|
+
if any(_search.get("keyword") or ""):
|
|
1086
|
+
_value = _search.get("keyword")
|
|
1087
|
+
_query = _query & (
|
|
1088
|
+
models.Q(meta__label__icontains=_value)
|
|
1089
|
+
| models.Q(address_line1__icontains=_value)
|
|
1090
|
+
| models.Q(address_line2__icontains=_value)
|
|
1091
|
+
| models.Q(postal_code__icontains=_value)
|
|
1092
|
+
| models.Q(person_name__icontains=_value)
|
|
1093
|
+
| models.Q(company_name__icontains=_value)
|
|
1094
|
+
| models.Q(country_code__icontains=_value)
|
|
1095
|
+
| models.Q(city__icontains=_value)
|
|
1096
|
+
| models.Q(email__icontains=_value)
|
|
1097
|
+
| models.Q(phone_number__icontains=_value)
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
if any(_search.get("usage") or ""):
|
|
1101
|
+
_value = _search.get("usage")
|
|
1102
|
+
_query = _query & models.Q(meta__usage__contains=_value)
|
|
1103
|
+
|
|
1104
|
+
queryset = manager.Address.access_by(info.context.request).filter(_query)
|
|
1105
|
+
|
|
1106
|
+
return utils.paginated_connection(queryset, **_filter.pagination())
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
@utils.authentication_required
|
|
1110
|
+
def resolve_address(info, id: str) -> typing.Optional[AddressTemplateType]:
|
|
1111
|
+
"""Resolver for getting a single saved address by ID."""
|
|
1112
|
+
return manager.Address.access_by(info.context.request).filter(
|
|
1113
|
+
id=id,
|
|
1114
|
+
meta__label__isnull=False,
|
|
1115
|
+
).first()
|
|
690
1116
|
|
|
691
1117
|
|
|
692
1118
|
@strawberry.type
|
|
693
|
-
class
|
|
1119
|
+
class ParcelTemplateType:
|
|
1120
|
+
"""Parcel template type for reusable parcel templates.
|
|
1121
|
+
|
|
1122
|
+
Uses the Parcel model directly with meta.label for template metadata,
|
|
1123
|
+
following the PRD pattern for direct model templates.
|
|
1124
|
+
All fields are resolved from the Django model's properties.
|
|
1125
|
+
"""
|
|
1126
|
+
|
|
694
1127
|
id: str
|
|
695
1128
|
object_type: str
|
|
696
1129
|
weight: typing.Optional[float]
|
|
@@ -706,193 +1139,187 @@ class ParcelType:
|
|
|
706
1139
|
dimension_unit: typing.Optional[utils.DimensionUnitEnum]
|
|
707
1140
|
freight_class: typing.Optional[str]
|
|
708
1141
|
reference_number: typing.Optional[str]
|
|
709
|
-
created_at: datetime.datetime
|
|
710
|
-
updated_at: datetime.datetime
|
|
711
|
-
created_by: UserType
|
|
1142
|
+
created_at: typing.Optional[datetime.datetime]
|
|
1143
|
+
updated_at: typing.Optional[datetime.datetime]
|
|
1144
|
+
created_by: typing.Optional[UserType]
|
|
1145
|
+
meta: typing.Optional[utils.JSON] = None
|
|
712
1146
|
|
|
713
1147
|
@strawberry.field
|
|
714
|
-
def items(self: manager.Parcel) -> typing.List[CommodityType]:
|
|
715
|
-
|
|
1148
|
+
def items(self: manager.Parcel) -> typing.Optional[typing.List[CommodityType]]:
|
|
1149
|
+
"""Items in the parcel."""
|
|
1150
|
+
items_rel = getattr(self, "items", None)
|
|
1151
|
+
if items_rel is None:
|
|
1152
|
+
return None
|
|
1153
|
+
if hasattr(items_rel, "all"):
|
|
1154
|
+
return list(items_rel.all())
|
|
1155
|
+
return items_rel
|
|
716
1156
|
|
|
717
1157
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1158
|
+
# Standalone resolver functions for saved parcels (ParcelTemplateType)
|
|
1159
|
+
@utils.authentication_required
|
|
1160
|
+
def resolve_parcels(
|
|
1161
|
+
info,
|
|
1162
|
+
filter: typing.Optional[inputs.TemplateFilter] = strawberry.UNSET,
|
|
1163
|
+
) -> utils.Connection[ParcelTemplateType]:
|
|
1164
|
+
"""Resolver for listing saved parcels."""
|
|
1165
|
+
_filter = inputs.TemplateFilter() if filter == strawberry.UNSET else filter
|
|
1166
|
+
_search = _filter.to_dict()
|
|
1167
|
+
_query = models.Q(meta__label__isnull=False) & ~models.Q(meta__label="")
|
|
726
1168
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
object_type: str
|
|
731
|
-
certify: typing.Optional[bool] = strawberry.UNSET
|
|
732
|
-
commercial_invoice: typing.Optional[bool] = strawberry.UNSET
|
|
733
|
-
content_type: typing.Optional[utils.CustomsContentTypeEnum] = strawberry.UNSET
|
|
734
|
-
content_description: typing.Optional[str] = strawberry.UNSET
|
|
735
|
-
incoterm: typing.Optional[utils.IncotermCodeEnum] = strawberry.UNSET
|
|
736
|
-
invoice: typing.Optional[str] = strawberry.UNSET
|
|
737
|
-
invoice_date: typing.Optional[str] = strawberry.UNSET
|
|
738
|
-
signer: typing.Optional[str] = strawberry.UNSET
|
|
739
|
-
created_at: typing.Optional[datetime.datetime] = strawberry.UNSET
|
|
740
|
-
updated_at: typing.Optional[datetime.datetime] = strawberry.UNSET
|
|
741
|
-
created_by: typing.Optional[UserType] = strawberry.UNSET
|
|
742
|
-
options: typing.Optional[utils.JSON] = strawberry.UNSET
|
|
743
|
-
duty_billing_address: typing.Optional[AddressType] = strawberry.UNSET
|
|
744
|
-
|
|
745
|
-
@strawberry.field
|
|
746
|
-
def duty(self: manager) -> typing.Optional[DutyType]:
|
|
747
|
-
if self.duty is None:
|
|
748
|
-
return None
|
|
1169
|
+
if any(_search.get("label") or ""):
|
|
1170
|
+
_value = _search.get("label")
|
|
1171
|
+
_query = _query & models.Q(meta__label__icontains=_value)
|
|
749
1172
|
|
|
750
|
-
|
|
1173
|
+
if any(_search.get("keyword") or ""):
|
|
1174
|
+
_value = _search.get("keyword")
|
|
1175
|
+
_query = _query & models.Q(meta__label__icontains=_value)
|
|
751
1176
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1177
|
+
if any(_search.get("usage") or ""):
|
|
1178
|
+
_value = _search.get("usage")
|
|
1179
|
+
_query = _query & models.Q(meta__usage__contains=_value)
|
|
755
1180
|
|
|
1181
|
+
queryset = manager.Parcel.access_by(info.context.request).filter(_query)
|
|
756
1182
|
|
|
757
|
-
|
|
758
|
-
class AddressTemplateType:
|
|
759
|
-
id: str
|
|
760
|
-
object_type: str
|
|
761
|
-
label: str
|
|
762
|
-
address: AddressType
|
|
763
|
-
is_default: typing.Optional[bool] = None
|
|
1183
|
+
return utils.paginated_connection(queryset, **_filter.pagination())
|
|
764
1184
|
|
|
765
|
-
@staticmethod
|
|
766
|
-
@utils.authentication_required
|
|
767
|
-
def resolve_list(
|
|
768
|
-
info,
|
|
769
|
-
filter: typing.Optional[inputs.AddressFilter] = strawberry.UNSET,
|
|
770
|
-
) -> utils.Connection["AddressTemplateType"]:
|
|
771
|
-
_filter = inputs.AddressFilter() if utils.is_unset(filter) else filter
|
|
772
|
-
_search = _filter.to_dict()
|
|
773
|
-
_query = models.Q()
|
|
774
|
-
|
|
775
|
-
if any(_search.get("label") or ""):
|
|
776
|
-
_value = _search.get("label")
|
|
777
|
-
_query = _query | models.Q(label__icontains=_value)
|
|
778
|
-
|
|
779
|
-
if any(_search.get("address") or ""):
|
|
780
|
-
_value = _search.get("address")
|
|
781
|
-
_query = (
|
|
782
|
-
_query
|
|
783
|
-
| models.Q(address__address_line1__icontains=_value)
|
|
784
|
-
| models.Q(address__address_line2__icontains=_value)
|
|
785
|
-
| models.Q(address__postal_code__icontains=_value)
|
|
786
|
-
| models.Q(address__person_name__icontains=_value)
|
|
787
|
-
| models.Q(address__company_name__icontains=_value)
|
|
788
|
-
| models.Q(address__country_code__icontains=_value)
|
|
789
|
-
| models.Q(address__city__icontains=_value)
|
|
790
|
-
| models.Q(address__email__icontains=_value)
|
|
791
|
-
| models.Q(address__phone_number__icontains=_value)
|
|
792
|
-
)
|
|
793
1185
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
| models.Q(address__postal_code__icontains=_value)
|
|
802
|
-
| models.Q(address__person_name__icontains=_value)
|
|
803
|
-
| models.Q(address__company_name__icontains=_value)
|
|
804
|
-
| models.Q(address__country_code__icontains=_value)
|
|
805
|
-
| models.Q(address__city__icontains=_value)
|
|
806
|
-
| models.Q(address__email__icontains=_value)
|
|
807
|
-
| models.Q(address__phone_number__icontains=_value)
|
|
808
|
-
)
|
|
1186
|
+
@utils.authentication_required
|
|
1187
|
+
def resolve_parcel(info, id: str) -> typing.Optional[ParcelTemplateType]:
|
|
1188
|
+
"""Resolver for getting a single saved parcel by ID."""
|
|
1189
|
+
return manager.Parcel.access_by(info.context.request).filter(
|
|
1190
|
+
id=id,
|
|
1191
|
+
meta__label__isnull=False,
|
|
1192
|
+
).first()
|
|
809
1193
|
|
|
810
|
-
_queryset = graph.Template.access_by(info.context.request).filter(
|
|
811
|
-
_query, address__isnull=False
|
|
812
|
-
)
|
|
813
1194
|
|
|
814
|
-
|
|
1195
|
+
@strawberry.type
|
|
1196
|
+
class ProductTemplateType:
|
|
1197
|
+
"""Product template type for reusable product/commodity templates.
|
|
815
1198
|
|
|
1199
|
+
Uses the Commodity model directly with meta.label for template metadata,
|
|
1200
|
+
following the PRD pattern for direct model templates.
|
|
1201
|
+
All fields are resolved from the Django model's properties.
|
|
1202
|
+
"""
|
|
816
1203
|
|
|
817
|
-
@strawberry.type
|
|
818
|
-
class ParcelTemplateType:
|
|
819
1204
|
id: str
|
|
820
1205
|
object_type: str
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1206
|
+
weight: float
|
|
1207
|
+
quantity: int
|
|
1208
|
+
sku: typing.Optional[str]
|
|
1209
|
+
title: typing.Optional[str]
|
|
1210
|
+
hs_code: typing.Optional[str]
|
|
1211
|
+
description: typing.Optional[str]
|
|
1212
|
+
value_amount: typing.Optional[float]
|
|
1213
|
+
weight_unit: typing.Optional[utils.WeightUnitEnum]
|
|
1214
|
+
origin_country: typing.Optional[utils.CountryCodeEnum]
|
|
1215
|
+
value_currency: typing.Optional[utils.CurrencyCodeEnum]
|
|
1216
|
+
created_at: typing.Optional[datetime.datetime]
|
|
1217
|
+
updated_at: typing.Optional[datetime.datetime]
|
|
1218
|
+
created_by: typing.Optional[UserType]
|
|
1219
|
+
metadata: typing.Optional[utils.JSON] = None
|
|
1220
|
+
meta: typing.Optional[utils.JSON] = None
|
|
824
1221
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1222
|
+
|
|
1223
|
+
# Standalone resolver functions for saved products (ProductTemplateType)
|
|
1224
|
+
@utils.authentication_required
|
|
1225
|
+
def resolve_products(
|
|
1226
|
+
info,
|
|
1227
|
+
filter: typing.Optional[inputs.ProductFilter] = strawberry.UNSET,
|
|
1228
|
+
) -> utils.Connection[ProductTemplateType]:
|
|
1229
|
+
"""Resolver for listing saved products."""
|
|
1230
|
+
_filter = filter if not utils.is_unset(filter) else inputs.ProductFilter()
|
|
1231
|
+
_search = _filter.to_dict()
|
|
1232
|
+
_query = models.Q(meta__label__isnull=False) & ~models.Q(meta__label="")
|
|
1233
|
+
|
|
1234
|
+
if any(_search.get("label") or ""):
|
|
1235
|
+
_value = _search.get("label")
|
|
1236
|
+
_query = _query & models.Q(meta__label__icontains=_value)
|
|
1237
|
+
|
|
1238
|
+
if any(_search.get("keyword") or ""):
|
|
1239
|
+
_value = _search.get("keyword")
|
|
1240
|
+
_query = _query & (
|
|
1241
|
+
models.Q(meta__label__icontains=_value)
|
|
1242
|
+
| models.Q(title__icontains=_value)
|
|
1243
|
+
| models.Q(sku__icontains=_value)
|
|
1244
|
+
| models.Q(description__icontains=_value)
|
|
1245
|
+
| models.Q(hs_code__icontains=_value)
|
|
846
1246
|
)
|
|
847
1247
|
|
|
848
|
-
|
|
1248
|
+
if any(_search.get("sku") or ""):
|
|
1249
|
+
_value = _search.get("sku")
|
|
1250
|
+
_query = _query & models.Q(sku__icontains=_value)
|
|
849
1251
|
|
|
1252
|
+
if _search.get("origin_country"):
|
|
1253
|
+
_query = _query & models.Q(origin_country=_search.get("origin_country"))
|
|
850
1254
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
object_type: str
|
|
855
|
-
label: str
|
|
856
|
-
customs: CustomsType
|
|
857
|
-
is_default: typing.Optional[bool]
|
|
1255
|
+
if any(_search.get("usage") or ""):
|
|
1256
|
+
_value = _search.get("usage")
|
|
1257
|
+
_query = _query & models.Q(meta__usage__contains=_value)
|
|
858
1258
|
|
|
859
|
-
|
|
860
|
-
@utils.authentication_required
|
|
861
|
-
def resolve_list(
|
|
862
|
-
info,
|
|
863
|
-
filter: typing.Optional[inputs.TemplateFilter] = strawberry.UNSET,
|
|
864
|
-
) -> utils.Connection["CustomsTemplateType"]:
|
|
865
|
-
_filter = filter if not utils.is_unset(filter) else inputs.TemplateFilter()
|
|
1259
|
+
queryset = manager.Commodity.access_by(info.context.request).filter(_query)
|
|
866
1260
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1261
|
+
return utils.paginated_connection(queryset, **_filter.pagination())
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
@utils.authentication_required
|
|
1265
|
+
def resolve_product(info, id: str) -> typing.Optional[ProductTemplateType]:
|
|
1266
|
+
"""Resolver for getting a single saved product by ID."""
|
|
1267
|
+
return manager.Commodity.access_by(info.context.request).filter(
|
|
1268
|
+
id=id,
|
|
1269
|
+
meta__label__isnull=False,
|
|
1270
|
+
).first()
|
|
876
1271
|
|
|
877
1272
|
|
|
878
1273
|
@strawberry.type
|
|
879
1274
|
class DefaultTemplatesType:
|
|
880
|
-
|
|
881
|
-
default_customs: typing.Optional[CustomsTemplateType] = None
|
|
882
|
-
default_parcel: typing.Optional[ParcelTemplateType] = None
|
|
1275
|
+
"""Default templates type for user's default address, parcel, and product templates.
|
|
883
1276
|
|
|
884
|
-
@
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
templates = graph.Template.access_by(info.context.request).filter(
|
|
888
|
-
is_default=True
|
|
889
|
-
)
|
|
1277
|
+
Uses strawberry.Private to store the actual model instances and @strawberry.field
|
|
1278
|
+
methods to return them as the correct strawberry types, ensuring proper field resolution.
|
|
1279
|
+
"""
|
|
890
1280
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1281
|
+
_default_address: strawberry.Private[typing.Optional[manager.Address]] = None
|
|
1282
|
+
_default_parcel: strawberry.Private[typing.Optional[manager.Parcel]] = None
|
|
1283
|
+
_default_product: strawberry.Private[typing.Optional[manager.Commodity]] = None
|
|
1284
|
+
|
|
1285
|
+
@strawberry.field
|
|
1286
|
+
def default_address(self) -> typing.Optional[AddressTemplateType]:
|
|
1287
|
+
"""Returns the default address template."""
|
|
1288
|
+
return self._default_address # type: ignore
|
|
1289
|
+
|
|
1290
|
+
@strawberry.field
|
|
1291
|
+
def default_parcel(self) -> typing.Optional[ParcelTemplateType]:
|
|
1292
|
+
"""Returns the default parcel template."""
|
|
1293
|
+
return self._default_parcel # type: ignore
|
|
1294
|
+
|
|
1295
|
+
@strawberry.field
|
|
1296
|
+
def default_product(self) -> typing.Optional[ProductTemplateType]:
|
|
1297
|
+
"""Returns the default product template."""
|
|
1298
|
+
return self._default_product # type: ignore
|
|
1299
|
+
|
|
1300
|
+
|
|
1301
|
+
# Standalone resolver function for DefaultTemplatesType
|
|
1302
|
+
@utils.authentication_required
|
|
1303
|
+
def resolve_default_templates(info) -> DefaultTemplatesType:
|
|
1304
|
+
"""Resolver for getting default templates."""
|
|
1305
|
+
default_address = manager.Address.access_by(info.context.request).filter(
|
|
1306
|
+
meta__is_default=True,
|
|
1307
|
+
meta__label__isnull=False,
|
|
1308
|
+
).first()
|
|
1309
|
+
default_parcel = manager.Parcel.access_by(info.context.request).filter(
|
|
1310
|
+
meta__is_default=True,
|
|
1311
|
+
meta__label__isnull=False,
|
|
1312
|
+
).first()
|
|
1313
|
+
default_product = manager.Commodity.access_by(info.context.request).filter(
|
|
1314
|
+
meta__is_default=True,
|
|
1315
|
+
meta__label__isnull=False,
|
|
1316
|
+
).first()
|
|
1317
|
+
|
|
1318
|
+
return DefaultTemplatesType(
|
|
1319
|
+
_default_address=default_address,
|
|
1320
|
+
_default_parcel=default_parcel,
|
|
1321
|
+
_default_product=default_product,
|
|
1322
|
+
)
|
|
896
1323
|
|
|
897
1324
|
|
|
898
1325
|
@strawberry.type
|
|
@@ -945,6 +1372,33 @@ class TrackingInfoType:
|
|
|
945
1372
|
)
|
|
946
1373
|
|
|
947
1374
|
|
|
1375
|
+
@strawberry.type
|
|
1376
|
+
class CarrierSnapshotType:
|
|
1377
|
+
"""Represents a carrier snapshot stored at the time of operation."""
|
|
1378
|
+
|
|
1379
|
+
connection_id: typing.Optional[str] = None
|
|
1380
|
+
connection_type: typing.Optional[str] = None
|
|
1381
|
+
carrier_code: typing.Optional[str] = None
|
|
1382
|
+
carrier_id: typing.Optional[str] = None
|
|
1383
|
+
carrier_name: typing.Optional[str] = None
|
|
1384
|
+
test_mode: typing.Optional[bool] = None
|
|
1385
|
+
|
|
1386
|
+
@staticmethod
|
|
1387
|
+
def parse(
|
|
1388
|
+
snapshot: typing.Optional[dict],
|
|
1389
|
+
) -> typing.Optional["CarrierSnapshotType"]:
|
|
1390
|
+
if not snapshot:
|
|
1391
|
+
return None
|
|
1392
|
+
return CarrierSnapshotType(
|
|
1393
|
+
connection_id=snapshot.get("connection_id"),
|
|
1394
|
+
connection_type=snapshot.get("connection_type"),
|
|
1395
|
+
carrier_code=snapshot.get("carrier_code"),
|
|
1396
|
+
carrier_id=snapshot.get("carrier_id"),
|
|
1397
|
+
carrier_name=snapshot.get("carrier_name"),
|
|
1398
|
+
test_mode=snapshot.get("test_mode"),
|
|
1399
|
+
)
|
|
1400
|
+
|
|
1401
|
+
|
|
948
1402
|
@strawberry.type
|
|
949
1403
|
class TrackerType:
|
|
950
1404
|
id: str
|
|
@@ -965,12 +1419,12 @@ class TrackerType:
|
|
|
965
1419
|
created_by: UserType
|
|
966
1420
|
|
|
967
1421
|
@strawberry.field
|
|
968
|
-
def carrier_id(self: manager.Tracking) -> str:
|
|
969
|
-
return
|
|
1422
|
+
def carrier_id(self: manager.Tracking) -> typing.Optional[str]:
|
|
1423
|
+
return (self.carrier or {}).get("carrier_id")
|
|
970
1424
|
|
|
971
1425
|
@strawberry.field
|
|
972
|
-
def carrier_name(self: manager.Tracking) -> str:
|
|
973
|
-
return
|
|
1426
|
+
def carrier_name(self: manager.Tracking) -> typing.Optional[str]:
|
|
1427
|
+
return (self.carrier or {}).get("carrier_name")
|
|
974
1428
|
|
|
975
1429
|
@strawberry.field
|
|
976
1430
|
def info(self: manager.Tracking) -> typing.Optional[TrackingInfoType]:
|
|
@@ -987,8 +1441,8 @@ class TrackerType:
|
|
|
987
1441
|
@strawberry.field
|
|
988
1442
|
def tracking_carrier(
|
|
989
1443
|
self: manager.Tracking,
|
|
990
|
-
) -> typing.Optional[
|
|
991
|
-
return self.
|
|
1444
|
+
) -> typing.Optional[CarrierSnapshotType]:
|
|
1445
|
+
return CarrierSnapshotType.parse(self.carrier)
|
|
992
1446
|
|
|
993
1447
|
@staticmethod
|
|
994
1448
|
@utils.authentication_required
|
|
@@ -1024,12 +1478,12 @@ class ManifestType:
|
|
|
1024
1478
|
updated_at: datetime.datetime
|
|
1025
1479
|
|
|
1026
1480
|
@strawberry.field
|
|
1027
|
-
def carrier_id(self: manager.Manifest) -> str:
|
|
1028
|
-
return
|
|
1481
|
+
def carrier_id(self: manager.Manifest) -> typing.Optional[str]:
|
|
1482
|
+
return (self.carrier or {}).get("carrier_id")
|
|
1029
1483
|
|
|
1030
1484
|
@strawberry.field
|
|
1031
|
-
def carrier_name(self: manager.Manifest) -> str:
|
|
1032
|
-
return
|
|
1485
|
+
def carrier_name(self: manager.Manifest) -> typing.Optional[str]:
|
|
1486
|
+
return (self.carrier or {}).get("carrier_name")
|
|
1033
1487
|
|
|
1034
1488
|
@strawberry.field
|
|
1035
1489
|
def messages(self: manager.Manifest) -> typing.List[MessageType]:
|
|
@@ -1038,8 +1492,8 @@ class ManifestType:
|
|
|
1038
1492
|
@strawberry.field
|
|
1039
1493
|
def manifest_carrier(
|
|
1040
1494
|
self: manager.Manifest,
|
|
1041
|
-
) -> typing.Optional[
|
|
1042
|
-
return self.
|
|
1495
|
+
) -> typing.Optional[CarrierSnapshotType]:
|
|
1496
|
+
return CarrierSnapshotType.parse(self.carrier)
|
|
1043
1497
|
|
|
1044
1498
|
@staticmethod
|
|
1045
1499
|
@utils.authentication_required
|
|
@@ -1059,6 +1513,100 @@ class ManifestType:
|
|
|
1059
1513
|
return utils.paginated_connection(queryset, **_filter.pagination())
|
|
1060
1514
|
|
|
1061
1515
|
|
|
1516
|
+
@strawberry.type
|
|
1517
|
+
class PickupType:
|
|
1518
|
+
"""GraphQL type for Pickup model."""
|
|
1519
|
+
|
|
1520
|
+
id: str
|
|
1521
|
+
object_type: str
|
|
1522
|
+
confirmation_number: typing.Optional[str]
|
|
1523
|
+
pickup_date: typing.Optional[str]
|
|
1524
|
+
ready_time: typing.Optional[str]
|
|
1525
|
+
closing_time: typing.Optional[str]
|
|
1526
|
+
instruction: typing.Optional[str]
|
|
1527
|
+
package_location: typing.Optional[str]
|
|
1528
|
+
test_mode: bool
|
|
1529
|
+
options: utils.JSON
|
|
1530
|
+
metadata: utils.JSON
|
|
1531
|
+
meta: typing.Optional[utils.JSON]
|
|
1532
|
+
created_at: datetime.datetime
|
|
1533
|
+
updated_at: datetime.datetime
|
|
1534
|
+
created_by: UserType
|
|
1535
|
+
|
|
1536
|
+
@strawberry.field
|
|
1537
|
+
def pickup_type(self: manager.Pickup) -> str:
|
|
1538
|
+
"""Pickup scheduling type: one_time, daily, or recurring."""
|
|
1539
|
+
return self.pickup_type or "one_time"
|
|
1540
|
+
|
|
1541
|
+
@strawberry.field
|
|
1542
|
+
def recurrence(self: manager.Pickup) -> typing.Optional[utils.JSON]:
|
|
1543
|
+
"""Recurrence config for recurring pickups."""
|
|
1544
|
+
return self.recurrence
|
|
1545
|
+
|
|
1546
|
+
@strawberry.field
|
|
1547
|
+
def carrier_id(self: manager.Pickup) -> typing.Optional[str]:
|
|
1548
|
+
return (self.carrier or {}).get("carrier_id")
|
|
1549
|
+
|
|
1550
|
+
@strawberry.field
|
|
1551
|
+
def carrier_name(self: manager.Pickup) -> typing.Optional[str]:
|
|
1552
|
+
return (self.carrier or {}).get("carrier_name")
|
|
1553
|
+
|
|
1554
|
+
@strawberry.field
|
|
1555
|
+
def address(self: manager.Pickup) -> typing.Optional[AddressType]:
|
|
1556
|
+
return AddressType.parse(self.address) if self.address else None
|
|
1557
|
+
|
|
1558
|
+
@strawberry.field
|
|
1559
|
+
def pickup_charge(self: manager.Pickup) -> typing.Optional[ChargeType]:
|
|
1560
|
+
return ChargeType.parse(self.pickup_charge) if self.pickup_charge else None
|
|
1561
|
+
|
|
1562
|
+
@strawberry.field
|
|
1563
|
+
def parcels(self: manager.Pickup) -> typing.List[ParcelType]:
|
|
1564
|
+
"""Parcels from related shipments."""
|
|
1565
|
+
return [
|
|
1566
|
+
ParcelType.parse(p)
|
|
1567
|
+
for shipment in self.shipments.all()
|
|
1568
|
+
for p in (shipment.parcels or [])
|
|
1569
|
+
]
|
|
1570
|
+
|
|
1571
|
+
@strawberry.field
|
|
1572
|
+
def tracking_numbers(self: manager.Pickup) -> typing.List[str]:
|
|
1573
|
+
"""Tracking numbers from related shipments."""
|
|
1574
|
+
return [
|
|
1575
|
+
s.tracking_number
|
|
1576
|
+
for s in self.shipments.all()
|
|
1577
|
+
if s.tracking_number
|
|
1578
|
+
]
|
|
1579
|
+
|
|
1580
|
+
@strawberry.field
|
|
1581
|
+
def shipments(self: manager.Pickup) -> typing.List["ShipmentType"]:
|
|
1582
|
+
"""Related shipments for this pickup."""
|
|
1583
|
+
return list(self.shipments.all())
|
|
1584
|
+
|
|
1585
|
+
@strawberry.field
|
|
1586
|
+
def pickup_carrier(
|
|
1587
|
+
self: manager.Pickup,
|
|
1588
|
+
) -> typing.Optional[CarrierSnapshotType]:
|
|
1589
|
+
"""Carrier snapshot with credentials protected."""
|
|
1590
|
+
return CarrierSnapshotType.parse(self.carrier)
|
|
1591
|
+
|
|
1592
|
+
@staticmethod
|
|
1593
|
+
@utils.authentication_required
|
|
1594
|
+
def resolve(info, id: str) -> typing.Optional["PickupType"]:
|
|
1595
|
+
return manager.Pickup.access_by(info.context.request).filter(id=id).first()
|
|
1596
|
+
|
|
1597
|
+
@staticmethod
|
|
1598
|
+
@utils.authentication_required
|
|
1599
|
+
def resolve_list(
|
|
1600
|
+
info,
|
|
1601
|
+
filter: typing.Optional[inputs.PickupFilter] = strawberry.UNSET,
|
|
1602
|
+
) -> utils.Connection["PickupType"]:
|
|
1603
|
+
_filter = filter if not utils.is_unset(filter) else inputs.PickupFilter()
|
|
1604
|
+
queryset = filters.PickupFilters(
|
|
1605
|
+
_filter.to_dict(), manager.Pickup.access_by(info.context.request)
|
|
1606
|
+
).qs
|
|
1607
|
+
return utils.paginated_connection(queryset, **_filter.pagination())
|
|
1608
|
+
|
|
1609
|
+
|
|
1062
1610
|
@strawberry.type
|
|
1063
1611
|
class PaymentType:
|
|
1064
1612
|
account_number: typing.Optional[str] = None
|
|
@@ -1070,23 +1618,17 @@ class ShipmentType:
|
|
|
1070
1618
|
id: str
|
|
1071
1619
|
object_type: str
|
|
1072
1620
|
test_mode: bool
|
|
1073
|
-
shipper: AddressType
|
|
1074
|
-
recipient: AddressType
|
|
1075
1621
|
options: utils.JSON
|
|
1076
1622
|
metadata: utils.JSON
|
|
1077
1623
|
status: utils.ShipmentStatusEnum
|
|
1078
|
-
return_address: typing.Optional[AddressType]
|
|
1079
|
-
billing_address: typing.Optional[AddressType]
|
|
1080
1624
|
meta: typing.Optional[utils.JSON]
|
|
1081
1625
|
label_type: typing.Optional[utils.LabelTypeEnum]
|
|
1082
1626
|
tracking_number: typing.Optional[str]
|
|
1083
1627
|
shipment_identifier: typing.Optional[str]
|
|
1084
1628
|
tracking_url: typing.Optional[str]
|
|
1085
1629
|
reference: typing.Optional[str]
|
|
1086
|
-
customs: typing.Optional[CustomsType]
|
|
1087
1630
|
services: typing.Optional[typing.List[str]]
|
|
1088
1631
|
service: typing.Optional[str]
|
|
1089
|
-
carrier_ids: typing.List[str]
|
|
1090
1632
|
selected_rate_id: typing.Optional[str]
|
|
1091
1633
|
tracker_id: typing.Optional[str]
|
|
1092
1634
|
label_url: typing.Optional[str]
|
|
@@ -1096,17 +1638,46 @@ class ShipmentType:
|
|
|
1096
1638
|
updated_at: datetime.datetime
|
|
1097
1639
|
created_by: UserType
|
|
1098
1640
|
|
|
1641
|
+
@strawberry.field
|
|
1642
|
+
def shipper(self: manager.Shipment) -> AddressType:
|
|
1643
|
+
return AddressType.parse(self.shipper)
|
|
1644
|
+
|
|
1645
|
+
@strawberry.field
|
|
1646
|
+
def recipient(self: manager.Shipment) -> AddressType:
|
|
1647
|
+
return AddressType.parse(self.recipient)
|
|
1648
|
+
|
|
1649
|
+
@strawberry.field
|
|
1650
|
+
def return_address(self: manager.Shipment) -> typing.Optional[AddressType]:
|
|
1651
|
+
return AddressType.parse(self.return_address)
|
|
1652
|
+
|
|
1653
|
+
@strawberry.field
|
|
1654
|
+
def billing_address(self: manager.Shipment) -> typing.Optional[AddressType]:
|
|
1655
|
+
return AddressType.parse(self.billing_address)
|
|
1656
|
+
|
|
1657
|
+
@strawberry.field
|
|
1658
|
+
def customs(self: manager.Shipment) -> typing.Optional[CustomsType]:
|
|
1659
|
+
return CustomsType.parse(self.customs)
|
|
1660
|
+
|
|
1099
1661
|
@strawberry.field
|
|
1100
1662
|
def carrier_id(self: manager.Shipment) -> typing.Optional[str]:
|
|
1101
|
-
|
|
1663
|
+
if self.selected_rate is None:
|
|
1664
|
+
return None
|
|
1665
|
+
return self.selected_rate.get("carrier_id")
|
|
1102
1666
|
|
|
1103
1667
|
@strawberry.field
|
|
1104
1668
|
def carrier_name(self: manager.Shipment) -> typing.Optional[str]:
|
|
1105
|
-
|
|
1669
|
+
if self.selected_rate is None:
|
|
1670
|
+
return None
|
|
1671
|
+
return self.selected_rate.get("carrier_name")
|
|
1672
|
+
|
|
1673
|
+
@strawberry.field
|
|
1674
|
+
def carrier_ids(self: manager.Shipment) -> typing.List[str]:
|
|
1675
|
+
return self.carrier_ids or []
|
|
1106
1676
|
|
|
1107
1677
|
@strawberry.field
|
|
1108
1678
|
def parcels(self: manager.Shipment) -> typing.List[ParcelType]:
|
|
1109
|
-
return
|
|
1679
|
+
# parcels is now a JSON field, return parsed ParcelType objects
|
|
1680
|
+
return [ParcelType.parse(p) for p in (self.parcels or [])]
|
|
1110
1681
|
|
|
1111
1682
|
@strawberry.field
|
|
1112
1683
|
def rates(self: manager.Shipment) -> typing.List[RateType]:
|
|
@@ -1119,8 +1690,10 @@ class ShipmentType:
|
|
|
1119
1690
|
@strawberry.field
|
|
1120
1691
|
def selected_rate_carrier(
|
|
1121
1692
|
self: manager.Shipment,
|
|
1122
|
-
) -> typing.Optional[
|
|
1123
|
-
|
|
1693
|
+
) -> typing.Optional[CarrierSnapshotType]:
|
|
1694
|
+
if self.carrier is None:
|
|
1695
|
+
return None
|
|
1696
|
+
return CarrierSnapshotType.parse(self.carrier)
|
|
1124
1697
|
|
|
1125
1698
|
@strawberry.field
|
|
1126
1699
|
def payment(self: manager.Shipment) -> typing.Optional[PaymentType]:
|
|
@@ -1261,6 +1834,74 @@ class ServiceRateType:
|
|
|
1261
1834
|
)
|
|
1262
1835
|
|
|
1263
1836
|
|
|
1837
|
+
@strawberry.type
|
|
1838
|
+
class ServiceLevelFeaturesType:
|
|
1839
|
+
"""Structured service level features.
|
|
1840
|
+
|
|
1841
|
+
Defines the capabilities and characteristics of a shipping service.
|
|
1842
|
+
Used for filtering, display, and setting default options.
|
|
1843
|
+
"""
|
|
1844
|
+
|
|
1845
|
+
# First Mile: How parcels get to the carrier
|
|
1846
|
+
# "pick_up" | "drop_off" | "pick_up_and_drop_off"
|
|
1847
|
+
first_mile: typing.Optional[str] = None
|
|
1848
|
+
|
|
1849
|
+
# Last Mile: How parcels are delivered to recipient
|
|
1850
|
+
# "home_delivery" | "service_point" | "mailbox"
|
|
1851
|
+
last_mile: typing.Optional[str] = None
|
|
1852
|
+
|
|
1853
|
+
# Form Factor: Type of package the service supports
|
|
1854
|
+
# "letter" | "parcel" | "mailbox" | "pallet"
|
|
1855
|
+
form_factor: typing.Optional[str] = None
|
|
1856
|
+
|
|
1857
|
+
# Type of Shipments: Business model support
|
|
1858
|
+
b2c: typing.Optional[bool] = None # Business to Consumer
|
|
1859
|
+
b2b: typing.Optional[bool] = None # Business to Business
|
|
1860
|
+
|
|
1861
|
+
# Shipment Direction: "outbound" | "returns" | "both"
|
|
1862
|
+
shipment_type: typing.Optional[str] = None
|
|
1863
|
+
|
|
1864
|
+
# Age Verification: null | "16" | "18"
|
|
1865
|
+
age_check: typing.Optional[str] = None
|
|
1866
|
+
|
|
1867
|
+
# Default signature requirement
|
|
1868
|
+
signature: typing.Optional[bool] = None
|
|
1869
|
+
|
|
1870
|
+
# Tracking availability
|
|
1871
|
+
tracked: typing.Optional[bool] = None
|
|
1872
|
+
|
|
1873
|
+
# Insurance availability
|
|
1874
|
+
insurance: typing.Optional[bool] = None
|
|
1875
|
+
|
|
1876
|
+
# Express/Priority service
|
|
1877
|
+
express: typing.Optional[bool] = None
|
|
1878
|
+
|
|
1879
|
+
# Dangerous goods support
|
|
1880
|
+
dangerous_goods: typing.Optional[bool] = None
|
|
1881
|
+
|
|
1882
|
+
# Weekend delivery options
|
|
1883
|
+
saturday_delivery: typing.Optional[bool] = None
|
|
1884
|
+
sunday_delivery: typing.Optional[bool] = None
|
|
1885
|
+
|
|
1886
|
+
# Multi-package shipment support
|
|
1887
|
+
multicollo: typing.Optional[bool] = None
|
|
1888
|
+
|
|
1889
|
+
# Neighbor delivery allowed
|
|
1890
|
+
neighbor_delivery: typing.Optional[bool] = None
|
|
1891
|
+
|
|
1892
|
+
@staticmethod
|
|
1893
|
+
def parse(features: typing.Optional[dict]) -> "ServiceLevelFeaturesType":
|
|
1894
|
+
"""Parse a features dict into ServiceLevelFeaturesType."""
|
|
1895
|
+
if not features or not isinstance(features, dict):
|
|
1896
|
+
return ServiceLevelFeaturesType()
|
|
1897
|
+
|
|
1898
|
+
import dataclasses
|
|
1899
|
+
field_names = {f.name for f in dataclasses.fields(ServiceLevelFeaturesType)}
|
|
1900
|
+
return ServiceLevelFeaturesType(**{
|
|
1901
|
+
k: v for k, v in features.items() if k in field_names
|
|
1902
|
+
})
|
|
1903
|
+
|
|
1904
|
+
|
|
1264
1905
|
@strawberry.type
|
|
1265
1906
|
class ServiceLevelType:
|
|
1266
1907
|
"""
|
|
@@ -1294,9 +1935,18 @@ class ServiceLevelType:
|
|
|
1294
1935
|
max_volume: typing.Optional[float]
|
|
1295
1936
|
cost: typing.Optional[float]
|
|
1296
1937
|
|
|
1938
|
+
# Volumetric weight fields
|
|
1939
|
+
dim_factor: typing.Optional[float]
|
|
1940
|
+
use_volumetric: typing.Optional[bool]
|
|
1941
|
+
|
|
1297
1942
|
domicile: typing.Optional[bool]
|
|
1298
1943
|
international: typing.Optional[bool]
|
|
1299
1944
|
|
|
1945
|
+
@strawberry.field
|
|
1946
|
+
def features(self: providers.ServiceLevel) -> ServiceLevelFeaturesType:
|
|
1947
|
+
"""Structured service features."""
|
|
1948
|
+
return ServiceLevelFeaturesType.parse(self.features)
|
|
1949
|
+
|
|
1300
1950
|
@strawberry.field
|
|
1301
1951
|
def metadata(self: providers.ServiceLevel) -> typing.Optional[utils.JSON]:
|
|
1302
1952
|
try:
|
|
@@ -1335,6 +1985,10 @@ class RateSheetType:
|
|
|
1335
1985
|
slug: str
|
|
1336
1986
|
carrier_name: utils.CarrierNameEnum
|
|
1337
1987
|
|
|
1988
|
+
@strawberry.field
|
|
1989
|
+
def origin_countries(self: providers.RateSheet) -> typing.Optional[typing.List[str]]:
|
|
1990
|
+
return self.origin_countries or []
|
|
1991
|
+
|
|
1338
1992
|
@strawberry.field
|
|
1339
1993
|
def metadata(self: providers.RateSheet) -> typing.Optional[utils.JSON]:
|
|
1340
1994
|
try:
|
|
@@ -1401,29 +2055,61 @@ class RateSheetType:
|
|
|
1401
2055
|
|
|
1402
2056
|
@strawberry.type
|
|
1403
2057
|
class SystemConnectionType:
|
|
2058
|
+
"""Represents a SystemConnection that can be enabled by users via BrokeredConnection."""
|
|
2059
|
+
|
|
1404
2060
|
id: str
|
|
1405
|
-
active: bool
|
|
1406
2061
|
carrier_id: str
|
|
2062
|
+
carrier_code: str
|
|
1407
2063
|
display_name: str
|
|
1408
2064
|
test_mode: bool
|
|
1409
2065
|
capabilities: typing.List[str]
|
|
1410
2066
|
created_at: typing.Optional[datetime.datetime]
|
|
1411
2067
|
updated_at: typing.Optional[datetime.datetime]
|
|
1412
2068
|
|
|
1413
|
-
|
|
1414
|
-
def carrier_name(self: providers.Carrier) -> str:
|
|
1415
|
-
return getattr(self, "settings", self).carrier_name
|
|
1416
|
-
|
|
1417
|
-
@strawberry.field
|
|
1418
|
-
def enabled(self: providers.Carrier, info: Info) -> bool:
|
|
1419
|
-
if hasattr(self, "active_orgs"):
|
|
1420
|
-
return self.active_orgs.filter(id=info.context.request.org.id).exists()
|
|
1421
|
-
|
|
1422
|
-
return self.active_users.filter(id=info.context.request.user.id).exists()
|
|
2069
|
+
active: bool
|
|
1423
2070
|
|
|
1424
2071
|
@strawberry.field
|
|
1425
|
-
def
|
|
1426
|
-
return
|
|
2072
|
+
def carrier_name(self: providers.SystemConnection) -> str:
|
|
2073
|
+
return self.carrier_code
|
|
2074
|
+
|
|
2075
|
+
@strawberry.field
|
|
2076
|
+
def enabled(self: providers.SystemConnection, info: Info) -> bool:
|
|
2077
|
+
"""Check if this SystemConnection is enabled for the current user/org."""
|
|
2078
|
+
if settings.MULTI_ORGANIZATIONS:
|
|
2079
|
+
org_id = getattr(info.context.request, "org", None)
|
|
2080
|
+
org_id = org_id.id if org_id else None
|
|
2081
|
+
return providers.BrokeredConnection.objects.filter(
|
|
2082
|
+
system_connection=self,
|
|
2083
|
+
is_enabled=True,
|
|
2084
|
+
link__org__id=org_id,
|
|
2085
|
+
).exists()
|
|
2086
|
+
else:
|
|
2087
|
+
user_id = getattr(info.context.request, "user", None)
|
|
2088
|
+
user_id = user_id.id if user_id else None
|
|
2089
|
+
return providers.BrokeredConnection.objects.filter(
|
|
2090
|
+
system_connection=self,
|
|
2091
|
+
is_enabled=True,
|
|
2092
|
+
created_by__id=user_id,
|
|
2093
|
+
).exists()
|
|
2094
|
+
|
|
2095
|
+
@strawberry.field
|
|
2096
|
+
def config(self: providers.SystemConnection, info: Info) -> typing.Optional[utils.JSON]:
|
|
2097
|
+
"""Get the user's config for this SystemConnection from their BrokeredConnection."""
|
|
2098
|
+
if settings.MULTI_ORGANIZATIONS:
|
|
2099
|
+
org_id = getattr(info.context.request, "org", None)
|
|
2100
|
+
org_id = org_id.id if org_id else None
|
|
2101
|
+
brokered = providers.BrokeredConnection.objects.filter(
|
|
2102
|
+
system_connection=self,
|
|
2103
|
+
link__org__id=org_id,
|
|
2104
|
+
).first()
|
|
2105
|
+
else:
|
|
2106
|
+
user_id = getattr(info.context.request, "user", None)
|
|
2107
|
+
user_id = user_id.id if user_id else None
|
|
2108
|
+
brokered = providers.BrokeredConnection.objects.filter(
|
|
2109
|
+
system_connection=self,
|
|
2110
|
+
created_by__id=user_id,
|
|
2111
|
+
).first()
|
|
2112
|
+
return brokered.config if brokered else None
|
|
1427
2113
|
|
|
1428
2114
|
@staticmethod
|
|
1429
2115
|
@utils.authentication_required
|
|
@@ -1432,42 +2118,46 @@ class SystemConnectionType:
|
|
|
1432
2118
|
filter: typing.Optional[inputs.CarrierFilter] = strawberry.UNSET,
|
|
1433
2119
|
) -> utils.Connection["SystemConnectionType"]:
|
|
1434
2120
|
_filter = filter if not utils.is_unset(filter) else inputs.CarrierFilter()
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
).qs
|
|
1444
|
-
return utils.paginated_connection(connections, **_filter.pagination())
|
|
2121
|
+
queryset = providers.SystemConnection.objects.filter(
|
|
2122
|
+
active=True,
|
|
2123
|
+
test_mode=getattr(info.context.request, "test_mode", False),
|
|
2124
|
+
)
|
|
2125
|
+
# Apply carrier filter if specified
|
|
2126
|
+
if _filter.carrier_name:
|
|
2127
|
+
queryset = queryset.filter(carrier_code=_filter.carrier_name)
|
|
2128
|
+
return utils.paginated_connection(queryset, **_filter.pagination())
|
|
1445
2129
|
|
|
1446
2130
|
|
|
1447
2131
|
@strawberry.type
|
|
1448
2132
|
class CarrierConnectionType:
|
|
2133
|
+
"""GraphQL type for carrier connections."""
|
|
2134
|
+
|
|
1449
2135
|
id: str
|
|
1450
2136
|
carrier_id: str
|
|
2137
|
+
carrier_code: str
|
|
1451
2138
|
carrier_name: str
|
|
1452
2139
|
display_name: str
|
|
1453
2140
|
active: bool
|
|
1454
|
-
is_system: bool
|
|
1455
2141
|
test_mode: bool
|
|
1456
|
-
credentials: utils.JSON
|
|
1457
2142
|
capabilities: typing.List[str]
|
|
1458
|
-
rate_sheet: typing.Optional[RateSheetType] = None
|
|
1459
2143
|
|
|
1460
2144
|
@strawberry.field
|
|
1461
|
-
def
|
|
2145
|
+
def credentials(self: providers.CarrierConnection, info: Info) -> utils.JSON:
|
|
2146
|
+
return self.credentials
|
|
2147
|
+
|
|
2148
|
+
@strawberry.field
|
|
2149
|
+
def metadata(self: providers.CarrierConnection, info: Info) -> typing.Optional[utils.JSON]:
|
|
1462
2150
|
return getattr(self, "metadata", None)
|
|
1463
2151
|
|
|
1464
2152
|
@strawberry.field
|
|
1465
|
-
def config(self: providers.
|
|
2153
|
+
def config(self: providers.CarrierConnection, info: Info) -> typing.Optional[utils.JSON]:
|
|
1466
2154
|
return getattr(self, "config", None)
|
|
1467
2155
|
|
|
2156
|
+
@strawberry.field
|
|
1468
2157
|
def rate_sheet(
|
|
1469
|
-
self: providers.
|
|
2158
|
+
self: providers.CarrierConnection, info: Info
|
|
1470
2159
|
) -> typing.Optional[RateSheetType]:
|
|
2160
|
+
# Access rate_sheet FK from the Django model
|
|
1471
2161
|
return getattr(self, "rate_sheet", None)
|
|
1472
2162
|
|
|
1473
2163
|
@staticmethod
|
|
@@ -1478,9 +2168,10 @@ class CarrierConnectionType:
|
|
|
1478
2168
|
filter: typing.Optional[inputs.CarrierFilter] = strawberry.UNSET,
|
|
1479
2169
|
) -> typing.List["CarrierConnectionType"]:
|
|
1480
2170
|
_filter = filter if not utils.is_unset(filter) else inputs.CarrierFilter()
|
|
2171
|
+
# Carrier model now only contains user/org-owned connections (no is_system filter needed)
|
|
1481
2172
|
connections = filters.CarrierFilters(
|
|
1482
2173
|
_filter.to_dict(),
|
|
1483
|
-
providers.
|
|
2174
|
+
providers.CarrierConnection.access_by(info.context.request),
|
|
1484
2175
|
).qs
|
|
1485
2176
|
return connections
|
|
1486
2177
|
|
|
@@ -1492,7 +2183,7 @@ class CarrierConnectionType:
|
|
|
1492
2183
|
id: str,
|
|
1493
2184
|
) -> typing.Optional["CarrierConnectionType"]:
|
|
1494
2185
|
connection = (
|
|
1495
|
-
providers.
|
|
2186
|
+
providers.CarrierConnection.access_by(info.context.request).filter(id=id).first()
|
|
1496
2187
|
)
|
|
1497
2188
|
return connection
|
|
1498
2189
|
|
|
@@ -1504,9 +2195,10 @@ class CarrierConnectionType:
|
|
|
1504
2195
|
filter: typing.Optional[inputs.CarrierFilter] = strawberry.UNSET,
|
|
1505
2196
|
) -> utils.Connection["CarrierConnectionType"]:
|
|
1506
2197
|
_filter = filter if not utils.is_unset(filter) else inputs.CarrierFilter()
|
|
2198
|
+
# Carrier model now only contains user/org-owned connections (no is_system filter needed)
|
|
1507
2199
|
queryset = filters.CarrierFilters(
|
|
1508
2200
|
_filter.to_dict(),
|
|
1509
|
-
providers.
|
|
2201
|
+
providers.CarrierConnection.access_by(info.context.request),
|
|
1510
2202
|
).qs
|
|
1511
2203
|
connections = utils.paginated_connection(queryset, **_filter.pagination())
|
|
1512
2204
|
|