diff --git a/addons/account/models/account.py b/addons/account/models/account.py index b003051e499..0d3a523d6bc 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -987,6 +987,14 @@ class AccountTax(models.Model): return incl_tax.compute_all(price)['total_excluded'] return price + @api.model + def _fix_tax_included_price_company(self, price, prod_taxes, line_taxes, company_id): + if company_id: + #To keep the same behavior as in _compute_tax_id + prod_taxes = prod_taxes.filtered(lambda tax: tax.company_id == company_id) + line_taxes = line_taxes.filtered(lambda tax: tax.company_id == company_id) + return self._fix_tax_included_price(price, prod_taxes, line_taxes) + class AccountReconcileModel(models.Model): _name = "account.reconcile.model" _description = "Preset to create journal entries during a invoices and payments matching" diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index f3af53af8fc..ae986406810 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -661,6 +661,7 @@ class AccountInvoice(models.Model): self.account_id = account_id self.payment_term_id = payment_term_id + self.date_due = False self.fiscal_position_id = fiscal_position if type in ('in_invoice', 'out_refund'): @@ -1384,7 +1385,7 @@ class AccountInvoice(models.Model): if self.origin: communication = '%s (%s)' % (communication, self.origin) - payment = self.env['account.payment'].create({ + payment_vals = { 'invoice_ids': [(6, 0, self.ids)], 'amount': pay_amount or self.residual, 'payment_date': date or fields.Date.context_today(self), @@ -1396,7 +1397,11 @@ class AccountInvoice(models.Model): 'payment_method_id': payment_method.id, 'payment_difference_handling': writeoff_acc and 'reconcile' or 'open', 'writeoff_account_id': writeoff_acc and writeoff_acc.id or False, - }) + } + if self.env.context.get('tx_currency_id'): + payment_vals['currency_id'] = self.env.context.get('tx_currency_id') + + payment = self.env['account.payment'].create(payment_vals) payment.post() return True diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 820f106aca0..4b6a914f186 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -1679,7 +1679,7 @@ class AccountPartialReconcile(models.Model): 'partner_id': line.partner_id.id, }) if newly_created_move: - if move_date > self.company_id.period_lock_date and newly_created_move.date != move_date: + if move_date > (self.company_id.period_lock_date or '0000-00-00') and newly_created_move.date != move_date: # The move date should be the maximum date between payment and invoice (in case # of payment in advance). However, we should make sure the move date is not # recorded before the period lock date as the tax statement for this period is diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 79e465f19c7..a16594d69cb 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -237,6 +237,7 @@ var StatementModel = BasicModel.extend({ return $.when(partner && this._changePartner(handle, partner.id)) .then(function() { line.reconciliation_proposition = []; + self._computeLine(line); return self.changeMode(handle, 'match'); }) .then(function () { diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js index d9f1f8ad5a4..134383c71d4 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js @@ -607,6 +607,10 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { * @param {keyup event} event */ _onInputKeyup: function (event) { + var target_partner_id = $(event.target).parents('[name="partner_id"]'); + if (target_partner_id.length === 1) { + return; + } if(event.keyCode === 13) { if (_.findWhere(this.model.lines, {mode: 'create'}).balance.amount) { this._onCreateProposition(); diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index 27f327febb1..88d9a7a0f78 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -741,7 +741,7 @@ QUnit.module('account', { }); QUnit.test('Reconciliation change partner', function (assert) { - assert.expect(13); + assert.expect(17); var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); @@ -759,6 +759,20 @@ QUnit.module('account', { var widget = clientAction.widgets[0]; assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Agrolait", "the partner many2one should display agrolait"); assert.strictEqual(widget.$('.match table tr').length, 2, "agrolait should have 2 propositions for reconciliation"); + + // Adding the two propositions + // This is in order to try that after changing partner the propositions are emptied + widget.$('.match .cell_account_code:first').trigger('click'); + widget.$('.match .cell_account_code:first').trigger('click'); + assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "Both proposition should be selected"); + + // Similate changing partner to one that does not have propositions to see if create mode is open after + widget.$('.o_input_dropdown input').trigger('click'); + $('.ui-autocomplete .ui-menu-item a:contains(partner 1)').trigger('mouseenter').trigger('click'); + clientAction._onAction({target: widget, name: 'change_partner', data: {data: {display_name: 'partner 1', id: 1}}, stopped: false}); + assert.strictEqual(widget.$('.o_input_dropdown input').val(), "partner 1", "the partner many2one should display partner 1"); + assert.strictEqual(widget.$('.match table tr.mv_line').length, 0, "partner 1 should have 0 propositions for reconciliation"); + assert.strictEqual(widget.$el.data('mode'), 'create', "widget should be in create mode"); // Simulate changing partner widget.$('.o_input_dropdown input').trigger('click'); diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml index 9ae605def70..a185965ef90 100644 --- a/addons/account/views/account_invoice_view.xml +++ b/addons/account/views/account_invoice_view.xml @@ -700,7 +700,7 @@ tree,kanban,form,calendar,pivot,graph [('type','=','out_refund')] - {'default_type': 'out_refund', 'journal_type': 'sale'} + {'default_type': 'out_refund', 'type': 'out_refund', 'journal_type': 'sale'}

