diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index aee35e460a6..02a34a6d518 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from collections import OrderedDict import json import re import uuid @@ -517,10 +518,7 @@ class AccountInvoice(models.Model): if not vals.get('journal_id') and vals.get('type'): vals['journal_id'] = self.with_context(type=vals.get('type'))._default_journal().id - onchanges = { - '_onchange_partner_id': ['account_id', 'payment_term_id', 'fiscal_position_id', 'partner_bank_id'], - '_onchange_journal_id': ['currency_id'], - } + onchanges = self._get_onchange_create() for onchange_method, changed_fields in onchanges.items(): if any(f not in vals for f in changed_fields): invoice = self.new(vals) @@ -658,7 +656,7 @@ class AccountInvoice(models.Model): """ # Split `From` and `CC` email address from received email to look for related partners to subscribe on the invoice subscribed_emails = email_split((msg_dict.get('from') or '') + ',' + (msg_dict.get('cc') or '')) - subscribed_partner_ids = [pid for pid in self._find_partner_from_emails(subscribed_emails) if pid] + seen_partner_ids = [pid for pid in self._find_partner_from_emails(subscribed_emails) if pid] # Detection of the partner_id of the invoice: # 1) check if the email_from correspond to a supplier @@ -682,7 +680,7 @@ class AccountInvoice(models.Model): # If the partner_id can be found, subscribe it to the bill, otherwise it's left empty to be manually filled if partner_id: - subscribed_partner_ids.append(partner_id) + seen_partner_ids.append(partner_id) # Find the right purchase journal based on the "TO" email address destination_emails = email_split((msg_dict.get('to') or '') + ',' + (msg_dict.get('cc') or '')) @@ -698,9 +696,13 @@ class AccountInvoice(models.Model): # Passing `type` in context so that _default_journal(...) can correctly set journal for new vendor bill invoice = super(AccountInvoice, self.with_context(type=values.get('type'))).message_new(msg_dict, values) - # Subscribe people on the newly created bill - if subscribed_partner_ids: - invoice.message_subscribe(subscribed_partner_ids) + # Subscribe internal users on the newly created bill + partners = self.env['res.partner'].browse(seen_partner_ids) + is_internal = lambda p: (p.user_ids and + all(p.user_ids.mapped(lambda u: u.user_has_groups('base.group_user')))) + partners_to_subscribe = partners.filtered(is_internal) + if partners_to_subscribe: + invoice.message_subscribe([p.id for p in partners_to_subscribe]) return invoice @api.model @@ -1615,6 +1617,13 @@ class AccountInvoice(models.Model): def _get_intrastat_country_id(self): return self.partner_id.country_id.id + def _get_onchange_create(self): + return OrderedDict([ + ('_onchange_partner_id', ['account_id', 'payment_term_id', 'fiscal_position_id', 'partner_bank_id']), + ('_onchange_journal_id', ['currency_id']), + ]) + + class AccountInvoiceLine(models.Model): _name = "account.invoice.line" _description = "Invoice Line" diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index 7d19678ebcf..34a81d82878 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -343,10 +343,10 @@ class account_journal(models.Model): action_name = 'action_view_bank_statement_tree' elif self.type == 'sale': action_name = 'action_invoice_tree1' - self = self.with_context(use_domain=[('type', 'in', ['out_invoice', 'out_refund'])]) + self = self.with_context(use_domain=[('journal_id', '=', self.id)]) elif self.type == 'purchase': action_name = 'action_vendor_bill_template' - self = self.with_context(use_domain=[('type', 'in', ['in_invoice', 'in_refund'])]) + self = self.with_context(use_domain=[('journal_id', '=', self.id)]) else: action_name = 'action_move_journal_line' diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 847130113df..3007f56197a 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -1030,7 +1030,7 @@ class AccountMoveLine(models.Model): writeoff_moves += writeoff_move # writeoff_move.post() - line_to_reconcile += writeoff_move.line_ids.filtered(lambda r: r.account_id == self[0].account_id) + line_to_reconcile += writeoff_move.line_ids.filtered(lambda r: r.account_id == self[0].account_id).sorted(key='id')[-1:] if writeoff_moves: writeoff_moves.post() # Return the writeoff move.line which is to be reconciled diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index 3915b9f9c1f..f4cb84a3531 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -661,9 +661,7 @@ class account_payment(models.Model): if any(len(record.invoice_ids) != 1 for record in self): # For multiple invoices, there is account.register.payments wizard raise UserError(_("This method should only be called to process a single invoice's payment.")) - res = self.post() - self.mapped('payment_transaction_id').filtered(lambda x: x.state == 'done' and not x.is_processed)._post_process_after_done() - return res + return self.post() def _create_payment_entry(self, amount): """ Create a journal entry corresponding to a payment, if the payment references invoice(s) they are reconciled. diff --git a/addons/account/models/account_reconcile_model.py b/addons/account/models/account_reconcile_model.py index 6794ab4ac0b..551edb7c3b4 100644 --- a/addons/account/models/account_reconcile_model.py +++ b/addons/account/models/account_reconcile_model.py @@ -395,6 +395,9 @@ class AccountReconcileModel(models.Model): aml.date_maturity AS aml_date_maturity, aml.amount_residual AS aml_amount_residual, aml.amount_residual_currency AS aml_amount_residual_currency, + aml.balance AS aml_balance, + aml.amount_currency AS aml_amount_currency, + account.internal_type AS account_internal_type, -- Determine a matching or not with the statement line communication using the move.name or move.ref. regexp_split_to_array(TRIM(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g')),'\s+') @@ -542,9 +545,12 @@ class AccountReconcileModel(models.Model): return True # Match total residual amount. - total_residual = sum( - aml['aml_currency_id'] and aml['aml_amount_residual_currency'] or aml['aml_amount_residual'] for aml in - candidates) + total_residual = 0.0 + for aml in candidates: + if aml['account_internal_type'] == 'liquidity': + total_residual += aml['aml_currency_id'] and aml['aml_amount_currency'] or aml['aml_balance'] + else: + total_residual += aml['aml_currency_id'] and aml['aml_amount_residual_currency'] or aml['aml_amount_residual'] line_residual = statement_line.currency_id and statement_line.amount_currency or statement_line.amount line_currency = statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.company_id.currency_id diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index eab1c9f42fb..e9be12bcefb 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -470,6 +470,67 @@ class TestReconciliation(AccountingTestCase): self.assertEquals(aml.amount_residual, 0, 'The journal item should be totally reconciled') self.assertEquals(aml.amount_residual_currency, 0, 'The journal item should be totally reconciled') + def test_manual_reconcile_wizard_same_account(self): + move_ids = self.env['account.move'] + debit_line_vals = { + 'name': '1', + 'debit': 728.35, + 'credit': 0.0, + 'account_id': self.account_rcv.id, + 'amount_currency': 795.05, + 'currency_id': self.currency_swiss_id, + } + credit_line_vals = { + 'name': '1', + 'debit': 0.0, + 'credit': 728.35, + 'account_id': self.account_rsa.id, + 'amount_currency': -795.05, + 'currency_id': self.currency_swiss_id, + } + vals = { + 'journal_id': self.bank_journal_euro.id, + 'date': time.strftime('%Y') + '-02-15', + 'line_ids': [(0,0, debit_line_vals), (0, 0, credit_line_vals)] + } + move_ids += self.env['account.move'].create(vals) + debit_line_vals = { + 'name': '2', + 'debit': 0.0, + 'credit': 737.10, + 'account_id': self.account_rcv.id, + 'amount_currency': -811.25, + 'currency_id': self.currency_swiss_id, + } + credit_line_vals = { + 'name': '2', + 'debit': 737.10, + 'credit': 0.0, + 'account_id': self.account_rsa.id, + 'amount_currency': 811.25, + 'currency_id': self.currency_swiss_id, + } + vals = { + 'journal_id': self.bank_journal_euro.id, + 'date': time.strftime('%Y') + '-07-15', + 'line_ids': [(0,0, debit_line_vals), (0, 0, credit_line_vals)] + } + move_ids += self.env['account.move'].create(vals) + + account_move_line = move_ids.mapped('line_ids').filtered(lambda l: l.account_id == self.account_rcv) + writeoff_vals = [{ + 'account_id': self.account_rcv.id, + 'journal_id': self.bank_journal_euro.id, + 'date': time.strftime('%Y') + '-04-15', + 'debit': 8.75, + 'credit': 0.0 + }] + writeoff_line = account_move_line._create_writeoff(writeoff_vals) + (account_move_line + writeoff_line).reconcile() + self.assertEquals(len(writeoff_line), 1, "The writeoff_line (balance_line) should have only one moves line") + self.assertTrue(all(l.reconciled for l in writeoff_line), 'The balance lines should be totally reconciled') + self.assertTrue(all(l.reconciled for l in account_move_line), 'The move lines should be totally reconciled') + def test_reconcile_bank_statement_with_payment_and_writeoff(self): # Use case: # Company is in EUR, create a bill for 80 USD and register payment of 80 USD. diff --git a/addons/account/tests/test_reconciliation_matching_rules.py b/addons/account/tests/test_reconciliation_matching_rules.py index f50173296b5..cb10bee162e 100644 --- a/addons/account/tests/test_reconciliation_matching_rules.py +++ b/addons/account/tests/test_reconciliation_matching_rules.py @@ -24,8 +24,10 @@ class TestReconciliationMatchingRules(AccountingTestCase): lines = invoice.move_id.line_ids return lines.filtered(lambda l: l.account_id == invoice.account_id) - def _check_statement_matching(self, rules, expected_values): - statement_lines = (self.bank_st + self.cash_st).mapped('line_ids') + def _check_statement_matching(self, rules, expected_values, statements=None): + if statements is None: + statements = self.bank_st + self.cash_st + statement_lines = statements.mapped('line_ids') matching_values = rules._apply_rules(statement_lines) for st_line_id, values in matching_values.items(): values.pop('reconciled_lines', None) @@ -34,6 +36,10 @@ class TestReconciliationMatchingRules(AccountingTestCase): def setUp(self): super(AccountingTestCase, self).setUp() + self.account_pay = self.env['account.account'].search([('internal_type', '=', 'payable')], limit=1) + self.account_liq = self.env['account.account'].search([('internal_type', '=', 'liquidity')], limit=1) + self.account_rcv = self.env['account.account'].search([('internal_type', '=', 'receivable')], limit=1) + self.partner_1 = self.env['res.partner'].create({'name': 'partner_1'}) self.partner_2 = self.env['res.partner'].create({'name': 'partner_2'}) @@ -45,7 +51,8 @@ class TestReconciliationMatchingRules(AccountingTestCase): current_assets_account = self.env['account.account'].search( [('user_type_id', '=', self.env.ref('account.data_account_type_current_assets').id)], limit=1) - self.rule_1 = self.env.ref('account.reconciliation_model_default_rule') + self.rule_0 = self.env.ref('account.reconciliation_model_default_rule') + self.rule_1 = self.rule_0.copy() self.rule_1.account_id = current_assets_account self.rule_1.match_partner = True self.rule_1.match_partner_ids |= self.partner_1 + self.partner_2 @@ -59,10 +66,10 @@ class TestReconciliationMatchingRules(AccountingTestCase): invoice_number = self.invoice_line_1.move_id.name - bank_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1) + self.bank_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1) self.bank_st = self.env['account.bank.statement'].create({ - 'name': 'test bank journal', 'journal_id': bank_journal.id, + 'name': 'test bank journal', 'journal_id': self.bank_journal.id, }) self.bank_line_1 = self.env['account.bank.statement.line'].create({ 'statement_id': self.bank_st.id, @@ -296,3 +303,47 @@ class TestReconciliationMatchingRules(AccountingTestCase): {'partner_id': self.partner_2.id, 'debit': 0.0, 'credit': 1000.0}, {'partner_id': self.partner_2.id, 'debit': 1000.0, 'credit': 0.0}, ]) + + def test_reverted_move_matching(self): + AccountMove = self.env['account.move'] + move = AccountMove.create({ + 'name': 'To Revert', + 'journal_id': self.bank_journal.id, + }) + + partner = self.env['res.partner'].create({'name': 'Eugene'}) + AccountMoveLine = self.env['account.move.line'].with_context(check_move_validity=False) + payment_payable_line = AccountMoveLine.create({ + 'account_id': self.account_pay.id, + 'move_id': move.id, + 'partner_id': partner.id, + 'name': 'One of these days', + 'debit': 10, + }) + payment_bnk_line = AccountMoveLine.create({ + 'account_id': self.account_liq.id, + 'move_id': move.id, + 'partner_id': partner.id, + 'name': 'I\'m gonna cut you into little pieces', + 'credit': 10, + }) + + move.post() + move_reversed = AccountMove.browse(move.reverse_moves()) + self.assertTrue(move_reversed.exists()) + + bank_st = self.env['account.bank.statement'].create({ + 'name': 'test bank journal', 'journal_id': self.bank_journal.id, + }) + bank_line_1 = self.env['account.bank.statement.line'].create({ + 'statement_id': bank_st.id, + 'name': '8', + 'partner_id': partner.id, + 'amount': -10, + 'sequence': 1, + }) + + expected_values = { + bank_line_1.id: {'aml_ids': [payment_bnk_line.id], 'model': self.rule_0} + } + self._check_statement_matching(self.rule_0, expected_values, statements=bank_st) diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml index c32413a4f1d..1c57b6892c9 100644 --- a/addons/account/views/report_invoice.xml +++ b/addons/account/views/report_invoice.xml @@ -51,29 +51,26 @@ - - + + - + + - - + + @@ -92,20 +89,23 @@ - + - - + + - @@ -113,12 +113,17 @@ - diff --git a/addons/account_check_printing/models/account_payment.py b/addons/account_check_printing/models/account_payment.py index 28e9f285c1d..cfba4270da8 100644 --- a/addons/account_check_printing/models/account_payment.py +++ b/addons/account_check_printing/models/account_payment.py @@ -28,7 +28,7 @@ class AccountRegisterPayments(models.TransientModel): def _onchange_amount(self): if hasattr(super(AccountRegisterPayments, self), '_onchange_amount'): super(AccountRegisterPayments, self)._onchange_amount() - self.check_amount_in_words = self.currency_id.amount_to_text(self.amount) + self.check_amount_in_words = self.currency_id.amount_to_text(self.amount) if self.currency_id else False def _prepare_payment_vals(self, invoices): res = super(AccountRegisterPayments, self)._prepare_payment_vals(invoices) diff --git a/addons/bus/models/bus.py b/addons/bus/models/bus.py index 7a7f8b497db..d3ab23e6c1f 100644 --- a/addons/bus/models/bus.py +++ b/addons/bus/models/bus.py @@ -140,7 +140,7 @@ class ImDispatch(object): event = self.Event() for channel in channels: - self.channels.setdefault(hashable(channel), []).append(event) + self.channels.setdefault(hashable(channel), set()).add(event) try: event.wait(timeout=timeout) with registry.cursor() as cr: @@ -149,6 +149,12 @@ class ImDispatch(object): except Exception: # timeout pass + finally: + # gc pointers to event + for channel in channels: + channel_events = self.channels.get(hashable(channel)) + if channel_events and event in channel_events: + channel_events.remove(event) return notifications def loop(self): @@ -169,7 +175,7 @@ class ImDispatch(object): # dispatch to local threads/greenlets events = set() for channel in channels: - events.update(self.channels.pop(hashable(channel), [])) + events.update(self.channels.pop(hashable(channel), set())) for event in events: event.set() diff --git a/addons/delivery/models/stock_picking.py b/addons/delivery/models/stock_picking.py index accc102c2b7..5d4da724ac6 100644 --- a/addons/delivery/models/stock_picking.py +++ b/addons/delivery/models/stock_picking.py @@ -111,7 +111,7 @@ class StockPicking(models.Model): for picking in self: picking.carrier_tracking_url = picking.carrier_id.get_tracking_link(picking) if picking.carrier_id and picking.carrier_tracking_ref else False - @api.depends('move_lines') + @api.depends('move_lines', 'move_ids_without_package') def _cal_weight(self): for picking in self: picking.weight = sum(move.weight for move in picking.move_lines if move.state != 'cancel') diff --git a/addons/hr_holidays/models/hr.py b/addons/hr_holidays/models/hr.py index 8fbbf89b8df..8a8d1716df0 100644 --- a/addons/hr_holidays/models/hr.py +++ b/addons/hr_holidays/models/hr.py @@ -87,6 +87,9 @@ class Employee(models.Model): show_leaves = fields.Boolean('Able to see Remaining Leaves', compute='_compute_show_leaves') is_absent_totay = fields.Boolean('Absent Today', compute='_compute_absent_employee', search='_search_absent_employee') + def _get_date_start_work(self): + return self.create_date + def _get_remaining_leaves(self): """ Helper to compute the remaining leaves for the current employees :returns dict where the key is the employee id, and the value is the remain leaves diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index 840537796e4..023b3aebeaf 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -167,16 +167,15 @@ class HolidaysAllocation(models.Model): # We have to check when the employee has been created # in order to not allocate him/her too much leaves - creation_date = fields.Datetime.from_string(holiday.employee_id.create_date) - + start_date = holiday.employee_id._get_date_start_work() # If employee is created after the period, we cancel the computation - if period_end <= creation_date: + if period_end <= start_date: holiday.write(values) continue # If employee created during the period, taking the date at which he has been created - if period_start <= creation_date: - period_start = creation_date + if period_start <= start_date: + period_start = start_date worked = holiday.employee_id._get_work_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days'] left = holiday.employee_id._get_leave_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days'] diff --git a/addons/hw_escpos/escpos/constants.py b/addons/hw_escpos/escpos/constants.py index 238e8bdbf0f..0000109a964 100644 --- a/addons/hw_escpos/escpos/constants.py +++ b/addons/hw_escpos/escpos/constants.py @@ -2,6 +2,9 @@ """ ESC/POS Commands (Constants) """ +# Control characters +ESC = '\x1b' + # Feed control sequences CTL_LF = '\x0a' # Print and line feed CTL_FF = '\x0c' # Form feed @@ -19,9 +22,10 @@ DLE_EOT_PAPER = '\x10\x04\x04' HW_INIT = '\x1b\x40' # Clear data in buffer and reset modes HW_SELECT = '\x1b\x3d\x01' # Printer select HW_RESET = '\x1b\x3f\x0a\x00' # Reset printer hardware -# Cash Drawer -CD_KICK_2 = '\x1b\x70\x00' # Sends a pulse to pin 2 [] -CD_KICK_5 = '\x1b\x70\x01' # Sends a pulse to pin 5 [] +# Cash Drawer (ESC p ) +_CASH_DRAWER = lambda m, t1='', t2='': ESC + 'p' + m + chr(t1) + chr(t2) +CD_KICK_2 = _CASH_DRAWER('\x00', 50, 50) # Sends a pulse to pin 2 [] +CD_KICK_5 = _CASH_DRAWER('\x01', 50, 50) # Sends a pulse to pin 5 [] # Paper PAPER_FULL_CUT = '\x1d\x56\x00' # Full cut paper PAPER_PART_CUT = '\x1d\x56\x01' # Partial cut paper diff --git a/addons/im_livechat/models/mail_channel.py b/addons/im_livechat/models/mail_channel.py index 8b81501db20..cb3f57f689c 100644 --- a/addons/im_livechat/models/mail_channel.py +++ b/addons/im_livechat/models/mail_channel.py @@ -30,7 +30,6 @@ class MailChannel(models.Model): _name = 'mail.channel' _inherit = ['mail.channel', 'rating.mixin'] - _description = 'Discussion channel' anonymous_name = fields.Char('Anonymous Name') channel_type = fields.Selection(selection_add=[('livechat', 'Livechat Conversation')]) diff --git a/addons/l10n_fr/data/account_tax_data.xml b/addons/l10n_fr/data/account_tax_data.xml index afd9efc4037..b7bdcf79e32 100644 --- a/addons/l10n_fr/data/account_tax_data.xml +++ b/addons/l10n_fr/data/account_tax_data.xml @@ -1024,7 +1024,7 @@ purchase - + diff --git a/addons/l10n_nl/data/account.account.template.csv b/addons/l10n_nl/data/account.account.template.csv index ef2fd529278..2164c15119f 100644 --- a/addons/l10n_nl/data/account.account.template.csv +++ b/addons/l10n_nl/data/account.account.template.csv @@ -324,6 +324,9 @@ "8043","Omzet buiten EU werk derden","8043","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False" "8044","Omzet Intercompany werk derden","8044","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False" "8090","Diverse overige opbrengsten","8090","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False" +"8110","Installatie/afstandsverkopen binnen EU handelsgoederen 1","80011","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False" +"8120","Installatie/afstandsverkopen binnen EU handelsgoederen 2","80012","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False" +"8130","Installatie/afstandsverkopen binnen EU handelsgoederen 3","80013","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_11","False" "8920","Koersverschillen","8920","account.data_account_type_revenue","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_31","False" "9011","Resultaat deelneming 1","9011","account.data_account_type_other_income","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_12","False" "9012","Resultaat deelneming 2","9012","account.data_account_type_other_income","l10n_nl.l10nnl_chart_template","l10n_nl.account_tag_12","False" diff --git a/addons/l10n_nl/data/account_fiscal_position_account_template.xml b/addons/l10n_nl/data/account_fiscal_position_account_template.xml index ab156438382..81647963b10 100644 --- a/addons/l10n_nl/data/account_fiscal_position_account_template.xml +++ b/addons/l10n_nl/data/account_fiscal_position_account_template.xml @@ -92,5 +92,30 @@ + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/addons/l10n_nl/data/account_fiscal_position_tax_template.xml b/addons/l10n_nl/data/account_fiscal_position_tax_template.xml index e59c852ef24..05e54233bcf 100644 --- a/addons/l10n_nl/data/account_fiscal_position_tax_template.xml +++ b/addons/l10n_nl/data/account_fiscal_position_tax_template.xml @@ -34,7 +34,7 @@ - + @@ -49,12 +49,12 @@ - + - + @@ -65,7 +65,7 @@ - + @@ -85,7 +85,7 @@ - + @@ -95,59 +95,59 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -172,22 +172,22 @@ - + - + - + - + @@ -196,6 +196,5 @@ - diff --git a/addons/l10n_nl/data/account_fiscal_position_template.xml b/addons/l10n_nl/data/account_fiscal_position_template.xml index f70c6fc6e1b..a6519cac9d7 100644 --- a/addons/l10n_nl/data/account_fiscal_position_template.xml +++ b/addons/l10n_nl/data/account_fiscal_position_template.xml @@ -28,83 +28,57 @@ + - + + - + + - + + - + + - + + - + + - + + - + + - + + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index e8a904f2d37..e48ca4cbd3d 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -318,9 +318,9 @@ class MassMailingCampaign(models.Model): campaign_id = fields.Many2one('utm.campaign', 'campaign_id', required=True, ondelete='cascade', help="This name helps you tracking your different campaign efforts, e.g. Fall_Drive, Christmas_Special") source_id = fields.Many2one('utm.source', string='Source', - help="This is the link source, e.g. Search Engine, another domain,or name of email list", default=lambda self: self.env.ref('utm.utm_source_newsletter')) + help="This is the link source, e.g. Search Engine, another domain,or name of email list", default=lambda self: self.env.ref('utm.utm_source_newsletter', False)) medium_id = fields.Many2one('utm.medium', string='Medium', - help="This is the delivery method, e.g. Postcard, Email, or Banner Ad", default=lambda self: self.env.ref('utm.utm_medium_email')) + help="This is the delivery method, e.g. Postcard, Email, or Banner Ad", default=lambda self: self.env.ref('utm.utm_medium_email', False)) tag_ids = fields.Many2many( 'mail.mass_mailing.tag', 'mail_mass_mailing_tag_rel', 'tag_id', 'campaign_id', string='Tags') diff --git a/addons/mrp/report/mrp_report_bom_structure.py b/addons/mrp/report/mrp_report_bom_structure.py index f687c7e15a8..544c4aaa009 100644 --- a/addons/mrp/report/mrp_report_bom_structure.py +++ b/addons/mrp/report/mrp_report_bom_structure.py @@ -208,10 +208,10 @@ class ReportBomStructure(models.AbstractModel): def _get_pdf_line(self, bom_id, product_id=False, qty=1, child_bom_ids=[], unfolded=False): - data = self._get_bom(bom_id=bom_id, product_id=product_id, line_qty=qty) + data = self._get_bom(bom_id=bom_id, product_id=product_id.id, line_qty=qty) def get_sub_lines(bom, product_id, line_qty, line_id, level): - data = self._get_bom(bom_id=bom.id, product_id=product_id, line_qty=line_qty, line_id=line_id, level=level) + data = self._get_bom(bom_id=bom.id, product_id=product_id.id, line_qty=line_qty, line_id=line_id, level=level) bom_lines = data['components'] lines = [] for bom_line in bom_lines: diff --git a/addons/payment/models/account_payment.py b/addons/payment/models/account_payment.py index 21c0b5530dd..220cb37db38 100644 --- a/addons/payment/models/account_payment.py +++ b/addons/payment/models/account_payment.py @@ -71,6 +71,11 @@ class AccountPayment(models.Model): return transactions + def action_validate_invoice_payment(self): + res = super(AccountPayment, self).action_validate_invoice_payment() + self.mapped('payment_transaction_id').filtered(lambda x: x.state == 'done' and not x.is_processed)._post_process_after_done() + return res + @api.multi def post(self): # Post the payments "normally" if no transactions are needed. diff --git a/addons/payment_buckaroo/tests/test_buckaroo.py b/addons/payment_buckaroo/tests/test_buckaroo.py index e23b1f0c002..c78c998304d 100644 --- a/addons/payment_buckaroo/tests/test_buckaroo.py +++ b/addons/payment_buckaroo/tests/test_buckaroo.py @@ -32,12 +32,12 @@ class BuckarooForm(BuckarooCommon): # ---------------------------------------- form_values = { - 'add_returndata': '', + 'add_returndata': '/payment/process', 'Brq_websitekey': self.buckaroo.brq_websitekey, 'Brq_amount': '2240.0', 'Brq_currency': 'EUR', 'Brq_invoicenumber': 'SO004', - 'Brq_signature': 'aa6fe072afdd9d1b463d55f43e6df4b272d3529a', # update me + 'Brq_signature': 'f09bb79451a7ddef3178462ea52b07e55d34e99e', # update me 'brq_test': 'True', 'Brq_return': urls.url_join(base_url, BuckarooController._return_url), 'Brq_returncancel': urls.url_join(base_url, BuckarooController._cancel_url), diff --git a/addons/payment_paypal/tests/test_paypal.py b/addons/payment_paypal/tests/test_paypal.py index b8158508a2a..c285829f08d 100644 --- a/addons/payment_paypal/tests/test_paypal.py +++ b/addons/payment_paypal/tests/test_paypal.py @@ -67,6 +67,7 @@ class PaypalForm(PaypalCommon): 'return': urls.url_join(base_url, PaypalController._return_url), 'notify_url': urls.url_join(base_url, PaypalController._notify_url), 'cancel_return': urls.url_join(base_url, PaypalController._cancel_url), + 'custom': '{"return_url": "/payment/process"}', } # check form result @@ -140,7 +141,7 @@ class PaypalForm(PaypalCommon): 'item_name': u'test_ref_2', 'address_country': u'France', 'charset': u'windows-1252', - 'custom': u'', + 'custom': u'{"return_url": "/payment/process"}', 'notify_version': u'3.7', 'address_name': u'Norbert Poilu', 'pending_reason': u'multi_currency', diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index 8b1bbc0eaea..0928aab1815 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -346,6 +346,12 @@ class ProductTemplate(models.Model): raise ValidationError(_('The default Unit of Measure and the purchase Unit of Measure must be in the same category.')) return True + @api.constrains('attribute_line_ids') + def _check_attribute_line(self): + if any(len(template.attribute_line_ids) != len(template.attribute_line_ids.mapped('attribute_id')) for template in self): + raise ValidationError(_('You cannot define two attribute lines for the same attribute.')) + return True + @api.onchange('uom_id') def _onchange_uom_id(self): if self.uom_id: diff --git a/addons/project/data/project_data.xml b/addons/project/data/project_data.xml index ff624b5a232..33535f15c19 100644 --- a/addons/project/data/project_data.xml +++ b/addons/project/data/project_data.xml @@ -2,16 +2,6 @@ - - - - ir.model.fields - project - project_time_mode_id_duplicate_xmlid - True - - Project: Send rating diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py index eaa71775476..9f63039afd7 100644 --- a/addons/purchase/models/account_invoice.py +++ b/addons/purchase/models/account_invoice.py @@ -131,7 +131,7 @@ class AccountInvoice(models.Model): res = super(AccountInvoice, self)._onchange_partner_id() if payment_term_id: self.payment_term_id = payment_term_id - if not self.env.context.get('default_journal_id') and self.partner_id and self.currency_id and\ + if not self.env.context.get('default_journal_id') and self.partner_id and\ self.type in ['in_invoice', 'in_refund'] and\ self.currency_id != self.partner_id.property_purchase_currency_id and\ self.partner_id.property_purchase_currency_id.id: @@ -172,6 +172,11 @@ class AccountInvoice(models.Model): invoice.message_post(body=message) return result + def _get_onchange_create(self): + res = super()._get_onchange_create() + res['_onchange_partner_id'].append('currency_id') + return res + class AccountInvoiceLine(models.Model): """ Override AccountInvoice_line to add the link to the purchase order line it is related to""" diff --git a/addons/purchase/models/product.py b/addons/purchase/models/product.py index 644bfc77b43..9567f28ce7e 100644 --- a/addons/purchase/models/product.py +++ b/addons/purchase/models/product.py @@ -90,3 +90,11 @@ class ProductCategory(models.Model): 'account.account', string="Price Difference Account", company_dependent=True, help="This account will be used to value price difference between purchase price and accounting cost.") + + +class SupplierInfo(models.Model): + _inherit = "product.supplierinfo" + + @api.onchange('name') + def _onchange_partner_id(self): + self.currency_id = self.name.property_purchase_currency_id or self.env.user.company_id.currency_id diff --git a/addons/purchase_stock/report/purchase_report_templates.xml b/addons/purchase_stock/report/purchase_report_templates.xml index 8e5adc9d45d..0c5a42fd0da 100644 --- a/addons/purchase_stock/report/purchase_report_templates.xml +++ b/addons/purchase_stock/report/purchase_report_templates.xml @@ -1,22 +1,32 @@ - + - + diff --git a/addons/repair/models/repair.py b/addons/repair/models/repair.py index de88001fb57..bcd44c95292 100644 --- a/addons/repair/models/repair.py +++ b/addons/repair/models/repair.py @@ -598,17 +598,24 @@ class RepairLine(models.Model): 'title': _('No pricelist found.'), 'message': _('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.')} - else: - price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner) - if price is False: - warning = { - 'title': _('No valid pricelist line found.'), - 'message': - _("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")} - else: - self.price_unit = price - if warning: return {'warning': warning} + else: + self._onchange_product_uom() + + @api.onchange('product_uom') + def _onchange_product_uom(self): + partner = self.repair_id.partner_id + pricelist = self.repair_id.pricelist_id + if pricelist and self.product_id and self.type != 'remove': + price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner, uom_id=self.product_uom.id) + if price is False: + warning = { + 'title': _('No valid pricelist line found.'), + 'message': + _("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")} + return {'warning': warning} + else: + self.price_unit = price class RepairFee(models.Model): @@ -658,14 +665,21 @@ class RepairFee(models.Model): 'title': _('No pricelist found.'), 'message': _('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.')} + return {'warning': warning} else: - price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner) + self._onchange_product_uom() + + @api.onchange('product_uom') + def _onchange_product_uom(self): + partner = self.repair_id.partner_id + pricelist = self.repair_id.pricelist_id + if pricelist and self.product_id: + price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner, uom_id=self.product_uom.id) if price is False: warning = { 'title': _('No valid pricelist line found.'), 'message': _("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")} + return {'warning': warning} else: self.price_unit = price - if warning: - return {'warning': warning} diff --git a/addons/sale/report/sale_report_templates.xml b/addons/sale/report/sale_report_templates.xml index edce182dcfc..28a5d105dfc 100644 --- a/addons/sale/report/sale_report_templates.xml +++ b/addons/sale/report/sale_report_templates.xml @@ -70,29 +70,26 @@
Description Source Document Quantity Unit Price Disc.(%) - Taxes - Amount - Total Price - AmountTotal Price
- - + + + + + +
+ Subtotal + |
- - + + - + + - - + + @@ -110,20 +107,23 @@ - + - - + + - @@ -131,12 +131,17 @@ - diff --git a/addons/sale/views/sale_portal_templates.xml b/addons/sale/views/sale_portal_templates.xml index 66fd9f388fa..410d378edbc 100644 --- a/addons/sale/views/sale_portal_templates.xml +++ b/addons/sale/views/sale_portal_templates.xml @@ -423,29 +423,26 @@
Description Quantity Unit Price Disc.(%) - Taxes - Amount - Total Price - AmountTotal Price
- - + + + + + +
+ Subtotal + |
- - + + - + + - - + + @@ -476,20 +473,23 @@ - + - - + + - @@ -497,12 +497,17 @@ - diff --git a/addons/stock/wizard/stock_warn_insufficient_qty.py b/addons/stock/wizard/stock_warn_insufficient_qty.py index 50247d130bf..3e480c73e83 100644 --- a/addons/stock/wizard/stock_warn_insufficient_qty.py +++ b/addons/stock/wizard/stock_warn_insufficient_qty.py @@ -34,3 +34,7 @@ class StockWarnInsufficientQtyScrap(models.TransientModel): def action_done(self): return self.scrap_id.do_scrap() + + def action_cancel(self): + # FIXME in master: we should not have created the scrap in a first place + return self.scrap_id.sudo().unlink() diff --git a/addons/stock/wizard/stock_warn_insufficient_qty_views.xml b/addons/stock/wizard/stock_warn_insufficient_qty_views.xml index f6fb8eee99e..01de1f8e482 100644 --- a/addons/stock/wizard/stock_warn_insufficient_qty_views.xml +++ b/addons/stock/wizard/stock_warn_insufficient_qty_views.xml @@ -44,6 +44,9 @@ + +
Products Quantity Unit Price Discount - Taxes - Amount - Total Price - AmountTotal Price
- - + + + + + +
+ Subtotal + |