diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 7cd59d4300a05e9b1bd9ba975eb70a47142729a4..04efbebf557189ef5a0a5568a1d83e6e16a6e0a7 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1004,6 +1004,21 @@ class AccountInvoice(models.Model): result.append((0, 0, values)) return result + @api.model + def _refund_tax_lines_account_change(self, lines, taxes_to_change): + # Let's change the account on tax lines when + # @param {list} lines: a list of orm commands + # @param {dict} taxes_to_change + # key: tax ID, value: refund account + + if not taxes_to_change: + return lines + + for line in lines: + if isinstance(line[2], dict) and line[2]['tax_id'] in taxes_to_change: + line[2]['account_id'] = taxes_to_change[line[2]['tax_id']] + return lines + def _get_refund_common_fields(self): return ['partner_id', 'payment_term_id', 'account_id', 'currency_id', 'journal_id'] @@ -1045,7 +1060,12 @@ class AccountInvoice(models.Model): values['invoice_line_ids'] = self._refund_cleanup_lines(invoice.invoice_line_ids) tax_lines = invoice.tax_line_ids - values['tax_line_ids'] = self._refund_cleanup_lines(tax_lines) + taxes_to_change = { + line.tax_id.id: line.tax_id.refund_account_id.id + for line in tax_lines.filtered(lambda l: l.tax_id.refund_account_id != l.tax_id.account_id) + } + cleaned_tax_lines = self._refund_cleanup_lines(tax_lines) + values['tax_line_ids'] = self._refund_tax_lines_account_change(cleaned_tax_lines, taxes_to_change) if journal_id: journal = self.env['account.journal'].browse(journal_id) diff --git a/addons/account/tests/test_account_customer_invoice.py b/addons/account/tests/test_account_customer_invoice.py index 12a164cd3cb2755127d3371d70bd1482a92fb20c..6c6c6a253faed52ccf8586ff5e532d4631f26737 100644 --- a/addons/account/tests/test_account_customer_invoice.py +++ b/addons/account/tests/test_account_customer_invoice.py @@ -170,3 +170,59 @@ class TestAccountCustomerInvoice(AccountTestUsers): )) self.assertEquals(invoice.amount_untaxed, sum([x.base for x in invoice.tax_line_ids])) + + def test_customer_invoice_tax_refund(self): + company = self.env.user.company_id + tax_account = self.env['account.account'].create({ + 'name': 'TAX', + 'code': 'TAX', + 'user_type_id': self.env.ref('account.data_account_type_current_assets').id, + 'company_id': company.id, + }) + + tax_refund_account = self.env['account.account'].create({ + 'name': 'TAX_REFUND', + 'code': 'TAX_R', + 'user_type_id': self.env.ref('account.data_account_type_current_assets').id, + 'company_id': company.id, + }) + + journalrec = self.env['account.journal'].search([('type', '=', 'sale')])[0] + partner3 = self.env.ref('base.res_partner_3') + account_id = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1).id + + tax = self.env['account.tax'].create({ + 'name': 'Tax 15.0', + 'amount': 15.0, + 'amount_type': 'percent', + 'type_tax_use': 'sale', + 'account_id': tax_account.id, + 'refund_account_id': tax_refund_account.id + }) + + invoice_line_data = [ + (0, 0, + { + 'product_id': self.env.ref('product.product_product_1').id, + 'quantity': 40.0, + 'account_id': account_id, + 'name': 'product test 1', + 'discount': 10.00, + 'price_unit': 2.27, + 'invoice_line_tax_ids': [(6, 0, [tax.id])], + } + )] + + invoice = self.env['account.invoice'].create(dict( + name="Test Customer Invoice", + reference_type="none", + journal_id=journalrec.id, + partner_id=partner3.id, + invoice_line_ids=invoice_line_data + )) + + invoice.action_invoice_open() + + refund = invoice.refund() + self.assertEqual(invoice.tax_line_ids.mapped('account_id'), tax_account) + self.assertEqual(refund.tax_line_ids.mapped('account_id'), tax_refund_account) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index fa6215ee0e87f1ad4eb86a06d2ea19c2458c3e84..2fe1efe8bfa25149c4f5cfa2a530f169b595d088 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -741,12 +741,20 @@ exports.PosModel = Backbone.Model.extend({ transfer.pipe(function(order_server_id){ // generate the pdf and download it - self.chrome.do_action('point_of_sale.pos_invoice_report',{additional_context:{ - active_ids:order_server_id, - }}).done(function () { - invoiced.resolve(); - done.resolve(); - }); + if (order_server_id.length) { + self.chrome.do_action('point_of_sale.pos_invoice_report',{additional_context:{ + active_ids:order_server_id, + }}).done(function () { + invoiced.resolve(); + done.resolve(); + }); + } else { + // The order has been pushed separately in batch when + // the connection came back. + // The user has to go to the backend to print the invoice + invoiced.reject({code:401, message:'Backend Invoice', data:{order: order}}); + done.reject(); + } }); return done; diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 82e6fa0e7b25f2b0a2c5963a01a8c08d21f3c08d..2b397e10a2e21065222aa4082e0fa84a497ba080 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -2012,6 +2012,17 @@ var PaymentScreenWidget = ScreenWidget.extend({ self.gui.show_screen('clientlist'); }, }); + } else if (error.message === 'Backend Invoice') { + self.gui.show_popup('confirm',{ + 'title': _t('Please print the invoice from the backend'), + 'body': _t('The order has been synchronized earlier. Please make the invoice from the backend for the order: ') + error.data.order.name, + confirm: function () { + this.gui.show_screen('receipt'); + }, + cancel: function () { + this.gui.show_screen('receipt'); + }, + }); } else if (error.code < 0) { // XmlHttpRequest Errors self.gui.show_popup('error',{ 'title': _t('The order could not be sent'), diff --git a/addons/pos_discount/static/src/js/discount.js b/addons/pos_discount/static/src/js/discount.js index 7ef38d6ffdbc2349393a0c9996ac472b55591b28..f29cf7968451920a049ecdf7ab2adffb391c8778 100644 --- a/addons/pos_discount/static/src/js/discount.js +++ b/addons/pos_discount/static/src/js/discount.js @@ -23,6 +23,13 @@ var DiscountButton = screens.ActionButtonWidget.extend({ var order = this.pos.get_order(); var lines = order.get_orderlines(); var product = this.pos.db.get_product_by_id(this.pos.config.discount_product_id[0]); + if (product === undefined) { + this.gui.show_popup('error', { + title : _t("No discount product found"), + body : _t("The discount product seems misconfigured. Make sure it is flagged as 'Can be Sold' and 'Available in Point of Sale'."), + }); + return; + } // Remove existing discounts var i = 0; diff --git a/addons/web/static/src/js/framework/crash_manager.js b/addons/web/static/src/js/framework/crash_manager.js index baf577b578de52a90f292f00c60b652bd2dbd0c0..36d8057a23c82cd90469a4e67c95e6017cd9b6c2 100644 --- a/addons/web/static/src/js/framework/crash_manager.js +++ b/addons/web/static/src/js/framework/crash_manager.js @@ -22,6 +22,7 @@ var map_title ={ var CrashManager = core.Class.extend({ init: function() { this.active = true; + this.isConnected = true; }, enable: function () { this.active = true; @@ -29,8 +30,29 @@ var CrashManager = core.Class.extend({ disable: function () { this.active = false; }, - rpc_error: function(error) { + handleLostConnection: function () { var self = this; + if (!this.isConnected) { + // already handled, nothing to do. This can happen when several + // rpcs are done in parallel and fail because of a lost connection. + return; + } + this.isConnected = false; + var delay = 2000; + core.bus.trigger('connection_lost'); + + setTimeout(function checkConnection() { + ajax.jsonRpc('/web/webclient/version_info', 'call', {}, {shadow:true}).then(function () { + core.bus.trigger('connection_restored'); + self.isConnected = true; + }).fail(function () { + // exponential backoff, with some jitter + delay = (delay * 1.5) + 500*Math.random(); + setTimeout(checkConnection, delay); + }); + }, delay); + }, + rpc_error: function(error) { if (!this.active) { return; } @@ -38,15 +60,7 @@ var CrashManager = core.Class.extend({ return; } if (error.code === -32098) { - core.bus.trigger('connection_lost'); - this.connection_lost = true; - var timeinterval = setInterval(function() { - ajax.jsonRpc('/web/webclient/version_info').then(function() { - clearInterval(timeinterval); - core.bus.trigger('connection_restored'); - self.connection_lost = false; - }); - }, 2000); + this.handleLostConnection(); return; } var handler = core.crash_registry.get(error.data.name, true); diff --git a/addons/web/static/src/less/navbar.less b/addons/web/static/src/less/navbar.less index 5f3aff0b98fd986178bd39a56051cd0231f1455b..7b31ec1108930bd37327aaa02cbde7bac4881713 100644 --- a/addons/web/static/src/less/navbar.less +++ b/addons/web/static/src/less/navbar.less @@ -94,7 +94,6 @@ position: relative; height: @odoo-navbar-height; - overflow: hidden; > ul { > li { @@ -110,6 +109,7 @@ &.o_menu_sections { width: 100%; + display: none; > li.open .dropdown-menu { position: static; @@ -158,6 +158,10 @@ .o_main_navbar { height: 100%; overflow: auto; + + .o_menu_sections { + display: block; + } } } } diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index 7b67bfcbb403993b57b2ea8a76c14072b9208003..3dfe07e9734088d554f23337b7271b308d6da37b 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -216,7 +216,7 @@ class WebsiteSlides(http.Controller): @http.route('''/slides/slide/<model("slide.slide", "[('channel_id.can_see', '=', True), ('download_security', '=', 'public')]"):slide>/download''', type='http', auth="public", website=True) def slide_download(self, slide): - if slide.download_security == 'public' or (slide.download_security == 'user' and request.session.uid): + if slide.download_security == 'public' or (slide.download_security == 'user' and request.env.user and request.env.user != request.website.user_id): filecontent = base64.b64decode(slide.datas) disposition = 'attachment; filename=%s.pdf' % werkzeug.urls.url_quote(slide.name) return request.make_response(