diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 1856c526d0f..96ade1cb3d9 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -520,6 +520,12 @@ class AccountJournal(models.Model): res += [(journal.id, name)] return res + @api.model + def name_search(self, name='', args=None, operator='ilike', limit=100): + args = args or [] + recs = self.search(['|', ('code', operator, name), ('name', operator, name)] + args, limit=limit) + return recs.name_get() + @api.multi @api.depends('company_id') def _belong_to_company(self): diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 26f09500b76..6c728362378 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -410,8 +410,6 @@ class AccountBankStatementLine(models.Model): for line in self: if line.journal_entry_ids.ids: raise UserError(_('In order to delete a bank statement line, you must first cancel it to delete related journal items.')) - if line.move_name: - raise UserError(_('It is not allowed to delete a bank statement line that already created a journal entry since it would create a gap in the numbering. You should create the journal entry again and cancel it thanks to a regular revert.')) return super(AccountBankStatementLine, self).unlink() @api.multi diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 8c0d227e02d..e8c0041068e 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -486,6 +486,8 @@ class AccountInvoice(models.Model): payment_term_id = False fiscal_position = False bank_id = False + warning = {} + domain = {} company_id = self.company_id.id p = self.partner_id if not company_id else self.partner_id.with_context(force_company=company_id) type = self.type @@ -520,7 +522,6 @@ class AccountInvoice(models.Model): } if p.invoice_warn == 'block': self.partner_id = False - return {'warning': warning} self.account_id = account_id self.payment_term_id = payment_term_id @@ -530,8 +531,14 @@ class AccountInvoice(models.Model): bank_ids = p.commercial_partner_id.bank_ids bank_id = bank_ids[0].id if bank_ids else False self.partner_bank_id = bank_id - return {'domain': {'partner_bank_id': [('id', 'in', bank_ids.ids)]}} - return {} + domain = {'partner_bank_id': [('id', 'in', bank_ids.ids)]} + + res = {} + if warning: + res['warning'] = warning + if domain: + res['domain'] = domain + return res @api.multi def get_delivery_partner_id(self): @@ -1029,6 +1036,20 @@ class AccountInvoice(models.Model): result.append((0, 0, values)) return result + def _get_refund_common_fields(self): + return ['partner_id', 'payment_term_id', 'account_id', 'currency_id', 'journal_id'] + + def _get_refund_prepare_fields(self): + return ['name', 'reference', 'comment', 'date_due'] + + def _get_refund_modify_read_fields(self): + read_fields = ['type', 'number', 'invoice_line_ids', 'tax_line_ids', 'date'] + return self._get_refund_common_fields() + self._get_refund_prepare_fields() + read_fields + + def _get_refund_copy_fields(self): + copy_fields = ['company_id', 'user_id', 'fiscal_position_id'] + return self._get_refund_common_fields() + self._get_refund_prepare_fields() + copy_fields + @api.model def _get_refund_common_fields(self): return ['partner_id', 'payment_term_id', 'account_id', 'currency_id', 'journal_id'] @@ -1397,6 +1418,12 @@ class AccountInvoiceLine(models.Model): """ pass + @api.multi + def unlink(self): + if self.filtered(lambda r: r.invoice_id and r.invoice_id.state != 'draft'): + raise UserError(_('You can only delete an invoice line if the invoice is in draft state.')) + return super(AccountInvoiceLine, self).unlink() + class AccountInvoiceTax(models.Model): _name = "account.invoice.tax" _description = "Invoice Tax" diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 7aad4d9a87e..dc5e0067791 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -228,8 +228,8 @@ class AccountMove(models.Model): 'date': date, 'journal_id': journal_id.id if journal_id else self.journal_id.id, 'ref': _('reversal of: ') + self.name}) - for acm_line in reversed_move.line_ids: - acm_line.with_context(check_move_validity=False).write({ + for acm_line in reversed_move.line_ids.with_context(check_move_validity=False): + acm_line.write({ 'debit': acm_line.credit, 'credit': acm_line.debit, 'amount_currency': -acm_line.amount_currency diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index 81d7d07a0de..f4ee2a4b4f1 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -52,7 +52,7 @@ class account_abstract_payment(models.AbstractModel): @api.constrains('amount') def _check_amount(self): if not self.amount > 0.0: - raise ValidationError('The payment amount must be strictly positive.') + raise ValidationError(_('The payment amount must be strictly positive.')) @api.one @api.depends('payment_type', 'journal_id') @@ -434,8 +434,16 @@ class account_payment(models.Model): amount_wo = total_payment_company_signed - total_residual_company_signed else: amount_wo = total_residual_company_signed - total_payment_company_signed - debit_wo = amount_wo > 0 and amount_wo or 0.0 - credit_wo = amount_wo < 0 and -amount_wo or 0.0 + # Align the sign of the secondary currency writeoff amount with the sign of the writeoff + # amount in the company currency + if amount_wo > 0: + debit_wo = amount_wo + credit_wo = 0.0 + amount_currency_wo = abs(amount_currency_wo) + else: + debit_wo = 0.0 + credit_wo = -amount_wo + amount_currency_wo = -abs(amount_currency_wo) writeoff_line['name'] = self.writeoff_label writeoff_line['account_id'] = self.writeoff_account_id.id writeoff_line['debit'] = debit_wo diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml index 2bcddda579c..619476fb240 100644 --- a/addons/account/views/account_invoice_view.xml +++ b/addons/account/views/account_invoice_view.xml @@ -322,7 +322,7 @@ - + diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index 6ce58693f2d..eee2bd92660 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -171,7 +171,7 @@ id="account.action_all_partner_invoices" name="All Invoices" res_model="account.invoice" - domain="[('partner_id', '=', active_id)]" + domain="[('partner_id', 'child_of', active_id)]" src_model="res.partner"/> account.journal.tree account.journal + 1 diff --git a/addons/account/wizard/account_invoice_refund.py b/addons/account/wizard/account_invoice_refund.py index 4923ef95040..f0840aa7856 100644 --- a/addons/account/wizard/account_invoice_refund.py +++ b/addons/account/wizard/account_invoice_refund.py @@ -95,7 +95,10 @@ class AccountInvoiceRefund(models.TransientModel): 'fiscal_position_id': inv.fiscal_position_id.id, }) for field in inv_obj._get_refund_common_fields(): - invoice[field] = invoice[field] and invoice[field][0] + if inv_obj._fields[field].type == 'many2one': + invoice[field] = invoice[field] and invoice[field][0] + else: + invoice[field] = invoice[field] or False inv_refund = inv_obj.create(invoice) if inv_refund.payment_term_id.id: inv_refund._onchange_payment_term_date_invoice() diff --git a/addons/account_asset/static/tests/account_asset_tests.js b/addons/account_asset/static/tests/account_asset_tests.js index eab7f5f5d99..8f353f3bf5c 100644 --- a/addons/account_asset/static/tests/account_asset_tests.js +++ b/addons/account_asset/static/tests/account_asset_tests.js @@ -147,7 +147,7 @@ QUnit.test('click events are correctly triggered', function (assert) { execute_action: function (event) { var data = event.data; assert.strictEqual(data.model, 'line', "should have correct model"); - assert.strictEqual(data.record_id, 4, "should have correct res_id"); + assert.deepEqual(data.res_ids, [4], "should have correct res_ids"); assert.strictEqual(data.action_data.name, 'create_move', "should call correct method"); }, diff --git a/addons/account_bank_statement_import/account_bank_statement_import.py b/addons/account_bank_statement_import/account_bank_statement_import.py index 2d45a95612d..07ab4c7dc6e 100644 --- a/addons/account_bank_statement_import/account_bank_statement_import.py +++ b/addons/account_bank_statement_import/account_bank_statement_import.py @@ -43,6 +43,8 @@ class AccountBankStatementImport(models.TransientModel): if not journal: # The active_id is passed in context so the wizard can call import_file again once the journal is created return self.with_context(active_id=self.ids[0])._journal_creation_wizard(currency, account_number) + if not journal.default_debit_account_id or not journal.default_credit_account_id: + raise UserError(_('You have to set a Default Debit Account and a Default Credit Account for the journal: %s') % (journal.name,)) # Prepare statement data to be used for bank statements creation stmts_vals = self._complete_stmts_vals(stmts_vals, journal, account_number) # Create the bank statements @@ -220,6 +222,9 @@ class AccountBankStatementImport(models.TransientModel): filtered_st_lines.append(line_vals) else: ignored_statement_lines_import_ids.append(line_vals['unique_import_id']) + if 'balance_start' in st_vals: + st_vals['balance_start'] += line_vals['amount'] + if len(filtered_st_lines) > 0: # Remove values that won't be used to create records st_vals.pop('transactions', None) diff --git a/addons/account_bank_statement_import/i18n/account_bank_statement_import.pot b/addons/account_bank_statement_import/i18n/account_bank_statement_import.pot index 43cc6e38d59..32c6952c711 100644 --- a/addons/account_bank_statement_import/i18n/account_bank_statement_import.pot +++ b/addons/account_bank_statement_import/i18n/account_bank_statement_import.pot @@ -468,6 +468,12 @@ msgstr "" msgid "You have already imported that file." msgstr "" +#. module: account_bank_statement_import +#: code:addons/account_bank_statement_import/account_bank_statement_import.py:47 +#, python-format +msgid "You have to set a Default Debit Account and a Default Credit Account for the journal: %s" +msgstr "" + #. module: account_bank_statement_import #: model:ir.ui.view,arch_db:account_bank_statement_import.account_bank_statement_import_view msgid "_Import" diff --git a/addons/account_check_printing/models/account_payment.py b/addons/account_check_printing/models/account_payment.py index 13b9e39cb22..ef33dc05ae4 100644 --- a/addons/account_check_printing/models/account_payment.py +++ b/addons/account_check_printing/models/account_payment.py @@ -29,13 +29,7 @@ class AccountRegisterPayments(models.TransientModel): def _onchange_amount(self): if hasattr(super(AccountRegisterPayments, self), '_onchange_amount'): super(AccountRegisterPayments, self)._onchange_amount() - # TODO: merge, refactor and complete the amount_to_text and amount_to_text_en classes - check_amount_in_words = amount_to_text_en.amount_to_text(math.floor(self.amount), lang='en', currency='') - check_amount_in_words = check_amount_in_words.replace(' and Zero Cent', '') # Ugh - decimals = self.amount % 1 - if decimals >= 10**-2: - check_amount_in_words += _(' and %s/100') % str(int(round(float_round(decimals*100, precision_rounding=1)))) - self.check_amount_in_words = check_amount_in_words + self.check_amount_in_words = self.env['account.payment']._get_check_amount_in_words(self.amount) def get_payment_vals(self): res = super(AccountRegisterPayments, self).get_payment_vals() @@ -56,6 +50,15 @@ class AccountPayment(models.Model): help="The selected journal is configured to print check numbers. If your pre-printed check paper already has numbers " "or if the current numbering is wrong, you can change it in the journal configuration page.") + def _get_check_amount_in_words(self, amount): + # TODO: merge, refactor and complete the amount_to_text and amount_to_text_en classes + check_amount_in_words = amount_to_text_en.amount_to_text(math.floor(amount), lang='en', currency='') + check_amount_in_words = check_amount_in_words.replace(' and Zero Cent', '') # Ugh + decimals = amount % 1 + if decimals >= 10**-2: + check_amount_in_words += _(' and %s/100') % str(int(round(float_round(decimals*100, precision_rounding=1)))) + return check_amount_in_words + @api.onchange('journal_id') def _onchange_journal_id(self): if hasattr(super(AccountPayment, self), '_onchange_journal_id'): @@ -67,12 +70,7 @@ class AccountPayment(models.Model): def _onchange_amount(self): if hasattr(super(AccountPayment, self), '_onchange_amount'): super(AccountPayment, self)._onchange_amount() - check_amount_in_words = amount_to_text_en.amount_to_text(math.floor(self.amount), lang='en', currency='') - check_amount_in_words = check_amount_in_words.replace(' and Zero Cent', '') # Ugh - decimals = self.amount % 1 - if decimals >= 10**-2: - check_amount_in_words += _(' and %s/100') % str(int(round(float_round(decimals*100, precision_rounding=1)))) - self.check_amount_in_words = check_amount_in_words + self.check_amount_in_words = self._get_check_amount_in_words(self.amount) def _check_communication(self, payment_method_id, communication): super(AccountPayment, self)._check_communication(payment_method_id, communication) diff --git a/addons/analytic/views/analytic_account_views.xml b/addons/analytic/views/analytic_account_views.xml index 83ef8ae2347..512b304ccfd 100644 --- a/addons/analytic/views/analytic_account_views.xml +++ b/addons/analytic/views/analytic_account_views.xml @@ -51,7 +51,7 @@ - {'search_default_group_date': 1, 'search_default_group_journal': 1, 'default_account_id': active_id} + {'search_default_group_date': 1, 'default_account_id': active_id} [('account_id','=', active_id)] Costs & Revenues account.analytic.line diff --git a/addons/barcodes/models/barcodes.py b/addons/barcodes/models/barcodes.py index 08482581bb5..6c3d228895e 100644 --- a/addons/barcodes/models/barcodes.py +++ b/addons/barcodes/models/barcodes.py @@ -44,8 +44,8 @@ class BarcodeNomenclature(models.Model): if len(code) != 8: return -1 - sum1 = ean[1] + ean[3] + ean[5] - sum2 = ean[0] + ean[2] + ean[4] + ean[6] + sum1 = int(ean[1]) + int(ean[3]) + int(ean[5]) + sum2 = int(ean[0]) + int(ean[2]) + int(ean[4]) + int(ean[6]) total = sum1 + 3 * sum2 return int((10 - total % 10) % 10) diff --git a/addons/barcodes/tests/__init__.py b/addons/barcodes/tests/__init__.py new file mode 100644 index 00000000000..6188e48fb99 --- /dev/null +++ b/addons/barcodes/tests/__init__.py @@ -0,0 +1 @@ +from . import test_barcode_nomenclature diff --git a/addons/barcodes/tests/test_barcode_nomenclature.py b/addons/barcodes/tests/test_barcode_nomenclature.py new file mode 100644 index 00000000000..d3ef2903d79 --- /dev/null +++ b/addons/barcodes/tests/test_barcode_nomenclature.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from odoo.tests import common + + +class TestBarcodeNomenclature(common.TransactionCase): + + def test_ean8_checksum(self): + barcode_nomenclature = self.env['barcode.nomenclature'] + ean8 = "87111125" + checksum = barcode_nomenclature.ean8_checksum(ean8) + self.assertEqual(checksum, int(ean8[-1])) + checksum = barcode_nomenclature.ean8_checksum("8711112") + self.assertEqual(checksum, -1) + checksum = barcode_nomenclature.ean8_checksum("871111256") + self.assertEqual(checksum, -1) diff --git a/addons/base_vat/models/res_partner.py b/addons/base_vat/models/res_partner.py index 8c57a0cf3c4..c83238088e2 100644 --- a/addons/base_vat/models/res_partner.py +++ b/addons/base_vat/models/res_partner.py @@ -223,10 +223,14 @@ class ResPartner(models.Model): return True # Norway VAT validation, contributed by Rolv Råen (adEgo) + # Support for MVA suffix contributed by Bringsvor Consulting AS (bringsvor@bringsvor.com) def check_vat_no(self, vat): - ''' + """ Check Norway VAT number.See http://www.brreg.no/english/coordination/number.html - ''' + """ + if len(vat) == 12 and vat.upper().endswith('MVA'): + vat = vat[:-3] # Strictly speaking we should enforce the suffix MVA but... + if len(vat) != 9: return False try: diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js index acf81039dbf..98d169c11e2 100644 --- a/addons/board/static/src/js/dashboard.js +++ b/addons/board/static/src/js/dashboard.js @@ -279,25 +279,26 @@ FormRenderer.include({ children: [] }); } + + // register actions, alongside a generated unique ID + _.each(node.children, function (column, column_index) { + _.each(column.children, function (action, action_index) { + action.attrs.id = 'action_' + column_index + '_' + action_index; + self.actionsDescr[action.attrs.id] = action.attrs; + }); + }); + var $html = $('
').append($(QWeb.render('DashBoard', {node: node}))); // render each view - _.each(node.children, function (column, column_index) { - _.each(column.children, function (action, action_index) { - var attrs = action.attrs; - var domain = Domain.prototype.stringToArray(attrs.domain, {}); - var context = new Context(attrs.context); - var actionID = _.str.toNumber(attrs.name); - self.actionsDescr[actionID] = attrs; - var $node = $html.find('.oe_action[data-id=' + actionID + '] .oe_content'); - self.defs.push(self._createController({ - $node: $node, - actionID: actionID, - context: context, - domain: domain, - viewType: attrs.view_mode, - })); - }); + _.each(this.actionsDescr, function (action) { + self.defs.push(self._createController({ + $node: $html.find('.oe_action[data-id=' + action.id + '] .oe_content'), + actionID: _.str.toNumber(action.name), + context: new Context(action.context), + domain: Domain.prototype.stringToArray(action.domain, {}), + viewType: action.view_mode, + })); }); $html.find('.oe_dashboard_column').sortable({ connectWith: '.oe_dashboard_column', @@ -341,7 +342,7 @@ FormRenderer.include({ */ _onFoldClick: function (event) { var $e = $(event.currentTarget); - var $action = $e.parents('.oe_action:first'); + var $action = $e.closest('.oe_action'); var id = $action.data('id'); var actionAttrs = this.actionsDescr[id]; diff --git a/addons/board/static/src/less/dashboard.less b/addons/board/static/src/less/dashboard.less index 72131e2fa2e..5b53c583604 100644 --- a/addons/board/static/src/less/dashboard.less +++ b/addons/board/static/src/less/dashboard.less @@ -108,6 +108,9 @@ } .oe_content { padding: 0 12px 12px 12px; + &.oe_folded { + display: none; + } } // Override border of many2manytags defined for form_views @@ -139,4 +142,5 @@ // Favorites menu in control panel .o_add_to_dashboard { display: none; // hidden by default + .o-search-options-dropdown-custom-li; } diff --git a/addons/board/static/src/xml/board.xml b/addons/board/static/src/xml/board.xml index 1c40084d13c..13453a945f6 100644 --- a/addons/board/static/src/xml/board.xml +++ b/addons/board/static/src/xml/board.xml @@ -18,7 +18,7 @@ -
+

