[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>
This commit is contained in:
Romain Derie
2019-10-16 15:15:08 +00:00
parent 0c67122201
commit d35ffd9ad9
4 changed files with 112 additions and 6 deletions
@@ -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
@@ -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)
@@ -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()
@@ -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.")