diff --git a/addons/account/static/src/js/reconciliation/reconciliation_action.js b/addons/account/static/src/js/reconciliation/reconciliation_action.js index 1cd47c70a9c..bd3e17fd4cd 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_action.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_action.js @@ -305,9 +305,12 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, { 'context': self.model.getContext(), }); _.each(result.handles, function (handle) { - self._getWidget(handle).destroy(); - var index = _.findIndex(self.widgets, function (widget) {return widget.handle===handle;}); - self.widgets.splice(index, 1); + var widget = self._getWidget(handle); + if (widget) { + widget.destroy(); + var index = _.findIndex(self.widgets, function (widget) {return widget.handle===handle;}); + self.widgets.splice(index, 1); + } }); // Get number of widget and if less than constant and if there are more to laod, load until constant if (self.widgets.length < self.model.defaultDisplayQty diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 52a81fc8723..e3e2bc99d2f 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -207,6 +207,25 @@ class HrExpense(models.Model): # Business # ---------------------------------------- + @api.multi + def _prepare_move_values(self): + """ + This function prepares move values related to an expense + """ + self.ensure_one() + journal = self.sheet_id.bank_journal_id if self.payment_mode == 'company_account' else self.sheet_id.journal_id + account_date = self.sheet_id.accounting_date or self.date + move_values = { + 'journal_id': journal.id, + 'company_id': self.env.user.company_id.id, + 'date': account_date, + 'ref': self.sheet_id.name, + # force the name to the default value, to avoid an eventual 'default_name' in the context + # to set it to '' which cause no number to be given to the account.move when posted. + 'name': '/', + } + return move_values + @api.multi def _get_account_move_by_sheet(self): """ Return a mapping between the expense sheet of current expense and its account move @@ -215,18 +234,8 @@ class HrExpense(models.Model): move_grouped_by_sheet = {} for expense in self: # create the move that will contain the accounting entries - account_date = expense.sheet_id.accounting_date or expense.date if expense.sheet_id.id not in move_grouped_by_sheet: - journal = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id - move = self.env['account.move'].create({ - 'journal_id': journal.id, - 'company_id': self.env.user.company_id.id, - 'date': account_date, - 'ref': expense.sheet_id.name, - # force the name to the default value, to avoid an eventual 'default_name' in the context - # to set it to '' which cause no number to be given to the account.move when posted. - 'name': '/', - }) + move = self.env['account.move'].create(expense._prepare_move_values()) move_grouped_by_sheet[expense.sheet_id.id] = move else: move = move_grouped_by_sheet[expense.sheet_id.id] diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py index fd69b44d222..92e4c2c4463 100644 --- a/addons/l10n_fr_fec/wizard/account_fr_fec.py +++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py @@ -305,7 +305,7 @@ class AccountFrFec(models.TransientModel): END AS PieceRef, TO_CHAR(am.date, 'YYYYMMDD') AS PieceDate, - CASE WHEN aml.name IS NULL THEN '/' ELSE replace(replace(aml.name, '|', '/'), '\t', '') END AS EcritureLib, + CASE WHEN aml.name IS NULL OR aml.name = '' THEN '/' ELSE replace(replace(aml.name, '|', '/'), '\t', '') END AS EcritureLib, replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '000000000000000D99') END, '.', ',') AS Debit, replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '000000000000000D99') END, '.', ',') AS Credit, CASE WHEN rec.name IS NULL THEN '' ELSE rec.name END AS EcritureLet, @@ -388,7 +388,7 @@ class AccountFrFec(models.TransientModel): rows_length = len(rows) for i, row in enumerate(rows): if not i == rows_length - 1: - row.append(lineterminator) + row[-1] += lineterminator writer.writerow(row) fecvalue = fecfile.getvalue() diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index 0413c45b9eb..70ee621d1e4 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -897,7 +897,7 @@ class PaymentTransaction(models.Model): # custom create custom_method_name = '%s_create' % acquirer.provider - if hasattr(acquirer, custom_method_name): + if hasattr(self, custom_method_name): values.update(getattr(self, custom_method_name)(values)) if not values.get('reference'): diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py index 79d0a7589c5..922d9df1bf1 100644 --- a/addons/payment_authorize/models/authorize_request.py +++ b/addons/payment_authorize/models/authorize_request.py @@ -226,6 +226,12 @@ class AuthorizeAPI(): etree.SubElement(customer, "email").text = partner.email or '' response = self._authorize_request(root) res = dict() + if not response.find('customerProfileId'): + _logger.warning( + 'Unable to create customer payment profile, data missing from transaction. Transaction_id: %s - Partner_id: %s' + % (transaction_id, partner) + ) + return res res['profile_id'] = response.find('customerProfileId').text res['payment_profile_id'] = response.find('customerPaymentProfileIdList/numericString').text root_profile = self._base_tree('getCustomerPaymentProfileRequest') diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py index 5a6ed0bf8e1..946f031484d 100644 --- a/addons/payment_authorize/models/payment.py +++ b/addons/payment_authorize/models/payment.py @@ -228,17 +228,18 @@ class TxAuthorize(models.Model): (self.type == 'form_save' or self.acquirer_id.save_token == 'always'): transaction = AuthorizeAPI(self.acquirer_id) res = transaction.create_customer_profile_from_tx(self.partner_id, self.acquirer_reference) - token_id = self.env['payment.token'].create({ - 'authorize_profile': res.get('profile_id'), - 'name': res.get('name'), - 'acquirer_ref': res.get('payment_profile_id'), - 'acquirer_id': self.acquirer_id.id, - 'partner_id': self.partner_id.id, - }) - self.payment_token_id = token_id + if res: + token_id = self.env['payment.token'].create({ + 'authorize_profile': res.get('profile_id'), + 'name': res.get('name'), + 'acquirer_ref': res.get('payment_profile_id'), + 'acquirer_id': self.acquirer_id.id, + 'partner_id': self.partner_id.id, + }) + self.payment_token_id = token_id - if self.payment_token_id: - self.payment_token_id.verified = True + if self.payment_token_id: + self.payment_token_id.verified = True return True elif status_code == self._authorize_pending_tx_status: self.write({'acquirer_reference': data.get('x_trans_id')}) diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index be9993ef2ec..ce87fa885a0 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -867,7 +867,7 @@ class PosOrder(models.Model): # a serialnumber always has a quantity of 1 product, a lot number takes the full quantity of the order line qty = 1.0 if stock_production_lot.product_id.tracking == 'lot': - qty = pos_pack_lot.pos_order_line_id.qty + qty = abs(pos_pack_lot.pos_order_line_id.qty) qty_done += qty pack_lots.append({'lot_id': stock_production_lot.id, 'qty': qty}) else: diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 8ad90c368d0..caf09ced6c9 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1473,7 +1473,7 @@ exports.Orderline = Backbone.Model.extend({ var lots_required = 1; if (this.product.tracking == 'serial') { - lots_required = this.quantity; + lots_required = Math.abs(this.quantity); } return lots_required; @@ -1878,8 +1878,11 @@ var PacklotlineCollection = Backbone.Collection.extend({ set_quantity_by_lot: function() { if (this.order_line.product.tracking == 'serial') { - var valid_lots = this.get_valid_lots(); - this.order_line.set_quantity(valid_lots.length); + var valid_lots_quantity = this.get_valid_lots().length; + if (this.order_line.quantity < 0){ + valid_lots_quantity = -valid_lots_quantity; + } + this.order_line.set_quantity(valid_lots_quantity); } } }); diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py index b1f861c041c..64e4c03ecbd 100644 --- a/addons/purchase_stock/models/purchase.py +++ b/addons/purchase_stock/models/purchase.py @@ -312,7 +312,9 @@ class PurchaseOrderLine(models.Model): for move in self.move_ids.filtered(lambda x: x.state != 'cancel' and not x.location_dest_id.usage == "supplier"): qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') template = { - 'name': self.name or '', + # truncate to 2000 to avoid triggering index limit error + # TODO: remove index in master? + 'name': (self.name or '')[:2000], 'product_id': self.product_id.id, 'product_uom': self.product_uom.id, 'date': self.order_id.date_order, diff --git a/addons/sale/models/account_invoice.py b/addons/sale/models/account_invoice.py index 9d487a64415..9ec43653015 100644 --- a/addons/sale/models/account_invoice.py +++ b/addons/sale/models/account_invoice.py @@ -49,7 +49,7 @@ class AccountInvoice(models.Model): inv_type = self.type or self.env.context.get('type', 'out_invoice') if inv_type == 'out_invoice': company = self.company_id or self.env.user.company_id - self.comment = company.with_context(lang=self.partner_id.lang).sale_note + self.comment = company.with_context(lang=self.partner_id.lang).sale_note or (self._origin.company_id == company and self.comment) @api.multi def action_invoice_open(self): diff --git a/addons/sms/models/mail_thread.py b/addons/sms/models/mail_thread.py index 1fb62a72de4..a439487e4e9 100644 --- a/addons/sms/models/mail_thread.py +++ b/addons/sms/models/mail_thread.py @@ -39,8 +39,8 @@ class MailThread(models.AbstractModel): if not partners: partners = self._get_default_sms_recipients() - # Collect numbers, we will consider the message to be sent if at least one number can be found - numbers = list(set([i.mobile for i in partners if i.mobile])) + # Collect numbers, we will consider the message to be sent if at least one number can be found + numbers = list(set([i.mobile for i in partners if i.mobile])) if numbers: try: diff --git a/addons/sms/wizard/send_sms.py b/addons/sms/wizard/send_sms.py index 448d9fcc626..986fd1c9239 100644 --- a/addons/sms/wizard/send_sms.py +++ b/addons/sms/wizard/send_sms.py @@ -45,8 +45,8 @@ class SendSMS(models.TransientModel): return number if not phonenumbers.is_possible_number(phone_nbr) or not phonenumbers.is_valid_number(phone_nbr): return number - phone_fmt = phonenumbers.PhoneNumberFormat.INTERNATIONAL - return phonenumbers.format_number(phone_nbr, phone_fmt).replace(' ', '') + phone_fmt = phonenumbers.PhoneNumberFormat.E164 + return phonenumbers.format_number(phone_nbr, phone_fmt) else: return number @@ -62,12 +62,11 @@ class SendSMS(models.TransientModel): @api.model def default_get(self, fields): result = super(SendSMS, self).default_get(fields) - active_model = self.env.context.get('active_model') - model = self.env[active_model] - records = self._get_records(model) - if getattr(records, '_get_default_sms_recipients'): + if not self.env.context.get('default_recipients') and active_model and hasattr(self.env[active_model], '_get_default_sms_recipients'): + model = self.env[active_model] + records = self._get_records(model) partners = records._get_default_sms_recipients() phone_numbers = [] no_phone_partners = [] @@ -84,12 +83,12 @@ class SendSMS(models.TransientModel): return result def action_send_sms(self): - numbers = self.recipients.split(',') + numbers = [number.strip() for number in self.recipients.split(',') if number.strip()] active_model = self.env.context.get('active_model') - model = self.env[active_model] - records = self._get_records(model) - if getattr(records, 'message_post_send_sms'): + if active_model and hasattr(self.env[active_model], 'message_post_send_sms'): + model = self.env[active_model] + records = self._get_records(model) records.message_post_send_sms(self.message, numbers=numbers) else: self.env['sms.api']._send_sms(numbers, self.message) diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index 0dd607ce3b6..5f583ec0682 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -458,6 +458,10 @@ class ProductTemplate(models.Model): def _is_cost_method_standard(self): return True + @api.depends( + 'product_variant_ids', + 'product_variant_ids.stock_quant_ids', + ) def _compute_quantities(self): res = self._compute_quantities_dict() for template in self: diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index c242d7777db..0d0fb1a68cd 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -462,6 +462,7 @@ class StockMove(models.Model): else: view = self.env.ref('stock.view_stock_move_nosuggest_operations') + picking_type_id = self.picking_type_id or self.picking_id.picking_type_id return { 'name': _('Detailed Operations'), 'type': 'ir.actions.act_window', @@ -474,8 +475,8 @@ class StockMove(models.Model): 'res_id': self.id, 'context': dict( self.env.context, - show_lots_m2o=self.has_tracking != 'none' and (self.picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), # able to create lots, whatever the value of ` use_create_lots`. - show_lots_text=self.has_tracking != 'none' and self.picking_type_id.use_create_lots and not self.picking_type_id.use_existing_lots and self.state != 'done' and not self.origin_returned_move_id.id, + show_lots_m2o=self.has_tracking != 'none' and (picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), # able to create lots, whatever the value of ` use_create_lots`. + show_lots_text=self.has_tracking != 'none' and picking_type_id.use_create_lots and not picking_type_id.use_existing_lots and self.state != 'done' and not self.origin_returned_move_id.id, show_source_location=self.location_id.child_ids and self.picking_type_id.code != 'incoming', show_destination_location=self.location_dest_id.child_ids and self.picking_type_id.code != 'outgoing', show_package=not self.location_id.usage == 'supplier', diff --git a/addons/stock/wizard/stock_picking_return_views.xml b/addons/stock/wizard/stock_picking_return_views.xml index 3bbba44a176..37b8af9a1e9 100644 --- a/addons/stock/wizard/stock_picking_return_views.xml +++ b/addons/stock/wizard/stock_picking_return_views.xml @@ -27,7 +27,7 @@ - +