@@ -27,8 +27,7 @@

- -
+
diff --git a/addons/board/static/tests/dashboard_tests.js b/addons/board/static/tests/dashboard_tests.js index d921cd56a67..1b55551fe2b 100644 --- a/addons/board/static/tests/dashboard_tests.js +++ b/addons/board/static/tests/dashboard_tests.js @@ -326,4 +326,55 @@ QUnit.test('can drag and drop a view', function (assert) { form.destroy(); }); +QUnit.test('twice the same action in a dashboard', function (assert) { + assert.expect(2); + + var form = createView({ + View: FormView, + model: 'board', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route) { + if (route === '/board/static/src/img/layout_1-1-1.png') { + return $.when(); + } + if (route === '/web/action/load') { + return $.when({ + res_model: 'partner', + views: [[4, 'list'],[5, 'kanban']], + }); + } + if (route === '/web/view/add_custom') { + assert.step('add custom'); + return $.when(true); + } + return this._super.apply(this, arguments); + }, + archs: { + 'partner,4,list': + '', + 'partner,5,kanban': + '' + + '
' + + '
', + }, + }); + + var $firstAction = form.$('.oe_action:contains(ABC)'); + assert.strictEqual($firstAction.find('.o_list_view').length, 1, + "list view should be displayed in 'ABC' block"); + var $secondAction = form.$('.oe_action:contains(DEF)'); + assert.strictEqual($secondAction.find('.o_kanban_view').length, 1, + "kanban view should be displayed in 'DEF' block"); + + form.destroy(); +}); + }); diff --git a/addons/calendar/models/mail_message.py b/addons/calendar/models/mail_message.py index 421b070dcb7..0dc4d24e364 100644 --- a/addons/calendar/models/mail_message.py +++ b/addons/calendar/models/mail_message.py @@ -25,7 +25,7 @@ class Message(models.Model): @api.model def _find_allowed_model_wise(self, doc_model, doc_dict): if doc_model == 'calendar.event': - order = self._context.get('order', self._order) + order = self._context.get('order', self.env[doc_model]._order) for virtual_id in self.env[doc_model].browse(doc_dict).get_recurrent_ids([], order=order): doc_dict.setdefault(virtual_id, doc_dict[get_real_ids(virtual_id)]) return super(Message, self)._find_allowed_model_wise(doc_model, doc_dict) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 8ca20f7a84b..7d55a0805e5 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -97,7 +97,7 @@ class Lead(models.Model): user_id = fields.Many2one('res.users', string='Salesperson', index=True, track_visibility='onchange', default=lambda self: self.env.user) referred = fields.Char('Referred By') - date_open = fields.Datetime('Assigned', readonly=True) + date_open = fields.Datetime('Assigned', readonly=True, default=fields.Datetime.now) day_open = fields.Float(compute='_compute_day_open', string='Days to Assign', store=True) day_close = fields.Float(compute='_compute_day_close', string='Days to Close', store=True) date_last_stage_update = fields.Datetime(string='Last Stage Update', index=True, default=fields.Datetime.now) @@ -685,7 +685,7 @@ class Lead(models.Model): email_split = tools.email_split(self.email_from) return { 'name': name, - 'user_id': self.user_id.id, + 'user_id': self.env.context.get('default_user_id') or self.user_id.id, 'comment': self.description, 'team_id': self.team_id.id, 'parent_id': parent_id, @@ -891,29 +891,30 @@ class Lead(models.Model): 'nb_opportunities': 0, } - opportunities = self.search([('type', '=', 'opportunity'), ('user_id', '=', self._uid), ('activity_date_deadline', '!=', False)]) + opportunities = self.search([('type', '=', 'opportunity'), ('user_id', '=', self._uid)]) for opp in opportunities: # Expected closing - if opp.date_deadline: - date_deadline = fields.Date.from_string(opp.date_deadline) - if date_deadline == date.today(): - result['closing']['today'] += 1 - if date.today() <= date_deadline <= date.today() + timedelta(days=7): - result['closing']['next_7_days'] += 1 - if date_deadline < date.today(): - result['closing']['overdue'] += 1 - # Next activities - for activity in opp.activity_ids: - date_deadline = fields.Date.from_string(activity.date_deadline) - if date_deadline == date.today(): - result['activity']['today'] += 1 - if date.today() <= date_deadline <= date.today() + timedelta(days=7): - result['activity']['next_7_days'] += 1 - if date_deadline < date.today(): - result['activity']['overdue'] += 1 + if opp.activity_date_deadline: + if opp.date_deadline: + date_deadline = fields.Date.from_string(opp.date_deadline) + if date_deadline == date.today(): + result['closing']['today'] += 1 + if date.today() <= date_deadline <= date.today() + timedelta(days=7): + result['closing']['next_7_days'] += 1 + if date_deadline < date.today() and not opp.date_closed: + result['closing']['overdue'] += 1 + # Next activities + for activity in opp.activity_ids: + date_deadline = fields.Date.from_string(activity.date_deadline) + if date_deadline == date.today(): + result['activity']['today'] += 1 + if date.today() <= date_deadline <= date.today() + timedelta(days=7): + result['activity']['next_7_days'] += 1 + if date_deadline < date.today(): + result['activity']['overdue'] += 1 # Won in Opportunities - if opp.date_closed: + if opp.date_closed and opp.stage_id.probability == 100: date_closed = fields.Date.from_string(opp.date_closed) if date.today().replace(day=1) <= date_closed <= date.today(): if opp.planned_revenue: diff --git a/addons/crm/models/res_config.py b/addons/crm/models/res_config.py index d83af136d50..0af455f6bdc 100644 --- a/addons/crm/models/res_config.py +++ b/addons/crm/models/res_config.py @@ -60,7 +60,10 @@ class CRMSettings(models.TransientModel): @api.onchange('default_generate_lead_from_alias') def _onchange_default_generate_lead_from_alias(self): - self.alias_prefix = 'info' if self.default_generate_lead_from_alias else False + if self.default_generate_lead_from_alias: + self.alias_prefix = self.alias_prefix or 'info' + else: + self.alias_prefix = False @api.model def get_default_alias_prefix(self, fields): diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index 4a46498c881..5f7442002a6 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -95,7 +95,9 @@ class Lead2OpportunityPartner(models.TransientModel): leads = self.env['crm.lead'].browse(vals.get('lead_ids')) for lead in leads: - partner_id = self._create_partner(lead.id, self.action, vals.get('partner_id') or lead.partner_id.id) + self_def_user = self.with_context(default_user_id=self.user_id.id) + partner_id = self_def_user._create_partner( + lead.id, self.action, vals.get('partner_id') or lead.partner_id.id) res = lead.convert_opportunity(partner_id, [], False) user_ids = vals.get('user_ids') @@ -135,9 +137,6 @@ class Lead2OpportunityPartner(models.TransientModel): leads = self.env['crm.lead'].browse(self._context.get('active_ids', [])) values.update({'lead_ids': leads.ids, 'user_ids': [self.user_id.id]}) self._convert_opportunity(values) - for lead in leads: - if lead.partner_id and lead.partner_id.user_id != lead.user_id: - self.env['res.partner'].browse(lead.partner_id.id).write({'user_id': lead.user_id.id}) return leads[0].redirect_opportunity_view() diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py index ec9bbe2ffae..44801880267 100644 --- a/addons/event_sale/models/sale_order.py +++ b/addons/event_sale/models/sale_order.py @@ -10,7 +10,8 @@ class SaleOrder(models.Model): def action_confirm(self): self.ensure_one() res = super(SaleOrder, self).action_confirm() - self.order_line._update_registrations(confirm=False, cancel_to_draft=False) + # confirm registration if it was free (otherwise it will be confirmed once invoice fully paid) + self.order_line._update_registrations(confirm=self.amount_total == 0, cancel_to_draft=False) if any(self.order_line.filtered(lambda line: line.event_id)): return self.env['ir.actions.act_window'].with_context(default_sale_order_id=self.id).for_xml_id('event_sale', 'action_sale_order_event_registration') return res diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml index 24a14c5e9f4..ffe7e8ed415 100644 --- a/addons/hr_expense/views/hr_expense_views.xml +++ b/addons/hr_expense/views/hr_expense_views.xml @@ -461,7 +461,6 @@ -

diff --git a/addons/stock/views/product_views.xml b/addons/stock/views/product_views.xml index bbcbafb7b08..f49daf00cf7 100644 --- a/addons/stock/views/product_views.xml +++ b/addons/stock/views/product_views.xml @@ -118,6 +118,7 @@ + product.template.stock.property.form.inherit product.template @@ -133,7 +134,7 @@ days

- +