karrio-server-graph 2025.5.4__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.
@@ -5,194 +5,1899 @@ import karrio.server.providers.models as providers
5
5
 
6
6
 
7
7
  class TestRateSheets(GraphTestCase):
8
+ """Tests for Rate Sheet CRUD operations."""
9
+
10
+ def setUp(self):
11
+ super().setUp()
12
+
13
+ # Create a test rate sheet with shared zones and surcharges
14
+ self.rate_sheet = providers.RateSheet.objects.create(
15
+ name="Test Rate Sheet",
16
+ carrier_name="ups",
17
+ slug="test_rate_sheet",
18
+ zones=[
19
+ {
20
+ "id": "zone_1",
21
+ "label": "Zone 1",
22
+ "cities": ["New York", "Los Angeles"],
23
+ "country_codes": ["US"],
24
+ }
25
+ ],
26
+ surcharges=[
27
+ {
28
+ "id": "surch_fuel",
29
+ "name": "Fuel Surcharge",
30
+ "amount": 10.0,
31
+ "surcharge_type": "percentage",
32
+ "active": True,
33
+ }
34
+ ],
35
+ service_rates=[
36
+ {
37
+ "service_id": None, # Will be set after service creation
38
+ "zone_id": "zone_1",
39
+ "rate": 10.00,
40
+ "cost": 8.00,
41
+ }
42
+ ],
43
+ created_by=self.user,
44
+ )
45
+
46
+ # Create a test service
47
+ self.service = providers.ServiceLevel.objects.create(
48
+ service_name="UPS Standard",
49
+ service_code="ups_standard",
50
+ carrier_service_code="11",
51
+ currency="USD",
52
+ active=True,
53
+ zone_ids=["zone_1"],
54
+ surcharge_ids=["surch_fuel"],
55
+ created_by=self.user,
56
+ )
57
+ self.rate_sheet.services.add(self.service)
58
+
59
+ # Update service_rates with actual service ID
60
+ self.rate_sheet.service_rates = [
61
+ {
62
+ "service_id": self.service.id,
63
+ "zone_id": "zone_1",
64
+ "rate": 10.00,
65
+ "cost": 8.00,
66
+ }
67
+ ]
68
+ self.rate_sheet.save()
69
+
70
+ # =========================================================================
71
+ # RATE SHEET QUERY TESTS
72
+ # =========================================================================
73
+
74
+ def test_query_rate_sheets(self):
75
+ """Test querying all rate sheets with zones, surcharges, and service_rates."""
76
+ response = self.query(
77
+ """
78
+ query get_rate_sheets {
79
+ rate_sheets {
80
+ edges {
81
+ node {
82
+ id
83
+ name
84
+ carrier_name
85
+ slug
86
+ zones {
87
+ id
88
+ label
89
+ cities
90
+ country_codes
91
+ }
92
+ surcharges {
93
+ id
94
+ name
95
+ amount
96
+ surcharge_type
97
+ active
98
+ }
99
+ service_rates {
100
+ service_id
101
+ zone_id
102
+ rate
103
+ cost
104
+ }
105
+ services {
106
+ id
107
+ service_name
108
+ service_code
109
+ carrier_service_code
110
+ active
111
+ currency
112
+ zone_ids
113
+ surcharge_ids
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ """,
120
+ operation_name="get_rate_sheets",
121
+ )
122
+ response_data = response.data
123
+
124
+ self.assertResponseNoErrors(response)
125
+ self.assertDictEqual(
126
+ lib.to_dict(response_data),
127
+ RATE_SHEETS_RESPONSE,
128
+ )
129
+
130
+ def test_query_single_rate_sheet(self):
131
+ """Test querying a single rate sheet by ID."""
132
+ response = self.query(
133
+ """
134
+ query get_rate_sheet($id: String!) {
135
+ rate_sheet(id: $id) {
136
+ id
137
+ name
138
+ carrier_name
139
+ zones {
140
+ id
141
+ label
142
+ }
143
+ surcharges {
144
+ id
145
+ name
146
+ }
147
+ services {
148
+ id
149
+ service_name
150
+ }
151
+ }
152
+ }
153
+ """,
154
+ operation_name="get_rate_sheet",
155
+ variables={"id": self.rate_sheet.id},
156
+ )
157
+
158
+ self.assertResponseNoErrors(response)
159
+ self.assertEqual(response.data["data"]["rate_sheet"]["name"], "Test Rate Sheet")
160
+ self.assertEqual(response.data["data"]["rate_sheet"]["carrier_name"], "ups")
161
+
162
+ def test_query_rate_sheet_not_found(self):
163
+ """Test querying a rate sheet that doesn't exist."""
164
+ response = self.query(
165
+ """
166
+ query get_rate_sheet($id: String!) {
167
+ rate_sheet(id: $id) {
168
+ id
169
+ name
170
+ }
171
+ }
172
+ """,
173
+ operation_name="get_rate_sheet",
174
+ variables={"id": "rsht_nonexistent"},
175
+ )
176
+
177
+ self.assertResponseNoErrors(response)
178
+ self.assertIsNone(response.data["data"]["rate_sheet"])
179
+
180
+ # =========================================================================
181
+ # RATE SHEET CREATE TESTS
182
+ # =========================================================================
183
+
184
+ def test_create_rate_sheet(self):
185
+ """Test creating a new rate sheet with services."""
186
+ response = self.query(
187
+ """
188
+ mutation create_rate_sheet($data: CreateRateSheetMutationInput!) {
189
+ create_rate_sheet(input: $data) {
190
+ rate_sheet {
191
+ id
192
+ name
193
+ carrier_name
194
+ zones {
195
+ id
196
+ label
197
+ postal_codes
198
+ }
199
+ surcharges {
200
+ id
201
+ name
202
+ amount
203
+ surcharge_type
204
+ }
205
+ services {
206
+ id
207
+ service_name
208
+ service_code
209
+ currency
210
+ zone_ids
211
+ surcharge_ids
212
+ }
213
+ }
214
+ }
215
+ }
216
+ """,
217
+ operation_name="create_rate_sheet",
218
+ variables=CREATE_RATE_SHEET_DATA,
219
+ )
220
+ response_data = response.data
221
+
222
+ self.assertResponseNoErrors(response)
223
+ self.assertDictEqual(response_data, CREATE_RATE_SHEET_RESPONSE)
224
+
225
+ def test_create_rate_sheet_minimal(self):
226
+ """Test creating a rate sheet with only required fields."""
227
+ response = self.query(
228
+ """
229
+ mutation create_rate_sheet($data: CreateRateSheetMutationInput!) {
230
+ create_rate_sheet(input: $data) {
231
+ rate_sheet {
232
+ id
233
+ name
234
+ carrier_name
235
+ zones {
236
+ id
237
+ }
238
+ surcharges {
239
+ id
240
+ }
241
+ services {
242
+ id
243
+ }
244
+ }
245
+ errors {
246
+ field
247
+ messages
248
+ }
249
+ }
250
+ }
251
+ """,
252
+ operation_name="create_rate_sheet",
253
+ variables={
254
+ "data": {
255
+ "name": "Minimal Rate Sheet",
256
+ "carrier_name": "generic",
257
+ }
258
+ },
259
+ )
260
+
261
+ self.assertResponseNoErrors(response)
262
+ rate_sheet = response.data["data"]["create_rate_sheet"]["rate_sheet"]
263
+ self.assertEqual(rate_sheet["name"], "Minimal Rate Sheet")
264
+ self.assertEqual(rate_sheet["zones"], [])
265
+ self.assertEqual(rate_sheet["surcharges"], [])
266
+ self.assertEqual(rate_sheet["services"], [])
267
+
268
+ # =========================================================================
269
+ # RATE SHEET UPDATE TESTS
270
+ # =========================================================================
271
+
272
+ def test_update_rate_sheet(self):
273
+ """Test updating a rate sheet's basic fields."""
274
+ response = self.query(
275
+ """
276
+ mutation update_rate_sheet($data: UpdateRateSheetMutationInput!) {
277
+ update_rate_sheet(input: $data) {
278
+ rate_sheet {
279
+ id
280
+ name
281
+ services {
282
+ id
283
+ service_name
284
+ zone_ids
285
+ }
286
+ }
287
+ }
288
+ }
289
+ """,
290
+ operation_name="update_rate_sheet",
291
+ variables={
292
+ "data": {
293
+ "id": self.rate_sheet.id,
294
+ "name": "Updated Rate Sheet",
295
+ "services": [
296
+ {
297
+ "id": self.service.id,
298
+ "service_name": "Updated Service",
299
+ "zone_ids": ["zone_1"],
300
+ }
301
+ ],
302
+ },
303
+ },
304
+ )
305
+ response_data = response.data
306
+
307
+ self.assertResponseNoErrors(response)
308
+ self.assertDictEqual(
309
+ lib.to_dict(response_data),
310
+ UPDATE_RATE_SHEET_RESPONSE,
311
+ )
312
+
313
+ def test_update_rate_sheet_name_only(self):
314
+ """Test updating only the rate sheet name."""
315
+ response = self.query(
316
+ """
317
+ mutation update_rate_sheet($data: UpdateRateSheetMutationInput!) {
318
+ update_rate_sheet(input: $data) {
319
+ rate_sheet {
320
+ id
321
+ name
322
+ }
323
+ errors {
324
+ field
325
+ messages
326
+ }
327
+ }
328
+ }
329
+ """,
330
+ operation_name="update_rate_sheet",
331
+ variables={
332
+ "data": {
333
+ "id": self.rate_sheet.id,
334
+ "name": "New Name Only",
335
+ }
336
+ },
337
+ )
338
+
339
+ self.assertResponseNoErrors(response)
340
+ self.assertEqual(
341
+ response.data["data"]["update_rate_sheet"]["rate_sheet"]["name"],
342
+ "New Name Only"
343
+ )
344
+
345
+ # =========================================================================
346
+ # RATE SHEET DELETE TESTS
347
+ # =========================================================================
348
+
349
+ def test_delete_rate_sheet(self):
350
+ """Test deleting a rate sheet."""
351
+ # First create a rate sheet to delete
352
+ new_sheet = providers.RateSheet.objects.create(
353
+ name="To Be Deleted",
354
+ carrier_name="ups",
355
+ slug="to_be_deleted",
356
+ created_by=self.user,
357
+ )
358
+
359
+ response = self.query(
360
+ """
361
+ mutation delete_rate_sheet($data: DeleteMutationInput!) {
362
+ delete_rate_sheet(input: $data) {
363
+ id
364
+ errors {
365
+ field
366
+ messages
367
+ }
368
+ }
369
+ }
370
+ """,
371
+ operation_name="delete_rate_sheet",
372
+ variables={
373
+ "data": {
374
+ "id": new_sheet.id,
375
+ }
376
+ },
377
+ )
378
+
379
+ self.assertResponseNoErrors(response)
380
+ self.assertEqual(response.data["data"]["delete_rate_sheet"]["id"], new_sheet.id)
381
+
382
+ # Verify it's deleted
383
+ self.assertFalse(providers.RateSheet.objects.filter(id=new_sheet.id).exists())
384
+
385
+ def test_delete_rate_sheet_cascades_services(self):
386
+ """Test that deleting a rate sheet also deletes its services."""
387
+ # Create a rate sheet with a service
388
+ sheet = providers.RateSheet.objects.create(
389
+ name="Sheet With Service",
390
+ carrier_name="ups",
391
+ slug="sheet_with_service",
392
+ created_by=self.user,
393
+ )
394
+ service = providers.ServiceLevel.objects.create(
395
+ service_name="Test Service",
396
+ service_code="test_service",
397
+ carrier_service_code="TEST",
398
+ currency="USD",
399
+ created_by=self.user,
400
+ )
401
+ sheet.services.add(service)
402
+ service_id = service.id
403
+
404
+ response = self.query(
405
+ """
406
+ mutation delete_rate_sheet($data: DeleteMutationInput!) {
407
+ delete_rate_sheet(input: $data) {
408
+ id
409
+ }
410
+ }
411
+ """,
412
+ operation_name="delete_rate_sheet",
413
+ variables={"data": {"id": sheet.id}},
414
+ )
415
+
416
+ self.assertResponseNoErrors(response)
417
+ # Verify service is also deleted
418
+ self.assertFalse(providers.ServiceLevel.objects.filter(id=service_id).exists())
419
+
420
+
421
+ class TestRateSheetZones(GraphTestCase):
422
+ """Tests for shared zone CRUD operations."""
423
+
424
+ def setUp(self):
425
+ super().setUp()
426
+
427
+ self.rate_sheet = providers.RateSheet.objects.create(
428
+ name="Zone Test Sheet",
429
+ carrier_name="ups",
430
+ slug="zone_test_sheet",
431
+ zones=[
432
+ {
433
+ "id": "zone_1",
434
+ "label": "Zone 1",
435
+ "cities": ["New York", "Los Angeles"],
436
+ "country_codes": ["US"],
437
+ "postal_codes": ["10001", "90001"],
438
+ }
439
+ ],
440
+ created_by=self.user,
441
+ )
442
+
443
+ self.service = providers.ServiceLevel.objects.create(
444
+ service_name="Test Service",
445
+ service_code="test_service",
446
+ carrier_service_code="TEST",
447
+ currency="USD",
448
+ zone_ids=["zone_1"],
449
+ created_by=self.user,
450
+ )
451
+ self.rate_sheet.services.add(self.service)
452
+
453
+ # =========================================================================
454
+ # ADD ZONE TESTS
455
+ # =========================================================================
456
+
457
+ def test_add_shared_zone(self):
458
+ """Test adding a new shared zone."""
459
+ response = self.query(
460
+ """
461
+ mutation add_zone($data: AddSharedZoneMutationInput!) {
462
+ add_shared_zone(input: $data) {
463
+ rate_sheet {
464
+ id
465
+ zones {
466
+ id
467
+ label
468
+ country_codes
469
+ }
470
+ }
471
+ errors {
472
+ field
473
+ messages
474
+ }
475
+ }
476
+ }
477
+ """,
478
+ operation_name="add_zone",
479
+ variables={
480
+ "data": {
481
+ "rate_sheet_id": self.rate_sheet.id,
482
+ "zone": {
483
+ "label": "Zone 2",
484
+ "country_codes": ["CA", "MX"],
485
+ },
486
+ },
487
+ },
488
+ )
489
+
490
+ self.assertResponseNoErrors(response)
491
+ zones = response.data["data"]["add_shared_zone"]["rate_sheet"]["zones"]
492
+ self.assertEqual(len(zones), 2)
493
+ self.assertEqual(zones[1]["label"], "Zone 2")
494
+ self.assertEqual(zones[1]["country_codes"], ["CA", "MX"])
495
+
496
+ def test_add_shared_zone_with_all_fields(self):
497
+ """Test adding a zone with all optional fields."""
498
+ response = self.query(
499
+ """
500
+ mutation add_zone($data: AddSharedZoneMutationInput!) {
501
+ add_shared_zone(input: $data) {
502
+ rate_sheet {
503
+ id
504
+ zones {
505
+ id
506
+ label
507
+ country_codes
508
+ cities
509
+ postal_codes
510
+ transit_days
511
+ transit_time
512
+ radius
513
+ latitude
514
+ longitude
515
+ }
516
+ }
517
+ }
518
+ }
519
+ """,
520
+ operation_name="add_zone",
521
+ variables={
522
+ "data": {
523
+ "rate_sheet_id": self.rate_sheet.id,
524
+ "zone": {
525
+ "label": "Full Zone",
526
+ "country_codes": ["US"],
527
+ "cities": ["Seattle", "Portland"],
528
+ "postal_codes": ["98101", "97201"],
529
+ "transit_days": 3,
530
+ "transit_time": 72,
531
+ "radius": 100.0,
532
+ "latitude": 47.6062,
533
+ "longitude": -122.3321,
534
+ },
535
+ },
536
+ },
537
+ )
538
+
539
+ self.assertResponseNoErrors(response)
540
+ zones = response.data["data"]["add_shared_zone"]["rate_sheet"]["zones"]
541
+ new_zone = zones[-1]
542
+ self.assertEqual(new_zone["label"], "Full Zone")
543
+ self.assertEqual(new_zone["cities"], ["Seattle", "Portland"])
544
+ self.assertEqual(new_zone["transit_days"], 3)
545
+ self.assertEqual(new_zone["radius"], 100.0)
546
+
547
+ def test_add_multiple_zones_sequentially(self):
548
+ """Test adding multiple zones one after another."""
549
+ for i in range(2, 5):
550
+ response = self.query(
551
+ """
552
+ mutation add_zone($data: AddSharedZoneMutationInput!) {
553
+ add_shared_zone(input: $data) {
554
+ rate_sheet {
555
+ zones {
556
+ id
557
+ label
558
+ }
559
+ }
560
+ }
561
+ }
562
+ """,
563
+ operation_name="add_zone",
564
+ variables={
565
+ "data": {
566
+ "rate_sheet_id": self.rate_sheet.id,
567
+ "zone": {
568
+ "label": f"Zone {i}",
569
+ "country_codes": ["US"],
570
+ },
571
+ },
572
+ },
573
+ )
574
+ self.assertResponseNoErrors(response)
575
+
576
+ # Verify all zones exist
577
+ self.rate_sheet.refresh_from_db()
578
+ self.assertEqual(len(self.rate_sheet.zones), 4)
579
+
580
+ # =========================================================================
581
+ # UPDATE ZONE TESTS
582
+ # =========================================================================
583
+
584
+ def test_update_shared_zone(self):
585
+ """Test updating an existing shared zone."""
586
+ response = self.query(
587
+ """
588
+ mutation update_zone($data: UpdateSharedZoneMutationInput!) {
589
+ update_shared_zone(input: $data) {
590
+ rate_sheet {
591
+ id
592
+ zones {
593
+ id
594
+ label
595
+ country_codes
596
+ }
597
+ }
598
+ }
599
+ }
600
+ """,
601
+ operation_name="update_zone",
602
+ variables={
603
+ "data": {
604
+ "rate_sheet_id": self.rate_sheet.id,
605
+ "zone_id": "zone_1",
606
+ "zone": {
607
+ "label": "Updated Zone 1",
608
+ "country_codes": ["US", "CA"],
609
+ },
610
+ },
611
+ },
612
+ )
613
+
614
+ self.assertResponseNoErrors(response)
615
+ zones = response.data["data"]["update_shared_zone"]["rate_sheet"]["zones"]
616
+ self.assertEqual(zones[0]["label"], "Updated Zone 1")
617
+ self.assertEqual(zones[0]["country_codes"], ["US", "CA"])
618
+
619
+ def test_update_zone_partial_fields(self):
620
+ """Test updating only specific fields of a zone."""
621
+ response = self.query(
622
+ """
623
+ mutation update_zone($data: UpdateSharedZoneMutationInput!) {
624
+ update_shared_zone(input: $data) {
625
+ rate_sheet {
626
+ zones {
627
+ id
628
+ label
629
+ cities
630
+ country_codes
631
+ }
632
+ }
633
+ }
634
+ }
635
+ """,
636
+ operation_name="update_zone",
637
+ variables={
638
+ "data": {
639
+ "rate_sheet_id": self.rate_sheet.id,
640
+ "zone_id": "zone_1",
641
+ "zone": {
642
+ "label": "New Label Only",
643
+ },
644
+ },
645
+ },
646
+ )
647
+
648
+ self.assertResponseNoErrors(response)
649
+ zone = response.data["data"]["update_shared_zone"]["rate_sheet"]["zones"][0]
650
+ self.assertEqual(zone["label"], "New Label Only")
651
+ # Other fields might be reset based on implementation
652
+
653
+ def test_update_nonexistent_zone(self):
654
+ """Test updating a zone that doesn't exist."""
655
+ response = self.query(
656
+ """
657
+ mutation update_zone($data: UpdateSharedZoneMutationInput!) {
658
+ update_shared_zone(input: $data) {
659
+ rate_sheet {
660
+ id
661
+ }
662
+ errors {
663
+ field
664
+ messages
665
+ }
666
+ }
667
+ }
668
+ """,
669
+ operation_name="update_zone",
670
+ variables={
671
+ "data": {
672
+ "rate_sheet_id": self.rate_sheet.id,
673
+ "zone_id": "nonexistent_zone",
674
+ "zone": {
675
+ "label": "Will Fail",
676
+ },
677
+ },
678
+ },
679
+ )
680
+
681
+ # Should have an error
682
+ self.assertIsNotNone(response.data.get("errors") or response.data["data"]["update_shared_zone"].get("errors"))
683
+
684
+ # =========================================================================
685
+ # DELETE ZONE TESTS
686
+ # =========================================================================
687
+
688
+ def test_delete_shared_zone(self):
689
+ """Test deleting a shared zone."""
690
+ # First add a second zone
691
+ self.rate_sheet.zones.append({
692
+ "id": "zone_2",
693
+ "label": "Zone 2",
694
+ "country_codes": ["CA"],
695
+ })
696
+ self.rate_sheet.save()
697
+
698
+ response = self.query(
699
+ """
700
+ mutation delete_zone($data: DeleteSharedZoneMutationInput!) {
701
+ delete_shared_zone(input: $data) {
702
+ rate_sheet {
703
+ id
704
+ zones {
705
+ id
706
+ label
707
+ }
708
+ }
709
+ errors {
710
+ field
711
+ messages
712
+ }
713
+ }
714
+ }
715
+ """,
716
+ operation_name="delete_zone",
717
+ variables={
718
+ "data": {
719
+ "rate_sheet_id": self.rate_sheet.id,
720
+ "zone_id": "zone_2",
721
+ },
722
+ },
723
+ )
724
+
725
+ self.assertResponseNoErrors(response)
726
+ zones = response.data["data"]["delete_shared_zone"]["rate_sheet"]["zones"]
727
+ self.assertEqual(len(zones), 1)
728
+ self.assertEqual(zones[0]["id"], "zone_1")
729
+
730
+ def test_delete_zone_removes_from_service_zone_ids(self):
731
+ """Test that deleting a zone removes it from services' zone_ids."""
732
+ # Add zone to service
733
+ self.service.zone_ids = ["zone_1"]
734
+ self.service.save()
735
+
736
+ response = self.query(
737
+ """
738
+ mutation delete_zone($data: DeleteSharedZoneMutationInput!) {
739
+ delete_shared_zone(input: $data) {
740
+ rate_sheet {
741
+ zones {
742
+ id
743
+ }
744
+ services {
745
+ id
746
+ zone_ids
747
+ }
748
+ }
749
+ }
750
+ }
751
+ """,
752
+ operation_name="delete_zone",
753
+ variables={
754
+ "data": {
755
+ "rate_sheet_id": self.rate_sheet.id,
756
+ "zone_id": "zone_1",
757
+ },
758
+ },
759
+ )
760
+
761
+ self.assertResponseNoErrors(response)
762
+ services = response.data["data"]["delete_shared_zone"]["rate_sheet"]["services"]
763
+ self.assertEqual(services[0]["zone_ids"], [])
764
+
765
+ def test_delete_zone_removes_service_rates(self):
766
+ """Test that deleting a zone removes associated service_rates."""
767
+ # Add service rate for the zone
768
+ self.rate_sheet.service_rates = [
769
+ {"service_id": self.service.id, "zone_id": "zone_1", "rate": 10.0}
770
+ ]
771
+ self.rate_sheet.save()
772
+
773
+ response = self.query(
774
+ """
775
+ mutation delete_zone($data: DeleteSharedZoneMutationInput!) {
776
+ delete_shared_zone(input: $data) {
777
+ rate_sheet {
778
+ zones {
779
+ id
780
+ }
781
+ service_rates {
782
+ zone_id
783
+ }
784
+ }
785
+ }
786
+ }
787
+ """,
788
+ operation_name="delete_zone",
789
+ variables={
790
+ "data": {
791
+ "rate_sheet_id": self.rate_sheet.id,
792
+ "zone_id": "zone_1",
793
+ },
794
+ },
795
+ )
796
+
797
+ self.assertResponseNoErrors(response)
798
+ service_rates = response.data["data"]["delete_shared_zone"]["rate_sheet"]["service_rates"]
799
+ self.assertEqual(service_rates, [])
800
+
801
+
802
+ class TestRateSheetSurcharges(GraphTestCase):
803
+ """Tests for shared surcharge CRUD operations."""
804
+
805
+ def setUp(self):
806
+ super().setUp()
807
+
808
+ self.rate_sheet = providers.RateSheet.objects.create(
809
+ name="Surcharge Test Sheet",
810
+ carrier_name="ups",
811
+ slug="surcharge_test_sheet",
812
+ surcharges=[
813
+ {
814
+ "id": "surch_fuel",
815
+ "name": "Fuel Surcharge",
816
+ "amount": 10.0,
817
+ "surcharge_type": "percentage",
818
+ "active": True,
819
+ "cost": 8.0,
820
+ }
821
+ ],
822
+ created_by=self.user,
823
+ )
824
+
825
+ self.service = providers.ServiceLevel.objects.create(
826
+ service_name="Test Service",
827
+ service_code="test_service",
828
+ carrier_service_code="TEST",
829
+ currency="USD",
830
+ surcharge_ids=["surch_fuel"],
831
+ created_by=self.user,
832
+ )
833
+ self.rate_sheet.services.add(self.service)
834
+
835
+ # =========================================================================
836
+ # ADD SURCHARGE TESTS
837
+ # =========================================================================
838
+
839
+ def test_add_shared_surcharge(self):
840
+ """Test adding a new shared surcharge."""
841
+ response = self.query(
842
+ """
843
+ mutation add_surcharge($data: AddSharedSurchargeMutationInput!) {
844
+ add_shared_surcharge(input: $data) {
845
+ rate_sheet {
846
+ id
847
+ surcharges {
848
+ id
849
+ name
850
+ amount
851
+ surcharge_type
852
+ active
853
+ }
854
+ }
855
+ }
856
+ }
857
+ """,
858
+ operation_name="add_surcharge",
859
+ variables={
860
+ "data": {
861
+ "rate_sheet_id": self.rate_sheet.id,
862
+ "surcharge": {
863
+ "name": "Energy Surcharge",
864
+ "amount": 2.50,
865
+ "surcharge_type": "fixed",
866
+ },
867
+ },
868
+ },
869
+ )
870
+
871
+ self.assertResponseNoErrors(response)
872
+ surcharges = response.data["data"]["add_shared_surcharge"]["rate_sheet"]["surcharges"]
873
+ self.assertEqual(len(surcharges), 2)
874
+ new_surcharge = surcharges[1]
875
+ self.assertEqual(new_surcharge["name"], "Energy Surcharge")
876
+ self.assertEqual(new_surcharge["amount"], 2.5)
877
+ self.assertEqual(new_surcharge["surcharge_type"], "fixed")
878
+ self.assertTrue(new_surcharge["active"])
879
+
880
+ def test_add_surcharge_percentage_type(self):
881
+ """Test adding a percentage-based surcharge."""
882
+ response = self.query(
883
+ """
884
+ mutation add_surcharge($data: AddSharedSurchargeMutationInput!) {
885
+ add_shared_surcharge(input: $data) {
886
+ rate_sheet {
887
+ surcharges {
888
+ id
889
+ name
890
+ amount
891
+ surcharge_type
892
+ cost
893
+ }
894
+ }
895
+ }
896
+ }
897
+ """,
898
+ operation_name="add_surcharge",
899
+ variables={
900
+ "data": {
901
+ "rate_sheet_id": self.rate_sheet.id,
902
+ "surcharge": {
903
+ "name": "Peak Season",
904
+ "amount": 15.0,
905
+ "surcharge_type": "percentage",
906
+ "cost": 12.0,
907
+ },
908
+ },
909
+ },
910
+ )
911
+
912
+ self.assertResponseNoErrors(response)
913
+ surcharges = response.data["data"]["add_shared_surcharge"]["rate_sheet"]["surcharges"]
914
+ new_surcharge = surcharges[-1]
915
+ self.assertEqual(new_surcharge["surcharge_type"], "percentage")
916
+ self.assertEqual(new_surcharge["cost"], 12.0)
917
+
918
+ def test_add_inactive_surcharge(self):
919
+ """Test adding a surcharge that starts as inactive."""
920
+ response = self.query(
921
+ """
922
+ mutation add_surcharge($data: AddSharedSurchargeMutationInput!) {
923
+ add_shared_surcharge(input: $data) {
924
+ rate_sheet {
925
+ surcharges {
926
+ id
927
+ name
928
+ active
929
+ }
930
+ }
931
+ }
932
+ }
933
+ """,
934
+ operation_name="add_surcharge",
935
+ variables={
936
+ "data": {
937
+ "rate_sheet_id": self.rate_sheet.id,
938
+ "surcharge": {
939
+ "name": "Future Surcharge",
940
+ "amount": 5.0,
941
+ "active": False,
942
+ },
943
+ },
944
+ },
945
+ )
946
+
947
+ self.assertResponseNoErrors(response)
948
+ surcharges = response.data["data"]["add_shared_surcharge"]["rate_sheet"]["surcharges"]
949
+ new_surcharge = surcharges[-1]
950
+ self.assertFalse(new_surcharge["active"])
951
+
952
+ # =========================================================================
953
+ # UPDATE SURCHARGE TESTS
954
+ # =========================================================================
955
+
956
+ def test_update_shared_surcharge(self):
957
+ """Test updating an existing shared surcharge."""
958
+ response = self.query(
959
+ """
960
+ mutation update_surcharge($data: UpdateSharedSurchargeMutationInput!) {
961
+ update_shared_surcharge(input: $data) {
962
+ rate_sheet {
963
+ id
964
+ surcharges {
965
+ id
966
+ name
967
+ amount
968
+ surcharge_type
969
+ active
970
+ }
971
+ }
972
+ }
973
+ }
974
+ """,
975
+ operation_name="update_surcharge",
976
+ variables={
977
+ "data": {
978
+ "rate_sheet_id": self.rate_sheet.id,
979
+ "surcharge_id": "surch_fuel",
980
+ "surcharge": {
981
+ "name": "Updated Fuel Surcharge",
982
+ "amount": 12.0,
983
+ "surcharge_type": "percentage",
984
+ "active": True,
985
+ },
986
+ },
987
+ },
988
+ )
989
+
990
+ self.assertResponseNoErrors(response)
991
+ surcharges = response.data["data"]["update_shared_surcharge"]["rate_sheet"]["surcharges"]
992
+ self.assertEqual(surcharges[0]["name"], "Updated Fuel Surcharge")
993
+ self.assertEqual(surcharges[0]["amount"], 12.0)
994
+
995
+ def test_update_surcharge_toggle_active(self):
996
+ """Test toggling surcharge active status."""
997
+ response = self.query(
998
+ """
999
+ mutation update_surcharge($data: UpdateSharedSurchargeMutationInput!) {
1000
+ update_shared_surcharge(input: $data) {
1001
+ rate_sheet {
1002
+ surcharges {
1003
+ id
1004
+ active
1005
+ }
1006
+ }
1007
+ }
1008
+ }
1009
+ """,
1010
+ operation_name="update_surcharge",
1011
+ variables={
1012
+ "data": {
1013
+ "rate_sheet_id": self.rate_sheet.id,
1014
+ "surcharge_id": "surch_fuel",
1015
+ "surcharge": {
1016
+ "name": "Fuel Surcharge",
1017
+ "amount": 10.0,
1018
+ "active": False,
1019
+ },
1020
+ },
1021
+ },
1022
+ )
1023
+
1024
+ self.assertResponseNoErrors(response)
1025
+ surcharges = response.data["data"]["update_shared_surcharge"]["rate_sheet"]["surcharges"]
1026
+ self.assertFalse(surcharges[0]["active"])
1027
+
1028
+ def test_update_nonexistent_surcharge(self):
1029
+ """Test updating a surcharge that doesn't exist."""
1030
+ response = self.query(
1031
+ """
1032
+ mutation update_surcharge($data: UpdateSharedSurchargeMutationInput!) {
1033
+ update_shared_surcharge(input: $data) {
1034
+ rate_sheet {
1035
+ id
1036
+ }
1037
+ errors {
1038
+ field
1039
+ messages
1040
+ }
1041
+ }
1042
+ }
1043
+ """,
1044
+ operation_name="update_surcharge",
1045
+ variables={
1046
+ "data": {
1047
+ "rate_sheet_id": self.rate_sheet.id,
1048
+ "surcharge_id": "nonexistent_surcharge",
1049
+ "surcharge": {
1050
+ "name": "Will Fail",
1051
+ "amount": 0,
1052
+ },
1053
+ },
1054
+ },
1055
+ )
1056
+
1057
+ # Should have an error
1058
+ self.assertIsNotNone(response.data.get("errors") or response.data["data"]["update_shared_surcharge"].get("errors"))
1059
+
1060
+ # =========================================================================
1061
+ # DELETE SURCHARGE TESTS
1062
+ # =========================================================================
1063
+
1064
+ def test_delete_shared_surcharge(self):
1065
+ """Test deleting a shared surcharge."""
1066
+ # Add a second surcharge
1067
+ self.rate_sheet.surcharges.append({
1068
+ "id": "surch_energy",
1069
+ "name": "Energy",
1070
+ "amount": 5.0,
1071
+ "surcharge_type": "fixed",
1072
+ "active": True,
1073
+ })
1074
+ self.rate_sheet.save()
1075
+
1076
+ response = self.query(
1077
+ """
1078
+ mutation delete_surcharge($data: DeleteSharedSurchargeMutationInput!) {
1079
+ delete_shared_surcharge(input: $data) {
1080
+ rate_sheet {
1081
+ id
1082
+ surcharges {
1083
+ id
1084
+ name
1085
+ }
1086
+ }
1087
+ }
1088
+ }
1089
+ """,
1090
+ operation_name="delete_surcharge",
1091
+ variables={
1092
+ "data": {
1093
+ "rate_sheet_id": self.rate_sheet.id,
1094
+ "surcharge_id": "surch_energy",
1095
+ },
1096
+ },
1097
+ )
1098
+
1099
+ self.assertResponseNoErrors(response)
1100
+ surcharges = response.data["data"]["delete_shared_surcharge"]["rate_sheet"]["surcharges"]
1101
+ self.assertEqual(len(surcharges), 1)
1102
+ self.assertEqual(surcharges[0]["id"], "surch_fuel")
1103
+
1104
+ def test_delete_surcharge_removes_from_service_surcharge_ids(self):
1105
+ """Test that deleting a surcharge removes it from services' surcharge_ids."""
1106
+ self.service.surcharge_ids = ["surch_fuel"]
1107
+ self.service.save()
1108
+
1109
+ response = self.query(
1110
+ """
1111
+ mutation delete_surcharge($data: DeleteSharedSurchargeMutationInput!) {
1112
+ delete_shared_surcharge(input: $data) {
1113
+ rate_sheet {
1114
+ surcharges {
1115
+ id
1116
+ }
1117
+ services {
1118
+ id
1119
+ surcharge_ids
1120
+ }
1121
+ }
1122
+ }
1123
+ }
1124
+ """,
1125
+ operation_name="delete_surcharge",
1126
+ variables={
1127
+ "data": {
1128
+ "rate_sheet_id": self.rate_sheet.id,
1129
+ "surcharge_id": "surch_fuel",
1130
+ },
1131
+ },
1132
+ )
1133
+
1134
+ self.assertResponseNoErrors(response)
1135
+ services = response.data["data"]["delete_shared_surcharge"]["rate_sheet"]["services"]
1136
+ self.assertEqual(services[0]["surcharge_ids"], [])
1137
+
1138
+
1139
+ class TestServiceRates(GraphTestCase):
1140
+ """Tests for service rate (service-zone rate mapping) operations."""
1141
+
1142
+ def setUp(self):
1143
+ super().setUp()
1144
+
1145
+ self.rate_sheet = providers.RateSheet.objects.create(
1146
+ name="Service Rate Test Sheet",
1147
+ carrier_name="ups",
1148
+ slug="service_rate_test_sheet",
1149
+ zones=[
1150
+ {"id": "zone_1", "label": "Zone 1", "country_codes": ["US"]},
1151
+ {"id": "zone_2", "label": "Zone 2", "country_codes": ["CA"]},
1152
+ {"id": "zone_3", "label": "Zone 3", "country_codes": ["MX"]},
1153
+ ],
1154
+ created_by=self.user,
1155
+ )
1156
+
1157
+ self.service = providers.ServiceLevel.objects.create(
1158
+ service_name="Test Service",
1159
+ service_code="test_service",
1160
+ carrier_service_code="TEST",
1161
+ currency="USD",
1162
+ zone_ids=["zone_1", "zone_2"],
1163
+ created_by=self.user,
1164
+ )
1165
+ self.rate_sheet.services.add(self.service)
1166
+
1167
+ # Add initial service rates
1168
+ self.rate_sheet.service_rates = [
1169
+ {"service_id": self.service.id, "zone_id": "zone_1", "rate": 10.00, "cost": 8.00},
1170
+ {"service_id": self.service.id, "zone_id": "zone_2", "rate": 15.00, "cost": 12.00},
1171
+ ]
1172
+ self.rate_sheet.save()
1173
+
1174
+ # =========================================================================
1175
+ # UPDATE SERVICE RATE TESTS
1176
+ # =========================================================================
1177
+
1178
+ def test_update_service_rate(self):
1179
+ """Test updating an existing service rate."""
1180
+ response = self.query(
1181
+ """
1182
+ mutation update_rate($data: UpdateServiceRateMutationInput!) {
1183
+ update_service_rate(input: $data) {
1184
+ rate_sheet {
1185
+ id
1186
+ service_rates {
1187
+ service_id
1188
+ zone_id
1189
+ rate
1190
+ cost
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+ """,
1196
+ operation_name="update_rate",
1197
+ variables={
1198
+ "data": {
1199
+ "rate_sheet_id": self.rate_sheet.id,
1200
+ "service_id": self.service.id,
1201
+ "zone_id": "zone_1",
1202
+ "rate": 15.00,
1203
+ "cost": 12.00,
1204
+ },
1205
+ },
1206
+ )
1207
+
1208
+ self.assertResponseNoErrors(response)
1209
+ service_rates = response.data["data"]["update_service_rate"]["rate_sheet"]["service_rates"]
1210
+ zone_1_rate = next(r for r in service_rates if r["zone_id"] == "zone_1")
1211
+ self.assertEqual(zone_1_rate["rate"], 15.0)
1212
+ self.assertEqual(zone_1_rate["cost"], 12.0)
1213
+
1214
+ def test_update_service_rate_creates_new(self):
1215
+ """Test that updating a non-existent service rate creates it."""
1216
+ response = self.query(
1217
+ """
1218
+ mutation update_rate($data: UpdateServiceRateMutationInput!) {
1219
+ update_service_rate(input: $data) {
1220
+ rate_sheet {
1221
+ service_rates {
1222
+ service_id
1223
+ zone_id
1224
+ rate
1225
+ }
1226
+ }
1227
+ }
1228
+ }
1229
+ """,
1230
+ operation_name="update_rate",
1231
+ variables={
1232
+ "data": {
1233
+ "rate_sheet_id": self.rate_sheet.id,
1234
+ "service_id": self.service.id,
1235
+ "zone_id": "zone_3", # New zone
1236
+ "rate": 20.00,
1237
+ },
1238
+ },
1239
+ )
1240
+
1241
+ self.assertResponseNoErrors(response)
1242
+ service_rates = response.data["data"]["update_service_rate"]["rate_sheet"]["service_rates"]
1243
+ self.assertEqual(len(service_rates), 3)
1244
+ zone_3_rate = next(r for r in service_rates if r["zone_id"] == "zone_3")
1245
+ self.assertEqual(zone_3_rate["rate"], 20.0)
1246
+
1247
+ def test_update_service_rate_with_weight_limits(self):
1248
+ """Test updating service rate with weight limits."""
1249
+ response = self.query(
1250
+ """
1251
+ mutation update_rate($data: UpdateServiceRateMutationInput!) {
1252
+ update_service_rate(input: $data) {
1253
+ rate_sheet {
1254
+ service_rates {
1255
+ service_id
1256
+ zone_id
1257
+ rate
1258
+ min_weight
1259
+ max_weight
1260
+ }
1261
+ }
1262
+ }
1263
+ }
1264
+ """,
1265
+ operation_name="update_rate",
1266
+ variables={
1267
+ "data": {
1268
+ "rate_sheet_id": self.rate_sheet.id,
1269
+ "service_id": self.service.id,
1270
+ "zone_id": "zone_1",
1271
+ "rate": 10.00,
1272
+ "min_weight": 0.0,
1273
+ "max_weight": 50.0,
1274
+ },
1275
+ },
1276
+ )
1277
+
1278
+ self.assertResponseNoErrors(response)
1279
+ service_rates = response.data["data"]["update_service_rate"]["rate_sheet"]["service_rates"]
1280
+ zone_1_rate = next(r for r in service_rates if r["zone_id"] == "zone_1")
1281
+ self.assertEqual(zone_1_rate["min_weight"], 0.0)
1282
+ self.assertEqual(zone_1_rate["max_weight"], 50.0)
1283
+
1284
+ # =========================================================================
1285
+ # BATCH UPDATE SERVICE RATES TESTS
1286
+ # =========================================================================
1287
+
1288
+ def test_batch_update_service_rates(self):
1289
+ """Test batch updating multiple service rates."""
1290
+ response = self.query(
1291
+ """
1292
+ mutation batch_update($data: BatchUpdateServiceRatesMutationInput!) {
1293
+ batch_update_service_rates(input: $data) {
1294
+ rate_sheet {
1295
+ service_rates {
1296
+ service_id
1297
+ zone_id
1298
+ rate
1299
+ cost
1300
+ }
1301
+ }
1302
+ }
1303
+ }
1304
+ """,
1305
+ operation_name="batch_update",
1306
+ variables={
1307
+ "data": {
1308
+ "rate_sheet_id": self.rate_sheet.id,
1309
+ "rates": [
1310
+ {
1311
+ "service_id": self.service.id,
1312
+ "zone_id": "zone_1",
1313
+ "rate": 11.00,
1314
+ "cost": 9.00,
1315
+ },
1316
+ {
1317
+ "service_id": self.service.id,
1318
+ "zone_id": "zone_2",
1319
+ "rate": 16.00,
1320
+ "cost": 13.00,
1321
+ },
1322
+ {
1323
+ "service_id": self.service.id,
1324
+ "zone_id": "zone_3",
1325
+ "rate": 21.00,
1326
+ "cost": 17.00,
1327
+ },
1328
+ ],
1329
+ },
1330
+ },
1331
+ )
1332
+
1333
+ self.assertResponseNoErrors(response)
1334
+ service_rates = response.data["data"]["batch_update_service_rates"]["rate_sheet"]["service_rates"]
1335
+ self.assertEqual(len(service_rates), 3)
1336
+
1337
+ # Verify each rate
1338
+ rates_by_zone = {r["zone_id"]: r for r in service_rates}
1339
+ self.assertEqual(rates_by_zone["zone_1"]["rate"], 11.0)
1340
+ self.assertEqual(rates_by_zone["zone_2"]["rate"], 16.0)
1341
+ self.assertEqual(rates_by_zone["zone_3"]["rate"], 21.0)
1342
+
1343
+
1344
+ class TestServiceAssignments(GraphTestCase):
1345
+ """Tests for service zone_ids and surcharge_ids assignment operations."""
1346
+
1347
+ def setUp(self):
1348
+ super().setUp()
1349
+
1350
+ self.rate_sheet = providers.RateSheet.objects.create(
1351
+ name="Assignment Test Sheet",
1352
+ carrier_name="ups",
1353
+ slug="assignment_test_sheet",
1354
+ zones=[
1355
+ {"id": "zone_1", "label": "Zone 1", "country_codes": ["US"]},
1356
+ {"id": "zone_2", "label": "Zone 2", "country_codes": ["CA"]},
1357
+ ],
1358
+ surcharges=[
1359
+ {"id": "surch_fuel", "name": "Fuel", "amount": 10.0, "surcharge_type": "percentage", "active": True},
1360
+ {"id": "surch_energy", "name": "Energy", "amount": 5.0, "surcharge_type": "fixed", "active": True},
1361
+ ],
1362
+ created_by=self.user,
1363
+ )
1364
+
1365
+ self.service = providers.ServiceLevel.objects.create(
1366
+ service_name="Test Service",
1367
+ service_code="test_service",
1368
+ carrier_service_code="TEST",
1369
+ currency="USD",
1370
+ zone_ids=["zone_1"],
1371
+ surcharge_ids=["surch_fuel"],
1372
+ created_by=self.user,
1373
+ )
1374
+ self.rate_sheet.services.add(self.service)
1375
+
1376
+ # =========================================================================
1377
+ # UPDATE SERVICE ZONE IDS TESTS
1378
+ # =========================================================================
1379
+
1380
+ def test_update_service_zone_ids(self):
1381
+ """Test updating service zone_ids."""
1382
+ response = self.query(
1383
+ """
1384
+ mutation update_zone_ids($data: UpdateServiceZoneIdsMutationInput!) {
1385
+ update_service_zone_ids(input: $data) {
1386
+ rate_sheet {
1387
+ id
1388
+ services {
1389
+ id
1390
+ zone_ids
1391
+ }
1392
+ }
1393
+ }
1394
+ }
1395
+ """,
1396
+ operation_name="update_zone_ids",
1397
+ variables={
1398
+ "data": {
1399
+ "rate_sheet_id": self.rate_sheet.id,
1400
+ "service_id": self.service.id,
1401
+ "zone_ids": ["zone_1", "zone_2"],
1402
+ },
1403
+ },
1404
+ )
1405
+
1406
+ self.assertResponseNoErrors(response)
1407
+ services = response.data["data"]["update_service_zone_ids"]["rate_sheet"]["services"]
1408
+ self.assertEqual(services[0]["zone_ids"], ["zone_1", "zone_2"])
1409
+
1410
+ def test_update_service_zone_ids_empty(self):
1411
+ """Test clearing service zone_ids."""
1412
+ response = self.query(
1413
+ """
1414
+ mutation update_zone_ids($data: UpdateServiceZoneIdsMutationInput!) {
1415
+ update_service_zone_ids(input: $data) {
1416
+ rate_sheet {
1417
+ services {
1418
+ id
1419
+ zone_ids
1420
+ }
1421
+ }
1422
+ }
1423
+ }
1424
+ """,
1425
+ operation_name="update_zone_ids",
1426
+ variables={
1427
+ "data": {
1428
+ "rate_sheet_id": self.rate_sheet.id,
1429
+ "service_id": self.service.id,
1430
+ "zone_ids": [],
1431
+ },
1432
+ },
1433
+ )
1434
+
1435
+ self.assertResponseNoErrors(response)
1436
+ services = response.data["data"]["update_service_zone_ids"]["rate_sheet"]["services"]
1437
+ self.assertEqual(services[0]["zone_ids"], [])
1438
+
1439
+ # =========================================================================
1440
+ # UPDATE SERVICE SURCHARGE IDS TESTS
1441
+ # =========================================================================
1442
+
1443
+ def test_update_service_surcharge_ids(self):
1444
+ """Test updating service surcharge_ids."""
1445
+ response = self.query(
1446
+ """
1447
+ mutation update_surcharge_ids($data: UpdateServiceSurchargeIdsMutationInput!) {
1448
+ update_service_surcharge_ids(input: $data) {
1449
+ rate_sheet {
1450
+ id
1451
+ services {
1452
+ id
1453
+ surcharge_ids
1454
+ }
1455
+ }
1456
+ }
1457
+ }
1458
+ """,
1459
+ operation_name="update_surcharge_ids",
1460
+ variables={
1461
+ "data": {
1462
+ "rate_sheet_id": self.rate_sheet.id,
1463
+ "service_id": self.service.id,
1464
+ "surcharge_ids": ["surch_fuel", "surch_energy"],
1465
+ },
1466
+ },
1467
+ )
1468
+
1469
+ self.assertResponseNoErrors(response)
1470
+ services = response.data["data"]["update_service_surcharge_ids"]["rate_sheet"]["services"]
1471
+ self.assertEqual(services[0]["surcharge_ids"], ["surch_fuel", "surch_energy"])
1472
+
1473
+ def test_update_service_surcharge_ids_empty(self):
1474
+ """Test clearing service surcharge_ids."""
1475
+ response = self.query(
1476
+ """
1477
+ mutation update_surcharge_ids($data: UpdateServiceSurchargeIdsMutationInput!) {
1478
+ update_service_surcharge_ids(input: $data) {
1479
+ rate_sheet {
1480
+ services {
1481
+ id
1482
+ surcharge_ids
1483
+ }
1484
+ }
1485
+ }
1486
+ }
1487
+ """,
1488
+ operation_name="update_surcharge_ids",
1489
+ variables={
1490
+ "data": {
1491
+ "rate_sheet_id": self.rate_sheet.id,
1492
+ "service_id": self.service.id,
1493
+ "surcharge_ids": [],
1494
+ },
1495
+ },
1496
+ )
1497
+
1498
+ self.assertResponseNoErrors(response)
1499
+ services = response.data["data"]["update_service_surcharge_ids"]["rate_sheet"]["services"]
1500
+ self.assertEqual(services[0]["surcharge_ids"], [])
1501
+
1502
+
1503
+ class TestRateSheetServices(GraphTestCase):
1504
+ """Tests for rate sheet service operations."""
1505
+
1506
+ def setUp(self):
1507
+ super().setUp()
1508
+
1509
+ self.rate_sheet = providers.RateSheet.objects.create(
1510
+ name="Service Test Sheet",
1511
+ carrier_name="ups",
1512
+ slug="service_test_sheet",
1513
+ zones=[
1514
+ {"id": "zone_1", "label": "Zone 1", "country_codes": ["US"]},
1515
+ ],
1516
+ created_by=self.user,
1517
+ )
1518
+
1519
+ self.service1 = providers.ServiceLevel.objects.create(
1520
+ service_name="Service 1",
1521
+ service_code="service_1",
1522
+ carrier_service_code="S1",
1523
+ currency="USD",
1524
+ zone_ids=["zone_1"],
1525
+ created_by=self.user,
1526
+ )
1527
+ self.service2 = providers.ServiceLevel.objects.create(
1528
+ service_name="Service 2",
1529
+ service_code="service_2",
1530
+ carrier_service_code="S2",
1531
+ currency="USD",
1532
+ zone_ids=["zone_1"],
1533
+ created_by=self.user,
1534
+ )
1535
+ self.rate_sheet.services.add(self.service1, self.service2)
1536
+
1537
+ def test_delete_rate_sheet_service(self):
1538
+ """Test deleting a service from a rate sheet."""
1539
+ response = self.query(
1540
+ """
1541
+ mutation delete_service($data: DeleteRateSheetServiceMutationInput!) {
1542
+ delete_rate_sheet_service(input: $data) {
1543
+ rate_sheet {
1544
+ id
1545
+ services {
1546
+ id
1547
+ service_name
1548
+ }
1549
+ }
1550
+ errors {
1551
+ field
1552
+ messages
1553
+ }
1554
+ }
1555
+ }
1556
+ """,
1557
+ operation_name="delete_service",
1558
+ variables={
1559
+ "data": {
1560
+ "rate_sheet_id": self.rate_sheet.id,
1561
+ "service_id": self.service2.id,
1562
+ },
1563
+ },
1564
+ )
1565
+
1566
+ self.assertResponseNoErrors(response)
1567
+ services = response.data["data"]["delete_rate_sheet_service"]["rate_sheet"]["services"]
1568
+ self.assertEqual(len(services), 1)
1569
+ self.assertEqual(services[0]["service_name"], "Service 1")
1570
+
1571
+ class TestRateSheetModelMethods(GraphTestCase):
1572
+ """Tests for RateSheet model methods (rate calculation, etc.)."""
1573
+
8
1574
  def setUp(self):
