[MRG] website_sale_coupon: move to community
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': "Coupons & Promotions for eCommerce",
|
||||
'summary': """Use coupon & promotion programs in your eCommerce store""",
|
||||
'description': """
|
||||
Create coupon and promotion codes to share in order to boost your sales (free products, discounts, etc.). Shoppers can use them in the eCommerce checkout.
|
||||
|
||||
Coupon & promotion programs can be edited in the Catalog menu of the Website app.
|
||||
""",
|
||||
'category': 'Website',
|
||||
'version': '1.0',
|
||||
'depends': ['website_sale', 'sale_coupon'],
|
||||
'data': [
|
||||
'views/website_sale_templates.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/sale_coupon_program_views.xml',
|
||||
],
|
||||
'auto_install': True,
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import http
|
||||
from odoo.addons.website_sale.controllers.main import WebsiteSale
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class WebsiteSale(WebsiteSale):
|
||||
|
||||
@http.route(['/shop/pricelist'])
|
||||
def pricelist(self, promo, **post):
|
||||
order = request.website.sale_get_order()
|
||||
coupon_status = request.env['sale.coupon.apply.code'].sudo().apply_coupon(order, promo)
|
||||
if coupon_status.get('not_found'):
|
||||
return super(WebsiteSale, self).pricelist(promo, **post)
|
||||
elif coupon_status.get('error'):
|
||||
request.session['error_promo_code'] = coupon_status['error']
|
||||
return request.redirect(post.get('r', '/shop/cart'))
|
||||
|
||||
@http.route(['/shop/payment'], type='http', auth="public", website=True)
|
||||
def payment(self, **post):
|
||||
order = request.website.sale_get_order()
|
||||
order.recompute_coupon_lines()
|
||||
return super(WebsiteSale, self).payment(**post)
|
||||
|
||||
@http.route(['/shop/cart'], type='http', auth="public", website=True)
|
||||
def cart(self, **post):
|
||||
order = request.website.sale_get_order()
|
||||
order.recompute_coupon_lines()
|
||||
return super(WebsiteSale, self).cart(**post)
|
||||
|
||||
def _update_website_sale_coupon(self, **post):
|
||||
order = request.website.sale_get_order()
|
||||
order.recompute_coupon_lines()
|
||||
free_shipping_lines = order._get_free_shipping_lines()
|
||||
currency = order.currency_id
|
||||
result = {}
|
||||
if free_shipping_lines:
|
||||
amount_free_shipping = sum(free_shipping_lines.mapped('price_subtotal'))
|
||||
result.update({
|
||||
'new_amount_delivery': self._format_amount(0.0, currency),
|
||||
'new_amount_untaxed': self._format_amount(order.amount_untaxed, currency),
|
||||
'new_amount_tax': self._format_amount(order.amount_tax, currency),
|
||||
'new_amount_total': self._format_amount(order.amount_total, currency),
|
||||
'new_amount_order_discounted': self._format_amount(order.reward_amount - amount_free_shipping, currency)
|
||||
})
|
||||
return result
|
||||
|
||||
# Override
|
||||
# Add in the rendering the free_shipping_line
|
||||
def _get_shop_payment_values(self, order, **kwargs):
|
||||
values = super(WebsiteSale, self)._get_shop_payment_values(order, **kwargs)
|
||||
values['free_shipping_lines'] = order._get_free_shipping_lines()
|
||||
return values
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import sale_coupon_program
|
||||
from . import sale_order
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- 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 SaleCouponProgram(models.Model):
|
||||
_name = 'sale.coupon.program'
|
||||
_inherit = ['sale.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!'))
|
||||
@@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = "sale.order"
|
||||
|
||||
@api.one
|
||||
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(SaleOrder, self)._compute_website_order_line()
|
||||
for program in self._get_applied_programs_with_rewards_on_current_order():
|
||||
program_lines = self.order_line.filtered(lambda line: line.product_id == program.discount_line_product_id)
|
||||
if len(program_lines) > 1:
|
||||
price_unit = sum(l.price_subtotal if self.env.user.has_group('sale.group_show_price_subtotal') else l.price_total for l in program_lines)
|
||||
self.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': self.id,
|
||||
'is_reward_line': True,
|
||||
})
|
||||
self.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
|
||||
|
||||
@api.multi
|
||||
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.no_code_promo_program_ids.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)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="98.162%" x2="0%" y1="1.838%" y2="100%"><stop offset="0%" stop-color="#797DA5"/><stop offset="50.799%" stop-color="#6D7194"/><stop offset="100%" stop-color="#626584"/></linearGradient><path id="d" d="M50 31.035a3.5 3.5 0 1 0 0 6.93v3.06a1.633 1.633 0 0 1-1.588 1.286L26.974 43l.998 4h18.955c.998 0 .998 2 0 2h-20.95L19.99 25h-1.996v1c0 .667-.332 1-.997 1S16 26.667 16 26v-2c.066-.667.398-1 .998-1h3.99c.516 0 .848.333.998 1l.998 3.281 25.428.037c1.045 0 1.588.761 1.588 2.018v1.7zM45.43 55a2.497 2.497 0 0 1-2.493-2.5c0-1.38 1.116-2.5 2.494-2.5a2.497 2.497 0 0 1 2.494 2.5c0 1.38-1.117 2.5-2.494 2.5zm-18.955 0a2.497 2.497 0 0 1-2.494-2.5c0-1.38 1.117-2.5 2.494-2.5a2.497 2.497 0 0 1 2.495 2.5c0 1.38-1.117 2.5-2.495 2.5zm12.7-24.996a.51.51 0 0 0-.409.2l-7.668 9.056c-.233.31.004.738.41.738l.696-.002a.51.51 0 0 0 .409-.2l7.63-9.056c.234-.312-.005-.74-.41-.738l-.658.002zm-5.841 4.371c1.29 0 2.334-.979 2.334-2.188 0-1.208-1.044-2.187-2.334-2.187S31 30.979 31 32.188c0 1.208 1.044 2.187 2.334 2.187zm0-3.125c.552 0 1 .42 1 .938 0 .517-.448.937-1 .937s-1-.42-1-.938c0-.517.448-.937 1-.937zm4.668 4.375c-1.29 0-2.334.979-2.334 2.188 0 1.208 1.044 2.187 2.334 2.187s2.334-.979 2.334-2.188c0-1.208-1.044-2.187-2.334-2.187zm0 3.125c-.553 0-1-.42-1-.938 0-.517.447-.937 1-.937.552 0 1 .42 1 .938 0 .517-.448.937-1 .937z"/><path id="e" d="M50 30a3 3 0 0 0 0 6v3.025a1.633 1.633 0 0 1-1.588 1.286L26.974 41l.998 4h18.955c.998 0 .998 2 0 2h-20.95L19.99 23h-1.996v1c0 .667-.332 1-.997 1S16 24.667 16 24v-2c.066-.667.398-1 .998-1h3.99c.516 0 .848.333.998 1l.998 3.281 25.428.037c1.045 0 1.588.761 1.588 2.018V30zm-4.57 23a2.497 2.497 0 0 1-2.493-2.5c0-1.38 1.116-2.5 2.494-2.5a2.497 2.497 0 0 1 2.494 2.5c0 1.38-1.117 2.5-2.494 2.5zm-18.955 0a2.497 2.497 0 0 1-2.494-2.5c0-1.38 1.117-2.5 2.494-2.5a2.497 2.497 0 0 1 2.495 2.5c0 1.38-1.117 2.5-2.495 2.5zM48 26a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm0 11a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-8.825-8.996a.51.51 0 0 0-.409.2l-7.668 9.056c-.233.31.004.738.41.738l.696-.002a.51.51 0 0 0 .409-.2l7.63-9.056c.234-.312-.005-.74-.41-.738l-.658.002zm-5.841 4.371c1.29 0 2.334-.979 2.334-2.188 0-1.208-1.044-2.187-2.334-2.187S31 28.979 31 30.188c0 1.208 1.044 2.187 2.334 2.187zm0-3.125c.552 0 1 .42 1 .938 0 .517-.448.937-1 .937s-1-.42-1-.938c0-.517.448-.937 1-.937zm4.668 4.375c-1.29 0-2.334.979-2.334 2.188 0 1.208 1.044 2.187 2.334 2.187s2.334-.979 2.334-2.188c0-1.208-1.044-2.187-2.334-2.187zm0 3.125c-.553 0-1-.42-1-.938 0-.517.447-.937 1-.937.552 0 1 .42 1 .938 0 .517-.448.937-1 .937z"/></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M33.317 69H4c-2 0-4-1-4-4V38.29l16.258-16.95L21 21l2 5h26.583l.371 3.987-3.862 4.883 3.764 1.445-.11 3.27L45.076 45H47l.507 1.61-1.993 1.943 1.815 3.434L33.317 69z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><use fill="#000" fill-rule="nonzero" opacity=".3" xlink:href="#d"/><use fill="#FFF" fill-rule="nonzero" xlink:href="#e"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,171 @@
|
||||
odoo.define('website_sale_coupon.test', function (require) {
|
||||
'use strict';
|
||||
|
||||
require("website_sale.tour");
|
||||
var tour = require("web_tour.tour");
|
||||
var ajax = require('web.ajax');
|
||||
|
||||
tour.register('shop_sale_coupon', {
|
||||
test: true,
|
||||
url: '/shop?search=Large%20Cabinet',
|
||||
},
|
||||
[
|
||||
{
|
||||
content: "open customize menu",
|
||||
extra_trigger: '.oe_website_sale .o_wsale_products_searchbar_form',
|
||||
trigger: '#customize-menu > a',
|
||||
},
|
||||
{
|
||||
content: "enable 'Show # found' if needed",
|
||||
trigger: "#customize-menu a:contains(Show # found)",
|
||||
run: function () {
|
||||
if (!$('#customize-menu a:contains(Show # found) input').prop('checked')) {
|
||||
$('#customize-menu a:contains(Show # found)').click();
|
||||
}
|
||||
}
|
||||
},
|
||||
/* 1. Buy 1 Large Cabinet, enable coupon code & insert 10% code */
|
||||
{
|
||||
content: "select Large Cabinet",
|
||||
extra_trigger: '.oe_search_found',
|
||||
trigger: '.oe_product_cart a:contains("Large Cabinet")',
|
||||
},
|
||||
{
|
||||
content: "add 2 Large Cabinet into cart",
|
||||
trigger: '#product_details input[name="add_qty"]',
|
||||
run: "text 2",
|
||||
},
|
||||
{
|
||||
content: "click on 'Add to Cart' button",
|
||||
trigger: "a:contains(Add to Cart)",
|
||||
},
|
||||
{
|
||||
content: "open customize menu",
|
||||
extra_trigger: '.oe_website_sale .oe_cart',
|
||||
trigger: '#customize-menu > a',
|
||||
},
|
||||
{
|
||||
content: "enable 'Promo Code' if needed",
|
||||
trigger: "#customize-menu a:contains(Promo Code)",
|
||||
run: function () {
|
||||
if (!$('#customize-menu a:contains(Promo Code) input').prop('checked')) {
|
||||
$('#customize-menu a:contains(Promo Code)').click();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
content: "click on 'I have a promo code'",
|
||||
extra_trigger: '.show_coupon',
|
||||
trigger: '.show_coupon',
|
||||
},
|
||||
{
|
||||
content: "insert promo code '10pc'",
|
||||
extra_trigger: 'form[name="coupon_code"]',
|
||||
trigger: 'form[name="coupon_code"] input[name="promo"]',
|
||||
run: "text 10pc",
|
||||
},
|
||||
{
|
||||
content: "validate the coupon",
|
||||
trigger: 'form[name="coupon_code"] .a-submit',
|
||||
},
|
||||
{
|
||||
content: "check reward product",
|
||||
trigger: '.reward_product:contains("10.0% discount on total amount")',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
/* 2. Add some cabinet to get a free one, play with quantity */
|
||||
{
|
||||
content: "go to shop",
|
||||
trigger: '.reward_product:contains("10.0% discount on total amount")',
|
||||
run: function () {
|
||||
ajax.jsonRpc('/web/dataset/call', 'call', {
|
||||
model: 'account.tax',
|
||||
method: 'create',
|
||||
args: [{
|
||||
'name':'15% tax incl ' + _.now(),
|
||||
'amount': 15,
|
||||
}],
|
||||
}).then(function (tax_id) {
|
||||
ajax.jsonRpc('/web/dataset/call', 'call', {
|
||||
model: 'product.template',
|
||||
method: 'create',
|
||||
args: [{
|
||||
'name': 'Taxed Product',
|
||||
'taxes_id': [([6, false, [tax_id]])],
|
||||
'list_price': 100,
|
||||
'website_published': true,
|
||||
}],
|
||||
}).then(function (data) {
|
||||
location.href = '/shop';
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "type Taxed Product in search",
|
||||
trigger: 'form input[name="search"]',
|
||||
run: "text Taxed Product",
|
||||
},
|
||||
{
|
||||
content: "start search",
|
||||
trigger: 'form:has(input[name="search"]) .oe_search_button',
|
||||
},
|
||||
{
|
||||
content: "select Taxed Product",
|
||||
extra_trigger: '.oe_search_found', // Wait to be on search results or it sometimes throws concurent error (sent search form + click on product on /shop)
|
||||
trigger: '.oe_product_cart a:containsExact("Taxed Product")',
|
||||
},
|
||||
{
|
||||
content: "click on 'Add to Cart' button",
|
||||
trigger: "a:contains(Add to Cart)",
|
||||
},
|
||||
{
|
||||
content: "check reduction amount got recomputed and merged both discount lines into one only",
|
||||
extra_trigger: '.oe_currency_value:contains("-75.50"):not(#cart_total .oe_currency_value:contains("-75.50"))',
|
||||
trigger: '.oe_website_sale .oe_cart',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
/* 3. Add some cabinet to get a free one, play with quantity */
|
||||
{
|
||||
content: "add one Large Cabinet",
|
||||
trigger: '#cart_products input.js_quantity',
|
||||
run: "text 3",
|
||||
},
|
||||
{
|
||||
content: "check reduction amount got recomputed when changing qty",
|
||||
trigger: '.oe_currency_value:contains("-107.50")',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "add more Large Cabinet into cart",
|
||||
trigger: '#cart_products input.js_quantity',
|
||||
run: "text 4",
|
||||
},
|
||||
{
|
||||
content: "check free product is added",
|
||||
trigger: '#wrap:has(.reward_product:contains("Free Product - Large Cabinet"))',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "remove one cabinet from cart",
|
||||
trigger: '#cart_products input.js_quantity[value="4"]',
|
||||
run: "text 3",
|
||||
},
|
||||
{
|
||||
content: "check free product is removed",
|
||||
trigger: '#wrap:not(:has(.reward_product:contains("Free Product - Large Cabinet")))',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
/* 4. Check /shop/payment does not break the `merged discount lines split per tax` (eg: with _compute_tax_id) */
|
||||
{
|
||||
content: "go to checkout",
|
||||
trigger: 'a[href="/shop/checkout?express=1"]',
|
||||
},
|
||||
{
|
||||
content: "check total is unchanged",
|
||||
trigger: 'tr#order_total .oe_currency_value:contains("967.50")',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
from . import test_shop_sale_coupon
|
||||
@@ -0,0 +1,19 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo.tests
|
||||
|
||||
|
||||
class TestUi(odoo.tests.HttpCase):
|
||||
|
||||
post_install = True
|
||||
at_install = False
|
||||
|
||||
def test_01_admin_shop_sale_coupon_tour(self):
|
||||
# pre enable "Show # found" option to avoid race condition...
|
||||
self.env.ref("website_sale.search count").write({"active": True})
|
||||
self.phantom_js(
|
||||
"/",
|
||||
"odoo.__DEBUG__.services['web_tour.tour'].run('shop_sale_coupon')",
|
||||
"odoo.__DEBUG__.services['web_tour.tour'].tours.shop_sale_coupon.ready",
|
||||
login="admin",
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.website.sale.coupon</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="website.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@id='website_sale_coupon']" position="after">
|
||||
<div class="content-group">
|
||||
<div class="mt16" attrs="{'invisible': [('module_sale_coupon', '=', False)]}">
|
||||
<button name="%(sale_coupon.sale_coupon_program_action_promo_program)d" icon="fa-arrow-right" type="action" string="Promotion Programs" class="btn-link"/>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('module_sale_coupon', '=', False)]}">
|
||||
<button name="%(sale_coupon.sale_coupon_program_action_coupon_program)d" icon="fa-arrow-right" type="action" string="Coupon Programs" class="btn-link"/>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<menuitem action="sale_coupon.sale_coupon_program_action_promo_program" id="menu_promotion_type_config" name="Promotion Programs" parent="website_sale.menu_catalog" groups="sales_team.group_sale_manager" sequence="50"/>
|
||||
<menuitem id="menu_coupon_type_config" action="sale_coupon.sale_coupon_program_action_coupon_program" name="Coupon Programs" parent="website_sale.menu_catalog" groups="sales_team.group_sale_manager" sequence="51"/>
|
||||
|
||||
<record model="ir.ui.view" id="sale_coupon_program_view_form_common_website">
|
||||
<field name="name">sale.coupon.program.common.form</field>
|
||||
<field name="model">sale.coupon.program</field>
|
||||
<field name="inherit_id" ref="sale_coupon.sale_coupon_program_view_form_common"/>
|
||||
<field name="arch" type="xml">
|
||||
<group name="validity" position="inside">
|
||||
<label for="website_id" groups="website.group_multi_website"/>
|
||||
<div>
|
||||
<field name="website_id" options="{'no_create': True}" groups="website.group_multi_website"/>
|
||||
</div>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_coupon_program_view_tree_website" model="ir.ui.view">
|
||||
<field name="name">sale.coupon.program.tree</field>
|
||||
<field name="model">sale.coupon.program</field>
|
||||
<field name="inherit_id" ref="sale_coupon.sale_coupon_program_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Shop Sale Coupon">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/website_sale_coupon/static/src/js/website_sale_coupon.test.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="website_sale_coupon_cart_hide_qty" inherit_id="website_sale.cart_lines" active="True">
|
||||
<xpath expr="//td[hasclass('td-qty')]/div" position="attributes">
|
||||
<attribute name="t-att-style">line.is_reward_line and "display:none;"</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//table[@id='cart_products']/tbody//tr//td[hasclass('td-product_name')]//div" position="attributes">
|
||||
<!-- Can't xpath "//table[@id='cart_products']/tbody//tr" since optional_product already xpath that one.
|
||||
There will be the same xpath attribute class on the same element. Last installed module will only be taken into account -->
|
||||
<attribute name="t-att-class">'reward_product' if line.is_reward_line else None</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//td[hasclass('td-qty')]/div" position="after">
|
||||
<t t-if="line.is_reward_line">
|
||||
<span class="js_quantity text-muted" t-esc="int(line.product_uom_qty)"/>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="sale_coupon_result" inherit_id="website_sale.coupon_form">
|
||||
<xpath expr="//form[@name='coupon_code']" position="after">
|
||||
<t t-if="website_sale_order and website_sale_order.applied_coupon_ids">
|
||||
<t t-foreach="website_sale_order.applied_coupon_ids" t-as="coupon">
|
||||
<div class="card bg-success text-white mt16">
|
||||
<div class="card-header clearfix">
|
||||
<span class="float-left">You have successfully applied following promo code: <b t-esc="coupon.code"/></span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="website_sale_order and website_sale_order.promo_code">
|
||||
<div class="card bg-success text-white mt16">
|
||||
<div class="card-header clearfix">
|
||||
<span class="float-left">You have successfully applied following promo code: <b t-esc="website_sale_order.promo_code"/></span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="website_sale_order and website_sale_order.generated_coupon_ids">
|
||||
<t t-foreach="website_sale_order.generated_coupon_ids.filtered(lambda c: c.state != 'expired')" t-as="coupon">
|
||||
<div class="card bg-success text-white mt16">
|
||||
<div class="card-header clearfix">
|
||||
<span class="float-left">Your reward <b t-esc="coupon.discount_line_product_id.name"/> is available on a next order with this promo code: <b t-esc="coupon.code"/></span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="request.params.get('code_not_available')">
|
||||
<div class="card bg-danger text-white mt16">
|
||||
<div class="card-header clearfix">
|
||||
<span class="float-left">Invalid or expired promo code</span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="website_sale_order.get_promo_code_error(delete=False)">
|
||||
<div class="card bg-danger text-white mt16">
|
||||
<div class="card-header clearfix">
|
||||
<span class="float-left"><t t-esc="website_sale_order.get_promo_code_error()" /></span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//t[@name='code_not_available']" position="replace"/>
|
||||
</template>
|
||||
|
||||
<template id="cart_discount" name="Show Discount in Subtotal" customize_show="True" active="False" inherit_id="website_sale.total">
|
||||
<xpath expr="//tr[@id='order_total_untaxed']" position="before">
|
||||
<tr id="order_discounted" t-if="website_sale_order and website_sale_order.reward_amount">
|
||||
<td class="text-right noborder text-muted" title="Discounted amount">Discount:</td>
|
||||
<td class="text-xl-right noborder text-muted">
|
||||
<span t-field="website_sale_order.reward_amount" style="white-space: nowrap;" t-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": website_sale_order.currency_id,
|
||||
}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="cart_summary" name="Payment" inherit_id="website_sale.cart_summary">
|
||||
<xpath expr="//table[@id='cart_products']/tbody/tr/td[hasclass('td-price')]/child::*" position="attributes">
|
||||
<attribute name="t-att-style">'display: None;' if free_shipping_lines and line in free_shipping_lines else ''</attribute>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user