diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index a39bca6e8c3..9c9b0832978 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -377,16 +377,50 @@ class AccountMove(models.Model): # ONCHANGE METHODS # ------------------------------------------------------------------------- - @api.onchange('invoice_date') + def _get_accounting_date(self, invoice_date, has_tax): + """Get correct accounting date for previous periods, taking tax lock date into account. + + When registering an invoice in the past, we still want the sequence to be increasing. + We then take the last day of the period, depending on the sequence format. + If there is a tax lock date and there are taxes involved, we register the invoice at the + last date of the first open period. + + :param invoice_date (datetime.date): The invoice date + :param has_tax (bool): Iff any taxes are involved in the lines of the invoice + :return (datetime.date): + """ + tax_lock_date = self.company_id.tax_lock_date + today = fields.Date.today() + if invoice_date and tax_lock_date and has_tax and invoice_date <= tax_lock_date: + invoice_date = tax_lock_date + timedelta(days=1) + + if self.is_sale_document(include_receipts=True): + return max(invoice_date, today) + elif self.is_purchase_document(include_receipts=True): + highest_name = self.highest_name or self._get_last_sequence(relaxed=True) + number_reset = self._deduce_sequence_number_reset(highest_name) + if not highest_name or number_reset == 'month': + if (today.year, today.month) > (invoice_date.year, invoice_date.month): + return date_utils.get_month(invoice_date)[1] + else: + return max(invoice_date, today) + elif number_reset == 'year': + if today.year > invoice_date.year: + return date(invoice_date.year, 12, 31) + else: + return max(invoice_date, today) + return invoice_date + + @api.onchange('invoice_date', 'highest_name', 'company_id') def _onchange_invoice_date(self): if self.invoice_date: if not self.invoice_payment_term_id and (not self.invoice_date_due or self.invoice_date_due < self.invoice_date): self.invoice_date_due = self.invoice_date - if ( - self.is_sale_document() and self.date != self.invoice_date - or self.is_purchase_document() and not self.date - ): - self.date = self.invoice_date + + has_tax = bool(self.line_ids.tax_ids or self.line_ids.tax_tag_ids) + accounting_date = self._get_accounting_date(self.invoice_date, has_tax) + if accounting_date != self.date: + self.date = accounting_date self._onchange_currency() @api.onchange('journal_id') @@ -2555,7 +2589,7 @@ class AccountMove(models.Model): # /!\ 'check_move_validity' must be there since the dynamic lines will be recomputed outside the 'onchange' # environment. if (move.company_id.tax_lock_date and move.date <= move.company_id.tax_lock_date) and (move.line_ids.tax_ids or move.line_ids.tax_tag_ids): - move.date = fields.Date.context_today(self) + move.date = move._get_accounting_date(move.invoice_date or move.date, True) move.with_context(check_move_validity=False)._onchange_currency() # Create the analytic lines in batch is faster as it leads to less cache invalidation. diff --git a/addons/account/tests/test_account_move_in_invoice.py b/addons/account/tests/test_account_move_in_invoice.py index e390599c54f..78f7538ece9 100644 --- a/addons/account/tests/test_account_move_in_invoice.py +++ b/addons/account/tests/test_account_move_in_invoice.py @@ -138,6 +138,17 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon): self.term_line_vals_1, ], self.move_vals) + def test_in_invoice_onchange_invoice_date(self): + for tax_date, invoice_date, accounting_date in [ + ('2019-03-31', '2019-05-12', '2019-05-31'), + ('2019-03-31', '2019-02-10', '2019-04-30'), + ('2019-05-31', '2019-06-15', '2019-06-30'), + ]: + self.invoice.company_id.tax_lock_date = tax_date + with Form(self.invoice) as move_form: + move_form.invoice_date = invoice_date + self.assertEqual(self.invoice.date, fields.Date.to_date(accounting_date)) + def test_in_invoice_line_onchange_product_1(self): move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: diff --git a/addons/account/tests/test_account_move_out_invoice.py b/addons/account/tests/test_account_move_out_invoice.py index 2d716d54d3f..ec03a072ba4 100644 --- a/addons/account/tests/test_account_move_out_invoice.py +++ b/addons/account/tests/test_account_move_out_invoice.py @@ -142,6 +142,18 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon): self.term_line_vals_1, ], self.move_vals) + @freeze_time('2020-01-15') + def test_out_invoice_onchange_invoice_date(self): + for tax_date, invoice_date, accounting_date in [ + ('2019-03-31', '2019-05-12', '2020-01-15'), + ('2019-03-31', '2019-02-10', '2020-01-15'), + ('2019-05-31', '2019-06-15', '2020-01-15'), + ]: + self.invoice.company_id.tax_lock_date = tax_date + with Form(self.invoice) as move_form: + move_form.invoice_date = invoice_date + self.assertEqual(self.invoice.date, fields.Date.to_date(accounting_date)) + def test_out_invoice_line_onchange_product_1(self): move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: diff --git a/addons/account/tests/test_payment_term.py b/addons/account/tests/test_payment_term.py index a3133247f6c..fb40d38c126 100644 --- a/addons/account/tests/test_payment_term.py +++ b/addons/account/tests/test_payment_term.py @@ -73,6 +73,7 @@ class TestAccountInvoiceRounding(AccountTestInvoicingCommon): with Form(self.invoice) as move_form: move_form.invoice_payment_term_id = pay_term move_form.invoice_date = invoice_date + move_form.date = invoice_date self.assertEqual( self.invoice.line_ids.filtered( lambda l: l.account_id == self.company_data['default_account_receivable']