diff --git a/addons/account/models/account_analytic_line.py b/addons/account/models/account_analytic_line.py index 4e610fd6dcf..2904565f173 100644 --- a/addons/account/models/account_analytic_line.py +++ b/addons/account/models/account_analytic_line.py @@ -34,7 +34,7 @@ class AccountAnalyticLine(models.Model): # Compute based on pricetype amount_unit = self.product_id.price_compute('standard_price', uom=unit)[self.product_id.id] amount = amount_unit * self.unit_amount or 0.0 - result = self.currency_id.round(amount) * -1 + result = (self.currency_id.round(amount) if self.currency_id else round(amount, 2)) * -1 self.amount = result self.general_account_id = account self.product_uom_id = unit diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index 121b71ca79a..7f09875044e 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -335,7 +335,7 @@ class account_payment(models.Model): payment.move_reconciled = rec company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', readonly=True) - name = fields.Char(readonly=True, copy=False, default="Draft Payment") # The name is attributed upon post() + name = fields.Char(readonly=True, copy=False) # The name is attributed upon post() state = fields.Selection([('draft', 'Draft'), ('posted', 'Posted'), ('sent', 'Sent'), ('reconciled', 'Reconciled'), ('cancelled', 'Cancelled')], readonly=True, default='draft', copy=False, string="Status") payment_type = fields.Selection(selection_add=[('transfer', 'Internal Transfer')]) @@ -515,23 +515,25 @@ class account_payment(models.Model): if any(inv.state != 'open' for inv in rec.invoice_ids): raise ValidationError(_("The payment cannot be processed because the invoice is not open!")) - # Use the right sequence to set the name - if rec.payment_type == 'transfer': - sequence_code = 'account.payment.transfer' - else: - if rec.partner_type == 'customer': - if rec.payment_type == 'inbound': - sequence_code = 'account.payment.customer.invoice' - if rec.payment_type == 'outbound': - sequence_code = 'account.payment.customer.refund' - if rec.partner_type == 'supplier': - if rec.payment_type == 'inbound': - sequence_code = 'account.payment.supplier.refund' - if rec.payment_type == 'outbound': - sequence_code = 'account.payment.supplier.invoice' - rec.name = self.env['ir.sequence'].with_context(ir_sequence_date=rec.payment_date).next_by_code(sequence_code) - if not rec.name and rec.payment_type != 'transfer': - raise UserError(_("You have to define a sequence for %s in your company.") % (sequence_code,)) + # keep the name in case of a payment reset to draft + if not rec.name: + # Use the right sequence to set the name + if rec.payment_type == 'transfer': + sequence_code = 'account.payment.transfer' + else: + if rec.partner_type == 'customer': + if rec.payment_type == 'inbound': + sequence_code = 'account.payment.customer.invoice' + if rec.payment_type == 'outbound': + sequence_code = 'account.payment.customer.refund' + if rec.partner_type == 'supplier': + if rec.payment_type == 'inbound': + sequence_code = 'account.payment.supplier.refund' + if rec.payment_type == 'outbound': + sequence_code = 'account.payment.supplier.invoice' + rec.name = self.env['ir.sequence'].with_context(ir_sequence_date=rec.payment_date).next_by_code(sequence_code) + if not rec.name and rec.payment_type != 'transfer': + raise UserError(_("You have to define a sequence for %s in your company.") % (sequence_code,)) # Create the journal entry amount = rec.amount * (rec.payment_type in ('outbound', 'transfer') and 1 or -1) diff --git a/addons/fleet/models/fleet_vehicle.py b/addons/fleet/models/fleet_vehicle.py index 79a67f544c9..ae8fa16f858 100644 --- a/addons/fleet/models/fleet_vehicle.py +++ b/addons/fleet/models/fleet_vehicle.py @@ -14,7 +14,7 @@ class FleetVehicle(models.Model): def _get_default_state(self): state = self.env.ref('fleet.vehicle_state_active', raise_if_not_found=False) - return state and state.id or False + return state if state and state.id else False name = fields.Char(compute="_compute_vehicle_name", store=True) active = fields.Boolean('Active', default=True, track_visibility="onchange") diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py index bd8e4cc44c0..032baf2d971 100644 --- a/addons/l10n_fr_fec/wizard/account_fr_fec.py +++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py @@ -265,7 +265,7 @@ class AccountFrFec(models.TransientModel): for row in self._cr.fetchall(): listrow = list(row) account_id = listrow.pop() - w.writerow([s.encode("utf-8") for s in listrow]) + w.writerow(listrow) # LINES sql_query = ''' diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 177324431f6..31c6af180ab 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -454,7 +454,7 @@ class MrpProduction(models.Model): move[0].with_context(do_not_unreserve=True).write({'product_uom_qty': quantity}) move[0]._recompute_state() move[0]._action_assign() - move.unit_factor = quantity / move.raw_material_production_id.product_qty + move[0].unit_factor = quantity / move[0].raw_material_production_id.product_qty elif quantity < 0: # Do not remove 0 lines if move[0].quantity_done > 0: raise UserError(_('Lines need to be deleted, but can not as you still have some quantities to consume in them. ')) diff --git a/addons/payment_authorize/controllers/main.py b/addons/payment_authorize/controllers/main.py index 9614327401a..a5f55f07afe 100644 --- a/addons/payment_authorize/controllers/main.py +++ b/addons/payment_authorize/controllers/main.py @@ -77,13 +77,14 @@ class AuthorizeController(http.Controller): 'id': token.id, 'short_name': token.short_name, '3d_secure': False, - 'verified': False, + 'verified': True, #Authorize.net does a transaction type of Authorization Only + #As Authorize.net already verify this card, we do not verify this card again. } - - if verify_validity != False: - token.validate() - res['verified'] = token.verified - + #token.validate() don't work with Authorize.net. + #Payments made via Authorize.net are settled and allowed to be refunded only on the next day. + #https://account.authorize.net/help/Miscellaneous/FAQ/Frequently_Asked_Questions.htm#Refund + #The original transaction that you wish to refund must have a status of Settled Successfully. + #You cannot issue refunds against unsettled, voided, declined or errored transactions. return res @http.route(['/payment/authorize/s2s/create'], type='http', auth='public') diff --git a/addons/purchase/data/mail_template_data.xml b/addons/purchase/data/mail_template_data.xml index 072342afc87..338fa149f0d 100644 --- a/addons/purchase/data/mail_template_data.xml +++ b/addons/purchase/data/mail_template_data.xml @@ -5,7 +5,7 @@ RFQ - Send by Email - ${(object.create_uid.email and '"%s" <%s>' % (object.create_uid.name, object.create_uid.email) or '')|safe} + ${(object.sudo().create_uid.email and '"%s" <%s>' % (object.sudo().create_uid.name, object.sudo().create_uid.email) or '')|safe} ${object.company_id.name} Order (Ref ${object.name or 'n/a' }) ${object.partner_id.id} @@ -32,11 +32,11 @@ amounting in ${format_amount(object.amount_total, object.currency_id)} -