9
1575
  super().setUp()
10
1576
 
11
- # Create a test rate sheet
12
1577
  self.rate_sheet = providers.RateSheet.objects.create(
13
- name="Test Rate Sheet",
1578
+ name="Calculation Test Sheet",
14
1579
  carrier_name="ups",
15
- slug="test_rate_sheet",
1580
+ slug="calculation_test_sheet",
1581
+ zones=[
1582
+ {"id": "zone_1", "label": "Zone 1", "country_codes": ["US"]},
1583
+ {"id": "zone_2", "label": "Zone 2", "country_codes": ["CA"], "transit_days": 5},
1584
+ ],
1585
+ surcharges=[
1586
+ {"id": "surch_fuel", "name": "Fuel", "amount": 10.0, "surcharge_type": "percentage", "active": True, "cost": 8.0},
1587
+ {"id": "surch_handling", "name": "Handling", "amount": 5.0, "surcharge_type": "fixed", "active": True, "cost": 3.0},
1588
+ {"id": "surch_inactive", "name": "Inactive", "amount": 100.0, "surcharge_type": "fixed", "active": False},
1589
+ ],
1590
+ service_rates=[],
16
1591
  created_by=self.user,
17
1592
  )
18
1593
 
19
- # Create a test service
20
1594
  self.service = providers.ServiceLevel.objects.create(
21
- service_name="UPS Standard",
22
- service_code="ups_standard",
23
- carrier_service_code="11",
1595
+ service_name="Test Service",
1596
+ service_code="test_service",
1597
+ carrier_service_code="TEST",
24
1598
  currency="USD",
25
- active=True,
26
- zones=[
27
- {
28
- "rate": 10.00,
29
- "label": "Zone 1",
30
- "cities": ["New York", "Los Angeles"],
31
- }
32
- ],
1599
+ zone_ids=["zone_1", "zone_2"],
1600
+ surcharge_ids=["surch_fuel", "surch_handling", "surch_inactive"],
33
1601
  created_by=self.user,
34
1602
  )
