diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py index 010d79be8519f..38d9374091268 100644 --- a/addons/account/report/account_aged_partner_balance.py +++ b/addons/account/report/account_aged_partner_balance.py @@ -42,7 +42,10 @@ def _get_partner_move_lines(self, account_type, date_from, target_move, period_l 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 @@ def _get_partner_move_lines(self, account_type, date_from, target_move, period_l 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 @@ def _get_partner_move_lines(self, account_type, date_from, target_move, period_l 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 8171601d4d9c9..5fa3604a34b5f 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 0b841ad0154b5..93c06fe63df8f 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 6b47f2a7e8eac..f5e5814c814dd 100644 --- a/addons/account/wizard/account_report_common.py +++ b/addons/account/wizard/account_report_common.py @@ -45,4 +45,4 @@ def check_report(self): 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 4d4c9cea441c6..bc364455ae272 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"/> - +
- +
- - + + diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py index c0da1d85f8329..67309d1ad2361 100644 --- a/addons/auth_signup/models/res_partner.py +++ b/addons/auth_signup/models/res_partner.py @@ -58,17 +58,17 @@ def _get_signup_url_for_action(self, action=None, view_type=None, menu_id=None, 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 07c42694ef10d..7591299f3e284 100644 --- a/addons/base_address_city/models/res_partner.py +++ b/addons/base_address_city/models/res_partner.py @@ -25,18 +25,58 @@ def _fields_view_get_address(self, arch): # 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 = """
- - + +
- """ % (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 94ef2e81701c2..ca6c726b0c996 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -1757,7 +1757,15 @@ def _sync_activities(self, values): 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 b764a4a946340..f3dcb3e0b508a 100644 --- a/addons/calendar/tests/test_calendar.py +++ b/addons/calendar/tests/test_calendar.py @@ -314,3 +314,73 @@ def test_recurring_around_dst(self): 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 2644163e5e348..3a7424c165c16 100644 --- a/addons/delivery/models/delivery_carrier.py +++ b/addons/delivery/models/delivery_carrier.py @@ -217,7 +217,7 @@ def fixed_rate_shipment(self, order): '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 63948b8429a79..e691ec98c7306 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -155,6 +155,18 @@ var Chatter = Widget.extend({ this.composer.clear_composer(); } }, + /** + * @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. * @@ -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 b15a446d7a9ea..64cc48285e5fa 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 599d8c23d3157..ec28fb6fa15a9 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 738b13d936200..7e7cc7a8bfde8 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 91f72d6caddaf..c0bd5138fabd8 100644 --- a/addons/product/report/product_pricelist.py +++ b/addons/product/report/product_pricelist.py @@ -16,7 +16,7 @@ def get_report_values(self, docids, data=None): 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 ce0cb5e2d2acc..06f53a446f41f 100644 --- a/addons/purchase_requisition/models/purchase_requisition.py +++ b/addons/purchase_requisition/models/purchase_requisition.py @@ -481,3 +481,15 @@ def _get_upstream_documents_and_responsibles(self, visited): 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 9d24f7adeffd5..188063018d532 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -469,10 +469,11 @@ def action_invoice_create(self, grouped=False, final=False): 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 c74e8c5eca41b..ed2033b60d0a7 100644 --- a/addons/sale/report/sale_report_views.xml +++ b/addons/sale/report/sale_report_views.xml @@ -18,7 +18,7 @@ sale.report - + diff --git a/addons/sale_margin/models/sale_order.py b/addons/sale_margin/models/sale_order.py index 57988de2ab9e0..a62813d8bd154 100644 --- a/addons/sale_margin/models/sale_order.py +++ b/addons/sale_margin/models/sale_order.py @@ -60,10 +60,6 @@ def _product_margin(self): 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 87134fdd99042..7c9d0cba78669 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -300,7 +300,10 @@ def _get_qty_procurement(self): 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 @@ def _check_routing(self): 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 632ae77d80d57..ac77ab0f0200b 100644 --- a/addons/stock/views/stock_move_views.xml +++ b/addons/stock/views/stock_move_views.xml @@ -398,7 +398,7 @@ - + kanban diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index c4f23549aa711..c16fc171d0389 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -256,7 +256,7 @@
- + diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index 55f9a2e85d7c4..c0d3ff5dc46cd 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 39484795f5a46..38025c255dd89 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: '
' + + '' + + '' + + '', + res_id: 1, + archs: { + 'instrument,false,form': '
'+ + ''+ + '' + + '', + + 'badassery,false,list': ''+ + ''+ + '', + }, + 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 ea6c5a8eee32b..74f14e4637471 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 -
-
- -
-
+
+
+ +
+
diff --git a/addons/website_form/controllers/main.py b/addons/website_form/controllers/main.py index 41a120008b4fd..f8a75af3130a6 100644 --- a/addons/website_form/controllers/main.py +++ b/addons/website_form/controllers/main.py @@ -221,10 +221,10 @@ def insert_attachment(self, model, id_record, files): 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': _('

Attached files :

'), 'model': model_name, diff --git a/doc/cla/corporate/cybrosys.md b/doc/cla/corporate/cybrosys.md index 0d413d31ba7e5..14fedd60e02a9 100644 --- a/doc/cla/corporate/cybrosys.md +++ b/doc/cla/corporate/cybrosys.md @@ -8,7 +8,7 @@ declaration. Signed, -Niyas Raphy niyas@cybrosys.com https://github.com/CybroOdoo +Sainu Abideen sainu@cybrosys.com https://github.com/CybroOdoo List of contributors: diff --git a/doc/cla/corporate/okia.md b/doc/cla/corporate/okia.md new file mode 100644 index 0000000000000..7bc41c9dce401 --- /dev/null +++ b/doc/cla/corporate/okia.md @@ -0,0 +1,15 @@ +Belgium, 2018-11-05 + +Okia SPRL agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Sylvain Van Hoof sylvain@okia.be https://github.com/sylvainvh + +List of contributors: + +Sylvain Van Hoof sylvain@okia.be https://github.com/sylvainvh diff --git a/doc/cla/corporate/subteno-it.md b/doc/cla/corporate/subteno-it.md new file mode 100644 index 0000000000000..8bcf028ec2d98 --- /dev/null +++ b/doc/cla/corporate/subteno-it.md @@ -0,0 +1,16 @@ +France, 2018-11-14 + +Subteno IT agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Sébastien LANGE sebastien.lange@subteno-it.fr https://github.com/sla-subteno-it + +List of contributors: + +Christian LECOUFLE christian.lecoufle@subteno-it.fr https://github.com/cle-subteno-it +Vincent COFFIN vincent.coffin@subteno-it.fr https://github.com/vco-subteno-it diff --git a/doc/cla/individual/ugaitzolaizola.md b/doc/cla/individual/ugaitzolaizola.md new file mode 100644 index 0000000000000..535dad871abd1 --- /dev/null +++ b/doc/cla/individual/ugaitzolaizola.md @@ -0,0 +1,11 @@ +Spain, 2018-10-17 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Ugaitz Olaizola Arbelaitz uolaizola@binovo.es diff --git a/odoo/PKG-INFO b/odoo/PKG-INFO deleted file mode 100644 index fba4a850f5144..0000000000000 --- a/odoo/PKG-INFO +++ /dev/null @@ -1,18 +0,0 @@ -Metadata-Version: 1.1 -Name: OpenERP -Version: 6.0.0 -Author: OpenERP S.A. -Author-email: fp at openerp com -Maintainer: OpenERP S.A. -Maintainer-email: fp at openerp com -Home-page: http://www.openerp.com -Download-url: http://www.openerp.com/downloads.html -Summary: OpenERP is an Enterprise Resource Management written entirely in python. -License: GPL-3 -Description: OpenERP is a complete ERP and CRM. The main features are accounting (analytic - and financial), stock management, sales and purchases management, tasks - automation, marketing campaigns, help desk, POS, etc. Technical features include - a distributed server, flexible workflows, an object database, a dynamic GUI, - customizable reports, and an XML-RPC interface. -Keywords: ERP, Accounting, Stock, CRM, Enterprise, Logistics, Management, Sales, Purchases -Platform: Linux, Win32 diff --git a/odoo/addons/base/rng/calendar_view.rng b/odoo/addons/base/rng/calendar_view.rng index 949c3f4831875..500a61b3389c4 100644 --- a/odoo/addons/base/rng/calendar_view.rng +++ b/odoo/addons/base/rng/calendar_view.rng @@ -32,7 +32,7 @@ - + diff --git a/requirements.txt b/requirements.txt index 4fdf4ac97681d..4619d232b1f7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ lxml ; sys_platform == 'win32' Mako==1.0.4 MarkupSafe==0.23 mock==2.0.0 -num2words==0.5.4 +num2words==0.5.6 ofxparse==0.16 passlib==1.6.5 Pillow==4.0.0 diff --git a/setup.py b/setup.py index 86920fccbc9d0..050be7a263304 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import os -import re -from glob import glob from setuptools import find_packages, setup from os.path import join, dirname @@ -11,114 +8,6 @@ exec(open(join(dirname(__file__), 'odoo', 'release.py'), 'rb').read()) # Load release variables lib_name = 'odoo' - -def py2exe_datafiles(): - data_files = {} - data_files['Microsoft.VC90.CRT'] = glob('C:\Microsoft.VC90.CRT\*.*') - - for root, dirnames, filenames in os.walk('odoo'): - for filename in filenames: - if not re.match(r'.*(\.pyc|\.pyo|\~)$', filename): - data_files.setdefault(root, []).append(join(root, filename)) - - import babel - data_files['babel/localedata'] = glob(join(dirname(babel.__file__), 'localedata', '*')) - others = ['global.dat', 'numbers.py', 'support.py', 'plural.py'] - data_files['babel'] = [join(dirname(babel.__file__), f) for f in others] - others = ['frontend.py', 'mofile.py'] - data_files['babel/messages'] = [join(dirname(babel.__file__), 'messages', f) for f in others] - - import pytz - tzdir = dirname(pytz.__file__) - for root, _, filenames in os.walk(join(tzdir, 'zoneinfo')): - base = join('pytz', root[len(tzdir) + 1:]) - data_files[base] = [join(root, f) for f in filenames] - - import docutils - import passlib - import reportlab - import requests - data_mapping = ((docutils, 'docutils'), - (passlib, 'passlib'), - (reportlab, 'reportlab'), - (requests, 'requests')) - - for mod, datadir in data_mapping: - basedir = dirname(mod.__file__) - for root, _, filenames in os.walk(basedir): - base = join(datadir, root[len(basedir) + 1:]) - data_files[base] = [join(root, f) - for f in filenames - if not f.endswith(('.py', '.pyc', '.pyo'))] - - return list(data_files.items()) - - -def py2exe_options(): - if os.name == 'nt': - import py2exe - return { - 'console': [ - {'script': 'odoo-bin', 'icon_resources': [ - (1, join('setup', 'win32', 'static', 'pixmaps', 'openerp-icon.ico')) - ]}, - ], - 'options': { - 'py2exe': { - 'skip_archive': 1, - 'optimize': 0, # Keep the assert running as the integrated tests rely on them. - 'dist_dir': 'dist', - 'packages': [ - 'asynchat', 'asyncore', - 'BeautifulSoup', - 'commands', - 'dateutil', - 'decimal', - 'decorator', - 'docutils', - 'email', - 'encodings', - 'HTMLParser', - 'imaplib', - 'jinja2', - 'lxml', 'lxml._elementpath', 'lxml.builder', 'lxml.etree', 'lxml.objectify', - 'mako', - 'markupsafe', - 'mock', - 'ofxparse', - 'odoo', - 'passlib', - 'PIL', - 'poplib', - 'psutil', - 'pychart', - 'pydot', - 'pyparsing', - 'PyPDF2', - 'pytz', - 'reportlab', - 'requests', - 'select', - 'smtplib', - 'suds', - 'uuid', - 'vatnumber', - 'vobject', - 'win32service', 'win32serviceutil', - 'xlrd', - 'xlsxwriter', - 'xlwt', - 'xml', 'xml.dom', - ], - 'excludes': ['Tkconstants', 'Tkinter', 'tcl'], - } - }, - 'data_files': py2exe_datafiles() - } - else: - return {} - - setup( name='odoo', version=version, @@ -174,5 +63,4 @@ def py2exe_options(): tests_require=[ 'mock', ], - **py2exe_options() ) diff --git a/setup/win32/Makefile b/setup/win32/Makefile index 2efa1db6f69eb..06d7dc9e84d6a 100644 --- a/setup/win32/Makefile +++ b/setup/win32/Makefile @@ -5,9 +5,6 @@ include Makefile.servicename SERVER_DIRECTORY=../.. FILES_DIRECTORY=release -LAUNCH_PY2EXE_SERVICE=/cygdrive/c/python${PYTHON_VERSION}/python.exe win32_setup.py py2exe -LAUNCH_PY2EXE=/cygdrive/c/python${PYTHON_VERSION}/python.exe setup.py py2exe - MAKENSIS_ARGUMENTS=/DVERSION=$(VERSION) /DSERVICENAME=${SERVICENAME} LAUNCH_MAKENSIS=/cygdrive/c/tools/cygwin/makensis $(MAKENSIS_ARGUMENTS) setup.nsi @@ -27,8 +24,6 @@ server_clean: rm -rf $(SERVER_DIRECTORY)/.cyg* allinone: server_clean - #(cd $(SERVER_DIRECTORY)/setup/win32 && $(LAUNCH_PY2EXE_SERVICE)) - #(cd $(SERVER_DIRECTORY) && $(LAUNCH_PY2EXE)) (cd $(SERVER_DIRECTORY)/setup/win32 && $(LAUNCH_MAKENSIS)) (cd $(SERVER_DIRECTORY)/setup/win32 && mkdir -p $(FILES_DIRECTORY)) (cd $(SERVER_DIRECTORY)/setup/win32 && cp openerp-*.exe $(FILES_DIRECTORY)/openerp-server-setup-$(VERSION).exe)