diff --git a/addons/delivery/models/delivery_carrier.py b/addons/delivery/models/delivery_carrier.py index 39a9064b31e..9e6884db010 100644 --- a/addons/delivery/models/delivery_carrier.py +++ b/addons/delivery/models/delivery_carrier.py @@ -208,7 +208,8 @@ class DeliveryCarrier(models.Model): # save the real price in case a free_over rule overide it to 0 res['carrier_price'] = res['price'] # free when order is large enough - if res['success'] and self.free_over and order._compute_amount_total_without_delivery() >= self.amount: + amount_without_delivery = order._compute_amount_total_without_delivery() + if res['success'] and self.free_over and self._compute_currency(order, amount_without_delivery, 'pricelist_to_company') >= self.amount: res['warning_message'] = _('The shipping is free since the order amount exceeds %.2f.') % (self.amount) res['price'] = 0.0 return res diff --git a/addons/delivery/models/sale_order.py b/addons/delivery/models/sale_order.py index 856d9e3c116..5e89ed6d01d 100644 --- a/addons/delivery/models/sale_order.py +++ b/addons/delivery/models/sale_order.py @@ -23,7 +23,7 @@ class SaleOrder(models.Model): def _compute_amount_total_without_delivery(self): self.ensure_one() delivery_cost = sum([l.price_total for l in self.order_line if l.is_delivery]) - return self.env['delivery.carrier']._compute_currency(self, self.amount_total - delivery_cost, 'pricelist_to_company') + return self.amount_total - delivery_cost @api.depends('order_line') def _compute_delivery_state(self): diff --git a/addons/loyalty_delivery/models/__init__.py b/addons/loyalty_delivery/models/__init__.py index 29b01a33788..6911fa64a98 100644 --- a/addons/loyalty_delivery/models/__init__.py +++ b/addons/loyalty_delivery/models/__init__.py @@ -3,3 +3,4 @@ from . import loyalty_program from . import loyalty_reward +from . import sale_order diff --git a/addons/loyalty_delivery/models/sale_order.py b/addons/loyalty_delivery/models/sale_order.py new file mode 100644 index 00000000000..49de5f84cd7 --- /dev/null +++ b/addons/loyalty_delivery/models/sale_order.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + def _compute_amount_total_without_delivery(self): + self.ensure_one() + lines = self.order_line.filtered(lambda l: l.coupon_id) + return super()._compute_amount_total_without_delivery() - sum(lines.mapped('price_unit')) diff --git a/addons/loyalty_delivery/tests/__init__.py b/addons/loyalty_delivery/tests/__init__.py new file mode 100644 index 00000000000..147bb08453a --- /dev/null +++ b/addons/loyalty_delivery/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_loyalty_delivery diff --git a/addons/loyalty_delivery/tests/test_loyalty_delivery.py b/addons/loyalty_delivery/tests/test_loyalty_delivery.py new file mode 100644 index 00000000000..9335ec678f8 --- /dev/null +++ b/addons/loyalty_delivery/tests/test_loyalty_delivery.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests import common, Form +from odoo.exceptions import ValidationError + +@common.tagged('post_install', '-at_install') +class TestLoyaltyDeliveryCost(common.TransactionCase): + + @classmethod + def setUpClass(cls): + super(TestLoyaltyDeliveryCost, cls).setUpClass() + cls.SaleOrder = cls.env['sale.order'] + cls.partner_1 = cls.env['res.partner'].create({'name': 'My Test Customer'}) + cls.pricelist = cls.env['product.pricelist'].create({ + 'name': 'Test Pricelist', + }) + cls.product_4 = cls.env['product.product'].create({'name': 'A product to deliver'}) + cls.product_uom_unit = cls.env.ref('uom.product_uom_unit') + + def test_delivery_cost_gift_card(self): + """ + A customer has a carrier with the amount greater than the one to have + free shipping cost, then uses a gift card that lowers that amount to less + than the threshold: the shipping cost should still be 0.0 + """ + + product_delivery_free = self.env['product.product'].create({ + 'name': 'Free Delivery Charges', + 'type': 'service', + 'list_price': 40.0, + 'categ_id': self.env.ref('delivery.product_category_deliveries').id, + }) + free_delivery = self.env['delivery.carrier'].create({ + 'name': 'Delivery Now Free Over 100', + 'fixed_price': 40, + 'delivery_type': 'fixed', + 'product_id': product_delivery_free.id, + 'free_over': True, + 'amount': 100, + }) + program_gift_card = self.env['loyalty.program'].create({ + 'name': 'Gift Cards', + 'applies_on': 'future', + 'program_type': 'gift_card', + 'trigger': 'auto', + 'reward_ids': [(0, 0, { + 'reward_type': 'discount', + 'discount': 1, + 'discount_mode': 'per_point', + 'discount_applicability': 'order', + })] + }) + self.env['loyalty.generate.wizard'].with_context(active_id=program_gift_card.id).create({ + 'coupon_qty': 1, + 'points_granted': 40, + }).generate_coupons() + gift_card = program_gift_card.coupon_ids[0] + + + sale_normal_delivery_charges = self.SaleOrder.create({ + 'partner_id': self.partner_1.id, + 'partner_invoice_id': self.partner_1.id, + 'partner_shipping_id': self.partner_1.id, + 'pricelist_id': self.pricelist.id, + 'order_line': [(0, 0, { + 'name': 'PC Assamble + 2GB RAM', + 'product_id': self.product_4.id, + 'product_uom_qty': 1, + 'product_uom': self.product_uom_unit.id, + 'price_unit': 120.00, + })] + }) + self._apply_promo_code(sale_normal_delivery_charges, gift_card.code) + sale_normal_delivery_charges.action_confirm() + + delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ + 'default_order_id': sale_normal_delivery_charges.id, + 'default_carrier_id': free_delivery.id, + })) + delivery_wizard.save().button_confirm() + + self.assertEqual(len(sale_normal_delivery_charges.order_line), 3) + self.assertEqual(sale_normal_delivery_charges.amount_untaxed, 80.0, "Delivery cost is not Added") + + def _apply_promo_code(self, order, code, no_reward_fail=True): + status = order._try_apply_code(code) + if 'error' in status: + raise ValidationError(status['error']) + if not status and no_reward_fail: + # Can happen if global discount got filtered out in `_get_claimable_rewards` + raise ValidationError('No reward to claim with this coupon') + coupons = self.env['loyalty.card'] + rewards = self.env['loyalty.reward'] + for coupon, coupon_rewards in status.items(): + coupons |= coupon + rewards |= coupon_rewards + if len(coupons) == 1 and len(rewards) == 1: + status = order._apply_program_reward(rewards, coupons) + if 'error' in status: + raise ValidationError(status['error']) diff --git a/addons/stock_delivery/models/stock_picking.py b/addons/stock_delivery/models/stock_picking.py index 7a95ffaa43d..2ff601ca871 100644 --- a/addons/stock_delivery/models/stock_picking.py +++ b/addons/stock_delivery/models/stock_picking.py @@ -165,8 +165,10 @@ class StockPicking(models.Model): def send_to_shipper(self): self.ensure_one() res = self.carrier_id.send_shipping(self)[0] - if self.carrier_id.free_over and self.sale_id and self.sale_id._compute_amount_total_without_delivery() >= self.carrier_id.amount: - res['exact_price'] = 0.0 + if self.carrier_id.free_over and self.sale_id: + amount_without_delivery = self.sale_id._compute_amount_total_without_delivery() + if self.carrier_id._compute_currency(self.sale_id, amount_without_delivery, 'pricelist_to_company') >= self.carrier_id.amount: + res['exact_price'] = 0.0 self.carrier_price = res['exact_price'] * (1.0 + (self.carrier_id.margin / 100.0)) if res['tracking_number']: previous_pickings = self.env['stock.picking']