Do not hesitate to contact us, further you have any question.

+

Do not hesitate to contact us if you have any further question.

Best regards,

-% if object.user_id and object.user_id.signature: - ${object.user_id.signature | safe} +% if object.sudo().create_uid and object.sudo().create_uid.signature: + ${object.sudo().create_uid.signature | safe} % endif

]]>
@@ -46,7 +46,7 @@ from ${object.company_id.name}. Purchase Order - Send by Email - ${(object.create_uid.email and '"%s" <%s>' % (object.create_uid.name, object.create_uid.email) or '')|safe} + ${(object.sudo().create_uid.email and '"%s" <%s>' % (object.sudo().create_uid.name, object.sudo().create_uid.email) or '')|safe} ${object.company_id.name} Order (Ref ${object.name or 'n/a' }) ${object.partner_id.id} @@ -74,6 +74,11 @@ from ${object.company_id.name}.

You can reply to this email if you have any questions.

Thank you,

+

+% if object.sudo().create_uid and object.sudo().create_uid.signature: + ${object.sudo().create_uid.signature | safe} +% endif +

]]>
diff --git a/addons/purchase/i18n/purchase.pot b/addons/purchase/i18n/purchase.pot index be6ed395606..5e3bae24754 100644 --- a/addons/purchase/i18n/purchase.pot +++ b/addons/purchase/i18n/purchase.pot @@ -36,11 +36,11 @@ msgid "\n" "from ${object.company_id.name}.\n" "

\n" "\n" -"

Do not hesitate to contact us, further you have any question.

\n" +"

Do not hesitate to contact us if you have any further question.

\n" "

Best regards,

\n" "

\n" -"% if object.user_id and object.user_id.signature:\n" -" ${object.user_id.signature | safe}\n" +"% if object.sudo().create_uid and object.sudo().create_uid.signature:\n" +" ${object.sudo().create_uid.signature | safe}\n" "% endif\n" "

\n" "" @@ -68,6 +68,11 @@ msgid "\n" "\n" "

You can reply to this email if you have any questions.

\n" "

Thank you,

\n" +"

\n" +"% if object.sudo().create_uid and object.sudo().create_uid.signature:\n" +" ${object.sudo().create_uid.signature | safe}\n" +"% endif\n" +"