35
1603
  self.rate_sheet.services.add(self.service)
36
1604
 
37
- def test_query_rate_sheets(self):
1605
+ # Add service rates
1606
+ self.rate_sheet.service_rates = [
1607
+ {"service_id": self.service.id, "zone_id": "zone_1", "rate": 100.00, "cost": 80.00},
1608
+ {"service_id": self.service.id, "zone_id": "zone_2", "rate": 150.00, "cost": 120.00},
1609
+ ]
1610
+ self.rate_sheet.save()
1611
+
1612
+ def test_get_zone(self):
1613
+ """Test getting a zone by ID."""
1614
+ zone = self.rate_sheet.get_zone("zone_1")
1615
+ self.assertIsNotNone(zone)
1616
+ self.assertEqual(zone["label"], "Zone 1")
1617
+
1618
+ zone_none = self.rate_sheet.get_zone("nonexistent")
1619
+ self.assertIsNone(zone_none)
1620
+
1621
+ def test_get_surcharge(self):
1622
+ """Test getting a surcharge by ID."""
1623
+ surcharge = self.rate_sheet.get_surcharge("surch_fuel")
1624
+ self.assertIsNotNone(surcharge)
1625
+ self.assertEqual(surcharge["name"], "Fuel")
1626
+
1627
+ surcharge_none = self.rate_sheet.get_surcharge("nonexistent")
1628
+ self.assertIsNone(surcharge_none)
1629
+
1630
+ def test_get_service_rate(self):
1631
+ """Test getting a service rate by service_id and zone_id."""
1632
+ rate = self.rate_sheet.get_service_rate(self.service.id, "zone_1")
1633
+ self.assertIsNotNone(rate)
1634
+ self.assertEqual(rate["rate"], 100.00)
1635
+
1636
+ rate_none = self.rate_sheet.get_service_rate(self.service.id, "nonexistent")
1637
+ self.assertIsNone(rate_none)
1638
+
1639
+ def test_apply_surcharges_to_rate_percentage(self):
1640
+ """Test applying percentage surcharges."""
1641
+ # 10% of 100 = 10
1642
+ total, breakdown = self.rate_sheet.apply_surcharges_to_rate(100.0, ["surch_fuel"])
1643
+ self.assertEqual(total, 110.0)
1644
+ self.assertEqual(len(breakdown), 1)
1645
+ self.assertEqual(breakdown[0]["amount"], 10.0)
1646
+ self.assertEqual(breakdown[0]["surcharge_type"], "percentage")
1647
+
1648
+ def test_apply_surcharges_to_rate_fixed(self):
1649
+ """Test applying fixed surcharges."""
1650
+ total, breakdown = self.rate_sheet.apply_surcharges_to_rate(100.0, ["surch_handling"])
1651
+ self.assertEqual(total, 105.0)
1652
+ self.assertEqual(len(breakdown), 1)
1653
+ self.assertEqual(breakdown[0]["amount"], 5.0)
1654
+ self.assertEqual(breakdown[0]["surcharge_type"], "fixed")
1655
+
1656
+ def test_apply_surcharges_to_rate_combined(self):
1657
+ """Test applying multiple surcharges."""
1658
+ # 100 + 10% (10) + 5 fixed = 115
1659
+ total, breakdown = self.rate_sheet.apply_surcharges_to_rate(100.0, ["surch_fuel", "surch_handling"])
1660
+ self.assertEqual(total, 115.0)
1661
+ self.assertEqual(len(breakdown), 2)
1662
+
1663
+ def test_apply_surcharges_skips_inactive(self):
1664
+ """Test that inactive surcharges are skipped."""
1665
+ total, breakdown = self.rate_sheet.apply_surcharges_to_rate(100.0, ["surch_fuel", "surch_inactive"])
1666
+ self.assertEqual(total, 110.0) # Only fuel applied
1667
+ self.assertEqual(len(breakdown), 1) # Only one active surcharge
1668
+
1669
+ def test_calculate_rate(self):
1670
+ """Test full rate calculation with surcharges."""
1671
+ total, breakdown = self.rate_sheet.calculate_rate(self.service.id, "zone_1")
1672
+ # Base: 100, Fuel (10%): 10, Handling (fixed): 5 = 115
1673
+ self.assertEqual(total, 115.0)
1674
+ self.assertEqual(breakdown["base_rate"], 100.0)
1675
+ self.assertEqual(breakdown["base_cost"], 80.0)
1676
+ self.assertEqual(len(breakdown["surcharges"]), 2) # Only active surcharges
1677
+
1678
+ def test_get_service_zones_for_rating(self):
1679
+ """Test getting zones with rates for SDK rating."""
1680
+ zones = self.rate_sheet.get_service_zones_for_rating(self.service.id)
1681
+ self.assertEqual(len(zones), 2)
1682
+
1683
+ zone_1 = next(z for z in zones if z["id"] == "zone_1")
1684
+ self.assertEqual(zone_1["rate"], 100.0)
1685
+ self.assertEqual(zone_1["cost"], 80.0)
1686
+
1687
+ zone_2 = next(z for z in zones if z["id"] == "zone_2")
1688
+ self.assertEqual(zone_2["rate"], 150.0)
1689
+ self.assertEqual(zone_2["transit_days"], 5) # From zone definition
1690
+
1691
+ def test_get_surcharges_for_rating(self):
1692
+ """Test getting surcharges for SDK rating."""
1693
+ surcharges = self.rate_sheet.get_surcharges_for_rating(["surch_fuel", "surch_handling", "surch_inactive"])
1694
+ # Should only return active surcharges
1695
+ self.assertEqual(len(surcharges), 2)
1696
+
1697
+ fuel = next(s for s in surcharges if s["id"] == "surch_fuel")
1698
+ self.assertEqual(fuel["amount"], 10.0)
1699
+ self.assertEqual(fuel["cost"], 8.0)
1700
+
1701
+
1702
+ class TestRateSheetEdgeCases(GraphTestCase):
1703
+ """Tests for edge cases and error handling."""
1704
+
1705
+ def setUp(self):
1706
+ super().setUp()
1707
+
1708
+ self.rate_sheet = providers.RateSheet.objects.create(
1709
+ name="Edge Case Sheet",
1710
+ carrier_name="ups",
1711
+ slug="edge_case_sheet",
1712
+ zones=[],
1713
+ surcharges=[],
1714
+ service_rates=[],
1715
+ created_by=self.user,
1716
+ )
1717
+
1718
+ def test_operations_on_empty_rate_sheet(self):
1719
+ """Test that operations work on an empty rate sheet."""
1720
+ # Add first zone
38
1721
  response = self.query(
39
1722
  """
40
- query get_rate_sheets {
41
- rate_sheets {
42
- edges {
43
- node {
1723
+ mutation add_zone($data: AddSharedZoneMutationInput!) {
1724
+ add_shared_zone(input: $data) {
1725
+ rate_sheet {
1726
+ zones {
44
1727
  id
45
- name
46
- carrier_name
47
- slug
48
- services {
49
- id
50
- service_name
51
- service_code
52
- carrier_service_code
53
- active
54
- currency
55
- zones {
56
- rate
57
- label
58
- cities
59
- postal_codes
60
- country_codes
61
- }
62
- }
1728
+ label
63
1729
  }
64
1730
  }
65
1731
  }
66
1732
  }
67
1733
  """,
68
- operation_name="get_rate_sheets",
1734
+ operation_name="add_zone",
1735
+ variables={
1736
+ "data": {
1737
+ "rate_sheet_id": self.rate_sheet.id,
1738
+ "zone": {
1739
+ "label": "First Zone",
1740
+ "country_codes": ["US"],
1741
+ },
1742
+ },
1743
+ },
69
1744
  )
