summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRomain Derie <rde@odoo.com>2019-09-25 14:54:20 +0000
committerRomain Derie <rde@odoo.com>2019-10-16 15:15:08 +0000
commitd35ffd9ad955823dfd074ea6dd7a8439134a02f1 (patch)
treef087d27778b7925de22767d4714fb2e483035987
parent0c671222014f3f66f2c561cbe4e3896661d62739 (diff)
[IMP] website_sale_coupon: garbage collect old order coupon
With this commit, when an ecommerce order is judged abandonned (more than 4 days without being touched, and in draft), we remove its coupon so it is available again. task-2058666 closes odoo/odoo#37441 Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com>
-rw-r--r--addons/website_sale_coupon/models/__init__.py1
-rw-r--r--addons/website_sale_coupon/models/ir_autovacuum.py13
-rw-r--r--addons/website_sale_coupon/models/sale_order.py22
-rw-r--r--addons/website_sale_coupon/tests/test_shop_sale_coupon.py82
4 files changed, 112 insertions, 6 deletions
diff --git a/addons/website_sale_coupon/models/__init__.py b/addons/website_sale_coupon/models/__init__.py
index 28c756b4fed..d31916a0b3c 100644
--- a/addons/website_sale_coupon/models/__init__.py
+++ b/addons/website_sale_coupon/models/__init__.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from . import ir_autovacuum
from . import sale_coupon_program
from . import sale_order
diff --git a/addons/website_sale_coupon/models/ir_autovacuum.py b/addons/website_sale_coupon/models/ir_autovacuum.py
new file mode 100644
index 00000000000..62b8b18cc48
--- /dev/null
+++ b/addons/website_sale_coupon/models/ir_autovacuum.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models
+
+
+class AutoVacuum(models.AbstractModel):
+ _inherit = 'ir.autovacuum'
+
+ @api.model
+ def power_on(self, *args, **kwargs):
+ self.env['sale.order']._garbage_collector(*args, **kwargs)
+ return super(AutoVacuum, self).power_on(*args, **kwargs)
diff --git a/addons/website_sale_coupon/models/sale_order.py b/addons/website_sale_coupon/models/sale_order.py
index 574e355455b..e9e07575bb2 100644
--- a/addons/website_sale_coupon/models/sale_order.py
+++ b/addons/website_sale_coupon/models/sale_order.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
-from odoo import models, fields, api
+from datetime import timedelta
+
+from odoo import api, fields, models
from odoo.http import request
@@ -50,7 +52,6 @@ class SaleOrder(models.Model):
})
order.website_order_line -= program_lines
-
def _compute_cart_info(self):
super(SaleOrder, self)._compute_cart_info()
for order in self:
@@ -75,3 +76,20 @@ class SaleOrder(models.Model):
return self.env['sale.order.line']
free_shipping_product_ids = free_shipping_prgs_ids.mapped('discount_line_product_id')
return self.order_line.filtered(lambda l: l.product_id in free_shipping_product_ids)
+
+ @api.model
+ def _garbage_collector(self, *args, **kwargs):
+ """Remove/free coupon from abandonned ecommerce order."""
+ ICP = self.env['ir.config_parameter']
+ validity = ICP.get_param('website_sale_coupon.abandonned_coupon_validity', 4)
+ validity = fields.Datetime.to_string(fields.datetime.now() - timedelta(days=int(validity)))
+ coupon_to_reset = self.env['sale.coupon'].search([
+ ('state', '=', 'used'),
+ ('sales_order_id.state', '=', 'draft'),
+ ('sales_order_id.write_date', '<', validity),
+ ('sales_order_id.website_id', '!=', False),
+ ])
+ for coupon in coupon_to_reset:
+ coupon.sales_order_id.applied_coupon_ids -= coupon
+ coupon_to_reset.write({'state': 'new'})
+ coupon_to_reset.mapped('sales_order_id').recompute_coupon_lines()
diff --git a/addons/website_sale_coupon/tests/test_shop_sale_coupon.py b/addons/website_sale_coupon/tests/test_shop_sale_coupon.py
index 76df76157a1..8cb5377ade3 100644
--- a/addons/website_sale_coupon/tests/test_shop_sale_coupon.py
+++ b/addons/website_sale_coupon/tests/test_shop_sale_coupon.py
@@ -1,15 +1,14 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from datetime import timedelta
-from odoo.tests import HttpCase, tagged
+from odoo import fields
+from odoo.tests import HttpCase, tagged, TransactionCase
from odoo.addons.sale.tests.test_sale_product_attribute_value_config import TestSaleProductAttributeValueSetup
@tagged('post_install', '-at_install')
class TestUi(TestSaleProductAttributeValueSetup, HttpCase):
- post_install = True
- at_install = False
-
def setUp(self):
super(TestUi, self).setUp()
@@ -22,3 +21,78 @@ class TestUi(TestSaleProductAttributeValueSetup, HttpCase):
# pre enable "Show # found" option to avoid race condition...
self.env.ref("website_sale.search_count_box").write({"active": True})
self.start_tour("/", 'shop_sale_coupon', login="admin")
+
+
+@tagged('post_install', '-at_install')
+class TestWebsiteSaleCoupon(TransactionCase):
+
+ def setUp(self):
+ super(TestWebsiteSaleCoupon, self).setUp()
+ program = self.env['sale.coupon.program'].create({
+ 'name': '10% TEST Discount',
+ 'promo_code_usage': 'code_needed',
+ 'discount_apply_on': 'on_order',
+ 'discount_type': 'percentage',
+ 'discount_percentage': 10.0,
+ 'program_type': 'coupon_program',
+ })
+
+ self.env['sale.coupon.generate'].with_context(active_id=program.id).create({}).generate_coupon()
+ self.coupon = program.coupon_ids[0]
+
+ 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
+ })
+
+ def test_01_gc_coupon(self):
+ # 1. Simulate a frontend order (website, product)
+ order = self.empty_order
+ order.website_id = self.env['website'].browse(1)
+ self.env['sale.order.line'].create({
+ 'product_id': self.env['product.product'].create({
+ 'name': 'Product A',
+ 'list_price': 100,
+ 'sale_ok': True,
+ }).id,
+ 'name': 'Product A',
+ 'product_uom_qty': 2.0,
+ 'order_id': order.id,
+ })
+
+ # 2. Apply the coupon
+ self.env['sale.coupon.apply.code'].with_context(active_id=order.id).create({
+ 'coupon_code': self.coupon.code
+ }).process_coupon()
+ order.recompute_coupon_lines()
+
+ self.assertEqual(len(order.applied_coupon_ids), 1, "The coupon should've been applied on the order")
+ self.assertEqual(self.coupon, order.applied_coupon_ids)
+ self.assertEqual(self.coupon.state, 'used')
+
+ # 3. Test recent order -> Should not be removed
+ order._garbage_collector()
+
+ self.assertEqual(len(order.applied_coupon_ids), 1, "The coupon shouldn't have been removed from the order no more than 4 days")
+ self.assertEqual(self.coupon.state, 'used', "Should not have been changed")
+
+ # 4. Test order not older than ICP validity -> Should not be removed
+ ICP = self.env['ir.config_parameter']
+ icp_validity = ICP.create({'key': 'website_sale_coupon.abandonned_coupon_validity', 'value': 5})
+ order.flush()
+ query = """UPDATE %s SET write_date = %%s WHERE id = %%s""" % (order._table,)
+ self.env.cr.execute(query, (fields.Datetime.to_string(fields.datetime.now() - timedelta(days=4, hours=2)), order.id))
+ order._garbage_collector()
+
+ self.assertEqual(len(order.applied_coupon_ids), 1, "The coupon shouldn't have been removed from the order the order is 4 days old but icp validity is 5 days")
+ self.assertEqual(self.coupon.state, 'used', "Should not have been changed (2)")
+
+ # 5. Test order with no ICP and older then 4 default days -> Should be removed
+ icp_validity.unlink()
+ order._garbage_collector()
+
+ self.assertEqual(len(order.applied_coupon_ids), 0, "The coupon should've been removed from the order as more than 4 days")
+ self.assertEqual(self.coupon.state, 'new', "Should have been reset.")