diff --git a/addons/account/views/report_overdue.xml b/addons/account/views/report_overdue.xml index a413e77d0d3..9357917e370 100644 --- a/addons/account/views/report_overdue.xml +++ b/addons/account/views/report_overdue.xml @@ -5,7 +5,8 @@
-
+ +
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py index ae4a5a91311..338c37e398f 100644 --- a/addons/auth_signup/controllers/main.py +++ b/addons/auth_signup/controllers/main.py @@ -32,7 +32,7 @@ class AuthSignupHome(Home): if 'error' not in qcontext and request.httprequest.method == 'POST': try: self.do_signup(qcontext) - return super(AuthSignupHome, self).web_login(*args, **kw) + return self.web_login(*args, **kw) except (SignupError, AssertionError), e: if request.env["res.users"].sudo().search([("login", "=", qcontext.get("login"))]): qcontext["error"] = _("Another user is already registered using this email address.") @@ -55,7 +55,7 @@ class AuthSignupHome(Home): try: if qcontext.get('token'): self.do_signup(qcontext) - return super(AuthSignupHome, self).web_login(*args, **kw) + return self.web_login(*args, **kw) else: login = qcontext.get('login') assert login, "No login provided." diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py index e0b18dd355a..ff55205bdf2 100644 --- a/addons/base_import_module/models/ir_module.py +++ b/addons/base_import_module/models/ir_module.py @@ -28,6 +28,8 @@ class IrModule(models.Model): installed_mods = [m.name for m in known_mods if m.state == 'installed'] terp = load_information_from_description_file(module, mod_path=path) + if not terp: + return False values = self.get_values_from_terp(terp) unmet_dependencies = set(terp['depends']).difference(installed_mods) @@ -110,8 +112,8 @@ class IrModule(models.Model): try: # assert mod_name.startswith('theme_') path = opj(module_dir, mod_name) - self.import_module(mod_name, path, force=force) - success.append(mod_name) + if self.import_module(mod_name, path, force=force): + success.append(mod_name) except Exception, e: _logger.exception('Error while importing module') errors[mod_name] = exception_to_unicode(e) diff --git a/addons/l10n_fr_pos_cert/i18n/l10n_fr_pos_cert.pot b/addons/l10n_fr_pos_cert/i18n/l10n_fr_pos_cert.pot index 4e1507eb9f0..ca60d0c4779 100644 --- a/addons/l10n_fr_pos_cert/i18n/l10n_fr_pos_cert.pot +++ b/addons/l10n_fr_pos_cert/i18n/l10n_fr_pos_cert.pot @@ -66,6 +66,11 @@ msgstr "" msgid "Corrupted data on point of sale order with id %s." msgstr "" +#. module: l10n_fr_pos_cert +#: model:ir.model,name:l10n_fr_pos_cert.model_account_fiscal_position +msgid "Fiscal Position" +msgstr "" + #. module: l10n_fr_pos_cert #: model:ir.ui.menu,name:l10n_fr_pos_cert.pos_fr_statements_menu msgid "French Statements" @@ -137,7 +142,13 @@ msgid "This session has been opened another day. To comply with the French law, msgstr "" #. module: l10n_fr_pos_cert -#: code:addons/l10n_fr_pos_cert/models/account_bank_statement.py:11 +#: code:addons/l10n_fr_pos_cert/models/account_fiscal_position.py:14 +#, python-format +msgid "You cannot modify a fiscal position used in a POS order. You should archive it and create a new one." +msgstr "" + +#. module: l10n_fr_pos_cert +#: code:addons/l10n_fr_pos_cert/models/account_bank_statement.py:12 #, python-format msgid "You cannot modify anything on a bank statement (name: %s) that was created by point of sale operations." msgstr "" diff --git a/addons/l10n_fr_pos_cert/models/__init__.py b/addons/l10n_fr_pos_cert/models/__init__.py index e56d2fcd33c..92fba1e252d 100644 --- a/addons/l10n_fr_pos_cert/models/__init__.py +++ b/addons/l10n_fr_pos_cert/models/__init__.py @@ -1,3 +1,4 @@ import account_bank_statement +from . import account_fiscal_position import res_company -import pos \ No newline at end of file +import pos diff --git a/addons/l10n_fr_pos_cert/models/account_fiscal_position.py b/addons/l10n_fr_pos_cert/models/account_fiscal_position.py new file mode 100644 index 00000000000..71b4b12155a --- /dev/null +++ b/addons/l10n_fr_pos_cert/models/account_fiscal_position.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +from odoo import _, models +from odoo.exceptions import UserError + + +class AccountFiscalPosition(models.Model): + _inherit = "account.fiscal.position" + + def write(self, vals): + if "tax_ids" in vals: + if self.env["pos.order"].sudo().search_count([("fiscal_position_id", "in", self.ids)]): + raise UserError( + _( + "You cannot modify a fiscal position used in a POS order. " + + "You should archive it and create a new one." + ) + ) + return super(AccountFiscalPosition, self).write(vals) diff --git a/addons/point_of_sale/data/point_of_sale_data.xml b/addons/point_of_sale/data/point_of_sale_data.xml index 5c7c6c3f511..5e8c2fbd1c5 100644 --- a/addons/point_of_sale/data/point_of_sale_data.xml +++ b/addons/point_of_sale/data/point_of_sale_data.xml @@ -1,13 +1,14 @@ - + Picking POS POS 5 - + + PoS Orders diff --git a/addons/website_event_sale/models/sale_order.py b/addons/website_event_sale/models/sale_order.py index edfb30e7e62..8b42076861e 100644 --- a/addons/website_event_sale/models/sale_order.py +++ b/addons/website_event_sale/models/sale_order.py @@ -41,7 +41,7 @@ class SaleOrder(models.Model): values['product_id'] = ticket.product_id.id values['event_id'] = ticket.event_id.id values['event_ticket_id'] = ticket.id - values['price_unit'] = ticket.price_reduce or ticket.price + values['price_unit'] = ticket.price_reduce values['name'] = "%s\n%s" % (ticket.event_id.display_name, ticket.name) # avoid writing related values that end up locking the product record diff --git a/addons/website_quote/controllers/main.py b/addons/website_quote/controllers/main.py index 0fcb9485d76..2cb66869051 100644 --- a/addons/website_quote/controllers/main.py +++ b/addons/website_quote/controllers/main.py @@ -2,8 +2,10 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import werkzeug +from functools import partial from odoo import fields, http, _ +from odoo.tools import formatLang from odoo.http import request from odoo.addons.website_mail.controllers.main import _message_post_helper @@ -104,8 +106,17 @@ class sale_quote(http.Controller): _message_post_helper(message=message, res_id=order_id, res_model='sale.order', **{'token': token, 'token_field': 'access_token'} if token else {}) return werkzeug.utils.redirect("/quote/%s/%s?message=2" % (order_id, token)) + # Deprecated because override opportunities are **really** limited + # In fact it should be removed in master ASAP @http.route(['/quote/update_line'], type='json', auth="public", website=True) def update(self, line_id, remove=False, unlink=False, order_id=None, token=None, **post): + values = self.update_line_dict(line_id, remove, unlink, order_id, token, **post) + if values: + return [values['order_line_product_uom_qty'], values['order_amount_total']] + return values + + @http.route(['/quote/update_line_dict'], type='json', auth="public", website=True) + def update_line_dict(self, line_id, remove=False, unlink=False, order_id=None, token=None, input_quantity=False, **kwargs): Order = request.env['sale.order'].sudo().browse(int(order_id)) if token != Order.access_token: return request.render('website.404') @@ -115,10 +126,27 @@ class sale_quote(http.Controller): if unlink: OrderLine.unlink() return False - number = -1 if remove else 1 - quantity = OrderLine.product_uom_qty + number + + if input_quantity is not False: + quantity = input_quantity + else: + number = -1 if remove else 1 + quantity = OrderLine.product_uom_qty + number + + if quantity < 0: + quantity = 0.0 OrderLine.write({'product_uom_qty': quantity}) - return [str(quantity), str(Order.amount_total)] + currency = Order.currency_id + format_price = partial(formatLang, request.env, digits=currency.decimal_places) + + return { + 'order_line_product_uom_qty': str(quantity), + 'order_line_price_total': format_price(OrderLine.price_total), + 'order_line_price_subtotal': format_price(OrderLine.price_subtotal), + 'order_amount_total': format_price(Order.amount_total), + 'order_amount_untaxed': format_price(Order.amount_untaxed), + 'order_amount_tax': format_price(Order.amount_tax), + } @http.route(["/quote/template/"], type='http', auth="user", website=True) def template_view(self, quote, **post): diff --git a/addons/website_quote/static/src/js/website_quotation.js b/addons/website_quote/static/src/js/website_quotation.js index 04705686f32..6985625c378 100644 --- a/addons/website_quote/static/src/js/website_quotation.js +++ b/addons/website_quote/static/src/js/website_quotation.js @@ -15,28 +15,160 @@ if(!$('.o_website_quote').length) { events: { 'click' : 'onClick', }, - onClick: function(ev){ - ev.preventDefault(); + /** + * @override + */ + start: function () { var self = this; - var href = this.$el.attr("href"); - var order_id = href.match(/order_id=([0-9]+)/); - var line_id = href.match(/update_line\/([0-9]+)/); - var token = href.match(/token=(.*)/); - ajax.jsonRpc("/quote/update_line", 'call', { - 'line_id': line_id[1], - 'order_id': parseInt(order_id[1]), - 'token': token[1], - 'remove': self.$el.is('[href*="remove"]'), - 'unlink': self.$el.is('[href*="unlink"]') - }).then(function (data) { - if(!data){ - location.reload(); - } - self.$el.parents('.input-group:first').find('.js_quantity').val(data[0]); - $('[data-id="total_amount"]>span').html(data[1]); + return this._super.apply(this, arguments).then(function () { + self.elems = self._getUpdatableElements(); + self.elems.$lineQuantity.change(function (ev) { + var quantity = parseInt(this.value); + self._onChangeQuantity(quantity); + }); }); + }, + /** + * Process the change in line quantity + * + * @private + * @param {Int} quantity, the new quantity of the line + * If not present it will increment/decrement the existing quantity + */ + _onChangeQuantity: function (quantity) { + var href = this.$el.attr("href"); + var order_id = href.match(/order_id=([0-9]+)/)[1]; + var line_id = href.match(/update_line(_dict)?\/([0-9]+)/)[2]; + var token = href.match(/token=([\w\d-]*)/)[1]; + + var callParams = { + 'line_id': parseInt(line_id), + 'order_id': parseInt(order_id), + 'token': token, + 'remove': this.$el.is('[href*="remove"]'), + 'unlink': this.$el.is('[href*="unlink"]'), + 'input_quantity': quantity >= 0 ? quantity : false, + }; + this._callUpdateLineRoute(callParams).then(this._updateOrderValues.bind(this)); return false; }, + /** + * Reacts to the click on the -/+ buttons + * + * @param {Event} ev + */ + onClick: function (ev) { + ev.preventDefault(); + return this._onChangeQuantity(); + }, + /** + * Calls the route to get updated values of the line and order + * when the quantity of a product has changed + * + * @private + * @param {Object} params + * @return {Deferred} + */ + _callUpdateLineRoute: function (params) { + var def = new $.Deferred(); + ajax.jsonRpc("/quote/update_line_dict", 'call', params) + .then(def.resolve.bind(def)) + .fail(function () { + // Compatibility: the server may not have been restarted + // So the real route may not exist + delete params.input_quantity; + ajax.jsonRpc("/quote/update_line", 'call', params) + .fail(def.reject.bind(def)) + .then(function (data) { + // Data is an array, convert it to a dict + var actualData = data; + if (data) { + actualData = { + order_amount_total: data[1], + order_line_product_uom_qty: data[0], + }; + } + def.resolve(actualData); + }); + }); + return def; + }, + /** + * Processes data from the server to update the UI + * + * @private + * @param {Object} data: contains order and line updated values + */ + _updateOrderValues: function (data) { + if (!data) { + window.location.reload(); + } + + var orderAmountTotal = data.order_amount_total; + var orderAmountUntaxed = data.order_amount_untaxed; + var orderAmountTax = data.order_amount_tax; + + var lineProductUomQty = data.order_line_product_uom_qty; + var linePriceTotal = data.order_line_price_total; + var linePriceSubTotal = data.order_line_price_subtotal; + + this.elems.$lineQuantity.val(lineProductUomQty) + + if (this.elems.$linePriceTotal.length && linePriceTotal !== undefined) { + this.elems.$linePriceTotal.text(linePriceTotal); + } + if (this.elems.$linePriceSubTotal.length && linePriceSubTotal !== undefined) { + this.elems.$linePriceSubTotal.text(linePriceSubTotal); + } + + if (orderAmountUntaxed !== undefined) { + this.elems.$orderAmountUntaxed.text(orderAmountUntaxed); + } + + if (orderAmountTax !== undefined) { + this.elems.$orderAmountTax.text(orderAmountTax); + } + + if (orderAmountTotal !== undefined) { + this.elems.$orderAmountTotal.text(orderAmountTotal); + } + }, + /** + * Locate in the DOM the elements to update + * Mostly for compatibility, when the module has not been upgraded + * In that case, we need to fall back to some other elements + * + * @private + * @return {Object}: Jquery elements to update + */ + _getUpdatableElements: function () { + var $parentTr = this.$el.parents('tr:first'); + var $linePriceTotal = $parentTr.find('.oe_order_line_price_total .oe_currency_value'); + var $linePriceSubTotal = $parentTr.find('.oe_order_line_price_subtotal .oe_currency_value'); + + if (!$linePriceTotal.length && !$linePriceSubTotal.length) { + $linePriceTotal = $linePriceSubTotal = $parentTr.find('.oe_currency_value').last(); + } + + var $orderAmountUntaxed = $('[data-id="total_untaxed"]>span'); + var $orderAmountTax = $('[data-id="total_taxes"]>span'); + var $orderAmountTotal = $('[data-id="total_amount"]>span'); + + if (!$orderAmountUntaxed.length && !$orderAmountTax.length) { + $orderAmountUntaxed = $orderAmountTotal.eq(1); + $orderAmountTax = $orderAmountTotal.eq(2); + $orderAmountTotal = $orderAmountTotal.eq(0).add($orderAmountTotal.eq(3)); + } + + return { + $lineQuantity: this.$el.parents('.input-group:first').find('.js_quantity'), + $linePriceSubTotal: $linePriceSubTotal, + $linePriceTotal: $linePriceTotal, + $orderAmountUntaxed: $orderAmountUntaxed, + $orderAmountTax: $orderAmountTax, + $orderAmountTotal: $orderAmountTotal, + } + } }); var update_button_list = []; diff --git a/addons/website_quote/views/sale_order_views.xml b/addons/website_quote/views/sale_order_views.xml index 566479d5374..3c886477b00 100644 --- a/addons/website_quote/views/sale_order_views.xml +++ b/addons/website_quote/views/sale_order_views.xml @@ -10,6 +10,12 @@ class="o_open_quotation" string="Preview" attrs="{'invisible': [('template_id', '=', False)]}"/> + + + + + + diff --git a/addons/website_quote/views/website_quote_templates.xml b/addons/website_quote/views/website_quote_templates.xml index aea7f0af146..706600e0887 100644 --- a/addons/website_quote/views/website_quote_templates.xml +++ b/addons/website_quote/views/website_quote_templates.xml @@ -69,11 +69,11 @@ t-options="{'widget': 'monetary', 'display_currency': quotation.pricelist_id.currency_id}"/>
- + - + @@ -102,7 +102,7 @@ Subtotal: - + @@ -111,7 +111,7 @@ Taxes: - +