\n" "" msgstr "" diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index d6916d05af4..a3c5dea9e18 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -484,6 +484,16 @@ class PurchaseOrder(models.Model): 'currency_id': currency.id, 'delay': 0, } + # In case the order partner is a contact address, a new supplierinfo is created on + # the parent company. In this case, we keep the product name and code. + seller = line.product_id._select_seller( + partner_id=line.partner_id, + quantity=line.product_qty, + date=line.order_id.date_order and line.order_id.date_order[:10], + uom_id=line.product_uom) + if seller: + supplierinfo['product_name'] = seller.product_name + supplierinfo['product_code'] = seller.product_code vals = { 'seller_ids': [(0, 0, supplierinfo)], } diff --git a/addons/sale_mrp/models/sale_mrp.py b/addons/sale_mrp/models/sale_mrp.py index 38a1e36b9c3..9a5ba682392 100644 --- a/addons/sale_mrp/models/sale_mrp.py +++ b/addons/sale_mrp/models/sale_mrp.py @@ -16,7 +16,7 @@ class SaleOrderLine(models.Model): if line.qty_delivered_method == 'stock_move': # In the case of a kit, we need to check if all components are shipped. Since the BOM might # have changed, we don't compute the quantities but verify the move state. - bom = self.env['mrp.bom']._bom_find(product=line.product_id) + bom = self.env['mrp.bom']._bom_find(product=line.product_id, company_id=line.company_id.id) if bom and bom.type == 'phantom': moves = line.move_ids.filtered(lambda m: m.picking_id and m.picking_id.state != 'cancel') bom_delivered = all([move.state == 'done' for move in moves]) diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py index 38d4203c391..471fcc48da2 100644 --- a/addons/stock/models/stock_location.py +++ b/addons/stock/models/stock_location.py @@ -167,6 +167,11 @@ class Route(models.Model): categ_ids = fields.Many2many('product.category', 'stock_location_route_categ', 'route_id', 'categ_id', 'Product Categories') warehouse_ids = fields.Many2many('stock.warehouse', 'stock_route_warehouse', 'route_id', 'warehouse_id', 'Warehouses') + @api.onchange('warehouse_selectable') + def _onchange_warehouse_selectable(self): + if not self.warehouse_selectable: + self.warehouse_ids = [] + def write(self, values): '''when a route is deactivated, deactivate also its pull and push rules''' res = super(Route, self).write(values) diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 43a010e3604..2b440cd716c 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -141,7 +141,7 @@ class StockMoveLine(models.Model): """ res = {} if self.qty_done and self.product_id.tracking == 'serial': - if float_compare(self.qty_done, 1.0, precision_rounding=self.move_id.product_id.uom_id.rounding) != 0: + if float_compare(self.qty_done, 1.0, precision_rounding=self.product_id.uom_id.rounding) != 0: message = _('You can only process 1.0 %s of products with unique serial number.') % self.product_id.uom_id.name res['warning'] = {'title': _('Warning'), 'message': message} return res diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index c8341ee3102..5481939602f 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -247,7 +247,8 @@ class Picking(models.Model): picking_type_id = fields.Many2one( 'stock.picking.type', 'Operation Type', required=True, - states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}) + readonly=True, + states={'draft': [('readonly', False)]}) picking_type_code = fields.Selection([ ('incoming', 'Vendors'), ('outgoing', 'Customers'), diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py index 88c21b7772a..ef116e1424b 100644 --- a/addons/stock/models/stock_warehouse.py +++ b/addons/stock/models/stock_warehouse.py @@ -467,6 +467,7 @@ class Warehouse(models.Model): 'name': self._format_routename(route_type=route_type), 'product_categ_selectable': True, 'product_selectable': False, + 'warehouse_selectable': True, 'company_id': self.company_id.id, 'sequence': 10, } diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js index a92951dce9b..aeada42de55 100644 --- a/addons/web/static/src/js/views/calendar/calendar_renderer.js +++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js @@ -359,6 +359,11 @@ return AbstractRenderer.extend({ this.$calendar = this.$(".o_calendar_widget"); + // This seems like a workaround but apparently passing the locale + // in the options is not enough. We should initialize it beforehand + var locale = moment.locale(); + $.fullCalendar.locale(locale); + //Documentation here : http://arshaw.com/fullcalendar/docs/ var fc_options = $.extend({}, this.state.fc_options, { eventDrop: function (event) { @@ -410,6 +415,7 @@ return AbstractRenderer.extend({ }, height: 'parent', unselectAuto: false, + locale: locale, // reset locale when fullcalendar has already been instanciated before now }); this.$calendar.fullCalendar(fc_options); diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 5e1f07dfd53..cf717027ebe 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -7,6 +7,7 @@ var Dialog = require('web.Dialog'); var fieldUtils = require('web.field_utils'); var mixins = require('web.mixins'); var testUtils = require('web.test_utils'); +var time = require('web.time'); var session = require('web.session'); var createActionManager = testUtils.createActionManager; @@ -2275,6 +2276,53 @@ QUnit.module('Views', { calendar.destroy(); }); + QUnit.test('fullcalendar initializes with right locale', function (assert) { + assert.expect(1); + + // Do not forward port this part in versions >= saas11.3 + testUtils.patch(time, { + getLangDateFormat: function () { + return 'DD/MM'; + }, + }); + + var initialLocale = moment.locale(); + // This will set the locale to zz + moment.defineLocale('zz', { + longDateFormat: { + L: 'DD/MM/YYYY' + }, + weekdaysShort: ["zz1.", "zz2.", "zz3.", "zz4.", "zz5.", "zz6.", "zz7."], + }); + + var calendar = createView({ + View: CalendarView, + model: 'event', + data: this.data, + arch: ' '+ + ''+ + '', + archs: archs, + viewOptions: { + initialDate: initialDate, + action: {views: [{viewID: 1, type: 'kanban'}, {viewID: 43, type: 'form'}]} + }, + + }); + + assert.strictEqual(calendar.$('.fc-day-header:first').text(), "zz1. 11/12", + 'The day should be in the given locale specific format'); + + moment.locale(initialLocale); + // Do not forward port this part in versions >= saas11.3 + testUtils.unpatch(time); + + calendar.destroy(); + }); + }); }); diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index 2ec8506321c..6599000d245 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -663,6 +663,10 @@ var SnippetsMenu = Widget.extend({ class: 'oe_drop_zone oe_insert', }); + function isFullWidth($elem) { + return $elem.parent().width() === $elem.outerWidth(true); + } + if ($selectorChildren) { $selectorChildren.each(function () { var $zone = $(this); @@ -680,7 +684,10 @@ var SnippetsMenu = Widget.extend({ display: 'inline-block', }); } else if (float === 'left' || float === 'right') { - $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().last().outerHeight()), 30)); + $drop.css('float', float); + if (!isFullWidth($zone)) { + $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().last().outerHeight()), 30)); + } } $drop = $drop.clone(); @@ -695,7 +702,10 @@ var SnippetsMenu = Widget.extend({ display: 'inline-block' }); } else if (float === 'left' || float === 'right') { - $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().first().outerHeight()), 30)); + $drop.css('float', float); + if (!isFullWidth($zone)) { + $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().first().outerHeight()), 30)); + } } if (test) { $drop.css({'float': 'none', 'display': 'inline-block'}); @@ -716,14 +726,20 @@ var SnippetsMenu = Widget.extend({ if ($zone.prev('.oe_drop_zone:visible').length === 0) { $drop = zone_template.clone(); if (float === 'left' || float === 'right') { - $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.prev().outerHeight() || Infinity), 30)); + $drop.css('float', float); + if (!isFullWidth($zone)) { + $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.prev().outerHeight() || Infinity), 30)); + } } $zone.before($drop); } if ($zone.next('.oe_drop_zone:visible').length === 0) { $drop = zone_template.clone(); if (float === 'left' || float === 'right') { - $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.next().outerHeight() || Infinity), 30)); + $drop.css('float', float); + if (!isFullWidth($zone)) { + $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.next().outerHeight() || Infinity), 30)); + } } $zone.after($drop); } diff --git a/addons/web_editor/static/src/less/web_editor.ui.less b/addons/web_editor/static/src/less/web_editor.ui.less index 43f218a4294..a7fb8b68c00 100644 --- a/addons/web_editor/static/src/less/web_editor.ui.less +++ b/addons/web_editor/static/src/less/web_editor.ui.less @@ -274,12 +274,12 @@ html[lang] > body.editor_enable [data-oe-translation-state] { } &.oe_vertical { - border: 2px dashed @odoo-brand-primary; + border: @odoo-dropzone-border; border-left: none; border-right: none; width: @odoo-dropzone-size; - float: left; + height: 100%; margin: 0 -@odoo-dropzone-size/2; &:after { diff --git a/doc/cla/individual/carelvd.md b/doc/cla/individual/carelvd.md new file mode 100644 index 00000000000..e4570a18845 --- /dev/null +++ b/doc/cla/individual/carelvd.md @@ -0,0 +1,11 @@ +South Africa, 05/02/2019 + +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, + +Carel van Dam carelvdam@gmail.com https://github.com/carelvd