diff --git a/addons/website_sale/models/res_config.py b/addons/website_sale/models/res_config.py
index aa280ab7626..67f73085efe 100644
--- a/addons/website_sale/models/res_config.py
+++ b/addons/website_sale/models/res_config.py
@@ -37,6 +37,7 @@ class WebsiteConfigSettings(models.TransientModel):
module_website_sale_options = fields.Boolean("Optional Products", help='Installs *e-Commerce Optional Products*')
module_website_sale_digital = fields.Boolean("Digital Content")
+ module_website_sale_wishlist = fields.Boolean("Wishlists ", help='Installs *e-Commerce Wishlist*')
module_sale_stock = fields.Boolean("Delivery Orders")
module_portal = fields.Boolean("Activate the customer portal", help="""Give your customers access to their documents.""")
diff --git a/addons/website_sale/views/res_config_view.xml b/addons/website_sale/views/res_config_view.xml
index a42498e63b7..2eaf895ca0f 100644
--- a/addons/website_sale/views/res_config_view.xml
+++ b/addons/website_sale/views/res_config_view.xml
@@ -146,6 +146,17 @@
+
+
+
+
+
+
+
+ Let returning shoppers save products in a whishlist
+
+
+
Checkout Process
diff --git a/addons/website_sale_wishlist/__init__.py b/addons/website_sale_wishlist/__init__.py
new file mode 100644
index 00000000000..5efbd253ebb
--- /dev/null
+++ b/addons/website_sale_wishlist/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import models
+import controllers
diff --git a/addons/website_sale_wishlist/__manifest__.py b/addons/website_sale_wishlist/__manifest__.py
new file mode 100644
index 00000000000..6c346f73150
--- /dev/null
+++ b/addons/website_sale_wishlist/__manifest__.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+{
+ 'name': 'Website Wishlist',
+ 'description': 'Let returning shoppers save products in a wishlist',
+ 'author': 'Odoo SA',
+ 'website': 'https://www.odoo.com',
+ 'category': 'Website',
+ 'version': '1.0',
+ 'depends': ['website_sale'],
+ 'data': [
+ 'security/website_sale_wishlist_security.xml',
+ 'security/ir.model.access.csv',
+ 'views/website_sale_wishlist_template.xml',
+ ],
+ 'installable': True,
+}
diff --git a/addons/website_sale_wishlist/controllers/__init__.py b/addons/website_sale_wishlist/controllers/__init__.py
new file mode 100644
index 00000000000..a84d81a791b
--- /dev/null
+++ b/addons/website_sale_wishlist/controllers/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+import main
diff --git a/addons/website_sale_wishlist/controllers/main.py b/addons/website_sale_wishlist/controllers/main.py
new file mode 100644
index 00000000000..bf2d0014e59
--- /dev/null
+++ b/addons/website_sale_wishlist/controllers/main.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+from odoo import http
+from odoo.http import request
+from odoo.addons.website_sale.controllers.main import WebsiteSale
+import json
+
+
+class WebsiteSaleWishlist(WebsiteSale):
+
+ @http.route(['/shop/wishlist/add'], type='json', auth="user", website=True)
+ def add_to_wishlist(self, product_id, price=False, **kw):
+ if not price:
+ compute_currency, pricelist_context, pl = self._get_compute_currency_and_context()
+ p = request.env['product.product'].with_context(pricelist_context, display_default_code=False).browse(product_id)
+ price = p.website_price
+
+ request.env['product.wishlist']._add_to_wishlist(
+ request.env.user.partner_id.id,
+ pl.id,
+ pl.currency_id.id,
+ request.website.id,
+ price,
+ product_id
+ )
+ return True
+
+ @http.route(['/shop/wishlist'], type='http', auth="user", website=True)
+ def get_wishlist(self, count=False, **kw):
+ values = request.env['product.wishlist'].with_context(display_default_code=False).search([('partner_id', '=', request.env.user.partner_id.id)])
+ if count:
+ return request.make_response(json.dumps(values.mapped('product_id').ids))
+
+ if not len(values):
+ return request.redirect("/shop")
+
+ return request.render("website_sale_wishlist.product_wishlist", dict(wishes=values))
diff --git a/addons/website_sale_wishlist/models/__init__.py b/addons/website_sale_wishlist/models/__init__.py
new file mode 100644
index 00000000000..9f849c2393b
--- /dev/null
+++ b/addons/website_sale_wishlist/models/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+import product_wishlist
\ No newline at end of file
diff --git a/addons/website_sale_wishlist/models/product_wishlist.py b/addons/website_sale_wishlist/models/product_wishlist.py
new file mode 100644
index 00000000000..54a31f5718e
--- /dev/null
+++ b/addons/website_sale_wishlist/models/product_wishlist.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, fields, models
+
+
+class ProductWishlist(models.Model):
+ _name = 'product.wishlist'
+
+ partner_id = fields.Many2one('res.partner', string='Owner', required=True)
+ pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', help='Pricelist when added')
+ currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', readonly=True)
+ website_id = fields.Many2one('website', required=True)
+ price = fields.Monetary(digits=0, currency_field='currency_id', string='Price', help='Price of the product when it has been added in the wishlist')
+ price_new = fields.Float(compute='compute_new_price', string='Current price', help='Current price of this product, using same pricelist, ...')
+ product_id = fields.Many2one('product.product', string='Product', required=True)
+ active = fields.Boolean(default=True, required=True)
+ create_date = fields.Datetime('Added Date', readonly=True, required=True)
+
+ @api.multi
+ @api.depends('pricelist_id', 'currency_id', 'product_id')
+ def compute_new_price(self):
+ for wish in self:
+ wish.price_new = wish.product_id.with_context(pricelist=wish.pricelist_id.id).website_price
+
+ @api.model
+ def _add_to_wishlist(self, partner_id, pricelist_id, currency_id, website_id, price, product_id):
+ wish = self.env['product.wishlist'].create({
+ 'partner_id': partner_id,
+ 'pricelist_id': pricelist_id,
+ 'currency_id': currency_id,
+ 'website_id': website_id,
+ 'price': price,
+ 'product_id': product_id,
+ })
+ return wish
diff --git a/addons/website_sale_wishlist/security/ir.model.access.csv b/addons/website_sale_wishlist/security/ir.model.access.csv
new file mode 100644
index 00000000000..f8486a0afcd
--- /dev/null
+++ b/addons/website_sale_wishlist/security/ir.model.access.csv
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_product_wishlist_default,access_product_wishlist_default,model_product_wishlist,,0,0,0,0
+access_product_wishlist_public,access_product_wishlist_public,model_product_wishlist,base.group_public,1,0,0,0
+access_product_wishlist_portal,access_product_wishlist_portal,model_product_wishlist,base.group_portal,1,1,1,1
+access_product_wishlist_user,access_product_wishlist_user,model_product_wishlist,base.group_user,1,1,1,1
\ No newline at end of file
diff --git a/addons/website_sale_wishlist/security/website_sale_wishlist_security.xml b/addons/website_sale_wishlist/security/website_sale_wishlist_security.xml
new file mode 100644
index 00000000000..cdca47c4f55
--- /dev/null
+++ b/addons/website_sale_wishlist/security/website_sale_wishlist_security.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ See own Wishlist
+
+ [('partner_id','=', user.partner_id.id)]
+
+
+
+
+ See all wishlist
+
+ [(1, '=', 1)]
+
+
+
+
+
diff --git a/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js b/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js
new file mode 100644
index 00000000000..f3fa749f13f
--- /dev/null
+++ b/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js
@@ -0,0 +1,131 @@
+odoo.define('website_sale_wishlist.wishlist', function (require) {
+"use strict";
+
+var ajax = require('web.ajax');
+var core = require('web.core');
+var Model = require('web.Model');
+var Widget = require('web.Widget');
+var base = require('web_editor.base');
+var website_sale_utils = require('website_sale.utils');
+
+
+if(!$('.oe_website_sale').length) {
+ return $.Deferred().reject("DOM doesn't contain '.oe_website_sale'");
+}
+
+var ProductWishlist = Widget.extend({
+ events: {
+ 'click #my_wish': 'display_wishlist',
+ },
+ init: function(){
+ var self = this;
+ this.wishlist_product_ids = [];
+
+ if (!odoo.session_info.is_website_user) {
+ $.get('/shop/wishlist', {'count': 1}).then(function(res) {
+ self.wishlist_product_ids = JSON.parse(res);
+ self.update_wishlist_view();
+ });
+ }
+ $('.oe_website_sale .o_add_wishlist, .oe_website_sale .o_add_wishlist_dyn').click(function (e){
+ self.add_new_products($(this), e);
+ });
+
+ if ($('.wishlist-section').length) {
+ $('.wishlist-section a.o_wish_rm').on('click', function (e){ self.wishlist_rm(e); });
+ $('.wishlist-section a.o_wish_add').on('click', function (e){ self.wishlist_add(e); });
+ $('.wishlist-section a.o_wish_mv').on('click', function (e){ self.wishlist_mv(e); });
+ }
+ },
+ add_new_products:function($el, e){
+ var self = this;
+ var product_id = $el.data('product-product-id');
+ if (e.currentTarget.classList.contains('o_add_wishlist_dyn')) {
+ product_id = parseInt($el.parent().find('.product_id').val());
+ }
+ if (odoo.session_info.is_website_user){
+ this.warning_not_logged();
+ } else {
+ if (!_.contains(self.wishlist_product_ids, product_id)) {
+ return ajax.jsonRpc('/shop/wishlist/add', 'call', {
+ 'product_id': product_id
+ }).then(function () {
+ self.wishlist_product_ids.push(product_id);
+ self.update_wishlist_view();
+ website_sale_utils.animate_clone($('#my_wish'), $el.closest('form'), 10, 10);
+ });
+ }
+ }
+ },
+ display_wishlist: function() {
+ if (odoo.session_info.is_website_user) {
+ this.warning_not_logged();
+ }
+ else if (this.wishlist_product_ids.length === 0) {
+ this.update_wishlist_view();
+ this.redirect_no_wish();
+ }
+ else {
+ window.location = '/shop/wishlist';
+ }
+ },
+ update_wishlist_view: function() {
+ if (this.wishlist_product_ids.length > 0) {
+ $('#my_wish').show();
+ $('.my_wish_quantity').text(this.wishlist_product_ids.length);
+ }
+ else {
+ $('#my_wish').hide();
+ }
+ },
+ wishlist_rm: function(e){
+ var tr = $(e.currentTarget).parents('tr');
+ var wish = tr.data('wish-id');
+ var product = tr.data('product-id');
+
+ new Model('product.wishlist')
+ .call('write', [[wish], { active: false }, base.get_context()])
+ .then(function(){
+ $(tr).hide();
+ });
+ this.wishlist_product_ids = _.without(this.wishlist_product_ids, product);
+ if (this.wishlist_product_ids.length === 0) {
+ this.redirect_no_wish();
+ }
+ this.update_wishlist_view();
+ },
+ wishlist_add: function(e){
+ var tr = $(e.currentTarget).parents('tr');
+ var product = tr.data('product-id');
+
+ // can be hidden if empty
+ $('#my_cart').removeClass('hidden');
+ website_sale_utils.animate_clone($('#my_cart'), tr, 0, 0);
+ this.add_to_cart(product, tr.find('qty').val() || 1);
+ },
+ wishlist_mv: function(e){
+ var tr = $(e.currentTarget).parents('tr');
+ var product = tr.data('product-id');
+
+ $('#my_cart').removeClass('hidden');
+ website_sale_utils.animate_clone($('#my_cart'), tr, 0, 0);
+ this.add_to_cart(product, tr.find('qty').val() || 1);
+ this.wishlist_rm(e);
+ },
+ add_to_cart: function(product_id, qty_id) {
+ ajax.jsonRpc("/shop/cart/update_json", 'call', {
+ 'product_id': parseInt(product_id, 10),
+ 'add_qty': parseInt(qty_id, 10)
+ });
+ },
+ redirect_no_wish: function() {
+ window.location = '/shop/cart';
+ },
+ warning_not_logged: function() {
+ window.location = '/shop/wishlist';
+ }
+});
+
+new ProductWishlist();
+
+});
diff --git a/addons/website_sale_wishlist/static/src/less/website_sale_wishlist.less b/addons/website_sale_wishlist/static/src/less/website_sale_wishlist.less
new file mode 100644
index 00000000000..2bac6365fa7
--- /dev/null
+++ b/addons/website_sale_wishlist/static/src/less/website_sale_wishlist.less
@@ -0,0 +1,14 @@
+.oe_website_sale {
+ .td-wish-btn {
+ width: 140px;
+ }
+}
+
+// XS size
+@media (max-width: @screen-xs-max) {
+ .oe_website_sale {
+ .td-wish-btn {
+ width: 100px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/addons/website_sale_wishlist/views/website_sale_wishlist_template.xml b/addons/website_sale_wishlist/views/website_sale_wishlist_template.xml
new file mode 100644
index 00000000000..db27cdc0e4e
--- /dev/null
+++ b/addons/website_sale_wishlist/views/website_sale_wishlist_template.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Shop Wishlist
+
+
+
+
+