karrio-server-graph 2025.5.5__py3-none-any.whl → 2025.5.6__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.
@@ -204,7 +204,11 @@ class RequestEmailChangeMutation(utils.BaseMutation):
204
204
  expiry=(datetime.datetime.now() + datetime.timedelta(hours=2)),
205
205
  )
206
206
  except Exception as e:
207
- logger.exception("Email change request failed", user_id=info.context.request.user.id, error=str(e))
207
+ logger.exception(
208
+ "Email change request failed",
209
+ user_id=info.context.request.user.id,
210
+ error=str(e),
211
+ )
208
212
  raise e
209
213
 
210
214
  return RequestEmailChangeMutation(user=info.context.request.user) # type:ignore
@@ -263,7 +267,9 @@ class RegisterUserMutation(utils.BaseMutation):
263
267
 
264
268
  return RegisterUserMutation(user=user) # type:ignore
265
269
  except Exception as e:
266
- logger.exception("User registration failed", email=input.get('email'), error=str(e))
270
+ logger.exception(
271
+ "User registration failed", email=input.get("email"), error=str(e)
272
+ )
267
273
  raise e
268
274
 
269
275
 
@@ -505,16 +511,24 @@ class CreateRateSheetMutation(utils.BaseMutation):
505
511
  ) -> "CreateRateSheetMutation":
506
512
  data = input.copy()
507
513
  carriers = data.pop("carriers", [])
514
+ zones_data = data.pop("zones", [])
515
+ surcharges_data = data.pop("surcharges", [])
516
+ service_rates_data = data.pop("service_rates", [])
517
+ services_data = [
518
+ (svc.copy() if isinstance(svc, dict) else dict(svc))
519
+ for svc in data.get("services", [])
520
+ ]
521
+
508
522
  slug = f"{input.get('name', '').lower()}_sheet".replace(" ", "").lower()
509
523
  serializer = serializers.RateSheetModelSerializer(
510
524
  data={**data, "slug": slug},
511
525
  context=info.context.request,
512
526
  )
513
-
514
527
  serializer.is_valid(raise_exception=True)
515
528
  rate_sheet = serializer.save()
516
529
 
517
- if "services" in data:
530
+ # Create services and build temp-to-real ID mapping
531
+ if services_data:
518
532
  save_many_to_many_data(
519
533
  "services",
520
534
  serializers.ServiceLevelModelSerializer,
@@ -522,16 +536,24 @@ class CreateRateSheetMutation(utils.BaseMutation):
522
536
  payload=data,
523
537
  context=info.context.request,
524
538
  )
539
+ rate_sheet.refresh_from_db()
540
+
541
+ temp_to_real_id = serializer.build_temp_to_real_service_map(services_data)
525
542
 
543
+ # Link carriers
526
544
  if any(carriers):
527
- (
528
- providers.Carrier.access_by(info.context.request)
529
- .filter(
530
- carrier_code=rate_sheet.carrier_name,
531
- id__in=carriers,
532
- )
533
- .update(rate_sheet=rate_sheet)
534
- )
545
+ providers.Carrier.access_by(info.context.request).filter(
546
+ carrier_code=rate_sheet.carrier_name,
547
+ id__in=carriers,
548
+ ).update(rate_sheet=rate_sheet)
549
+
550
+ # Process zones, surcharges, and service_rates via serializer
551
+ if zones_data:
552
+ serializer.process_zones(zones_data)
553
+ if surcharges_data:
554
+ serializer.process_surcharges(surcharges_data)
555
+ if service_rates_data:
556
+ serializer.process_service_rates(service_rates_data, temp_to_real_id)
535
557
 
536
558
  return CreateRateSheetMutation(rate_sheet=rate_sheet)
537
559
 
@@ -551,17 +573,17 @@ class UpdateRateSheetMutation(utils.BaseMutation):
551
573
  )
552
574
  data = input.copy()
553
575
  carriers = data.pop("carriers", [])
576
+
554
577
  serializer = serializers.RateSheetModelSerializer(
555
578
  instance,
556
579
  data=data,
557
580
  context=info.context.request,
558
581
  partial=True,
559
582
  )
