diff options
Diffstat (limited to 'addons/website_sale_coupon/models')
| -rw-r--r-- | addons/website_sale_coupon/models/__init__.py | 6 | ||||
| -rw-r--r-- | addons/website_sale_coupon/models/sale_coupon.py | 13 | ||||
| -rw-r--r-- | addons/website_sale_coupon/models/sale_coupon_program.py | 36 | ||||
| -rw-r--r-- | addons/website_sale_coupon/models/sale_order.py | 95 |
4 files changed, 150 insertions, 0 deletions
diff --git a/addons/website_sale_coupon/models/__init__.py b/addons/website_sale_coupon/models/__init__.py new file mode 100644 index 00000000..3ac5d489 --- /dev/null +++ b/addons/website_sale_coupon/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import sale_coupon +from . import sale_coupon_program +from . import sale_order diff --git a/addons/website_sale_coupon/models/sale_coupon.py b/addons/website_sale_coupon/models/sale_coupon.py new file mode 100644 index 00000000..0b8029ef --- /dev/null +++ b/addons/website_sale_coupon/models/sale_coupon.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 SaleCoupon(models.Model): + _inherit = 'coupon.coupon' + + def _check_coupon_code(self, order): + if self.program_id.website_id and self.program_id.website_id != order.website_id: + return {'error': 'This coupon is not valid on this website.'} + return super()._check_coupon_code(order) diff --git a/addons/website_sale_coupon/models/sale_coupon_program.py b/addons/website_sale_coupon/models/sale_coupon_program.py new file mode 100644 index 00000000..b55d2a0e --- /dev/null +++ b/addons/website_sale_coupon/models/sale_coupon_program.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, models, _ +from odoo.exceptions import ValidationError + + +class CouponProgram(models.Model): + _name = 'coupon.program' + _inherit = ['coupon.program', 'website.multi.mixin'] + + @api.constrains('promo_code', 'website_id') + def _check_promo_code_constraint(self): + """ Only case where multiple same code could coexists is if they all belong to their own website. + If the program is website generic, we should ensure there is no generic and no specific (even for other website) already + If the program is website specific, we should ensure there is no existing code for this website or False + """ + for program in self.filtered(lambda p: p.promo_code): + domain = [('id', '!=', program.id), ('promo_code', '=', program.promo_code)] + if program.website_id: + domain += program.website_id.website_domain() + if self.search(domain): + raise ValidationError(_('The program code must be unique by website!')) + + def _filter_programs_on_website(self, order): + return self.filtered(lambda program: not program.website_id or program.website_id.id == order.website_id.id) + + @api.model + def _filter_programs_from_common_rules(self, order, next_order=False): + programs = self._filter_programs_on_website(order) + return super(CouponProgram, programs)._filter_programs_from_common_rules(order, next_order) + + def _check_promo_code(self, order, coupon_code): + if self.website_id and self.website_id != order.website_id: + return {'error': 'This promo code is not valid on this website.'} + return super()._check_promo_code(order, coupon_code) diff --git a/addons/website_sale_coupon/models/sale_order.py b/addons/website_sale_coupon/models/sale_order.py new file mode 100644 index 00000000..08ff0123 --- /dev/null +++ b/addons/website_sale_coupon/models/sale_order.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +from datetime import timedelta + +from odoo import api, fields, models +from odoo.http import request + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + def _compute_website_order_line(self): + """ This method will merge multiple discount lines generated by a same program + into a single one (temporary line with `new()`). + This case will only occur when the program is a discount applied on multiple + products with different taxes. + In this case, each taxes will have their own discount line. This is required + to have correct amount of taxes according to the discount. + But we wan't these lines to be `visually` merged into a single one in the + e-commerce since the end user should only see one discount line. + This is only possible since we don't show taxes in cart. + eg: + line 1: 10% discount on product with tax `A` - $15 + line 2: 10% discount on product with tax `B` - $11.5 + line 3: 10% discount on product with tax `C` - $10 + would be `hidden` and `replaced` by + line 1: 10% discount - $36.5 + + Note: The line will be created without tax(es) and the amount will be computed + depending if B2B or B2C is enabled. + """ + super()._compute_website_order_line() + for order in self: + # TODO: potential performance bottleneck downstream + programs = order._get_applied_programs_with_rewards_on_current_order() + for program in programs: + program_lines = order.order_line.filtered(lambda line: + line.product_id == program.discount_line_product_id) + if len(program_lines) > 1: + if self.env.user.has_group('sale.group_show_price_subtotal'): + price_unit = sum(program_lines.mapped('price_subtotal')) + else: + price_unit = sum(program_lines.mapped('price_total')) + # TODO: batch then flush + order.website_order_line += self.env['sale.order.line'].new({ + 'product_id': program_lines[0].product_id.id, + 'price_unit': price_unit, + 'name': program_lines[0].name, + 'product_uom_qty': 1, + 'product_uom': program_lines[0].product_uom.id, + 'order_id': order.id, + 'is_reward_line': True, + }) + order.website_order_line -= program_lines + + def _compute_cart_info(self): + super(SaleOrder, self)._compute_cart_info() + for order in self: + reward_lines = order.website_order_line.filtered(lambda line: line.is_reward_line) + order.cart_quantity -= int(sum(reward_lines.mapped('product_uom_qty'))) + + def get_promo_code_error(self, delete=True): + error = request.session.get('error_promo_code') + if error and delete: + request.session.pop('error_promo_code') + return error + + def _cart_update(self, product_id=None, line_id=None, add_qty=0, set_qty=0, **kwargs): + res = super(SaleOrder, self)._cart_update(product_id=product_id, line_id=line_id, add_qty=add_qty, set_qty=set_qty, **kwargs) + self.recompute_coupon_lines() + return res + + def _get_free_shipping_lines(self): + self.ensure_one() + free_shipping_prgs_ids = self._get_applied_programs_with_rewards_on_current_order().filtered(lambda p: p.reward_type == 'free_shipping') + if not free_shipping_prgs_ids: + 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.autovacuum + def _gc_abandoned_coupons(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['coupon.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() |