@@ -774,7 +774,7 @@ tree,kanban,form,calendar,pivot,graph [('type','=','in_refund')] - {'default_type': 'in_refund', 'journal_type': 'purchase'} + {'default_type': 'in_refund', 'type': 'in_refund', 'journal_type': 'purchase'}

diff --git a/addons/account/views/report_journal.xml b/addons/account/views/report_journal.xml index ebeff1f3d7f..06dbab99ae4 100644 --- a/addons/account/views/report_journal.xml +++ b/addons/account/views/report_journal.xml @@ -49,7 +49,7 @@ - + diff --git a/addons/auth_ldap/models/res_company_ldap.py b/addons/auth_ldap/models/res_company_ldap.py index 4f280530bbe..92914a93c4d 100644 --- a/addons/auth_ldap/models/res_company_ldap.py +++ b/addons/auth_ldap/models/res_company_ldap.py @@ -6,6 +6,7 @@ import logging from ldap.filter import filter_format from odoo import api, fields, models, tools +from odoo.tools.pycompat import to_native _logger = logging.getLogger(__name__) @@ -107,7 +108,7 @@ class CompanyLDAP(models.Model): if len(results) == 1: dn = results[0][0] conn = self.connect(conf) - conn.simple_bind_s(dn, password.encode('utf-8')) + conn.simple_bind_s(dn, to_native(password)) conn.unbind() entry = results[0] except ldap.INVALID_CREDENTIALS: @@ -144,8 +145,8 @@ class CompanyLDAP(models.Model): conn = self.connect(conf) ldap_password = conf['ldap_password'] or '' ldap_binddn = conf['ldap_binddn'] or '' - conn.simple_bind_s(ldap_binddn.encode('utf-8'), ldap_password.encode('utf-8')) - results = conn.search_st(conf['ldap_base'].encode('utf-8'), ldap.SCOPE_SUBTREE, filter, retrieve_attributes, timeout=60) + conn.simple_bind_s(to_native(ldap_binddn), to_native(ldap_password)) + results = conn.search_st(to_native(conf['ldap_base']), ldap.SCOPE_SUBTREE, filter, retrieve_attributes, timeout=60) conn.unbind() except ldap.INVALID_CREDENTIALS: _logger.error('LDAP bind failed.') diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 7669357c342..6d5b1649dc3 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -917,7 +917,7 @@ class PosOrderLine(models.Model): self.tax_ids = self.product_id.taxes_id.filtered(lambda r: not self.company_id or r.company_id == self.company_id) fpos = self.order_id.fiscal_position_id tax_ids_after_fiscal_position = fpos.map_tax(self.tax_ids, self.product_id, self.order_id.partner_id) if fpos else self.tax_ids - self.price_unit = self.env['account.tax']._fix_tax_included_price(price, self.product_id.taxes_id, tax_ids_after_fiscal_position) + self.price_unit = self.env['account.tax']._fix_tax_included_price_company(price, self.product_id.taxes_id, tax_ids_after_fiscal_position, self.company_id) @api.onchange('qty', 'discount', 'price_unit', 'tax_ids') def _onchange_qty(self): diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 422c0bd30f6..efeab4979dc 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -825,7 +825,7 @@ class PurchaseOrderLine(models.Model): if not seller: return - price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, self.product_id.supplier_taxes_id, self.taxes_id) if seller else 0.0 + price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, self.product_id.supplier_taxes_id, self.taxes_id, self.company_id) if seller else 0.0 if price_unit and seller and self.order_id.currency_id and seller.currency_id != self.order_id.currency_id: price_unit = seller.currency_id.compute(price_unit, self.order_id.currency_id) @@ -908,7 +908,7 @@ class ProcurementRule(models.Model): date=po.date_order and po.date_order[:10], uom_id=product_id.uom_po_id) - price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, line.product_id.supplier_taxes_id, line.taxes_id) if seller else 0.0 + price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, line.product_id.supplier_taxes_id, line.taxes_id, values['company_id']) if seller else 0.0 if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id: price_unit = seller.currency_id.compute(price_unit, po.currency_id) @@ -955,7 +955,7 @@ class ProcurementRule(models.Model): if taxes_id: taxes_id = taxes_id.filtered(lambda x: x.company_id.id == values['company_id'].id) - price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, product_id.supplier_taxes_id, taxes_id) if seller else 0.0 + price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, product_id.supplier_taxes_id, taxes_id, values['company_id']) if seller else 0.0 if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id: price_unit = seller.currency_id.compute(price_unit, po.currency_id) diff --git a/addons/report_intrastat/views/report_intrastat_views.xml b/addons/report_intrastat/views/report_intrastat_views.xml index 86247cb0fd9..4c6766248ce 100644 --- a/addons/report_intrastat/views/report_intrastat_views.xml +++ b/addons/report_intrastat/views/report_intrastat_views.xml @@ -93,7 +93,7 @@ report.intrastat.kanban report.intrastat - + diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index a9f053dbe7e..b3a5035c6bc 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -1021,7 +1021,7 @@ class SaleOrderLine(models.Model): self._compute_tax_id() if self.order_id.pricelist_id and self.order_id.partner_id: - vals['price_unit'] = self.env['account.tax']._fix_tax_included_price(self._get_display_price(product), product.taxes_id, self.tax_id) + vals['price_unit'] = self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_id, self.company_id) self.update(vals) return result @@ -1041,7 +1041,7 @@ class SaleOrderLine(models.Model): uom=self.product_uom.id, fiscal_position=self.env.context.get('fiscal_position') ) - self.price_unit = self.env['account.tax']._fix_tax_included_price(self._get_display_price(product), product.taxes_id, self.tax_id) + self.price_unit = self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_id, self.company_id) @api.multi def unlink(self): @@ -1117,7 +1117,7 @@ class SaleOrderLine(models.Model): price, rule_id = self.order_id.pricelist_id.with_context(pricelist_context).get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id) new_list_price, currency_id = self.with_context(context_partner)._get_real_price_currency(self.product_id, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) - new_list_price = self.env['account.tax']._fix_tax_included_price(new_list_price, self.product_id.taxes_id, self.tax_id) + new_list_price = self.env['account.tax']._fix_tax_included_price_company(new_list_price, self.product_id.taxes_id, self.tax_id, self.company_id) if new_list_price != 0: if self.product_id.company_id and self.order_id.pricelist_id.currency_id != self.product_id.company_id.currency_id: diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index ef7ec08ab35..c06c2bf165d 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -248,6 +248,7 @@ diff --git a/addons/sale_payment/models/payment.py b/addons/sale_payment/models/payment.py index 75dfbcadb11..fab6f546d6f 100644 --- a/addons/sale_payment/models/payment.py +++ b/addons/sale_payment/models/payment.py @@ -87,7 +87,7 @@ class PaymentTransaction(models.Model): _logger.warning('<%s> transaction completed, could not auto-generate payment for %s (ID %s) (no journal set on acquirer)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) self.acquirer_id.journal_id = default_journal - created_invoice.pay_and_reconcile(self.acquirer_id.journal_id, pay_amount=created_invoice.amount_total) + created_invoice.with_context(tx_currency_id=self.currency_id.id).pay_and_reconcile(self.acquirer_id.journal_id, pay_amount=created_invoice.amount_total) if created_invoice.payment_ids: created_invoice.payment_ids[0].payment_transaction_id = self else: diff --git a/addons/stock/models/procurement.py b/addons/stock/models/procurement.py index 988a87ada71..105416390de 100644 --- a/addons/stock/models/procurement.py +++ b/addons/stock/models/procurement.py @@ -302,6 +302,13 @@ class ProcurementGroup(models.Model): 1000 orderpoints. This is appropriate for batch jobs only. """ + if company_id and self.env.user.company_id.id != company_id: + # To ensure that the company_id is taken into account for + # all the processes triggered by this method + # i.e. If a PO is generated by the run of the procurements the + # sequence to use is the one for the specified company not the + # one of the user's company + self = self.with_context(company_id=company_id, force_company=company_id) OrderPoint = self.env['stock.warehouse.orderpoint'] domain = self._get_orderpoint_domain(company_id=company_id) orderpoints_noprefetch = OrderPoint.with_context(prefetch_fields=False).search(domain, diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js index c19fd8ab3e2..10b7838b58e 100644 --- a/addons/web/static/src/js/chrome/view_manager.js +++ b/addons/web/static/src/js/chrome/view_manager.js @@ -294,12 +294,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, { }).fail(view.loaded.reject.bind(view.loaded)); } else { view.loaded = view.loaded.then(function() { - // By default, the view will be loaded in readonly mode - // Returns to this default mode if you load action from breadcrumb view_options = _.extend({}, view_options, self.env); - if (view_type === 'form') { - view_options.mode = view_options.mode || 'readonly'; - } return view.controller.reload(view_options); }); } @@ -384,8 +379,14 @@ var ViewManager = Widget.extend(ControlPanelMixin, { }); }, select_view: function (index) { - var view_type = this.view_stack[index].type; - return this.switch_mode(view_type); + var viewType = this.view_stack[index].type; + var viewOptions = {}; + if (viewType === 'form') { + // reload form views in readonly, except for inline actions (i.e. + // settings views) that stay in edit + viewOptions.mode = this.action.target === 'inline' ? 'edit' : 'readonly'; + } + return this.switch_mode(viewType, viewOptions); }, /** * Renders the switch buttons for multi- and mono-record views and adds diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js index 46359188577..481fc0cf4dd 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -71,7 +71,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { return $.when(false); } - var message = _t("The record has been modified, your changes will be discarded. Are you sure you want to ?"); + var message = _t("The record has been modified, your changes will be discarded. Do you want to proceed?"); var def = $.Deferred(); var dialog = Dialog.confirm(this, message, { title: _t("Warning"), diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index 9c71c9eb2a3..db47f93588d 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -13,6 +13,7 @@ var FormRenderer = BasicRenderer.extend({ className: "o_form_view", events: _.extend({}, BasicRenderer.prototype.events, { 'click .o_notification_box .oe_field_translate': '_onTranslate', + 'click .oe_title, .o_group': '_onClick', }), /** * @override @@ -832,6 +833,16 @@ var FormRenderer = BasicRenderer.extend({ // Handlers //-------------------------------------------------------------------------- + /** + * Makes the Edit button bounce in readonly + * + * @private + */ + _onClick: function () { + if (this.mode === 'readonly') { + this.trigger_up('bounce_edit'); + } + }, /** * @override * @private diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index 89aa3d865dd..06058069ab6 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -192,6 +192,7 @@ ListRenderer.include({ * @param {string} recordID */ removeLine: function (state, recordID) { + var self = this; var rowIndex = _.findIndex(this.state.data, {id: recordID}); this.state = state; if (rowIndex === -1) { @@ -746,6 +747,17 @@ ListRenderer.include({ this._super.apply(this, arguments); } }, + /** + * Overrides to prevent from sorting if we are currently editing a record. + * + * @override + * @private + */ + _onSortColumn: function () { + if (this.currentRow === null) { + this._super.apply(this, arguments); + } + }, /** * Triggers a delete event. I don't know why we stop the propagation of the * event. diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js index 26b73c27ac5..3573e9232e5 100644 --- a/addons/web/static/src/js/widgets/date_picker.js +++ b/addons/web/static/src/js/widgets/date_picker.js @@ -75,9 +75,16 @@ var DateWidget = Widget.extend({ * set datetime value */ changeDatetime: function () { - if(this.isValid()) { + if (this.isValid()) { + var oldValue = this.getValue(); this._setValueFromUi(); - this.trigger("datetime_changed"); + var newValue = this.getValue(); + + if (!oldValue !== !newValue || oldValue && newValue && !oldValue.isSame(newValue)) { + // The condition is strangely written; this is because the + // values can be false/undefined + this.trigger("datetime_changed"); + } } }, /** diff --git a/addons/web/static/src/less/rainbow.less b/addons/web/static/src/less/rainbow.less index ba63437fc2f..ec5a701d31d 100644 --- a/addons/web/static/src/less/rainbow.less +++ b/addons/web/static/src/less/rainbow.less @@ -2,6 +2,7 @@ // Variables @reward-size: 400px; +@reward-size-mobile: 300px; @reward-text-color: #727880; @reward-base-time: 1.4s; @@ -11,6 +12,9 @@ z-index: @zindex-modal + 1; padding: 50px; margin: -5% auto 0 -(@reward-size / 2); + @media (max-width: @screen-xs-max) { + margin: -5% auto 0 -(@reward-size-mobile / 2); + } background-image: -webkit-radial-gradient(#EDEFF4 30%, transparent 70%, transparent); background-image: -o-radial-gradient(#EDEFF4 30%, transparent 70%, transparent); background-image: radial-gradient(#EDEFF4 30%, transparent 70%, transparent); @@ -18,6 +22,9 @@ .o-animation(reward-fading, @reward-base-time * 0.5, ease-in-out, 0s, 1, normal, forwards); .o-position-absolute(20%, auto, auto, 50%); .size(@reward-size, @reward-size); + @media (max-width: @screen-xs-max) { + .size(@reward-size-mobile, @reward-size-mobile); + } &.o_reward_fading { display: block; @@ -70,6 +77,9 @@ .o_reward_stars { display: block; .size(@reward-size * 0.75, @reward-size / 2); + @media (max-width: @screen-xs-max) { + .size(@reward-size-mobile * 0.75, @reward-size-mobile / 2); + } .o-position-absolute(18%, 7%); svg { diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 716e581b44a..6a3fdb0fbea 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -5946,5 +5946,36 @@ QUnit.module('Views', { delete widgetRegistry.map.test; }); + QUnit.test('bounce edit button in readonly mode', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '

' + + '
' + + '' + + '
' + + '
', + res_id: 1, + intercepts: { + bounce_edit: function() { + assert.step('bounce'); + }, + }, + }); + + // in readonly + form.$('[name="display_name"]').click(); + assert.verifySteps(['bounce']); + + // in edit + form.$buttons.find('.o_form_button_edit').click(); + form.$('[name="display_name"]').click(); + assert.verifySteps(['bounce']); + + form.destroy(); + }); }); }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 0f1725b6d82..52149f008eb 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -487,6 +487,39 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('editable list view: save data when list sorting in edit mode', function (assert) { + assert.expect(3); + + this.data.foo.fields.foo.sortable = true; + + var list = createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '', + mockRPC: function (route, args) { + if (args.method === 'write') { + assert.deepEqual(args.args, [[1], {foo: 'xyz'}], + "should correctly save the edited record"); + } + return this._super.apply(this, arguments); + } + }); + + list.$('.o_data_cell:first').click(); + list.$('input[name="foo"]').val('xyz').trigger('input'); + list.$('.o_column_sortable').click(); + + assert.ok(list.$('.o_data_row:first').hasClass('o_selected_row'), + "first row should still be in edition"); + + list.$buttons.find('.o_list_button_save').click(); + assert.ok(!list.$buttons.hasClass('o-editing'), + "list buttons should be back to their readonly mode"); + + list.destroy(); + }); + QUnit.test('selection changes are triggered correctly', function (assert) { assert.expect(8); @@ -912,6 +945,7 @@ QUnit.module('Views', { assert.ok(form.$('tbody tr:eq(2) td input').val(), "Value 2 should be third (shouldn't be sorted)"); + form.$('.o_form_sheet_bg').click(); // validate the row before sorting $o2m.find('.o_column_sortable').click(); assert.ok(form.$('tbody tr:first td:contains(Value 3)').length, "Value 3 should be first"); diff --git a/addons/web/static/tests/widgets/domain_selector_tests.js b/addons/web/static/tests/widgets/domain_selector_tests.js index 19731156a2c..77d8eeb24da 100644 --- a/addons/web/static/tests/widgets/domain_selector_tests.js +++ b/addons/web/static/tests/widgets/domain_selector_tests.js @@ -151,7 +151,7 @@ QUnit.module('DomainSelector', { }); QUnit.test("building a domain with a datetime", function (assert) { - assert.expect(1); + assert.expect(2); var $target = $("#qunit-fixture"); @@ -163,8 +163,16 @@ QUnit.module('DomainSelector', { domainSelector.appendTo($target); // Check that there is a datepicker to choose the date - assert.strictEqual(domainSelector.$(".o_datepicker:visible").length, 1, + var $datepicker = domainSelector.$(".o_datepicker:visible"); + assert.strictEqual($datepicker.length, 1, "there should be a datepicker"); + + var val = $datepicker.find('input').focus().click().val(); + $('.bootstrap-datetimepicker-widget :not(.today)[data-action="selectDay"]').click(); + assert.notEqual(domainSelector.$(".o_datepicker:visible input").val(), val, + "datepicker value should have changed"); + + domainSelector.destroy(); }); }); }); diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py index ce230e56e1b..5298a1ce7a0 100644 --- a/addons/website_sale/models/sale_order.py +++ b/addons/website_sale/models/sale_order.py @@ -97,7 +97,7 @@ class SaleOrder(models.Model): if order.pricelist_id and order.partner_id: order_line = order._cart_find_product_line(product.id) if order_line: - pu = self.env['account.tax']._fix_tax_included_price(pu, product.taxes_id, order_line[0].tax_id) + pu = self.env['account.tax']._fix_tax_included_price_company(pu, product.taxes_id, order_line[0].tax_id, self.company_id) return { 'product_id': product_id, @@ -185,10 +185,11 @@ class SaleOrder(models.Model): 'pricelist': order.pricelist_id.id, }) product = self.env['product.product'].with_context(product_context).browse(product_id) - values['price_unit'] = self.env['account.tax']._fix_tax_included_price( + values['price_unit'] = self.env['account.tax']._fix_tax_included_price_company( order_line._get_display_price(product), order_line.product_id.taxes_id, - order_line.tax_id + order_line.tax_id, + self.company_id ) order_line.write(values) diff --git a/doc/reference/testing.rst b/doc/reference/testing.rst index 7b5bb666988..ab32a9d3e0a 100644 --- a/doc/reference/testing.rst +++ b/doc/reference/testing.rst @@ -48,6 +48,11 @@ related to testing Odoo content (modules, mainly): .. autoclass:: odoo.tests.common.SingleTransactionCase :members: browse_ref, ref +.. autoclass:: odoo.tests.common.SavepointCase + +.. autoclass:: odoo.tests.common.HttpCase + :members: browse_ref, ref, url_open, phantom_js + By default, tests are run once right after the corresponding module has been installed. Test cases can also be configured to run after all modules have been installed, and not run right after the module installation: diff --git a/odoo/addons/base/ir/ir_http.py b/odoo/addons/base/ir/ir_http.py index f4d830efe12..255f034d994 100644 --- a/odoo/addons/base/ir/ir_http.py +++ b/odoo/addons/base/ir/ir_http.py @@ -228,7 +228,7 @@ class IrHttp(models.AbstractModel): if not hasattr(cls, '_routing_map'): _logger.info("Generating routing map") installed = request.registry._init_modules - {'web'} - if tools.config['test_enable']: + if tools.config['test_enable'] and odoo.modules.module.current_test: installed.add(odoo.modules.module.current_test) mods = [''] + odoo.conf.server_wide_modules + sorted(installed) # Note : when routing map is generated, we put it on the class `cls` diff --git a/odoo/addons/base/res/res_partner_view.xml b/odoo/addons/base/res/res_partner_view.xml index 31b689826d0..e1933a5184c 100644 --- a/odoo/addons/base/res/res_partner_view.xml +++ b/odoo/addons/base/res/res_partner_view.xml @@ -283,6 +283,11 @@ + + + + + diff --git a/odoo/addons/base/static/pdf/preview_background.pdf b/odoo/addons/base/static/pdf/preview_background.pdf index e69de29bb2d..d33687c16b8 100644 Binary files a/odoo/addons/base/static/pdf/preview_background.pdf and b/odoo/addons/base/static/pdf/preview_background.pdf differ diff --git a/odoo/addons/base/static/pdf/preview_boxed.pdf b/odoo/addons/base/static/pdf/preview_boxed.pdf index e69de29bb2d..e73c1131d04 100644 Binary files a/odoo/addons/base/static/pdf/preview_boxed.pdf and b/odoo/addons/base/static/pdf/preview_boxed.pdf differ diff --git a/odoo/addons/base/static/pdf/preview_clean.pdf b/odoo/addons/base/static/pdf/preview_clean.pdf index e69de29bb2d..4f1c42b4814 100644 Binary files a/odoo/addons/base/static/pdf/preview_clean.pdf and b/odoo/addons/base/static/pdf/preview_clean.pdf differ diff --git a/odoo/addons/base/static/pdf/preview_standard.pdf b/odoo/addons/base/static/pdf/preview_standard.pdf index e69de29bb2d..4d537376850 100644 Binary files a/odoo/addons/base/static/pdf/preview_standard.pdf and b/odoo/addons/base/static/pdf/preview_standard.pdf differ diff --git a/odoo/cli/shell.py b/odoo/cli/shell.py index 0aaa9ab2d84..e75e453a231 100644 --- a/odoo/cli/shell.py +++ b/odoo/cli/shell.py @@ -63,7 +63,7 @@ class Shell(Command): def console(self, local_vars): if not os.isatty(sys.stdin.fileno()): - exec(sys.stdin, local_vars) + exec(sys.stdin.read(), local_vars) else: if 'env' not in local_vars: print('No environment set, use `%s shell -d dbname` to get one.' % sys.argv[0])