diff --git a/website_coupon/controllers/main.py b/website_coupon/controllers/main.py
index abcbc7e2b..aa2e9099b 100644
--- a/website_coupon/controllers/main.py
+++ b/website_coupon/controllers/main.py
@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
-from datetime import datetime
-from dateutil import parser
from odoo import http
from odoo.http import request
@@ -55,124 +53,26 @@ class WebsiteCoupon(http.Controller):
applied and coupon balance will also be updated.
"""
- curr_user = request.env.user
coupon = request.env['gift.coupon'].sudo().search(
- [('code', '=', promo_voucher)], limit=1)
- flag = True
- if coupon and coupon.total_avail > 0:
- applied_coupons = request.env['partner.coupon'].sudo().search(
- [('coupon', '=', promo_voucher),
- ('partner_id', '=', curr_user.partner_id.id)],
- limit=1)
+ [('code', '=', promo_voucher)])
+ base_url = '/shop/cart'
- # checking voucher date and limit for each user for this coupon
- if coupon.partner_id:
- if curr_user.partner_id.id != coupon.partner_id.id:
- flag = False
- today = datetime.now().date()
- voucher_expiry = parser.parse(coupon.voucher.expiry_date).date()
- if (flag
- and applied_coupons.number < coupon.limit
- and today <= voucher_expiry):
- # checking coupon validity
- # checking date of coupon
- if coupon.start_date and coupon.end_date:
- if (today < parser.parse(coupon.start_date).date()
- or today > parser.parse(coupon.end_date).date()):
- flag = False
- elif coupon.start_date:
- if today < parser.parse(coupon.start_date).date():
- flag = False
- elif coupon.end_date:
- if today > parser.parse(coupon.end_date).date():
- flag = False
- else:
- flag = False
- else:
- flag = False
- if flag:
- voucher_type = coupon.voucher.voucher_type
- voucher_val = coupon.voucher_val
- type = coupon.type
- coupon_product = request.env['product.product'].sudo().search(
- [('name', '=', 'Gift Coupon')], limit=1)
- if coupon_product:
- order = request.website.sale_get_order(force_create=1)
- flag_product = False
- for line in order.order_line:
- if line.product_id.name == 'Gift Coupon':
- flag = False
- break
- if flag and order.order_line:
- if voucher_type == 'product':
- # the voucher type is product
- product_id = coupon.voucher.product_id
- for line in order.order_line:
- if line.product_id.name == product_id.name:
- flag_product = True
- elif voucher_type == 'category':
- # the voucher type is category
- voucher_cat = coupon.voucher.product_categ
- for ol in order.order_line:
- if ol.product_id.categ_id.name == voucher_cat.name:
- flag_product = True
- elif voucher_type == 'all':
- # the voucher is applicable to all products
- flag_product = True
- if flag_product:
- # the voucher is applicable
- if type == 'fixed':
- # coupon type is 'fixed'
- if voucher_val < order.amount_total:
- coupon_product.product_tmpl_id.write(
- {'list_price': -voucher_val})
+ if not coupon or not coupon.is_valid(request.env.user.partner_id):
+ return request.redirect(base_url + '?coupon_not_available=1')
- else:
- return request.redirect(
- "/shop/cart?coupon_not_available=3")
- elif type == 'percentage':
- # coupon type is percentage
- amount_final = 0
- factor = (voucher_val / 100)
- if voucher_type == 'product':
- for ol in order.order_line:
- if ol.product_id.name == product_id.name:
- amount_final = factor * ol.price_total
- break
- elif voucher_type == 'category':
- for ol in order.order_line:
- cat_name = ol.product_id.categ_id.name
- if cat_name == voucher_cat.name:
- amount_final += factor * ol.price_total
- elif voucher_type == 'all':
- amount_final = factor * order.amount_total
- coupon_product.product_tmpl_id.write(
- {'list_price': -amount_final})
- order._cart_update(product_id=coupon_product.id,
- set_qty=1, add_qty=1)
- # updating coupon balance
- total = coupon.total_avail - 1
- coupon.write({'total_avail': total})
- # creating a record for this partner,
- # i.e he is used this coupon once
- if not applied_coupons:
- curr_user.partner_id.write({
- 'applied_coupon': [(0, 0, {
- 'partner_id': curr_user.partner_id.id,
- 'coupon': coupon.code,
- 'number': 1,
- })]
- })
- else:
- applied_coupons.write(
- {'number': applied_coupons.number + 1})
- else:
- return request.redirect(
- "/shop/cart?coupon_not_available=1")
- else:
- return request.redirect(
- "/shop/cart?coupon_not_available=2")
- else:
- return request.redirect("/shop/cart?coupon_not_available=1")
+ order = request.website.sale_get_order(force_create=1)
+ gift_product = request.env['product.product'].sudo().search(
+ [('name', '=', 'Gift Coupon')], limit=1)
- return request.redirect("/shop/cart")
+ # Make sure the gift product is not already in current order
+ if any(True for line in order.order_line
+ if line.product_id == gift_product):
+ return request.redirect(base_url + '?coupon_not_available=2')
+
+ used, amount = coupon.consume_coupon(order)
+ if used:
+ gift_product.product_tmpl_id.list_price = -amount
+ order._cart_update(product_id=gift_product.id, set_qty=used)
+ return request.redirect(base_url)
+ else:
+ return request.redirect(base_url + '?coupon_not_available=1')
diff --git a/website_coupon/models/gift_voucher.py b/website_coupon/models/gift_voucher.py
index 9e5362232..c531f1817 100644
--- a/website_coupon/models/gift_voucher.py
+++ b/website_coupon/models/gift_voucher.py
@@ -21,6 +21,9 @@
#
##############################################################################
+from datetime import datetime
+from dateutil import parser
+
import string
import random
@@ -46,6 +49,19 @@ class GiftVoucher(models.Model):
max_value = fields.Integer(string="Maximum Voucher Value", required=True)
expiry_date = fields.Date(string="Expiry Date", required=True)
+ def is_order_line_eligible(self, order_line):
+ if self.voucher_type == 'product':
+ return order_line.product_id == self.product_id
+ elif self.voucher_type == 'category':
+ return order_line.product_id.categ_id == self.product_categ
+ elif self.voucher_type == 'all':
+ return True
+
+ @api.multi
+ def eligible_lines(self, order):
+ self.ensure_one()
+ return filter(self.is_order_line_eligible, order.order_line or ())
+
class GiftCoupon(models.Model):
_name = 'gift.coupon'
@@ -80,6 +96,108 @@ class GiftCoupon(models.Model):
or self.voucher_val < self.voucher.min_value):
raise UserError(_("Please check the voucher value"))
+ @api.multi
+ def amount(self, product_price):
+ if self.type == 'fixed':
+ return self.voucher_val
+ elif self.type == 'percentage':
+ return product_price * self.voucher_val / 100.
+
+ @api.multi
+ def applied_coupons(self, partner):
+ self.ensure_one()
+ return self.env['partner.coupon'].sudo().search([
+ ('coupon', '=', self.code),
+ ('partner_id', '=', partner.id),
+ ], limit=1)
+
+ @api.multi
+ def available_coupons(self, partner):
+ if not self.limit > 0:
+ return self.total_avail
+ else:
+ return min(self.total_avail,
+ self.limit - self.applied_coupons(partner).number or 0)
+
+ @api.multi
+ def consume_coupon(self, order):
+ """ Consume present coupon as much as possible to reduce given order's
+ price, and return a integer number of consumed coupons and a (positive)
+ amount.
+ """
+
+ # Determine coupon amount from eligible order lines
+ eligible_lines = self.voucher.eligible_lines(order)
+ if not eligible_lines:
+ return 0, 0
+
+ available = self.available_coupons(order.partner_id)
+
+ coupons_used, coupons_amount = 0, 0.
+ for line in sorted(eligible_lines, reverse=True,
+ key=lambda l: l.product_id.list_price):
+ used_qtty = min(line.product_uom_qty, available)
+ if used_qtty:
+ coupons_amount += used_qtty * self.amount(
+ line.product_id.list_price)
+ coupons_used += used_qtty
+ available -= used_qtty
+
+ if coupons_used:
+ # update coupon balance
+ self.write({'total_avail': self.total_avail - coupons_used})
+ # create a record for this partner: he has used this coupon once
+ self.use_coupon(order.partner_id, coupons_used)
+
+ return coupons_used, coupons_amount
+
+ @api.multi
+ def use_coupon(self, partner, number):
+ """ Register given partner usage of the coupon `number` of times
+ and adjust the total_avail attribute of the coupon accordingly.
+ """
+ self.ensure_one()
+ applied_coupons = self.applied_coupons(partner)
+ if not applied_coupons:
+ partner.write({
+ 'applied_coupon': [(0, 0, {
+ 'partner_id': partner.id,
+ 'coupon': self.code,
+ 'number': number,
+ })]
+ })
+ else:
+ applied_coupons.write(
+ {'number': applied_coupons.number + number})
+ self.total_avail -= number
+
+ @api.multi
+ def is_valid(self, partner):
+ self.ensure_one()
+
+ if self.total_avail <= 0:
+ return False
+
+ if self.partner_id and self.partner_id != partner:
+ return False
+
+ if (self.limit > 0
+ and self.applied_coupons(partner).number >= self.limit):
+ return False
+
+ today = datetime.now().date()
+
+ if today > parser.parse(self.voucher.expiry_date).date():
+ return False
+
+ if self.start_date and today < parser.parse(self.start_date).date():
+ return False
+
+ if self.end_date and today > parser.parse(self.end_date).date():
+ return False
+
+ return True
+
class CouponPartner(models.Model):
_name = 'partner.coupon'
diff --git a/website_coupon/tests/__init__.py b/website_coupon/tests/__init__.py
new file mode 100644
index 000000000..065dc6ea0
--- /dev/null
+++ b/website_coupon/tests/__init__.py
@@ -0,0 +1 @@
+import test_coupon # noqa
diff --git a/website_coupon/tests/test_coupon.py b/website_coupon/tests/test_coupon.py
new file mode 100644
index 000000000..4ccca6ad3
--- /dev/null
+++ b/website_coupon/tests/test_coupon.py
@@ -0,0 +1,194 @@
+from datetime import datetime, timedelta
+
+from odoo.tests.common import TransactionCase, at_install, post_install
+
+
+@at_install(False)
+@post_install(True)
+class CouponTC(TransactionCase):
+
+ def setUp(self):
+ super(CouponTC, self).setUp()
+ self.voucher = self.env['gift.voucher'].create(self.base_voucher_vals)
+ self.coupon = self.env['gift.coupon'].create(self.base_coupon_vals)
+ self.partner1 = self.env.ref('base.res_partner_1')
+ self.partner2 = self.env.ref('base.res_partner_2')
+
+ @property
+ def base_voucher_vals(self):
+ return {
+ 'name': 'test voucher',
+ 'voucher_type': 'all',
+ 'min_value': 0.,
+ 'max_value': 100000.,
+ 'expiry_date': '2100-01-01',
+ 'product_id': False,
+ 'product_categ': False,
+ }
+
+ @property
+ def base_coupon_vals(self):
+ return {
+ 'name': 'test coupon',
+ 'code': 'TEST_CODE',
+ 'total_avail': '1000',
+ 'voucher_val': 10,
+ 'type': 'fixed',
+ 'voucher': self.voucher.id,
+ 'partner_id': False,
+ 'limit': 0, # means no limit
+ 'start_date': False,
+ 'end_date': False,
+ }
+
+ def reset_coupon(self):
+ self.voucher.update(self.base_voucher_vals)
+ self.coupon.update(self.base_coupon_vals)
+ self.env['partner.coupon'].sudo().search([
+ ('coupon', '=', self.coupon.code),
+ ]).unlink()
+ assert self.coupon.is_valid(self.partner1)
+ assert self.coupon.is_valid(self.partner2)
+
+ def test_use_coupon(self):
+ self.reset_coupon()
+
+ self.coupon.use_coupon(self.partner1, 10)
+ self.assertEqual(self.coupon.total_avail, 990)
+ self.coupon.use_coupon(self.partner1, 20)
+ self.assertEqual(self.coupon.total_avail, 970)
+ self.assertEqual(self.coupon.applied_coupons(self.partner1).number, 30)
+
+ self.coupon.use_coupon(self.partner2, 50)
+ self.assertEqual(self.coupon.total_avail, 920)
+ self.assertEqual(self.coupon.applied_coupons(self.partner2).number, 50)
+
+ def test_is_valid(self):
+ partner1 = self.partner1
+ partner2 = self.partner2
+ coupon = self.coupon
+
+ self.reset_coupon()
+
+ # Check partner field behaviour
+ coupon.partner_id = partner1.id
+ self.assertFalse(coupon.is_valid(partner2))
+ self.assertTrue(coupon.is_valid(partner1))
+
+ # Check total_avail + partner.coupon behaviour
+ coupon.total_avail = 0
+ self.assertFalse(coupon.is_valid(partner2))
+
+ self.reset_coupon()
+
+ coupon.partner_id = partner2.id
+ coupon.use_coupon(partner2, 999)
+ self.assertTrue(coupon.is_valid(partner2))
+ coupon.use_coupon(partner2, 1)
+ self.assertFalse(coupon.is_valid(partner2))
+
+ self.reset_coupon()
+
+ # Check per-partner limit behaviour
+ coupon.limit = 10
+ coupon.use_coupon(partner2, 9)
+ self.assertTrue(coupon.is_valid(partner2))
+ coupon.use_coupon(partner2, 1)
+ self.assertFalse(coupon.is_valid(partner2))
+
+ self.reset_coupon()
+
+ # Check time limits
+ today = datetime.now().date()
+ coupon.start_date = today + timedelta(days=1)
+ self.assertFalse(coupon.is_valid(partner2))
+
+ self.reset_coupon()
+
+ coupon.end_date = today - timedelta(days=1)
+ self.assertFalse(coupon.is_valid(partner2))
+
+ coupon.start_date = today - timedelta(days=10)
+ coupon.end_date = today - timedelta(days=2)
+ self.assertFalse(coupon.is_valid(partner2))
+
+ coupon.start_date = today - timedelta(days=1)
+ coupon.end_date = today + timedelta(days=1)
+ self.assertTrue(coupon.is_valid(partner2))
+
+ def test_consume_coupon(self):
+ order = self.env['sale.order'].create({'partner_id': self.partner2.id})
+ categ = self.env['product.category'].create({'name': 'test categ 1'})
+ product1 = self.env['product.product'].create({
+ 'name': 'test product 1',
+ 'list_price': 100.,
+ 'categ_id': categ.id,
+ })
+ product2 = self.env['product.product'].create({
+ 'name': 'test product 2',
+ 'list_price': 300.,
+ })
+ product3 = self.env['product.product'].create({
+ 'name': 'test product 3',
+ 'list_price': 200.,
+ 'categ_id': categ.id,
+ })
+ self.env['sale.order.line'].create({
+ 'name': 'test line 1',
+ 'product_id': product1.id,
+ 'product_uom_qty': 10,
+ 'order_id': order.id,
+ 'product_uom': product1.uom_id.id,
+ 'price_unit': 120.,
+ })
+ self.env['sale.order.line'].create({
+ 'name': 'test line 2',
+ 'product_id': product2.id,
+ 'product_uom_qty': 5,
+ 'order_id': order.id,
+ 'product_uom': product2.uom_id.id,
+ 'price_unit': 360.,
+ })
+ self.env['sale.order.line'].create({
+ 'name': 'test line 3',
+ 'product_id': product3.id,
+ 'product_uom_qty': 20,
+ 'order_id': order.id,
+ 'product_uom': product3.uom_id.id,
+ 'price_unit': 240.,
+ })
+
+ used, amount = self.coupon.consume_coupon(order)
+ self.assertEqual((used, amount), (35, 350))
+
+ self.reset_coupon()
+
+ self.coupon.type = 'percentage'
+ self.coupon.voucher_val = 10
+ used, amount = self.coupon.consume_coupon(order)
+ self.assertEqual((used, amount), (35, 10*10+5*30+20*20))
+
+ self.reset_coupon()
+
+ self.coupon.type = 'percentage'
+ self.coupon.voucher_val = 10
+ self.voucher.voucher_type = 'product'
+ self.voucher.product_id = product2.id
+ used, amount = self.coupon.consume_coupon(order)
+ self.assertEqual((used, amount), (5, 5*30))
+
+ self.reset_coupon()
+
+ self.coupon.type = 'percentage'
+ self.coupon.voucher_val = 10
+ self.voucher.voucher_type = 'category'
+ self.voucher.product_categ = categ.id
+ used, amount = self.coupon.consume_coupon(order)
+ self.assertEqual((used, amount), (10+20, 10*10+20*20))
+
+ self.reset_coupon()
+
+ self.coupon.type = 'percentage'
+ self.coupon.voucher_val = 10
+ self.coupon.total_avail = 7
+ used, amount = self.coupon.consume_coupon(order)
diff --git a/website_coupon/views/gift_voucher.xml b/website_coupon/views/gift_voucher.xml
index 52562f663..6a362b215 100644
--- a/website_coupon/views/gift_voucher.xml
+++ b/website_coupon/views/gift_voucher.xml
@@ -18,6 +18,7 @@
+
@@ -122,4 +123,4 @@
-
\ No newline at end of file
+