diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-11-15 20:39:10 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-11-15 20:39:10 +0100 |
| commit | 15f686eef60e716869f6a07069dc0d8f9a3c4f08 (patch) | |
| tree | 13452a9c11e4fd6835619beb52eccfd4360d9af5 /addons | |
| parent | f1e999d33ebfb249eb0bbfa9e99f903a18842278 (diff) | |
| parent | 468dab6748ad43865567444380abe52fb4c7a56d (diff) | |
[MERGE] forward port branch 11.0 up to 468dab6748a
Diffstat (limited to 'addons')
28 files changed, 282 insertions, 61 deletions
diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py index 010d79be851..38d93740912 100644 --- a/addons/account/report/account_aged_partner_balance.py +++ b/addons/account/report/account_aged_partner_balance.py @@ -42,7 +42,10 @@ class ReportAgedPartnerBalance(models.AbstractModel): res = [] total = [] cr = self.env.cr - company_ids = self.env.context.get('company_ids', (self.env.user.company_id.id,)) + user_company = self.env.user.company_id + user_currency = user_company.currency_id + ResCurrency = self.env['res.currency'].with_context(date=date_from) + company_ids = self._context.get('company_ids') or [user_company.id] move_state = ['draft', 'posted'] if target_move == 'posted': move_state = ['posted'] @@ -99,15 +102,15 @@ class ReportAgedPartnerBalance(models.AbstractModel): partner_id = line.partner_id.id or False if partner_id not in undue_amounts: undue_amounts[partner_id] = 0.0 - line_amount = line.balance - if line.balance == 0: + line_amount = ResCurrency._compute(line.company_id.currency_id, user_currency, line.balance) + if user_currency.is_zero(line_amount): continue for partial_line in line.matched_debit_ids: if partial_line.max_date <= date_from: - line_amount += partial_line.amount + line_amount += ResCurrency._compute(partial_line.company_id.currency_id, user_currency, partial_line.amount) for partial_line in line.matched_credit_ids: if partial_line.max_date <= date_from: - line_amount -= partial_line.amount + line_amount -= ResCurrency._compute(partial_line.company_id.currency_id, user_currency, partial_line.amount) if not self.env.user.company_id.currency_id.is_zero(line_amount): undue_amounts[partner_id] += line_amount lines[partner_id].append({ @@ -151,15 +154,15 @@ class ReportAgedPartnerBalance(models.AbstractModel): partner_id = line.partner_id.id or False if partner_id not in partners_amount: partners_amount[partner_id] = 0.0 - line_amount = line.balance - if line.balance == 0: + line_amount = ResCurrency._compute(line.company_id.currency_id, user_currency, line.balance) + if user_currency.is_zero(line_amount): continue for partial_line in line.matched_debit_ids: if partial_line.max_date <= date_from: - line_amount += partial_line.amount + line_amount += ResCurrency._compute(partial_line.company_id.currency_id, user_currency, partial_line.amount) for partial_line in line.matched_credit_ids: if partial_line.max_date <= date_from: - line_amount -= partial_line.amount + line_amount -= ResCurrency._compute(partial_line.company_id.currency_id, user_currency, partial_line.amount) if not self.env.user.company_id.currency_id.is_zero(line_amount): partners_amount[partner_id] += line_amount diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv index 8171601d4d9..5fa3604a34b 100644 --- a/addons/account/security/ir.model.access.csv +++ b/addons/account/security/ir.model.access.csv @@ -39,8 +39,6 @@ access_account_journal_invoice,account.journal invoice,model_account_journal,acc access_account_invoice_group_invoice,account.invoice group invoice,model_account_invoice,account.group_account_invoice,1,1,1,1 access_res_currency_account_manager,res.currency account manager,base.model_res_currency,group_account_manager,1,1,1,1 access_res_currency_rate_account_manager,res.currency.rate account manager,base.model_res_currency_rate,group_account_manager,1,1,1,1 -access_account_invoice_user,account.invoice user,model_account_invoice,base.group_user,1,0,0,0 -access_account_invoice_line_user,account.invoice.line user,model_account_invoice_line,base.group_user,1,0,0,0 access_account_invoice_portal,account.invoice.portal,account.model_account_invoice,base.group_portal,1,0,0,0 access_account_invoice_line_portal,account.invoice.line.portal,account.model_account_invoice_line,base.group_portal,1,0,0,0 access_account_payment_term_partner_manager,account.payment.term partner manager,model_account_payment_term,base.group_user,1,0,0,0 diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 0b841ad0154..93c06fe63df 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -1208,8 +1208,9 @@ var ManualModel = StatementModel.extend({ }); var domainReconcile = []; - if (context && context.company_ids) { - domainReconcile.push(['company_id', 'in', context.company_ids]); + var company_ids = context && context.company_ids || [session.company_id] + if (company_ids) { + domainReconcile.push(['company_id', 'in', company_ids]); } var def_reconcileModel = this._rpc({ model: 'account.reconcile.model', diff --git a/addons/account/wizard/account_report_common.py b/addons/account/wizard/account_report_common.py index 6b47f2a7e8e..f5e5814c814 100644 --- a/addons/account/wizard/account_report_common.py +++ b/addons/account/wizard/account_report_common.py @@ -45,4 +45,4 @@ class AccountCommonReport(models.TransientModel): data['form'] = self.read(['date_from', 'date_to', 'journal_ids', 'target_move', 'company_id'])[0] used_context = self._build_contexts(data) data['form']['used_context'] = dict(used_context, lang=self.env.context.get('lang') or 'en_US') - return self._print_report(data) + return self.with_context(discard_logo_check=True)._print_report(data) diff --git a/addons/account_asset/views/account_asset_views.xml b/addons/account_asset/views/account_asset_views.xml index 4d4c9cea441..bc364455ae2 100644 --- a/addons/account_asset/views/account_asset_views.xml +++ b/addons/account_asset/views/account_asset_views.xml @@ -33,7 +33,7 @@ attrs="{'invisible': [('type','!=','sale')]}" style="font-weight: bold" class="o_light_label"/> </div> - <field name="account_asset_id" nolabel="1" attrs="{'invisible': [('type','=', False)]}"/> + <field name="account_asset_id" nolabel="1" attrs="{'invisible': [('type','=', False)]}" domain="[('company_id', '=', company_id)]"/> <div> <label for="account_depreciation_id" attrs="{'invisible': [('type','!=','purchase')]}" @@ -42,7 +42,7 @@ attrs="{'invisible': [('type','!=','sale')]}" style="font-weight: bold" class="o_light_label"/> </div> - <field name="account_depreciation_id" nolabel="1"/> + <field name="account_depreciation_id" nolabel="1" domain="[('company_id', '=', company_id)]"/> <div> <label for="account_depreciation_expense_id" attrs="{'invisible': [('type','!=','purchase')]}" @@ -51,8 +51,8 @@ attrs="{'invisible': [('type','!=','sale')]}" style="font-weight: bold" class="o_light_label"/> </div> - <field name="account_depreciation_expense_id" nolabel="1"/> - <field name="account_analytic_id" groups="analytic.group_analytic_accounting"/> + <field name="account_depreciation_expense_id" nolabel="1" domain="[('company_id', '=', company_id)]"/> + <field name="account_analytic_id" domain="[('company_id', '=', company_id)]" groups="analytic.group_analytic_accounting"/> </group> <group string="Periodicity"> <field name="method_time" string="Time Method Based On" widget="radio" attrs="{'invisible': [('type','!=','purchase')]}"/> diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py index c0da1d85f83..67309d1ad23 100644 --- a/addons/auth_signup/models/res_partner.py +++ b/addons/auth_signup/models/res_partner.py @@ -58,17 +58,17 @@ class ResPartner(models.Model): for partner in self: # when required, make sure the partner has a valid signup token if self.env.context.get('signup_valid') and not partner.user_ids: - partner.signup_prepare() + partner.sudo().signup_prepare() route = 'login' # the parameters to encode for the query query = dict(db=self.env.cr.dbname) - signup_type = self.env.context.get('signup_force_type_in_url', partner.signup_type or '') + signup_type = self.env.context.get('signup_force_type_in_url', partner.sudo().signup_type or '') if signup_type: route = 'reset_password' if signup_type == 'reset' else signup_type - if partner.signup_token and signup_type: - query['token'] = partner.signup_token + if partner.sudo().signup_token and signup_type: + query['token'] = partner.sudo().signup_token elif partner.user_ids: query['login'] = partner.user_ids[0].login else: diff --git a/addons/base_address_city/models/res_partner.py b/addons/base_address_city/models/res_partner.py index 07c42694ef1..7591299f3e2 100644 --- a/addons/base_address_city/models/res_partner.py +++ b/addons/base_address_city/models/res_partner.py @@ -25,18 +25,58 @@ class Partner(models.Model): # render the partner address accordingly to address_view_id doc = etree.fromstring(arch) if doc.xpath("//field[@name='city_id']"): - return arch - label = _('City') - for city_node in doc.xpath("//field[@name='city']"): - replacement_xml = """ + return arch + + replacement_xml = """ <div> <field name="country_enforce_cities" invisible="1"/> - <field name='city' placeholder="%s" attrs="{'invisible': [('country_enforce_cities', '=', True), ('city_id', '!=', False)], 'readonly': [('type', '=', 'contact'), ('parent_id', '!=', False)]}"/> - <field name='city_id' placeholder="%s" string="%s" attrs="{'invisible': [('country_enforce_cities', '=', False)], 'readonly': [('type', '=', 'contact'), ('parent_id', '!=', False)]}" context="{'default_country_id': country_id}" domain="[('country_id', '=', country_id)]"/> + <field name='city' placeholder="%(placeholder)s" + attrs="{ + 'invisible': [('country_enforce_cities', '=', True), ('city_id', '!=', False)], + 'readonly': [('type', '=', 'contact')%(parent_condition)s] + }" + /> + <field name='city_id' placeholder="%(placeholder)s" string="%(placeholder)s" + context="{'default_country_id': country_id}" + domain="[('country_id', '=', country_id)]" + attrs="{ + 'invisible': [('country_enforce_cities', '=', False)], + 'readonly': [('type', '=', 'contact')%(parent_condition)s] + }" + /> </div> - """ % (label, label, label) - city_id_node = etree.fromstring(replacement_xml) - city_node.getparent().replace(city_node, city_id_node) + """ + + replacement_data = { + 'placeholder': _('City'), + } + + def _arch_location(node): + in_subview = False + view_type = False + parent = node.getparent() + while parent is not None and (not view_type or not in_subview): + if parent.tag == 'field': + in_subview = True + elif parent.tag in ['list', 'tree', 'kanban', 'form']: + view_type = parent.tag + parent = parent.getparent() + return { + 'view_type': view_type, + 'in_subview': in_subview, + } + + for city_node in doc.xpath("//field[@name='city']"): + location = _arch_location(city_node) + replacement_data['parent_condition'] = '' + if location['view_type'] == 'form' or not location['in_subview']: + replacement_data['parent_condition'] = ", ('parent_id', '!=', False)" + + replacement_formatted = replacement_xml % replacement_data + for replace_node in etree.fromstring(replacement_formatted).getchildren(): + city_node.addprevious(replace_node) + parent = city_node.getparent() + parent.remove(city_node) arch = etree.tostring(doc, encoding='unicode') return arch diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index 94ef2e81701..ca6c726b0c9 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -1757,7 +1757,15 @@ class Meeting(models.Model): if values.get('description'): activity_values['note'] = values['description'] if values.get('start'): - activity_values['date_deadline'] = fields.Datetime.from_string(values['start']).date() + # self.start is a datetime UTC *only when the event is not allday* + # activty.date_deadline is a date (No TZ, but should represent the day in which the user's TZ is) + # See 72254129dbaeae58d0a2055cba4e4a82cde495b7 for the same issue, but elsewhere + deadline = fields.Datetime.from_string(values['start']) + user_tz = self.env.context.get('tz') + if user_tz and not self.allday: + deadline = pytz.UTC.localize(deadline) + deadline = deadline.astimezone(pytz.timezone(user_tz)) + activity_values['date_deadline'] = deadline.date() if values.get('user_id'): activity_values['user_id'] = values['user_id'] if activity_values.keys(): diff --git a/addons/calendar/tests/test_calendar.py b/addons/calendar/tests/test_calendar.py index b764a4a9463..f3dcb3e0b50 100644 --- a/addons/calendar/tests/test_calendar.py +++ b/addons/calendar/tests/test_calendar.py @@ -314,3 +314,73 @@ class TestCalendar(TransactionCase): else: self.assertEqual(d.hour, 15) self.assertEqual(d.minute, 30) + + def test_event_activity_timezone(self): + activty_type = self.env['mail.activity.type'].create({ + 'name': 'Meeting', + 'category': 'meeting' + }) + + activity_id = self.env['mail.activity'].create({ + 'summary': 'Meeting with partner', + 'activity_type_id': activty_type.id, + 'res_model_id': self.env['ir.model'].search([('model', '=', 'res.partner')], limit=1).id, + 'res_id': self.env['res.partner'].search([('name', 'ilike', 'Agrolait')], limit=1).id, + }) + + calendar_event = self.env['calendar.event'].create({ + 'name': 'Meeting with partner', + 'activity_ids': [(6, False, activity_id.ids)], + 'start': '2018-11-12 21:00:00', + 'stop': '2018-11-13 00:00:00', + }) + + # Check output in UTC + self.assertEqual(activity_id.date_deadline, '2018-11-12') + + # Check output in the user's tz + # write on the event to trigger sync of activities + calendar_event.with_context({'tz': 'Australia/Brisbane'}).write({ + 'start': '2018-11-12 21:00:00', + }) + + self.assertEqual(activity_id.date_deadline, '2018-11-13') + + def test_event_allday_activity_timezone(self): + # Covers use case of commit eef4c3b48bcb4feac028bf640b545006dd0c9b91 + # Also, read the comment in the code at calendar.event._inverse_dates + activty_type = self.env['mail.activity.type'].create({ + 'name': 'Meeting', + 'category': 'meeting' + }) + + activity_id = self.env['mail.activity'].create({ + 'summary': 'Meeting with partner', + 'activity_type_id': activty_type.id, + 'res_model_id': self.env['ir.model'].search([('model', '=', 'res.partner')], limit=1).id, + 'res_id': self.env['res.partner'].search([('name', 'ilike', 'Agrolait')], limit=1).id, + }) + + calendar_event = self.env['calendar.event'].create({ + 'name': 'All Day', + 'start': "2018-10-16 00:00:00", + 'start_date': "2018-10-16", + 'start_datetime': False, + 'stop': "2018-10-18 00:00:00", + 'stop_date': "2018-10-18", + 'stop_datetime': False, + 'allday': True, + 'activity_ids': [(6, False, activity_id.ids)], + }) + + # Check output in UTC + self.assertEqual(activity_id.date_deadline, '2018-10-16') + + # Check output in the user's tz + # write on the event to trigger sync of activities + calendar_event.with_context({'tz': 'Pacific/Honolulu'}).write({ + 'start': '2018-10-16 00:00:00', + 'start_date': '2018-10-16', + }) + + self.assertEqual(activity_id.date_deadline, '2018-10-16') diff --git a/addons/delivery/models/delivery_carrier.py b/addons/delivery/models/delivery_carrier.py index 2644163e5e3..3a7424c165c 100644 --- a/addons/delivery/models/delivery_carrier.py +++ b/addons/delivery/models/delivery_carrier.py @@ -217,7 +217,7 @@ class DeliveryCarrier(models.Model): 'error_message': _('Error: this delivery method is not available for this address.'), 'warning_message': False} price = self.fixed_price - if self.company_id.currency_id.id != order.currency_id.id: + if self.company_id and self.company_id.currency_id.id != order.currency_id.id: price = self.env['res.currency']._compute(self.company_id.currency_id, order.currency_id, price) return {'success': True, 'price': price, diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index 63948b8429a..e691ec98c73 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -156,6 +156,18 @@ var Chatter = Widget.extend({ } }, /** + * @private + */ + _disableComposer: function () { + this.$(".o_composer_button_send").prop('disabled', true); + }, + /** + * @private + */ + _enableComposer: function () { + this.$(".o_composer_button_send").prop('disabled', false); + }, + /** * Discard changes on the record. * * @private @@ -231,12 +243,15 @@ var Chatter = Widget.extend({ self.composer.focus(); } self.composer.on('post_message', self, function (message) { + self._disableComposer(); self._discardOnReload(message).then(function () { self.fields.thread.postMessage(message).then(function () { self._closeComposer(true); if (self._reloadAfterPost(message)) { self.trigger_up('reload'); } + }).fail(function () { + self._enableComposer(); }); }); }); diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js index b15a446d7a9..64cc48285e5 100644 --- a/addons/mail/static/src/js/composer.js +++ b/addons/mail/static/src/js/composer.js @@ -486,13 +486,10 @@ var BasicComposer = Widget.extend({ }, send_message: function () { - var $button = this.$('.o_composer_button_send'); - if (this.is_empty() || !this.do_check_attachment_upload()) { return; } - $button.prop('disabled', true); clearTimeout(this.canned_timeout); var self = this; this.preprocess_message().then(function (message) { diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 599d8c23d31..ec28fb6fa15 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -436,6 +436,8 @@ td { display: flex; -webkit-flex: 1; flex: 1; + max-width: -moz-available; + max-width: -webkit-fill-available; } .pos .orders { display: -webkit-flex; diff --git a/addons/pos_restaurant/static/src/js/multiprint.js b/addons/pos_restaurant/static/src/js/multiprint.js index 738b13d9362..7e7cc7a8bfd 100644 --- a/addons/pos_restaurant/static/src/js/multiprint.js +++ b/addons/pos_restaurant/static/src/js/multiprint.js @@ -139,8 +139,10 @@ models.Orderline = models.Orderline.extend({ } }, set_dirty: function(dirty) { - this.mp_dirty = dirty; - this.trigger('change',this); + if (this.mp_dirty !== dirty) { + this.mp_dirty = dirty; + this.trigger('change', this); + } }, get_line_diff_hash: function(){ if (this.get_note()) { diff --git a/addons/product/report/product_pricelist.py b/addons/product/report/product_pricelist.py index 91f72d6cadd..c0bd5138fab 100644 --- a/addons/product/report/product_pricelist.py +++ b/addons/product/report/product_pricelist.py @@ -16,7 +16,7 @@ class report_product_pricelist(models.AbstractModel): quantities = self._get_quantity(data) return { 'doc_ids': data.get('ids', data.get('active_ids')), - 'doc_model': 'hr.contribution.register', + 'doc_model': 'product.pricelist', 'docs': products, 'data': dict( data, diff --git a/addons/purchase_requisition/models/purchase_requisition.py b/addons/purchase_requisition/models/purchase_requisition.py index ce0cb5e2d2a..06f53a446f4 100644 --- a/addons/purchase_requisition/models/purchase_requisition.py +++ b/addons/purchase_requisition/models/purchase_requisition.py @@ -481,3 +481,15 @@ class StockMove(models.Model): return [(requisition_line.requisition_id, requisition_line.requisition_id.user_id, visited) for requisition_line in self.requisition_line_ids if requisition_line.state not in ('done', 'cancel')] else: return super(StockMove, self)._get_upstream_documents_and_responsibles(visited) + + +class Orderpoint(models.Model): + _inherit = "stock.warehouse.orderpoint" + + def _quantity_in_progress(self): + res = super(Orderpoint, self)._quantity_in_progress() + for op in self: + for pr in self.env['purchase.requisition'].search([('state','=','draft'),('origin','=',op.name)]): + for prline in pr.line_ids.filtered(lambda l: l.product_id.id == op.product_id.id): + res[op.id] += prline.product_uom_id._compute_quantity(prline.product_qty, op.product_uom, round=False) + return res diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 9d24f7adeff..188063018d5 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -469,10 +469,11 @@ class SaleOrder(models.Model): raise UserError(_('There is no invoiceable line.')) for invoice in invoices.values(): + invoice.compute_taxes() if not invoice.invoice_line_ids: raise UserError(_('There is no invoiceable line.')) # If invoice is negative, do a refund invoice instead - if invoice.amount_untaxed < 0: + if invoice.amount_total < 0: invoice.type = 'out_refund' for line in invoice.invoice_line_ids: line.quantity = -line.quantity diff --git a/addons/sale/report/sale_report_views.xml b/addons/sale/report/sale_report_views.xml index c74e8c5eca4..ed2033b60d0 100644 --- a/addons/sale/report/sale_report_views.xml +++ b/addons/sale/report/sale_report_views.xml @@ -18,7 +18,7 @@ <field name="model">sale.report</field> <field name="arch" type="xml"> <graph string="Sales Analysis"> - <field name="date" type="row"/> + <field name="confirmation_date" type="row"/> <field name="price_subtotal" type="measure"/> </graph> </field> diff --git a/addons/sale_margin/models/sale_order.py b/addons/sale_margin/models/sale_order.py index 57988de2ab9..a62813d8bd1 100644 --- a/addons/sale_margin/models/sale_order.py +++ b/addons/sale_margin/models/sale_order.py @@ -60,10 +60,6 @@ class SaleOrderLine(models.Model): for line in self: currency = line.order_id.pricelist_id.currency_id price = line.purchase_price - if not price: - from_cur = line.env.user.company_id.currency_id.with_context(date=line.order_id.date_order) - price = from_cur.compute(line.product_id.standard_price, currency, round=False) - line.margin = currency.round(line.price_subtotal - (price * line.product_uom_qty)) diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index 87134fdd990..7c9d0cba786 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -300,7 +300,10 @@ class SaleOrderLine(models.Model): self.ensure_one() qty = 0.0 for move in self.move_ids.filtered(lambda r: r.state != 'cancel'): - qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') + if move.picking_code == 'outgoing': + qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') + elif move.picking_code == 'incoming': + qty -= move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') return qty @api.multi @@ -406,7 +409,8 @@ class SaleOrderLine(models.Model): return is_available def _update_line_quantity(self, values): - if self.mapped('qty_delivered') and values['product_uom_qty'] < max(self.mapped('qty_delivered')): + precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') + if self.mapped('qty_delivered') and float_compare(values['product_uom_qty'], max(self.mapped('qty_delivered')), precision_digits=precision) == -1: raise UserError(_('You cannot decrease the ordered quantity below the delivered quantity.\n' 'Create a return first.')) super(SaleOrderLine, self)._update_line_quantity(values) diff --git a/addons/stock/views/stock_move_views.xml b/addons/stock/views/stock_move_views.xml index 632ae77d80d..ac77ab0f020 100644 --- a/addons/stock/views/stock_move_views.xml +++ b/addons/stock/views/stock_move_views.xml @@ -398,7 +398,7 @@ <field name="act_window_id" ref="stock_move_action"/> </record> - <record model="ir.actions.act_window.view" id="action_stock_move_graph_all"> + <record model="ir.actions.act_window.view" id="action_stock_move_kanban_all"> <field name="sequence" eval="4"/> <field name="view_mode">kanban</field> <field name="view_id" ref="view_move_kandan"/> diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index c4f23549aa7..c16fc171d03 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -256,7 +256,7 @@ </group> <notebook> <page string="Detailed Operations" attrs="{'invisible': [('show_operations', '=', False)]}"> - <field name="move_line_ids" attrs="{'readonly': ['|', '&', ('show_operations', '=', False), ('state', '=', 'done'), ('is_locked', '=', True)]}" context="{'default_picking_id': id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id}"> + <field name="move_line_ids" attrs="{'readonly': ['|', ('show_operations', '=', False), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}" context="{'default_picking_id': id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id}"> <tree editable="bottom" decoration-muted="(state == 'done' and is_locked == True)" decoration-danger="qty_done>product_uom_qty and state!='done'" decoration-success="qty_done==product_uom_qty and state!='done' and not result_package_id"> <field name="product_id" required="1" attrs="{'readonly': ['|', ('state', '=', 'done'), ('move_id', '!=', False)]}"/> <field name="move_id" invisible="1"/> diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index 55f9a2e85d7..c0d3ff5dc46 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -165,9 +165,12 @@ var FormViewDialog = ViewDialog.extend({ } fields_view_def.then(function (viewInfo) { + var refinedContext = _.pick(self.context, function (value, key) { + return key.indexOf('_view_ref') === -1; + }); var formview = new FormView(viewInfo, { modelName: self.res_model, - context: self.context, + context: refinedContext, ids: self.res_id ? [self.res_id] : [], currentId: self.res_id || undefined, index: 0, diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js index 39484795f5a..38025c255dd 100644 --- a/addons/web/static/tests/views/view_dialogs_tests.js +++ b/addons/web/static/tests/views/view_dialogs_tests.js @@ -364,6 +364,61 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.test('Form dialog and subview with _view_ref contexts', function (assert) { + assert.expect(2); + + this.data.instrument.records = [{id: 1, name: 'Tromblon', badassery: [1]}]; + this.data.partner.records[0].instrument = 1; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<field name="name"/>' + + '<field name="instrument" context="{\'tree_view_ref\': \'some_tree_view\'}"/>' + + '</form>', + res_id: 1, + archs: { + 'instrument,false,form': '<form>'+ + '<field name="name"/>'+ + '<field name="badassery" context="{\'tree_view_ref\': \'some_other_tree_view\'}"/>' + + '</form>', + + 'badassery,false,list': '<tree>'+ + '<field name="level"/>'+ + '</tree>', + }, + viewOptions: { + mode: 'edit', + }, + + mockRPC: function(route, args) { + if (args.method === 'get_formview_id') { + return $.when(false); + } + return this._super(route, args); + }, + + interceptsPropagate: { + load_views: function (ev) { + var evaluatedContext = ev.data.context.eval(); + if (ev.data.modelName === 'instrument') { + assert.deepEqual(evaluatedContext, {tree_view_ref: 'some_tree_view'}, + 'The correct _view_ref should have been sent to the server, first time'); + } + if (ev.data.modelName === 'badassery') { + assert.deepEqual(evaluatedContext, {tree_view_ref: 'some_other_tree_view'}, + 'The correct _view_ref should have been sent to the server for the subview'); + } + }, + }, + }); + + form.$('.o_field_widget[name="instrument"] button.o_external_button').click(); + form.destroy(); + }); + QUnit.test('SelectCreateDialog: save current search', function (assert) { assert.expect(4); diff --git a/addons/web_editor/static/src/js/editor/summernote.js b/addons/web_editor/static/src/js/editor/summernote.js index ea6c5a8eee3..74f14e46374 100644 --- a/addons/web_editor/static/src/js/editor/summernote.js +++ b/addons/web_editor/static/src/js/editor/summernote.js @@ -1893,6 +1893,15 @@ $.summernote.pluginEvents.formatBlock = function (event, editor, layoutInfo, sTa if (!r) { return; } + // select content since container (that firefox selects) may be removed + if (r.so === 0) { + r.sc = dom.firstChild(r.sc); + } + if (dom.nodeLength(r.ec) >= r.eo) { + r.ec = dom.lastChild(r.ec); + r.eo = dom.nodeLength(r.ec); + } + r = range.create(r.sc, r.so, r.ec, r.eo); r.reRange().select(); if (sTagName === "blockquote" || sTagName === "pre") { @@ -1906,6 +1915,10 @@ $.summernote.pluginEvents.formatBlock = function (event, editor, layoutInfo, sTa for (var i=0; i<nodes.length; i++) { if (dom.isBR(nodes[i]) || (dom.isText(nodes[i]) && dom.isVisibleText(nodes[i])) || dom.isB(nodes[i]) || dom.isU(nodes[i]) || dom.isS(nodes[i]) || dom.isI(nodes[i]) || dom.isFont(nodes[i])) { var ancestor = dom.ancestor(nodes[i], isFormatNode); + if ($(ancestor).parent().is('blockquote')) { + // firefox may wrap formatting block in blockquote + $(ancestor).unwrap(); + } if (!ancestor) { dom.wrap(nodes[i], sTagName); } else if (ancestor.tagName.toLowerCase() !== sTagName) { @@ -2273,7 +2286,8 @@ options.onCreateLink = function (sLinkUrl) { // pass } else if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) { sLinkUrl = 'mailto:' + sLinkUrl; - } else if (sLinkUrl.indexOf('://') === -1 && sLinkUrl.indexOf('/') !== 0 && sLinkUrl.indexOf('#') !== 0) { + } else if (sLinkUrl.indexOf('://') === -1 && sLinkUrl[0] !== '/' + && sLinkUrl[0] !== '#' && sLinkUrl.slice(0, 2) !== '${') { sLinkUrl = 'http://' + sLinkUrl; } return sLinkUrl; diff --git a/addons/website/models/ir_actions.py b/addons/website/models/ir_actions.py index f26d6263004..4add5587d7f 100644 --- a/addons/website/models/ir_actions.py +++ b/addons/website/models/ir_actions.py @@ -38,7 +38,7 @@ class ServerAction(models.Model): def _get_website_url(self): for action in self: if action.state == 'code' and action.website_published: - action.website_url = self._compute_website_url(action.website_path, action.xml_id) + action.website_url = action._compute_website_url(action.website_path, action.xml_id) @api.model def _get_eval_context(self, action): diff --git a/addons/website_event/views/event_templates.xml b/addons/website_event/views/event_templates.xml index 752d227620e..724b0713667 100644 --- a/addons/website_event/views/event_templates.xml +++ b/addons/website_event/views/event_templates.xml @@ -81,14 +81,14 @@ </li> </ul> </div> - <div class="row"> - <div class="col-sm-6 col-xs-offset-3"> - <t t-call="website.pager"/> - </div> - </div> <div class="col-md-3 oe_structure" id="right_column"> </div> </div> + <div class="row"> + <div class="col-sm-6 col-xs-offset-3"> + <t t-call="website.pager"/> + </div> + </div> </div> <div class="oe_structure"/> </div> diff --git a/addons/website_form/controllers/main.py b/addons/website_form/controllers/main.py index 41a120008b4..f8a75af3130 100644 --- a/addons/website_form/controllers/main.py +++ b/addons/website_form/controllers/main.py @@ -221,10 +221,10 @@ class WebsiteForm(http.Controller): else: orphan_attachment_ids.append(attachment_id.id) - # If some attachments didn't match a field on the model, - # we create a mail.message to link them to the record - if orphan_attachment_ids: - if model_name != 'mail.mail': + if model_name != 'mail.mail': + # If some attachments didn't match a field on the model, + # we create a mail.message to link them to the record + if orphan_attachment_ids: values = { 'body': _('<p>Attached files : </p>'), 'model': model_name, |