70
- response_data = response.data
71
1745
 
72
1746
  self.assertResponseNoErrors(response)
73
- self.assertDictEqual(
74
- lib.to_dict(response_data),
75
- RATE_SHEETS_RESPONSE,
76
- )
1747
+ zones = response.data["data"]["add_shared_zone"]["rate_sheet"]["zones"]
1748
+ self.assertEqual(len(zones), 1)
77
1749
 
78
- def test_create_rate_sheet(self):
1750
+ def test_operations_on_nonexistent_rate_sheet(self):
1751
+ """Test that operations fail gracefully on nonexistent rate sheet."""
79
1752
  response = self.query(
80
1753
  """
81
- mutation create_rate_sheet($data: CreateRateSheetMutationInput!) {
82
- create_rate_sheet(input: $data) {
1754
+ mutation add_zone($data: AddSharedZoneMutationInput!) {
1755
+ add_shared_zone(input: $data) {
83
1756
  rate_sheet {
84
1757
  id
85
- name
86
- carrier_name
87
- services {
1758
+ }
1759
+ errors {
1760
+ field
1761
+ messages
1762
+ }
1763
+ }
1764
+ }
1765
+ """,
1766
+ operation_name="add_zone",
1767
+ variables={
1768
+ "data": {
1769
+ "rate_sheet_id": "rsht_nonexistent",
1770
+ "zone": {
1771
+ "label": "Will Fail",
1772
+ },
1773
+ },
1774
+ },
1775
+ )
1776
+
1777
+ # Should have an error
1778
+ self.assertIsNotNone(response.data.get("errors") or response.data["data"]["add_shared_zone"].get("errors"))
1779
+
1780
+ def test_zone_with_empty_arrays(self):
1781
+ """Test zone with empty country_codes, cities, postal_codes."""
1782
+ response = self.query(
1783
+ """
1784
+ mutation add_zone($data: AddSharedZoneMutationInput!) {
1785
+ add_shared_zone(input: $data) {
1786
+ rate_sheet {
1787
+ zones {
88
1788
  id
89
- service_name
90
- service_code
91
- currency
92
- zones {
93
- rate
94
- label
95
- postal_codes
96
- }
1789
+ label
1790
+ country_codes
1791
+ cities
1792
+ postal_codes
97
1793
  }
98
1794
  }
99
1795
  }
100
1796
  }
101
1797
  """,
102
- operation_name="create_rate_sheet",
103
- variables=CREATE_RATE_SHEET_DATA,
1798
+ operation_name="add_zone",
1799
+ variables={
1800
+ "data": {
1801
+ "rate_sheet_id": self.rate_sheet.id,
1802
+ "zone": {
1803
+ "label": "Empty Zone",
1804
+ "country_codes": [],
1805
+ "cities": [],
1806
+ "postal_codes": [],
1807
+ },
1808
+ },
1809
+ },
104
1810
  )