560
-
561
583
  serializer.is_valid(raise_exception=True)
562
584
  rate_sheet = serializer.save()
563
585
 
564
- # Handle services updates like in CreateRateSheetMutation
586
+ # Handle services updates
565
587
  if "services" in data:
566
588
  save_many_to_many_data(
567
589
  "services",
@@ -571,25 +593,14 @@ class UpdateRateSheetMutation(utils.BaseMutation):
571
593
  context=info.context.request,
572
594
  )
573
595
 
596
+ # Link/unlink carriers
574
597
  if any(carriers):
575
- # Link listed carriers to rate sheet
576
- (
577
- providers.Carrier.access_by(info.context.request)
578
- .filter(
579
- carrier_code=rate_sheet.carrier_name,
580
- id__in=carriers,
581
- )
582
- .update(rate_sheet=rate_sheet)
598
+ carrier_qs = providers.Carrier.access_by(info.context.request).filter(
599
+ carrier_code=rate_sheet.carrier_name
583
600
  )
584
- # Unlink missing carriers from rate sheet
585
- (
586
- providers.Carrier.access_by(info.context.request)
587
- .filter(
588
- carrier_code=rate_sheet.carrier_name,
589
- rate_sheet=rate_sheet,
590
- )
591
- .exclude(id__in=carriers)
592
- .update(rate_sheet=None)
601
+ carrier_qs.filter(id__in=carriers).update(rate_sheet=rate_sheet)
602
+ carrier_qs.filter(rate_sheet=rate_sheet).exclude(id__in=carriers).update(
603
+ rate_sheet=None
593
604
  )
594
605
 
595
606
  return UpdateRateSheetMutation(
@@ -598,115 +609,317 @@ class UpdateRateSheetMutation(utils.BaseMutation):
598
609
 
599
610
 
600
611
  @strawberry.type
601
- class UpdateRateSheetZoneCellMutation(utils.BaseMutation):
612
+ class DeleteRateSheetServiceMutation(utils.BaseMutation):
602
613
  rate_sheet: typing.Optional[types.RateSheetType] = None
603
614
 
604
615
  @staticmethod
605
616
  @transaction.atomic
606
617
  @utils.authentication_required
607
618
  def mutate(
608
- info: Info, **input: inputs.UpdateRateSheetZoneCellMutationInput
609
- ) -> "UpdateRateSheetZoneCellMutation":
619
+ info: Info, **input: inputs.DeleteRateSheetServiceMutationInput
620
+ ) -> "DeleteRateSheetServiceMutation":
610
621
  rate_sheet = providers.RateSheet.access_by(info.context.request).get(
611
- id=input["id"]
622
+ id=input["rate_sheet_id"]
612
623
  )
613
624
  service = rate_sheet.services.get(id=input["service_id"])
614
625
 
626
+ # Remove service from rate sheet and delete it
627
+ rate_sheet.services.remove(service)
628
+ service.delete()
629
+
630
+ return DeleteRateSheetServiceMutation(rate_sheet=rate_sheet)
631
+
632
+
633
+ # ─────────────────────────────────────────────────────────────────────────────
634
+ # SHARED ZONE MUTATIONS (Rate Sheet Level)
635
+ # ─────────────────────────────────────────────────────────────────────────────
636
+
637
+
638
+ @strawberry.type
639
+ class AddSharedZoneMutation(utils.BaseMutation):
640
+ rate_sheet: typing.Optional[types.RateSheetType] = None
641
+
642
+ @staticmethod
643
+ @transaction.atomic
644
+ @utils.authentication_required
645
+ def mutate(
646
+ info: Info, **input: inputs.AddSharedZoneMutationInput
647
+ ) -> "AddSharedZoneMutation":
648
+ rate_sheet = providers.RateSheet.access_by(info.context.request).get(
649
+ id=input["rate_sheet_id"]
650
+ )
651
+ zone_data = input["zone"]
652
+ zone_dict = {k: v for k, v in zone_data.items() if not utils.is_unset(v)}
653
+
615
654
  try:
616
- service.update_zone_cell(
617
- zone_id=input["zone_id"], field=input["field"], value=input["value"]
618
- )
655
+ rate_sheet.add_zone(zone_dict)
619
656
  except ValueError as e:
620
- logger.error("Invalid zone ID", zone_id=input["zone_id"], error=str(e))
621
- raise exceptions.ValidationError({"zone_id": "invalid zone id"})
657
+ raise exceptions.ValidationError({"zone": str(e)})
622
658
 
623
- return UpdateRateSheetZoneCellMutation(rate_sheet=rate_sheet)
659
+ return AddSharedZoneMutation(rate_sheet=rate_sheet)
624
660
 
625
661
 
626
662
  @strawberry.type
627
- class BatchUpdateRateSheetCellsMutation(utils.BaseMutation):
663
+ class UpdateSharedZoneMutation(utils.BaseMutation):
628
664
  rate_sheet: typing.Optional[types.RateSheetType] = None
629
665
 
630
666
  @staticmethod
631
667
  @transaction.atomic
632
668
  @utils.authentication_required
633
669
  def mutate(
634
- info: Info, **input: inputs.BatchUpdateRateSheetCellsMutationInput
635
- ) -> "BatchUpdateRateSheetCellsMutation":
670
+ info: Info, **input: inputs.UpdateSharedZoneMutationInput
671
+ ) -> "UpdateSharedZoneMutation":
636
672
  rate_sheet = providers.RateSheet.access_by(info.context.request).get(
637
- id=input["id"]
673
+ id=input["rate_sheet_id"]
638
674
  )
675
+ zone_data = input["zone"]
676
+ zone_dict = {k: v for k, v in zone_data.items() if not utils.is_unset(v)}
677
+
678
+ try:
679
+ rate_sheet.update_zone(input["zone_id"], zone_dict)
680
+ except ValueError as e:
681
+ raise exceptions.ValidationError({"zone_id": str(e)})
639
682
 
640
- # Group updates by service_id for efficient processing
641
- service_updates = {}
642
- for update in input["updates"]:
643
- service_id = update["service_id"]
644
- if service_id not in service_updates:
645
- service_updates[service_id] = []
646
- service_updates[service_id].append(
647
- {
648
- "zone_id": update["zone_id"],
649
- "field": update["field"],
650
- "value": update["value"],
651
- }
683
+ return UpdateSharedZoneMutation(rate_sheet=rate_sheet)
684
+
685
+
686
+ @strawberry.type
687
+ class DeleteSharedZoneMutation(utils.BaseMutation):
688
+ rate_sheet: typing.Optional[types.RateSheetType] = None
689
+
690
+ @staticmethod
691
+ @transaction.atomic
692
+ @utils.authentication_required
693
+ def mutate(
694
+ info: Info, **input: inputs.DeleteSharedZoneMutationInput
695
+ ) -> "DeleteSharedZoneMutation":
696
+ rate_sheet = providers.RateSheet.access_by(info.context.request).get(
697
+ id=input["rate_sheet_id"]
698
+ )
699
+
700
+ try:
701
+ rate_sheet.remove_zone(input["zone_id"])
702
+ except ValueError as e:
703
+ raise exceptions.ValidationError({"zone_id": str(e)})
704
+
705
+ return DeleteSharedZoneMutation(rate_sheet=rate_sheet)
706
+
707
+
708
+ # ─────────────────────────────────────────────────────────────────────────────
709
+ # SHARED SURCHARGE MUTATIONS (Rate Sheet Level)
710
+ # ─────────────────────────────────────────────────────────────────────────────
711
+
712
+
713
+ @strawberry.type
714
+ class AddSharedSurchargeMutation(utils.BaseMutation):
715
+ rate_sheet: typing.Optional[types.RateSheetType] = None
716
+
717
+ @staticmethod
718
+ @transaction.atomic
719
+ @utils.authentication_required
720
+ def mutate(
721
+ info: Info, **input: inputs.AddSharedSurchargeMutationInput
722
+ ) -> "AddSharedSurchargeMutation":
723
+ rate_sheet = providers.RateSheet.access_by(info.context.request).get(
724
+ id=input["rate_sheet_id"]
725
+ )
726
+ surcharge_data = input["surcharge"]
727
+ surcharge_dict = {k: v for k, v in surcharge_data.items() if not utils.is_unset(v)}
728
+
729
+ try:
730
+ rate_sheet.add_surcharge(surcharge_dict)
731
+ except ValueError as e:
732
+ raise exceptions.ValidationError({"surcharge": str(e)})
733
+
734
+ return AddSharedSurchargeMutation(rate_sheet=rate_sheet)
735
+
736
+
737
+ @strawberry.type
738
+ class UpdateSharedSurchargeMutation(utils.BaseMutation):
739
+ rate_sheet: typing.Optional[types.RateSheetType] = None
740
+
741
+ @staticmethod
742
+ @transaction.atomic
743
+ @utils.authentication_required
744
+ def mutate(
745
+ info: Info, **input: inputs.UpdateSharedSurchargeMutationInput
746
+ ) -> "UpdateSharedSurchargeMutation":
747
+ rate_sheet = providers.RateSheet.access_by(info.context.request).get(
748
+ id=input["rate_sheet_id"]
749
+ )
750
+ surcharge_data = input["surcharge"]
751
+ surcharge_dict = {k: v for k, v in surcharge_data.items() if not utils.is_unset(v)}
752
+
753
+ try:
754
+ rate_sheet.update_surcharge(input["surcharge_id"], surcharge_dict)
755
+ except ValueError as e:
756
+ raise exceptions.ValidationError({"surcharge_id": str(e)})
757
+
758
+ return UpdateSharedSurchargeMutation(rate_sheet=rate_sheet)
759
+
760
+
761
+ @strawberry.type
762
+ class DeleteSharedSurchargeMutation(utils.BaseMutation):
763
+ rate_sheet: typing.Optional[types.RateSheetType] = None
764
+
765
+ @staticmethod
766
+ @transaction.atomic
767
+ @utils.authentication_required
768
+ def mutate(
769
+ info: Info, **input: inputs.DeleteSharedSurchargeMutationInput
770
+ ) -> "DeleteSharedSurchargeMutation":
771
+ rate_sheet = providers.RateSheet.access_by(info.context.request).get(
772
+ id=input["rate_sheet_id"]
773
+ )
774
+
775
+ try:
776
+ rate_sheet.remove_surcharge(input["surcharge_id"])
777
+ except ValueError as e:
778
+ raise exceptions.ValidationError({"surcharge_id": str(e)})
779
+
780
+ return DeleteSharedSurchargeMutation(rate_sheet=rate_sheet)
781
+
782
+
783
+ @strawberry.type
784
+ class BatchUpdateSurchargesMutation(utils.BaseMutation):
785
+ rate_sheet: typing.Optional[types.RateSheetType] = None
786
+
787
+ @staticmethod
788
+ @transaction.atomic
789
+ @utils.authentication_required
790
+ def mutate(
791
+ info: Info, **input: inputs.BatchUpdateSurchargesMutationInput
792
+ ) -> "BatchUpdateSurchargesMutation":
793
+ rate_sheet = providers.RateSheet.access_by(info.context.request).get(
794
+ id=input["rate_sheet_id"]
795
+ )
796
+ surcharges = [
797
+ {k: v for k, v in s.items() if not utils.is_unset(v)}
798
+ for s in input["surcharges"]
799
+ ]
800
+
801
+ try:
802
+ rate_sheet.batch_update_surcharges(surcharges)
803
+ except ValueError as e:
804
+ raise exceptions.ValidationError({"surcharges": str(e)})
805
+
806
+ return BatchUpdateSurchargesMutation(rate_sheet=rate_sheet)
807
+
808
+
809
+ # ─────────────────────────────────────────────────────────────────────────────
810
+ # SERVICE RATE MUTATIONS (Service-Zone Rate Mapping)
811
+ # ─────────────────────────────────────────────────────────────────────────────
812
+
813
+
814
+ @strawberry.type
815
+ class UpdateServiceRateMutation(utils.BaseMutation):
816
+ rate_sheet: typing.Optional[types.RateSheetType] = None
817
+
818
+ @staticmethod
819
+ @transaction.atomic
820
+ @utils.authentication_required
821
+ def mutate(
822
+ info: Info, **input: inputs.UpdateServiceRateMutationInput
823
+ ) -> "UpdateServiceRateMutation":
824
+ rate_sheet = providers.RateSheet.access_by(info.context.request).get(
825
+ id=input["rate_sheet_id"]
826
+ )
827
+
828
+ # Build the rate update dict from input fields
829
+ rate_data = {}
830
+ rate_fields = ["rate", "cost", "min_weight", "max_weight", "transit_days", "transit_time"]
831
+ for field in rate_fields:
832
+ if field in input and not utils.is_unset(input[field]):
833
+ rate_data[field] = input[field]
834
+
835
+ # Update or create the service-zone rate mapping
836
+ try:
837
+ rate_sheet.update_service_rate(
838
+ service_id=input["service_id"],
839
+ zone_id=input["zone_id"],
840
+ rate_data=rate_data
652
841
  )
842
+ except ValueError as e:
843
+ raise exceptions.ValidationError({"rate": str(e)})
653
844
 
654
- # Use optimized structure if available, otherwise fall back to legacy
655
- if rate_sheet.zones is not None and rate_sheet.service_rates is not None:
656
- # Use optimized batch update on rate sheet
657
- all_updates = []
658
- for service_id, updates in service_updates.items():
659
- for update in updates:
660
- all_updates.append(
661
- {
662
- "service_id": service_id,
663
- "zone_id": update["zone_id"],
664
- "field": update["field"],
665
- "value": update["value"],
666
- }
667
- )
668
- try:
669
- rate_sheet.batch_update_service_rates(all_updates)
670
- except Exception as e:
671
- logger.error("Failed to batch update rate sheet", rate_sheet_id=input["id"], error=str(e))
672
- raise exceptions.ValidationError(
673
- {"rate_sheet": "failed to update rate sheet"}
674
- )
675
- else:
676
- # Fall back to legacy per-service updates
677
- for service_id, updates in service_updates.items():
678
- try:
679
- service = rate_sheet.services.get(id=service_id)
680
- service.batch_update_cells(updates)
681
- except ValueError as e:
682
- logger.error("Failed to update service", service_id=service_id, error=str(e))
683
- raise exceptions.ValidationError(
684
- {"service_id": "failed to update service"}
685
- )
845
+ return UpdateServiceRateMutation(rate_sheet=rate_sheet)
846
+
847
+
848
+ @strawberry.type
849
+ class BatchUpdateServiceRatesMutation(utils.BaseMutation):
850
+ rate_sheet: typing.Optional[types.RateSheetType] = None
851
+
852
+ @staticmethod
853
+ @transaction.atomic
854
+ @utils.authentication_required
855
+ def mutate(
856
+ info: Info, **input: inputs.BatchUpdateServiceRatesMutationInput
857
+ ) -> "BatchUpdateServiceRatesMutation":
858
+ rate_sheet = providers.RateSheet.access_by(info.context.request).get(
859
+ id=input["rate_sheet_id"]
860
+ )
686
861
 
687
- return BatchUpdateRateSheetCellsMutation(rate_sheet=rate_sheet)
862
+ # Convert rates to the format expected by batch_update_service_rates
863
+ # Format: [{'service_id': str, 'zone_id': str, 'rate': float, 'cost': float, ...}]
864
+ updates = []
865
+
866
+ for rate in input["rates"]:
867
+ rate_dict = {k: v for k, v in rate.items() if not utils.is_unset(v)}
868
+ updates.append(rate_dict)
869
+
870
+ try:
871
+ rate_sheet.batch_update_service_rates(updates)
872
+ except ValueError as e:
873
+ raise exceptions.ValidationError({"rates": str(e)})
874
+
875
+ return BatchUpdateServiceRatesMutation(rate_sheet=rate_sheet)
876
+
877
+
878
+ # ─────────────────────────────────────────────────────────────────────────────
879
+ # SERVICE ZONE/SURCHARGE ASSIGNMENT MUTATIONS
880
+ # ─────────────────────────────────────────────────────────────────────────────
688
881
 
689
882
 
690
883
  @strawberry.type
691
- class DeleteRateSheetServiceMutation(utils.BaseMutation):
884
+ class UpdateServiceZoneIdsMutation(utils.BaseMutation):
692
885
  rate_sheet: typing.Optional[types.RateSheetType] = None
693
886
 
694
887
  @staticmethod
695
888
  @transaction.atomic
696
889
  @utils.authentication_required
697
890
  def mutate(
698
- info: Info, **input: inputs.DeleteRateSheetServiceMutationInput
699
- ) -> "DeleteRateSheetServiceMutation":
891
+ info: Info, **input: inputs.UpdateServiceZoneIdsMutationInput
892
+ ) -> "UpdateServiceZoneIdsMutation":
700
893
  rate_sheet = providers.RateSheet.access_by(info.context.request).get(
701
894
  id=input["rate_sheet_id"]
702
895
  )
703
896
  service = rate_sheet.services.get(id=input["service_id"])
704
897
 
705
- # Remove service from rate sheet and delete it
706
- rate_sheet.services.remove(service)
707
- service.delete()
898
+ service.zone_ids = input["zone_ids"]
899
+ service.save(update_fields=["zone_ids"])
708
900
 
709
- return DeleteRateSheetServiceMutation(rate_sheet=rate_sheet)
901
+ return UpdateServiceZoneIdsMutation(rate_sheet=rate_sheet)
902
+
903
+
904
+ @strawberry.type
905
+ class UpdateServiceSurchargeIdsMutation(utils.BaseMutation):
906
+ rate_sheet: typing.Optional[types.RateSheetType] = None
907
+
908
+ @staticmethod
909
+ @transaction.atomic
910
+ @utils.authentication_required
911
+ def mutate(
912
+ info: Info, **input: inputs.UpdateServiceSurchargeIdsMutationInput
913
+ ) -> "UpdateServiceSurchargeIdsMutation":
914
+ rate_sheet = providers.RateSheet.access_by(info.context.request).get(
915
+ id=input["rate_sheet_id"]
916
+ )
917
+ service = rate_sheet.services.get(id=input["service_id"])
918
+
919
+ service.surcharge_ids = input["surcharge_ids"]
920
+ service.save(update_fields=["surcharge_ids"])
921
+
922
+ return UpdateServiceSurchargeIdsMutation(rate_sheet=rate_sheet)
710
923
 
711
924
 
712
925
  @strawberry.type
@@ -736,7 +949,9 @@ class PartialShipmentMutation(utils.BaseMutation):
736
949
 
737
950
  return PartialShipmentMutation(errors=None, shipment=update) # type:ignore
738
951
  except Exception as e:
739
- logger.exception("Shipment mutation failed", shipment_id=input.get("id"), error=str(e))
952
+ logger.exception(
953
+ "Shipment mutation failed", shipment_id=input.get("id"), error=str(e)
954
+ )
740
955
  raise e
741
956
 
742
957
 
@@ -934,30 +1149,6 @@ class SystemCarrierMutation(utils.BaseMutation):
934
1149
  ) # type: ignore
935
1150
 
936
1151
 
937
- @strawberry.type
938
- class UpdateServiceZoneMutation(utils.BaseMutation):
939
- rate_sheet: typing.Optional[types.RateSheetType] = None
940
-
941
- @staticmethod
942
- @transaction.atomic
943
- @utils.authentication_required
944
- def mutate(
945
- info: Info, **input: inputs.UpdateServiceZoneMutationInput
946
- ) -> "UpdateServiceZoneMutation":
947
- rate_sheet = providers.RateSheet.access_by(info.context.request).get(
948
- id=input["id"]
949
- )
950
- service = rate_sheet.services.get(id=input["service_id"])
951
-
952
- serializer = serializers.ServiceLevelModelSerializer(
953
- service,
954
- context=info.context.request,
955
- )
956
- serializer.update_zone(input["zone_index"], input["zone"])
957
-
958
- return UpdateServiceZoneMutation(rate_sheet=rate_sheet) # type:ignore
959
-
960
-
961
1152
  @strawberry.type
962
1153
  class CreateMetafieldMutation(utils.BaseMutation):
963
1154
  metafield: typing.Optional[types.MetafieldType] = None