1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
|
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.sale_coupon.tests.common import TestSaleCouponCommon
from odoo.exceptions import UserError
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class TestSaleCouponProgramNumbers(TestSaleCouponCommon):
def setUp(self):
super(TestSaleCouponProgramNumbers, self).setUp()
self.largeCabinet = self.env['product.product'].create({
'name': 'Large Cabinet',
'list_price': 320.0,
'taxes_id': False,
})
self.conferenceChair = self.env['product.product'].create({
'name': 'Conference Chair',
'list_price': 16.5,
'taxes_id': False,
})
self.pedalBin = self.env['product.product'].create({
'name': 'Pedal Bin',
'list_price': 47.0,
'taxes_id': False,
})
self.drawerBlack = self.env['product.product'].create({
'name': 'Drawer Black',
'list_price': 25.0,
'taxes_id': False,
})
self.largeMeetingTable = self.env['product.product'].create({
'name': 'Large Meeting Table',
'list_price': 40000.0,
'taxes_id': False,
})
self.steve = self.env['res.partner'].create({
'name': 'Steve Bucknor',
'email': 'steve.bucknor@example.com',
})
self.empty_order = self.env['sale.order'].create({
'partner_id': self.steve.id
})
self.p1 = self.env['coupon.program'].create({
'name': 'Code for 10% on orders',
'promo_code_usage': 'code_needed',
'promo_code': 'test_10pc',
'discount_type': 'percentage',
'discount_percentage': 10.0,
'program_type': 'promotion_program',
})
self.p2 = self.env['coupon.program'].create({
'name': 'Buy 3 cabinets, get one for free',
'promo_code_usage': 'no_code_needed',
'reward_type': 'product',
'program_type': 'promotion_program',
'reward_product_id': self.largeCabinet.id,
'rule_min_quantity': 3,
'rule_products_domain': '[["name","ilike","large cabinet"]]',
})
self.p3 = self.env['coupon.program'].create({
'name': 'Buy 1 drawer black, get a free Large Meeting Table',
'promo_code_usage': 'no_code_needed',
'reward_type': 'product',
'program_type': 'promotion_program',
'reward_product_id': self.largeMeetingTable.id,
'rule_products_domain': '[["name","ilike","drawer black"]]',
})
self.discount_coupon_program = self.env['coupon.program'].create({
'name': '$100 coupon',
'program_type': 'coupon_program',
'reward_type': 'discount',
'discount_type': 'fixed_amount',
'discount_fixed_amount': 100,
'active': True,
'discount_apply_on': 'on_order',
'rule_minimum_amount': 100.00,
})
def test_program_numbers_free_and_paid_product_qty(self):
# These tests will focus on numbers (free product qty, SO total, reduction total..)
order = self.empty_order
sol1 = self.env['sale.order.line'].create({
'product_id': self.largeCabinet.id,
'name': 'Large Cabinet',
'product_uom_qty': 4.0,
'order_id': order.id,
})
# Check we correctly get a free product
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 2, "We should have 2 lines as we now have one 'Free Large Cabinet' line as we bought 4 of them")
# Check free product's price is not added to total when applying reduction (Or the discount will also be applied on the free product's price)
self.env['sale.coupon.apply.code'].sudo().apply_coupon(order, 'test_10pc')
self.assertEqual(len(order.order_line.ids), 3, "We should 3 lines as we should have a new line for promo code reduction")
self.assertEqual(order.amount_total, 864, "Only paid product should have their price discounted")
order.order_line.filtered(lambda x: 'Discount' in x.name).unlink() # Remove Discount
# Check free product is removed since we are below minimum required quantity
sol1.product_uom_qty = 3
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 1, "Free Large Cabinet should have been removed")
# Free product in cart will be considered as paid product when changing quantity of paid product, so the free product quantity computation will be wrong.
# 100 Large Cabinet in cart, 25 free, set quantity to 10 Large Cabinet, you should have 2 free Large Cabinet but you get 8 because it add the 25 initial free Large Cabinet to the total paid Large Cabinet when computing (25+10 > 35 > /4 = 8 free Large Cabinet)
sol1.product_uom_qty = 100
order.recompute_coupon_lines()
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).product_uom_qty, 25, "We should have 25 Free Large Cabinet")
sol1.product_uom_qty = 10
order.recompute_coupon_lines()
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).product_uom_qty, 2, "We should have 2 Free Large Cabinet")
def test_program_numbers_check_eligibility(self):
# These tests will focus on numbers (free product qty, SO total, reduction total..)
# Check if we have enough paid product to receive free product in case of a free product that is different from the paid product required
# Buy A, get free b. (remember we need a paid B in cart to receive free b). If your cart is 4A 1B then you should receive 1b (you are eligible to receive 4 because you have 4A but since you dont have enought B in your cart, you are limited to the B quantity)
order = self.empty_order
sol1 = self.env['sale.order.line'].create({
'product_id': self.drawerBlack.id,
'name': 'drawer black',
'product_uom_qty': 4.0,
'order_id': order.id,
})
sol2 = self.env['sale.order.line'].create({
'product_id': self.largeMeetingTable.id,
'name': 'Large Meeting Table',
'product_uom_qty': 1.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 3, "We should have a 'Free Large Meeting Table' promotion line")
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).product_uom_qty, 1, "We should receive one and only one free Large Meeting Table")
# Check the required value amount to be eligible for the program is correctly computed (eg: it does not add negative value (from free product) to total)
# A = free b | Have your cart with A 2B b | cart value should be A + 1B but in code it is only A (free b value is subsstract 2 times)
# This is because _amount_all() is summing all SO lines (so + (-b.value)) and again in _check_promo_code() order.amount_untaxed + order.reward_amount | amount_untaxed has already free product value substracted (_amount_all)
sol1.product_uom_qty = 1
sol2.product_uom_qty = 2
self.p1.rule_minimum_amount = 5000
order.recompute_coupon_lines()
self.env['sale.coupon.apply.code'].sudo().apply_coupon(order, 'test_10pc')
self.assertEqual(len(order.order_line.ids), 4, "We should have 4 lines as we should have a new line for promo code reduction")
# Check you can still have auto applied promotion if you have a promo code set to the order
self.env['sale.order.line'].create({
'product_id': self.largeCabinet.id,
'name': 'Large Cabinet',
'product_uom_qty': 4.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 6, "We should have 2 more lines as we now have one 'Free Large Cabinet' line since we bought 4 of them")
def test_program_numbers_taxes_and_rules(self):
percent_tax = self.env['account.tax'].create({
'name': "15% Tax",
'amount_type': 'percent',
'amount': 15,
'price_include': True,
})
p_specific_product = self.env['coupon.program'].create({
'name': '20% reduction on Large Cabinet in cart',
'promo_code_usage': 'no_code_needed',
'reward_type': 'discount',
'program_type': 'promotion_program',
'discount_type': 'percentage',
'discount_percentage': 20.0,
'rule_minimum_amount': 320.00,
'discount_apply_on': 'specific_products',
'discount_specific_product_ids': [(6, 0, [self.largeCabinet.id])],
})
order = self.empty_order
self.largeCabinet.taxes_id = percent_tax
sol1 = self.env['sale.order.line'].create({
'product_id': self.largeCabinet.id,
'name': 'Large Cabinet',
'product_uom_qty': 1.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 1, "We should not get the reduction line since we dont have 320$ tax excluded (cabinet is 320$ tax included)")
sol1.tax_id.price_include = False
sol1._compute_tax_id()
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 2, "We should now get the reduction line since we have 320$ tax included (cabinet is 320$ tax included)")
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Conference Chair | 1 | 320.00 | 15% excl | 320.00 | 368.00 | 48.00
# 20% discount on | 1 | -64.00 | 15% excl | -64.00 | -73.60 | -9.60
# large cabinet |
# --------------------------------------------------------------------------------
# TOTAL | 256.00 | 294.40 | 38.40
self.assertAlmostEqual(order.amount_total, 294.4, 2, "Check discount has been applied correctly (eg: on taxes aswell)")
# test coupon with code works the same as auto applied_programs
p_specific_product.write({'promo_code_usage': 'code_needed', 'promo_code': '20pc'})
order.order_line.filtered(lambda l: l.is_reward_line).unlink()
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 1, "Reduction should be removed since we deleted it and it is now a promo code usage, it shouldn't be automatically reapplied")
self.env['sale.coupon.apply.code'].sudo().apply_coupon(order, '20pc')
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 2, "We should now get the reduction line since we have 320$ tax included (cabinet is 320$ tax included)")
# check discount applied only on Large Cabinet
self.env['sale.order.line'].create({
'product_id': self.drawerBlack.id,
'name': 'Drawer Black',
'product_uom_qty': 10.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Drawer Black | 10 | 25.00 | / | 250.00 | 250.00 | /
# Large Cabinet | 1 | 320.00 | 15% excl | 320.00 | 368.00 | 48.00
# 20% discount on | 1 | -64.00 | 15% excl | -64.00 | -73.60 | -9.60
# large cabinet |
# --------------------------------------------------------------------------------
# TOTAL | 506.00 | 544.40 | 38.40
self.assertEqual(order.amount_total, 544.4, "We should only get reduction on cabinet")
sol1.product_uom_qty = 10
order.recompute_coupon_lines()
# Note: Since we now have 2 free Large Cabinet, we should discount only 8 of the 10 Large Cabinet in carts since we don't want to discount free Large Cabinet
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Drawer Black | 10 | 25.00 | / | 250.00 | 250.00 | /
# Large Cabinet | 10 | 320.00 | 15% excl | 3200.00 | 3680.00 | 480.00
# Free Large Cabinet | 2 | -320.00 | 15% excl | -640.00 | -736.00 | -96.00
# 20% discount on | 1 | -512.00 | 15% excl | -512.00 | -588.80 | -78.80
# large cabinet |
# --------------------------------------------------------------------------------
# TOTAL | 2298.00 | 2605.20 | 305.20
self.assertAlmostEqual(order.amount_total, 2605.20, 2, "Changing cabinet quantity should change discount amount correctly")
p_specific_product.discount_max_amount = 200
order.recompute_coupon_lines()
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Drawer Black | 10 | 25.00 | / | 250.00 | 250.00 | /
# Large Cabinet | 10 | 320.00 | 15% excl | 3200.00 | 3680.00 | 480.00
# Free Large Cabinet | 2 | -320.00 | 15% excl | -640.00 | -736.00 | -96.00
# 20% discount on | 1 | -200.00 | 15% excl | -200.00 | -230.00 | -30.00
# large cabinet |
# limited to 200 HTVA
# --------------------------------------------------------------------------------
# TOTAL | 2610.00 | 2964.00 | 354.00
self.assertEqual(order.amount_total, 2964, "The discount should be limited to $200 tax excluded")
self.assertEqual(order.amount_untaxed, 2610, "The discount should be limited to $200 tax excluded (2)")
def test_program_numbers_one_discount_line_per_tax(self):
order = self.empty_order
# Create taxes
self.tax_15pc_excl = self.env['account.tax'].create({
'name': "15% Tax excl",
'amount_type': 'percent',
'amount': 15,
})
self.tax_50pc_excl = self.env['account.tax'].create({
'name': "50% Tax excl",
'amount_type': 'percent',
'amount': 50,
})
self.tax_35pc_incl = self.env['account.tax'].create({
'name': "35% Tax incl",
'amount_type': 'percent',
'amount': 35,
'price_include': True,
})
# Set tax and prices on products as neeed for the test
(self.product_A + self.largeCabinet + self.conferenceChair + self.pedalBin + self.drawerBlack).write({'list_price': 100})
(self.largeCabinet + self.drawerBlack).write({'taxes_id': [(4, self.tax_15pc_excl.id, False)]})
self.conferenceChair.taxes_id = self.tax_10pc_incl
self.pedalBin.taxes_id = None
self.product_A.taxes_id = (self.tax_35pc_incl + self.tax_50pc_excl)
# Add products in order
self.env['sale.order.line'].create({
'product_id': self.largeCabinet.id,
'name': 'Large Cabinet',
'product_uom_qty': 7.0,
'order_id': order.id,
})
sol2 = self.env['sale.order.line'].create({
'product_id': self.conferenceChair.id,
'name': 'Conference Chair',
'product_uom_qty': 5.0,
'order_id': order.id,
})
self.env['sale.order.line'].create({
'product_id': self.pedalBin.id,
'name': 'Pedal Bin',
'product_uom_qty': 10.0,
'order_id': order.id,
})
self.env['sale.order.line'].create({
'product_id': self.product_A.id,
'name': 'product A with multiple taxes',
'product_uom_qty': 3.0,
'order_id': order.id,
})
self.env['sale.order.line'].create({
'product_id': self.drawerBlack.id,
'name': 'Drawer Black',
'product_uom_qty': 2.0,
'order_id': order.id,
})
# Create needed programs
self.p2.active = False
self.p_large_cabinet = self.env['coupon.program'].create({
'name': 'Buy 1 large cabinet, get one for free',
'promo_code_usage': 'no_code_needed',
'reward_type': 'product',
'program_type': 'promotion_program',
'reward_product_id': self.largeCabinet.id,
'rule_products_domain': '[["name","ilike","large cabinet"]]',
})
self.p_conference_chair = self.env['coupon.program'].create({
'name': 'Buy 1 chair, get one for free',
'promo_code_usage': 'no_code_needed',
'reward_type': 'product',
'program_type': 'promotion_program',
'reward_product_id': self.conferenceChair.id,
'rule_products_domain': '[["name","ilike","conference chair"]]',
})
self.p_pedal_bin = self.env['coupon.program'].create({
'name': 'Buy 1 bin, get one for free',
'promo_code_usage': 'no_code_needed',
'reward_type': 'product',
'program_type': 'promotion_program',
'reward_product_id': self.pedalBin.id,
'rule_products_domain': '[["name","ilike","pedal bin"]]',
})
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Conference Chair | 5 | 100.00 | 10% incl | 454.55 | 500.00 | 45.45
# Pedal bin | 10 | 100.00 | / | 1000.00 | 1000.00 | /
# Large Cabinet | 7 | 100.00 | 15% excl | 700.00 | 805.00 | 105.00
# Drawer Black | 2 | 100.00 | 15% excl | 200.00 | 230.00 | 30.00
# Product A | 3 | 100.00 | 35% incl | 222.22 | 411.11 | 188.89
# 50% excl
# --------------------------------------------------------------------------------
# TOTAL | 2576.77 | 2946.11 | 369.34
self.assertEqual(order.amount_total, 2946.11, "The order total without any programs should be 2946.11")
self.assertEqual(order.amount_untaxed, 2576.77, "The order untaxed total without any programs should be 2576.77")
self.assertEqual(len(order.order_line.ids), 5, "The order without any programs should have 5 lines")
# Apply all the programs
order.recompute_coupon_lines()
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Free ConferenceChair | 2 | -100.00 | 10% incl | -181.82 | -200.00 | -18.18
# Free Pedal Bin | 5 | -100.00 | / | -500.00 | -500.00 | /
# Free Large Cabinet | 3 | -100.00 | 15% excl | -300.00 | -345.00 | -45.00
# --------------------------------------------------------------------------------
# TOTAL AFTER APPLYING FREE PRODUCT PROGRAMS | 1594.95 | 1901.11 | 306.16
self.assertAlmostEqual(order.amount_total, 1901.11, 2, "The order total with programs should be 1901.11")
self.assertEqual(order.amount_untaxed, 1594.95, "The order untaxed total with programs should be 1594.95")
self.assertEqual(len(order.order_line.ids), 8, "Order should contains 5 regular product lines and 3 free product lines")
# Apply 10% on top of everything
self.env['sale.coupon.apply.code'].sudo().apply_coupon(order, 'test_10pc')
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# 10% on tax 10% incl | 1 | -30.00 | 10% incl | -27.27 | -30.00 | -2.73
# 10% on no tax | 1 | -50.00 | / | -50.00 | -50.00 | /
# 10% on tax 15% excl | 1 | -40.00 | 15% excl | -60.00 | -69.00 | -9.00
# 10% on tax 35%+50% | 1 | -30.00 | 35% incl | -22.22 | -45.00 | -18.89
# 50% excl
# --------------------------------------------------------------------------------
# TOTAL AFTER APPLYING 10% GLOBAL PROGRAM | 1435.46 | 1711.00 | 275.54
self.assertEqual(order.amount_total, 1711, "The order total with programs should be 1711")
self.assertEqual(order.amount_untaxed, 1435.46, "The order untaxed total with programs should be 1435.46")
self.assertEqual(len(order.order_line.ids), 12, "Order should contains 5 regular product lines, 3 free product lines and 4 discount lines (one for every tax)")
# -- This is a test inside the test
order.order_line._compute_tax_id()
self.assertEqual(order.amount_total, 1711, "Recomputing tax on sale order lines should not change total amount")
self.assertEqual(order.amount_untaxed, 1435.46, "Recomputing tax on sale order lines should not change untaxed amount")
self.assertEqual(len(order.order_line.ids), 12, "Recomputing tax on sale order lines should not change number of order line")
order.recompute_coupon_lines()
self.assertEqual(order.amount_total, 1711, "Recomputing tax on sale order lines should not change total amount")
self.assertEqual(order.amount_untaxed, 1435.46, "Recomputing tax on sale order lines should not change untaxed amount")
self.assertEqual(len(order.order_line.ids), 12, "Recomputing tax on sale order lines should not change number of order line")
# -- End test inside the test
# Now we want to apply a 20% discount only on Large Cabinet
self.env['coupon.program'].create({
'name': '20% reduction on Large Cabinet in cart',
'promo_code_usage': 'no_code_needed',
'reward_type': 'discount',
'program_type': 'promotion_program',
'discount_type': 'percentage',
'discount_percentage': 20.0,
'discount_apply_on': 'specific_products',
'discount_specific_product_ids': [(6, 0, [self.largeCabinet.id])],
})
order.recompute_coupon_lines()
# Note: we have 7 regular Large Cabinets and 3 free Large Cabinets. We should then discount only 4 really paid Large Cabinets
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# 20% on Large Cabinet | 1 | -80.00 | 15% excl | -80.00 | -92.00 | -12.00
# --------------------------------------------------------------------------------
# TOTAL AFTER APPLYING 20% ON LARGE CABINET | 1355.45 | 1619.00 | 263.54
self.assertEqual(order.amount_total, 1619, "The order total with programs should be 1619")
self.assertEqual(order.amount_untaxed, 1355.46, "The order untaxed total with programs should be 1435.45")
self.assertEqual(len(order.order_line.ids), 13, "Order should have a new discount line for 20% on Large Cabinet")
# Check that if you delete one of the discount tax line, the others tax lines from the same promotion got deleted as well.
order.order_line.filtered(lambda l: '10%' in l.name)[0].unlink()
self.assertEqual(len(order.order_line.ids), 9, "All of the 10% discount line per tax should be removed")
# At this point, removing the Conference Chair's discount line (split per tax) removed also the others discount lines
# linked to the same program (eg: other taxes lines). So the coupon got removed from the SO since there were no discount lines left
# Add back the coupon to continue the test flow
self.env['sale.coupon.apply.code'].sudo().apply_coupon(order, 'test_10pc')
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 13, "The 10% discount line should be back")
# Check that if you change a product qty, his discount tax line got updated
sol2.product_uom_qty = 7
order.recompute_coupon_lines()
# Conference Chair | 5 | 100.00 | 10% incl | 454.55 | 500.00 | 45.45
# Free ConferenceChair | 2 | -100.00 | 10% incl | -181.82 | -200.00 | -18.18
# 10% on tax 10% incl | 1 | -30.00 | 10% incl | -27.27 | -30.00 | -2.73
# --------------------------------------------------------------------------------
# TOTAL OF Conference Chair LINES | 245.46 | 270.00 | 24.54
# ==> Should become:
# Conference Chair | 7 | 100.00 | 10% incl | 636.36 | 700.00 | 63.64
# Free ConferenceChair | 3 | -100.00 | 10% incl | -272.73 | -300.00 | -27.27
# 10% on tax 10% incl | 1 | -40.00 | 10% incl | -36.36 | -40.00 | -3.64
# --------------------------------------------------------------------------------
# TOTAL OF Conference Chair LINES | 327.27 | 360.00 | 32.73
# AFTER ADDING 2 Conference Chair |
# --------------------------------------------------------------------------------
# => DIFFERENCES BEFORE/AFTER | 81.81 | 90.00 | 8.19
self.assertEqual(order.amount_untaxed, 1355.46 + 81.81, "The order should have one more paid Conference Chair with 10% incl tax and discounted by 10%")
# Check that if you remove a product, his reward lines got removed, especially the discount per tax one
sol2.unlink()
order.recompute_coupon_lines()
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Pedal Bins | 10 | 100.00 | / | 1000.00 | 1000.00 | /
# Large Cabinet | 7 | 100.00 | 15% excl | 700.00 | 805.00 | 105.00
# Drawer Black | 2 | 100.00 | 15% excl | 200.00 | 230.00 | 30.00
# Product A | 3 | 100.00 | 35% incl | 222.22 | 411.11 | 188.89
# 50% excl
# Free Pedal Bin | 5 | -100.00 | / | -500.00 | -500.00 | /
# Free Large Cabinet | 3 | -100.00 | 15% excl | -300.00 | -345.00 | -45.00
# 20% on Large Cabinet | 1 | -80.00 | 15% excl | -80.00 | -92.00 | -12.00
# --------------------------------------------------------------------------------
# TOTAL | 1242.22 | 1509.11 | 266.89
self.assertAlmostEqual(order.amount_total, 1509.11, 2, "The order total with programs should be 1509.11")
self.assertEqual(order.amount_untaxed, 1242.22, "The order untaxed total with programs should be 1242.22")
self.assertEqual(len(order.order_line.ids), 7, "Order should contains 7 lines: 4 products lines, 2 free products lines and a 20% discount line")
def test_program_numbers_extras(self):
# Check that you can't apply a global discount promo code if there is already an auto applied global discount
self.p1.copy({'promo_code_usage': 'no_code_needed', 'name': 'Auto applied 10% global discount'})
order = self.empty_order
self.env['sale.order.line'].create({
'product_id': self.largeCabinet.id,
'name': 'Large Cabinet',
'product_uom_qty': 1.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 2, "We should get 1 Large Cabinet line and 1 10% auto applied global discount line")
self.assertEqual(order.amount_total, 288, "320$ - 10%")
with self.assertRaises(UserError):
# Can't apply a second global discount
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': 'test_10pc'
}).process_coupon()
def test_program_fixed_price(self):
# Check fixed amount discount
order = self.empty_order
fixed_amount_program = self.env['coupon.program'].create({
'name': '$249 discount',
'promo_code_usage': 'no_code_needed',
'program_type': 'promotion_program',
'discount_type': 'fixed_amount',
'discount_fixed_amount': 249.0,
})
self.tax_0pc_excl = self.env['account.tax'].create({
'name': "0% Tax excl",
'amount_type': 'percent',
'amount': 0,
})
fixed_amount_program.discount_line_product_id.write({'taxes_id': [(4, self.tax_0pc_excl.id, False)]})
sol1 = self.env['sale.order.line'].create({
'product_id': self.drawerBlack.id,
'name': 'Drawer Black',
'product_uom_qty': 1.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
self.assertEqual(order.amount_total, 0, "Total should be null. The fixed amount discount is higher than the SO total, it should be reduced to the SO total")
self.assertEqual(len(order.order_line.ids), 2, "There should be the product line and the reward line")
sol1.product_uom_qty = 17
order.recompute_coupon_lines()
self.assertEqual(order.amount_total, 176, "Fixed amount discount should be totally deduced")
self.assertEqual(len(order.order_line.ids), 2, "Number of lines should be unchanged as we just recompute the reward line")
sol2 = order.order_line.filtered(lambda l: l.id != sol1.id)
self.assertEqual(len(sol2.tax_id.ids), 1, "One tax should be present on the reward line")
self.assertEqual(sol2.tax_id.id, self.tax_0pc_excl.id, "The tax should be 0% Tax excl")
fixed_amount_program.write({'active': False}) # Check archived product will remove discount lines on recompute
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 1, "Archiving the program should remove the program reward line")
def test_program_next_order(self):
order = self.empty_order
self.env['coupon.program'].create({
'name': 'Free Pedal Bin if at least 1 article',
'promo_code_usage': 'no_code_needed',
'promo_applicability': 'on_next_order',
'program_type': 'promotion_program',
'reward_type': 'product',
'reward_product_id': self.pedalBin.id,
'rule_min_quantity': 2,
})
sol1 = self.env['sale.order.line'].create({
'product_id': self.largeCabinet.id,
'name': 'Large Cabinet',
'product_uom_qty': 1.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 1, "Nothing should be added to the cart")
self.assertEqual(len(order.generated_coupon_ids), 0, "No coupon should have been generated yet")
sol1.product_uom_qty = 2
order.recompute_coupon_lines()
generated_coupon = order.generated_coupon_ids
self.assertEqual(len(order.order_line.ids), 1, "Nothing should be added to the cart (2)")
self.assertEqual(len(generated_coupon), 1, "A coupon should have been generated")
self.assertEqual(generated_coupon.state, 'reserved', "The coupon should be reserved")
sol1.product_uom_qty = 1
order.recompute_coupon_lines()
generated_coupon = order.generated_coupon_ids
self.assertEqual(len(order.order_line.ids), 1, "Nothing should be added to the cart (3)")
self.assertEqual(len(generated_coupon), 1, "No more coupon should have been generated and the existing one should not have been deleted")
self.assertEqual(generated_coupon.state, 'expired', "The coupon should have been set as expired as it is no more valid since we don't have the required quantity")
sol1.product_uom_qty = 2
order.recompute_coupon_lines()
generated_coupon = order.generated_coupon_ids
self.assertEqual(len(generated_coupon), 1, "We should still have only 1 coupon as we now benefit again from the program but no need to create a new one (see next assert)")
self.assertEqual(generated_coupon.state, 'reserved', "The coupon should be set back to reserved as we had already an expired one, no need to create a new one")
def test_coupon_rule_minimum_amount(self):
""" Ensure coupon with minimum amount rule are correctly
applied on orders
"""
order = self.empty_order
self.env['sale.order.line'].create({
'product_id': self.conferenceChair.id,
'name': 'Conference Chair',
'product_uom_qty': 10.0,
'order_id': order.id,
})
self.assertEqual(order.amount_total, 165.0, "The order amount is not correct")
self.env['coupon.generate.wizard'].with_context(active_id=self.discount_coupon_program.id).create({}).generate_coupon()
coupon = self.discount_coupon_program.coupon_ids[0]
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': coupon.code
}).process_coupon()
self.assertEqual(order.amount_total, 65.0, "The coupon should be correctly applied")
order.recompute_coupon_lines()
self.assertEqual(order.amount_total, 65.0, "The coupon should not be removed from the order")
def test_coupon_and_program_discount_fixed_amount(self):
""" Ensure coupon and program discount both with
minimum amount rule can cohexists without making
the order go below 0
"""
order = self.empty_order
orderline = self.env['sale.order.line'].create({
'product_id': self.conferenceChair.id,
'name': 'Conference Chair',
'product_uom_qty': 10.0,
'order_id': order.id,
})
self.assertEqual(order.amount_total, 165.0, "The order amount is not correct")
self.env['coupon.program'].create({
'name': '$100 promotion program',
'program_type': 'promotion_program',
'promo_code_usage': 'code_needed',
'promo_code': 'testpromo',
'reward_type': 'discount',
'discount_type': 'fixed_amount',
'discount_fixed_amount': 100,
'active': True,
'discount_apply_on': 'on_order',
'rule_minimum_amount': 100.00,
})
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': 'testpromo'
}).process_coupon()
self.assertEqual(order.amount_total, 65.0, "The promotion program should be correctly applied")
order.recompute_coupon_lines()
self.assertEqual(order.amount_total, 65.0, "The promotion program should not be removed after recomputation")
self.env['coupon.generate.wizard'].with_context(active_id=self.discount_coupon_program.id).create({}).generate_coupon()
coupon = self.discount_coupon_program.coupon_ids[0]
with self.assertRaises(UserError):
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': coupon.code
}).process_coupon()
orderline.write({'product_uom_qty': 15})
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': coupon.code
}).process_coupon()
self.assertEqual(order.amount_total, 47.5, "The promotion program should now be correctly applied")
orderline.write({'product_uom_qty': 5})
order.recompute_coupon_lines()
self.assertEqual(order.amount_total, 82.5, "The promotion programs should have been removed from the order to avoid negative amount")
def test_coupon_and_coupon_discount_fixed_amount_tax_excl(self):
""" Ensure multiple coupon can cohexists without making
the order go below 0
* Have an order of 300 (3 lines: 1 tax excl 15%, 2 notax)
* Apply a coupon A of 10% discount, unconditioned
* Apply a coupon B of 288.5 discount, unconditioned
* Order should not go below 0
* Even applying the coupon in reverse order should yield same result
"""
coupon_program = self.env['coupon.program'].create({
'name': '$288.5 coupon',
'program_type': 'coupon_program',
'reward_type': 'discount',
'discount_type': 'fixed_amount',
'discount_fixed_amount': 288.5,
'active': True,
'discount_apply_on': 'on_order',
})
order = self.empty_order
orderline = self.env['sale.order.line'].create([
{
'product_id': self.conferenceChair.id,
'name': 'Conference Chair',
'product_uom_qty': 1.0,
'price_unit': 100.0,
'order_id': order.id,
'tax_id': [(6, 0, (self.tax_15pc_excl.id,))],
},
{
'product_id': self.pedalBin.id,
'name': 'Computer Case',
'product_uom_qty': 1.0,
'price_unit': 100.0,
'order_id': order.id,
'tax_id': [(6, 0, [])],
},
{
'product_id': self.product_A.id,
'name': 'Computer Case',
'product_uom_qty': 1.0,
'price_unit': 100.0,
'order_id': order.id,
'tax_id': [(6, 0, [])],
},
])
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': 'test_10pc'
}).process_coupon()
self.assertEqual(order.amount_total, 283.5, "The promotion program should be correctly applied")
self.env['coupon.generate.wizard'].with_context(active_id=coupon_program.id).create({
'generation_type': 'nbr_coupon',
'nbr_coupons': 1,
}).generate_coupon()
coupon = coupon_program.coupon_ids
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': coupon.code
}).process_coupon()
order.recompute_coupon_lines()
#TODO fix numbers
# Need an in-depth inspection on the behavior with
# - multiple product with different VAT +
# - a fixed amount (greater than remaining amount to pay) +
# - discount amount
# And user should be able to swap the promotion order with a meaningful result.
self.assertEqual(order.amount_tax, 13.5)
self.assertEqual(order.amount_untaxed, 0.0, "The untaxed amount should not go below 0")
self.assertEqual(order.amount_total, 13.5, "The promotion program should not make the order total go below 0")
order.order_line[3:].unlink() #remove all coupon
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line), 3, "The promotion program should be removed")
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': coupon.code
}).process_coupon()
self.assertEqual(order.amount_total, 26.5, "The promotion program should be correctly applied")
order.recompute_coupon_lines()
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': 'test_10pc'
}).process_coupon()
order.recompute_coupon_lines()
#TODO fix numbers
self.assertEqual(order.amount_tax, 13.5)
self.assertEqual(order.amount_untaxed, 0.0)
self.assertEqual(order.amount_total, 13.5, "The promotion program should not make the order total go below 0be altered after recomputation")
def test_coupon_and_coupon_discount_fixed_amount_tax_incl(self):
""" Ensure multiple coupon can cohexists without making
the order go below 0
* Have an order of 300 (3 lines: 1 tax incl 10%, 2 notax)
* Apply a coupon A of 10% discount, unconditioned
* Apply a coupon B of 290 discount, unconditioned
* Order should not go below 0
* Even applying the coupon in reverse order should yield same result
"""
coupon_program = self.env['coupon.program'].create({
'name': '$290 coupon',
'program_type': 'coupon_program',
'reward_type': 'discount',
'discount_type': 'fixed_amount',
'discount_fixed_amount': 290,
'active': True,
'discount_apply_on': 'on_order',
})
order = self.empty_order
orderline = self.env['sale.order.line'].create([
{
'product_id': self.conferenceChair.id,
'name': 'Conference Chair',
'product_uom_qty': 1.0,
'price_unit': 100.0,
'order_id': order.id,
'tax_id': [(6, 0, (self.tax_10pc_incl.id,))],
},
{
'product_id': self.pedalBin.id,
'name': 'Computer Case',
'product_uom_qty': 1.0,
'price_unit': 100.0,
'order_id': order.id,
'tax_id': [(6, 0, [])],
},
{
'product_id': self.product_A.id,
'name': 'Computer Case',
'product_uom_qty': 1.0,
'price_unit': 100.0,
'order_id': order.id,
'tax_id': [(6, 0, [])],
},
])
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': 'test_10pc'
}).process_coupon()
self.assertEqual(order.amount_total, 270.0, "The promotion program should be correctly applied")
self.env['coupon.generate.wizard'].with_context(active_id=coupon_program.id).create({
'generation_type': 'nbr_coupon',
'nbr_coupons': 1,
}).generate_coupon()
coupon = coupon_program.coupon_ids
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': coupon.code
}).process_coupon()
self.assertEqual(order.amount_total, 0.0, "The promotion program should not make the order total go below 0")
order.recompute_coupon_lines()
#TODO fix numbers
self.assertEqual(order.amount_total, 9.09, "The promotion program should not be altered after recomputation")
self.assertEqual(order.amount_tax, 8.18)
self.assertEqual(order.amount_untaxed, 0.91)
order.order_line[3:].unlink() #remove all coupon
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line), 3, "The promotion program should be removed")
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': coupon.code
}).process_coupon()
self.assertEqual(order.amount_total, 10.0, "The promotion program should be correctly applied")
order.recompute_coupon_lines()
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': 'test_10pc'
}).process_coupon()
order.recompute_coupon_lines()
#TODO fix numbers
self.assertEqual(order.amount_tax, 9.01)
self.assertEqual(order.amount_untaxed, 0.08)
self.assertEqual(order.amount_total, 9.09, "The promotion program should not be altered after recomputation")
def test_program_discount_on_multiple_specific_products(self):
""" Ensure a discount on multiple specific products is correctly computed.
- Simple: Discount must be applied on all the products set on the promotion
- Advanced: This discount must be split by different taxes
"""
order = self.empty_order
p_specific_products = self.env['coupon.program'].create({
'name': '20% reduction on Conference Chair and Drawer Black in cart',
'promo_code_usage': 'no_code_needed',
'reward_type': 'discount',
'program_type': 'promotion_program',
'discount_type': 'percentage',
'discount_percentage': 25.0,
'discount_apply_on': 'specific_products',
'discount_specific_product_ids': [(6, 0, [self.conferenceChair.id, self.drawerBlack.id])],
})
self.env['sale.order.line'].create({
'product_id': self.conferenceChair.id,
'name': 'Conference Chair',
'product_uom_qty': 4.0,
'order_id': order.id,
})
sol2 = self.env['sale.order.line'].create({
'product_id': self.drawerBlack.id,
'name': 'Drawer Black',
'product_uom_qty': 2.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 3, "Conference Chair + Drawer Black + 20% discount line")
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Conference Chair | 4 | 16.50 | / | 66.00 | 66.00 | 0.00
# Drawer Black | 2 | 25.00 | / | 50.00 | 50.00 | 0.00
# 25% discount | 1 | -29.00 | / | -29.00 | -29.00 | 0.00
# --------------------------------------------------------------------------------
# TOTAL | 87.00 | 87.00 | 0.00
self.assertEqual(order.amount_total, 87.00, "Total should be 87.00, see above comment")
# remove Drawer Black case from promotion
p_specific_products.discount_specific_product_ids = [(6, 0, [self.conferenceChair.id])]
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 3, "Should still be Conference Chair + Drawer Black + 20% discount line")
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Conference Chair | 4 | 16.50 | / | 66.00 | 66.00 | 0.00
# Drawer Black | 2 | 25.00 | / | 50.00 | 50.00 | 0.00
# 25% discount | 1 | -16.50 | / | -16.50 | -16.50 | 0.00
# --------------------------------------------------------------------------------
# TOTAL | 99.50 | 99.50 | 0.00
self.assertEqual(order.amount_total, 99.50, "The 12.50 discount from the drawer black should be gone")
# =========================================================================
# PART 2: Same flow but with different taxes on products to ensure discount is split per VAT
# Add back Drawer Black in promotion
p_specific_products.discount_specific_product_ids = [(6, 0, [self.conferenceChair.id, self.drawerBlack.id])]
percent_tax = self.env['account.tax'].create({
'name': "30% Tax",
'amount_type': 'percent',
'amount': 30,
'price_include': True,
})
sol2.tax_id = percent_tax
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 4, "Conference Chair + Drawer Black + 20% on no TVA product (Conference Chair) + 20% on 15% tva product (Drawer Black)")
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Conference Chair | 4 | 16.50 | / | 66.00 | 66.00 | 0.00
# Drawer Black | 2 | 25.00 | 30% incl | 38.46 | 50.00 | 11.54
# 25% discount | 1 | -16.50 | / | -16.50 | -16.50 | 0.00
# 25% discount | 1 | -12.50 | 30% incl | -9.62 | -12.50 | -2.88
# --------------------------------------------------------------------------------
# TOTAL | 78.34 | 87.00 | 8.66
self.assertEqual(order.amount_total, 87.00, "Total untaxed should be as per above comment")
self.assertEqual(order.amount_untaxed, 78.34, "Total with taxes should be as per above comment")
def test_program_numbers_free_prod_with_min_amount_and_qty_on_same_prod(self):
# This test focus on giving a free product based on both
# minimum amount and quantity condition on an
# auto applied promotion program
order = self.empty_order
self.p3 = self.env['coupon.program'].create({
'name': 'Buy 2 Chairs, get 1 free',
'promo_code_usage': 'no_code_needed',
'reward_type': 'product',
'program_type': 'promotion_program',
'reward_product_id': self.conferenceChair.id,
'rule_min_quantity': 2,
'rule_minimum_amount': self.conferenceChair.lst_price * 2,
'rule_products_domain': '[["sale_ok","=",True], ["id","=", %d]]' % self.conferenceChair.id,
})
sol1 = self.env['sale.order.line'].create({
'product_id': self.conferenceChair.id,
'name': 'Conf Chair',
'product_uom_qty': 2.0,
'order_id': order.id,
})
sol2 = self.env['sale.order.line'].create({
'product_id': self.drawerBlack.id,
'name': 'Drawer',
'product_uom_qty': 1.0,
'order_id': order.id,
}) # dummy line
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 2, "The promotion lines should not be applied")
sol1.write({'product_uom_qty': 3.0})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 3, "The promotion lines should have been added")
self.assertEqual(order.amount_total, self.conferenceChair.lst_price * (sol1.product_uom_qty - 1) + self.drawerBlack.lst_price * sol2.product_uom_qty, "The promotion line was not applied to the amount total")
sol2.unlink()
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 2, "The other product should not affect the promotion")
self.assertEqual(order.amount_total, self.conferenceChair.lst_price * (sol1.product_uom_qty - 1), "The promotion line was not applied to the amount total")
sol1.write({'product_uom_qty': 2.0})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line.ids), 1, "The promotion lines should have been removed")
def test_program_step_percentages(self):
# test step-like percentages increase over amount
testprod = self.env['product.product'].create({
'name': 'testprod',
'lst_price': 118.0,
})
self.env['coupon.program'].create({
'name': '10% discount',
'promo_code_usage': 'no_code_needed',
'program_type': 'promotion_program',
'discount_type': 'percentage',
'discount_percentage': 10.0,
'rule_minimum_amount': 1500.0,
'rule_minimum_amount_tax_inclusion': 'tax_included',
})
self.env['coupon.program'].create({
'name': '15% discount',
'promo_code_usage': 'no_code_needed',
'program_type': 'promotion_program',
'discount_type': 'percentage',
'discount_percentage': 15.0,
'rule_minimum_amount': 1750.0,
'rule_minimum_amount_tax_inclusion': 'tax_included',
})
self.env['coupon.program'].create({
'name': '20% discount',
'promo_code_usage': 'no_code_needed',
'program_type': 'promotion_program',
'discount_type': 'percentage',
'discount_percentage': 20.0,
'rule_minimum_amount': 2000.0,
'rule_minimum_amount_tax_inclusion': 'tax_included',
})
self.env['coupon.program'].create({
'name': '25% discount',
'promo_code_usage': 'no_code_needed',
'program_type': 'promotion_program',
'discount_type': 'percentage',
'discount_percentage': 25.0,
'rule_minimum_amount': 2500.0,
'rule_minimum_amount_tax_inclusion': 'tax_included',
})
#apply 10%
order = self.empty_order
order_line = self.env['sale.order.line'].create({
'product_id': testprod.id,
'name': 'testprod',
'product_uom_qty': 14.0,
'price_unit': 118.0,
'order_id': order.id,
'tax_id': False,
})
order.recompute_coupon_lines()
self.assertEqual(order.amount_total, 1486.80, "10% discount should be applied")
self.assertEqual(len(order.order_line.ids), 2, "discount should be applied")
#switch to 15%
order_line.write({'product_uom_qty': 15})
self.assertEqual(order.amount_total, 1604.8, "Discount improperly applied")
self.assertEqual(len(order.order_line.ids), 2, "No discount applied while it should")
#switch to 20%
order_line.write({'product_uom_qty': 17})
order.recompute_coupon_lines()
self.assertEqual(order.amount_total, 1604.8, "Discount improperly applied")
self.assertEqual(len(order.order_line.ids), 2, "No discount applied while it should")
#still 20%
order_line.write({'product_uom_qty': 20})
order.recompute_coupon_lines()
self.assertEqual(order.amount_total, 1888.0, "Discount improperly applied")
self.assertEqual(len(order.order_line.ids), 2, "No discount applied while it should")
#back to 10%
order_line.write({'product_uom_qty': 14})
order.recompute_coupon_lines()
self.assertEqual(order.amount_total, 1486.80, "Discount improperly applied")
self.assertEqual(len(order.order_line.ids), 2, "No discount applied while it should")
def test_program_free_prods_with_min_qty_and_reward_qty_and_rule(self):
order = self.empty_order
coupon_program = self.env['coupon.program'].create({
'name': '2 free conference chair if at least 1 large cabinet',
'promo_code_usage': 'code_needed',
'program_type': 'promotion_program',
'reward_type': 'product',
'reward_product_quantity': 2,
'reward_product_id': self.conferenceChair.id,
'rule_min_quantity': 1,
'rule_products_domain': '["&", ["sale_ok","=",True], ["name","ilike","large cabinet"]]',
})
# set large cabinet and conference chair prices
self.largeCabinet.write({'list_price': 500, 'sale_ok': True,})
self.conferenceChair.write({'list_price': 100, 'sale_ok': True})
# create SOL
sol1 = self.env['sale.order.line'].create({
'product_id': self.largeCabinet.id,
'name': 'Large Cabinet',
'product_uom_qty': 1.0,
'order_id': order.id,
})
sol2 = self.env['sale.order.line'].create({
'product_id': self.conferenceChair.id,
'name': 'Conference chair',
'product_uom_qty': 2.0,
'order_id': order.id,
})
self.assertEqual(len(order.order_line), 2, 'The order must contain 2 order lines since the coupon is not yet applied')
self.assertEqual(order.amount_total, 700.0, 'The price must be 500.0 since the coupon is not yet applied')
# generate and apply coupon
self.env['coupon.generate.wizard'].with_context(active_id=coupon_program.id).create({
'generation_type': 'nbr_coupon',
'nbr_coupons': 1,
}).generate_coupon()
coupon = coupon_program.coupon_ids
self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
'coupon_code': coupon.code
}).process_coupon()
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Conference Chair | 2 | 100.00 | / | 200.00 | 200.00 | /
# Large Cabinet | 1 | 500.00 | / | 500.00 | 500.00 | /
#
# Free Conference Chair | 2 | -100.00 | / | -200.00 | -200.00 | /
# --------------------------------------------------------------------------------
# TOTAL | 500.00 | 500.00 | /
self.assertEqual(len(order.order_line), 3, 'The order must contain 3 order lines including one for free conference chair')
self.assertEqual(order.amount_total, 500.0, 'The price must be 500.0 since two conference chairs are free')
self.assertEqual(order.order_line[2].price_total, -200.0, 'The last order line should apply a reduction of 200.0 since there are two conference chairs that cost 100.0 each')
# prevent user to get illicite discount by decreasing the to 1 the reward product qty after applying the coupon
sol2.product_uom_qty = 1.0
order.recompute_coupon_lines()
# in this case user should not have -200.0
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Conference Chair | 1 | 100.00 | / | 100.00 | 100.00 | /
# Large Cabine | 1 | 500.00 | / | 500.00 | 500.00 | /
#
# Free Conference Chair | 2 | -100.00 | / | -200.00 | -200.00 | /
# --------------------------------------------------------------------------------
# TOTAL | 400.00 | 400.00 | /
# he should rather have this one
# Name | Qty | price_unit | Tax | HTVA | TVAC | TVA |
# --------------------------------------------------------------------------------
# Conference Chair | 1 | 100.00 | / | 100.00 | 100.00 | /
# Large Cabinet | 1 | 500.00 | / | 500.00 | 500.00 | /
#
# Free Conference Chair | 1 | -100.00 | / | -100.00 | -100.00 | /
# --------------------------------------------------------------------------------
# TOTAL | 500.00 | 500.00 | /
self.assertEqual(order.amount_total, 500.0, 'The price must be 500.0 since two conference chairs are free and the user only bought one')
self.assertEqual(order.order_line[2].price_total, -100.0, 'The last order line should apply a reduction of 100.0 since there is one conference chair that cost 100.0')
def test_program_free_product_different_than_rule_product_with_multiple_application(self):
order = self.empty_order
self.env['sale.order.line'].create({
'product_id': self.drawerBlack.id,
'product_uom_qty': 2.0,
'order_id': order.id,
})
sol_B = self.env['sale.order.line'].create({
'product_id': self.largeMeetingTable.id,
'product_uom_qty': 1.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line), 3, 'The order must contain 3 order lines: 1x for Black Drawer, 1x for Large Meeting Table and 1x for free Large Meeting Table')
self.assertEqual(order.amount_total, self.drawerBlack.list_price * 2, 'The price must be 50.0 since the Large Meeting Table is free: 2*25.00 (Black Drawer) + 1*40000.00 (Large Meeting Table) - 1*40000.00 (free Large Meeting Table)')
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).product_uom_qty, 1, "Only one free Large Meeting Table should be offered, as only one paid Large Meeting Table is in cart. You can't have more free product than paid product.")
sol_B.product_uom_qty = 2
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line), 3, 'The order must contain 3 order lines: 1x for Black Drawer, 1x for Large Meeting Table and 1x for free Large Meeting Table')
self.assertEqual(order.amount_total, self.drawerBlack.list_price * 2, 'The price must be 50.0 since the 2 Large Meeting Table are free: 2*25.00 (Black Drawer) + 2*40000.00 (Large Meeting Table) - 2*40000.00 (free Large Meeting Table)')
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).product_uom_qty, 2, 'The 2 Large Meeting Table should be offered, as the promotion says 1 Black Drawer = 1 free Large Meeting Table and there are 2 Black Drawer')
def test_program_modify_reward_line_qty(self):
order = self.empty_order
product_F = self.env['product.product'].create({
'name': 'Product F',
'list_price': 100,
'sale_ok': True,
'taxes_id': [(6, 0, [])],
})
self.env['coupon.program'].create({
'name': '1 Product F = 5$ discount',
'promo_code_usage': 'no_code_needed',
'reward_type': 'discount',
'discount_type': 'fixed_amount',
'discount_fixed_amount': 5,
'rule_products_domain': "[('id', 'in', [%s])]" % (product_F.id),
'active': True,
})
self.env['sale.order.line'].create({
'product_id': product_F.id,
'product_uom_qty': 2.0,
'order_id': order.id,
})
order.recompute_coupon_lines()
self.assertEqual(len(order.order_line), 2, 'The order must contain 2 order lines: 1x Product F and 1x 5$ discount')
self.assertEqual(order.amount_total, 195.0, 'The price must be 195.0 since there is a 5$ discount and 2x Product F')
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).product_uom_qty, 1, 'The reward line should have a quantity of 1 since Fixed Amount discounts apply only once per Sale Order')
order.order_line[1].product_uom_qty = 2
self.assertEqual(len(order.order_line), 2, 'The order must contain 2 order lines: 1x Product F and 1x 5$ discount')
self.assertEqual(order.amount_total, 190.0, 'The price must be 190.0 since there is now 2x 5$ discount and 2x Product F')
self.assertEqual(order.order_line.filtered(lambda x: x.is_reward_line).price_unit, -5, 'The discount unit price should still be -5 after the quantity was manually changed')
|