105
- response_data = response.data
106
1811
 
107
1812
  self.assertResponseNoErrors(response)
108
- self.assertDictEqual(response_data, CREATE_RATE_SHEET_RESPONSE)
1813
+ zone = response.data["data"]["add_shared_zone"]["rate_sheet"]["zones"][0]
1814
+ self.assertEqual(zone["country_codes"], [])
1815
+ self.assertEqual(zone["cities"], [])
1816
+ self.assertEqual(zone["postal_codes"], [])
109
1817
 
110
- def test_update_rate_sheet(self):
1818
+ def test_surcharge_with_zero_amount(self):
1819
+ """Test adding a surcharge with zero amount."""
111
1820
  response = self.query(
112
1821
  """
113
- mutation update_rate_sheet($data: UpdateRateSheetMutationInput!) {
114
- update_rate_sheet(input: $data) {
1822
+ mutation add_surcharge($data: AddSharedSurchargeMutationInput!) {
1823
+ add_shared_surcharge(input: $data) {
115
1824
  rate_sheet {
116
- id
117
- name
118
- services {
1825
+ surcharges {
119
1826
  id
120
- service_name
121
- zones {
122
- rate
123
- label
124
- country_codes
125
- }
1827
+ name
1828
+ amount
126
1829
  }
127
1830
  }
128
1831
  }
129
1832
  }
130
1833
  """,
131
- operation_name="update_rate_sheet",
1834
+ operation_name="add_surcharge",
132
1835
  variables={
133
1836
  "data": {
134
- "id": self.rate_sheet.id,
135
- "name": "Updated Rate Sheet",
136
- "services": [
137
- {
138
- "id": self.service.id,
139
- "service_name": "Updated Service",
140
- "zones": [
141
- {
142
- "rate": 20.0,
143
- "label": "Updated Zone",
144
- "country_codes": ["US", "CA"],
145
- }
146
- ],
147
- }
148
- ],
1837
+ "rate_sheet_id": self.rate_sheet.id,
1838
+ "surcharge": {
1839
+ "name": "Zero Surcharge",
1840
+ "amount": 0.0,
1841
+ },
149
1842
  },
150
1843
  },
151
1844
  )
152
- response_data = response.data
153
1845
 
154
1846
  self.assertResponseNoErrors(response)
155
- self.assertDictEqual(
156
- lib.to_dict(response_data),
157
- UPDATE_RATE_SHEET_RESPONSE,
1847
+ surcharge = response.data["data"]["add_shared_surcharge"]["rate_sheet"]["surcharges"][0]
1848
+ self.assertEqual(surcharge["amount"], 0.0)
1849
+
1850
+ def test_service_rate_with_null_cost(self):
1851
+ """Test service rate with null cost."""
1852
+ # First add a zone and service
1853
+ self.rate_sheet.zones = [{"id": "zone_1", "label": "Zone 1", "country_codes": ["US"]}]
1854
+ self.rate_sheet.save()
1855
+
1856
+ service = providers.ServiceLevel.objects.create(
1857
+ service_name="Test Service",
1858
+ service_code="test_service",
1859
+ carrier_service_code="TEST",
1860
+ currency="USD",
1861
+ created_by=self.user,
158
1862
  )
1863
+ self.rate_sheet.services.add(service)
159
1864
 
160
- def test_update_service_zone(self):
161
1865
  response = self.query(
162
1866
  """
163
- mutation update_zone($data: UpdateServiceZoneMutationInput!) {
164
- update_service_zone(input: $data) {
1867
+ mutation update_rate($data: UpdateServiceRateMutationInput!) {
1868
+ update_service_rate(input: $data) {
165
1869
  rate_sheet {
166
- id
167
- services {
168
- id
169
- zones {
170
- rate
171
- label
172
- country_codes
173
- }
1870
+ service_rates {
1871
+ service_id
1872
+ zone_id
1873
+ rate
1874
+ cost
174
1875
  }
175
1876
  }
176
1877
  }
177
1878
  }
178
1879
  """,
179
- operation_name="update_zone",
1880
+ operation_name="update_rate",
180
1881
  variables={
181
1882
  "data": {
182
- "id": self.rate_sheet.id,
183
- "service_id": self.service.id,
184
- **UPDATE_ZONE_DATA["data"],
1883
+ "rate_sheet_id": self.rate_sheet.id,
1884
+ "service_id": service.id,
1885
+ "zone_id": "zone_1",
1886
+ "rate": 10.00,
1887
+ # cost is not provided
185
1888
  },
186
1889
  },
187
1890
  )
188
- response_data = response.data
189
1891
 
190
1892
  self.assertResponseNoErrors(response)
191
- self.assertDictEqual(
192
- lib.to_dict(response_data),
193
- UPDATE_ZONE_RESPONSE,
194
- )
1893
+ service_rate = response.data["data"]["update_service_rate"]["rate_sheet"]["service_rates"][0]
1894
+ self.assertEqual(service_rate["rate"], 10.0)
1895
+ # cost should be null or default
1896
+
195
1897
 
1898
+ # =========================================================================
1899
+ # EXPECTED RESPONSES
1900
+ # =========================================================================
196
1901
 
197
1902
  RATE_SHEETS_RESPONSE = {
198
1903
  "data": {
@@ -203,6 +1908,32 @@ RATE_SHEETS_RESPONSE = {
203
1908
  "carrier_name": "ups",
204
1909
  "id": ANY,
205
1910
  "name": "Test Rate Sheet",
1911
+ "slug": "test_rate_sheet",
1912
+ "zones": [
1913
+ {
1914
+ "id": "zone_1",
1915
+ "label": "Zone 1",
1916
+ "cities": ["New York", "Los Angeles"],
1917
+ "country_codes": ["US"],
1918
+ }
1919
+ ],
1920
+ "surcharges": [
1921
+ {
1922
+ "id": "surch_fuel",
1923
+ "name": "Fuel Surcharge",
1924
+ "amount": 10.0,
1925
+ "surcharge_type": "percentage",
1926
+ "active": True,
1927
+ }
1928
+ ],
1929
+ "service_rates": [
1930
+ {
1931
+ "service_id": ANY,
1932
+ "zone_id": "zone_1",
1933
+ "rate": 10.0,
1934
+ "cost": 8.0,
1935
+ }
1936
+ ],
206
1937
  "services": [
207
1938
  {
208
1939
  "active": True,
@@ -211,16 +1942,10 @@ RATE_SHEETS_RESPONSE = {
211
1942
  "id": ANY,
212
1943
  "service_code": "ups_standard",
213
1944
  "service_name": "UPS Standard",
214
- "zones": [
215
- {
216
- "cities": ["New York", "Los Angeles"],
217
- "label": "Zone 1",
218
- "rate": 10.0,
219
- }
220
- ],
1945
+ "zone_ids": ["zone_1"],
1946
+ "surcharge_ids": ["surch_fuel"],
221
1947
  }
222
1948
  ],
223
- "slug": "test_rate_sheet",
224
1949
  }
225
1950
  }
226
1951
  ]
@@ -238,13 +1963,8 @@ CREATE_RATE_SHEET_DATA = {
238
1963
  "service_code": "fedex_ground",
239
1964
  "carrier_service_code": "FEDEX_GROUND",
240
1965
  "currency": "USD",
241
- "zones": [
242
- {
243
- "rate": 15.0,
244
- "label": "Zone A",
245
- "postal_codes": ["12345", "67890"],
246
- }
247
- ],
1966
+ "zone_ids": [],
1967
+ "surcharge_ids": [],
248
1968
  }
249
1969
  ],
250
1970
  }
@@ -257,19 +1977,16 @@ CREATE_RATE_SHEET_RESPONSE = {
257
1977
  "id": ANY,
258
1978
  "name": "New Rate Sheet",
259
1979
  "carrier_name": "fedex",
1980
+ "zones": [],
1981
+ "surcharges": [],
260
1982
  "services": [
261
1983
  {
262
1984
  "id": ANY,
263
1985
  "service_name": "FedEx Ground",
264
1986
  "service_code": "fedex_ground",
265
1987
  "currency": "USD",
266
- "zones": [
267
- {
268
- "rate": 15.0,
269
- "label": "Zone A",
270
- "postal_codes": ["12345", "67890"],
271
- }
272
- ],
1988
+ "zone_ids": [],
1989
+ "surcharge_ids": [],
273
1990
  }
274
1991
  ],
275
1992
  }
@@ -277,76 +1994,133 @@ CREATE_RATE_SHEET_RESPONSE = {
277
1994
  }
278
1995
  }
279
1996
 
280
- UPDATE_RATE_SHEET_DATA = {
1997
+ UPDATE_RATE_SHEET_RESPONSE = {
281
1998
  "data": {
282
- "name": "Updated Rate Sheet",
283
- "services": [
284
- {
285
- "id": ANY, # Will be replaced with actual service ID in test
286
- "service_name": "Updated Service",
1999
+ "update_rate_sheet": {
2000
+ "rate_sheet": {
2001
+ "id": ANY,
2002
+ "name": "Updated Rate Sheet",
2003
+ "services": [
2004
+ {
2005
+ "id": ANY,
2006
+ "service_name": "Updated Service",
2007
+ "zone_ids": ["zone_1"],
2008
+ }
2009
+ ],
2010
+ }
2011
+ }
2012
+ }
2013
+ }
2014
+
2015
+ ADD_SHARED_ZONE_RESPONSE = {
2016
+ "data": {
2017
+ "add_shared_zone": {
2018
+ "rate_sheet": {
2019
+ "id": ANY,
2020
+ "zones": [
2021
+ {
2022
+ "id": "zone_1",
2023
+ "label": "Zone 1",
2024
+ "country_codes": ["US"],
2025
+ },
2026
+ {
2027
+ "id": ANY,
2028
+ "label": "Zone 2",
2029
+ "country_codes": ["CA", "MX"],
2030
+ },
2031
+ ],
2032
+ }
2033
+ }
2034
+ }
2035
+ }
2036
+
2037
+ UPDATE_SHARED_ZONE_RESPONSE = {
2038
+ "data": {
2039
+ "update_shared_zone": {
2040
+ "rate_sheet": {
2041
+ "id": ANY,
287
2042
  "zones": [
288
2043
  {
289
- "rate": 20.0,
290
- "label": "Updated Zone",
2044
+ "id": "zone_1",
2045
+ "label": "Updated Zone 1",
291
2046
  "country_codes": ["US", "CA"],
292
- }
2047
+ },
293
2048
  ],
294
2049
  }
295
- ],
2050
+ }
296
2051
  }
297
2052
  }
298
2053
 
299
- UPDATE_RATE_SHEET_RESPONSE = {
2054
+ ADD_SHARED_SURCHARGE_RESPONSE = {
300
2055
  "data": {
301
- "update_rate_sheet": {
2056
+ "add_shared_surcharge": {
302
2057
  "rate_sheet": {
303
2058
  "id": ANY,
304
- "name": "Updated Rate Sheet",
305
- "services": [
2059
+ "surcharges": [
2060
+ {
2061
+ "id": "surch_fuel",
2062
+ "name": "Fuel Surcharge",
2063
+ "amount": 10.0,
2064
+ "surcharge_type": "percentage",
2065
+ "active": True,
2066
+ },
306
2067
  {
307
2068
  "id": ANY,
308
- "service_name": "Updated Service",
309
- "zones": [
310
- {
311
- "country_codes": ["US", "CA"],
312
- "label": "Updated Zone",
313
- "rate": 20.0,
314
- }
315
- ],
316
- }
2069
+ "name": "Energy Surcharge",
2070
+ "amount": 2.5,
2071
+ "surcharge_type": "fixed",
2072
+ "active": True,
2073
+ },
317
2074
  ],
318
2075
  }
319
2076
  }
320
2077
  }
321
2078
  }
322
2079
 
323
- UPDATE_ZONE_DATA = {
2080
+ UPDATE_SERVICE_RATE_RESPONSE = {
324
2081
  "data": {
325
- "zone_index": 0,
326
- "zone": {
327
- "rate": 25.0,
328
- "label": "Modified Zone",
329
- "country_codes": ["MX"],
330
- },
2082
+ "update_service_rate": {
2083
+ "rate_sheet": {
2084
+ "id": ANY,
2085
+ "service_rates": [
2086
+ {
2087
+ "service_id": ANY,
2088
+ "zone_id": "zone_1",
2089
+ "rate": 15.0,
2090
+ "cost": 12.0,
2091
+ },
2092
+ ],
2093
+ }
2094
+ }
331
2095
  }
332
2096
  }
333
2097
 
334
- UPDATE_ZONE_RESPONSE = {
2098
+ UPDATE_SERVICE_ZONE_IDS_RESPONSE = {
335
2099
  "data": {
336
- "update_service_zone": {
2100
+ "update_service_zone_ids": {
337
2101
  "rate_sheet": {
338
2102
  "id": ANY,
339
2103
  "services": [
340
2104
  {
341
2105
  "id": ANY,
342
- "zones": [
343
- {
344
- "rate": 25.0,
345
- "label": "Modified Zone",
346
- "country_codes": ["MX"],
347
- }
348
- ],
349
- }
2106
+ "zone_ids": ["zone_1", "zone_2"],
2107
+ },
2108
+ ],
2109
+ }
2110
+ }
2111
+ }
2112
+ }
2113
+
2114
+ UPDATE_SERVICE_SURCHARGE_IDS_RESPONSE = {
2115
+ "data": {
2116
+ "update_service_surcharge_ids": {
2117
+ "rate_sheet": {
2118
+ "id": ANY,
2119
+ "services": [
2120
+ {
2121
+ "id": ANY,
2122
+ "surcharge_ids": ["surch_fuel", "surch_energy"],
2123
+ },
350
2124
  ],
351
2125
  }
352
2126
  }