You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
9.2 KiB
Python
180 lines
9.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from odoo import models, fields, _
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools.misc import format_date
|
|
from odoo.tools import date_utils
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
|
|
class AccountMove(models.Model):
|
|
_inherit = "account.move"
|
|
|
|
# used for VAT closing, containing the end date of the period this entry closes
|
|
tax_closing_end_date = fields.Date()
|
|
# technical field used to know if there was a failed control check
|
|
tax_report_control_error = fields.Boolean()
|
|
|
|
def _post(self, soft=True):
|
|
# Overridden to create carryover external values and join the pdf of the report when posting the tax closing
|
|
processed_moves = self.env['account.move']
|
|
for move in self.filtered(lambda m: not m.posted_before and m.tax_closing_end_date):
|
|
# Generate carryover values
|
|
report, options = move._get_report_options_from_tax_closing_entry()
|
|
|
|
company_ids = [comp_opt['id'] for comp_opt in options.get('multi_company', [])] or self.env.company.ids
|
|
if len(company_ids) >= 2:
|
|
# For tax units, we only do the carryover for all the companies when the last of their closing moves for the period is posted.
|
|
# If a company has no closing move for this tax_closing_date, we consider the closing hasn't been done for it.
|
|
closing_domains = [
|
|
('company_id', 'in', company_ids),
|
|
('tax_closing_end_date', '=', move.tax_closing_end_date),
|
|
'|', ('state', '=', 'posted'), ('id', 'in', processed_moves.ids),
|
|
]
|
|
|
|
if move.fiscal_position_id:
|
|
closing_domains.append(('fiscal_position_id.foreign_vat', '=', move.fiscal_position_id.foreign_vat))
|
|
|
|
posted_closings_from_unit_count = self.env['account.move'].sudo().search_count(closing_domains)
|
|
|
|
if posted_closings_from_unit_count == len(company_ids) - 1: # -1 to exclude the company of the current move
|
|
report.with_context(allowed_company_ids=company_ids)._generate_carryover_external_values(options)
|
|
else:
|
|
report._generate_carryover_external_values(options)
|
|
|
|
processed_moves += move
|
|
|
|
# Post the pdf of the tax report in the chatter, and set the lock date if possible
|
|
self._close_tax_period()
|
|
|
|
return super()._post(soft)
|
|
|
|
def action_open_tax_report(self):
|
|
action = self.env["ir.actions.actions"]._for_xml_id("account_reports.action_account_report_gt")
|
|
options = self._get_report_options_from_tax_closing_entry()[1]
|
|
# Pass options in context and set ignore_session: read to prevent reading previous options
|
|
action.update({'params': {'options': options, 'ignore_session': 'read'}})
|
|
return action
|
|
|
|
def _close_tax_period(self):
|
|
""" Closes tax closing entries. The tax closing activities on them will be marked done, and the next tax closing entry
|
|
will be generated or updated (if already existing). Also, a pdf of the tax report at the time of closing
|
|
will be posted in the chatter of each move.
|
|
|
|
The tax lock date of each move's company will be set to the move's date in case no other draft tax closing
|
|
move exists for that company (whatever their foreign VAT fiscal position) before or at that date, meaning that
|
|
all the tax closings have been performed so far.
|
|
"""
|
|
if not self.user_has_groups('account.group_account_manager'):
|
|
raise UserError(_('Only Billing Administrators are allowed to change lock dates!'))
|
|
|
|
tax_closing_activity_type = self.env.ref('account_reports.tax_closing_activity_type')
|
|
|
|
for move in self:
|
|
# Change lock date to end date of the period, if all other tax closing moves before this one have been treated
|
|
open_previous_closing = self.env['account.move'].search([
|
|
('activity_ids.activity_type_id', '=', tax_closing_activity_type.id),
|
|
('company_id', '=', move.company_id.id),
|
|
('date', '<=', move.date),
|
|
('state', '=', 'draft'),
|
|
('id', '!=', move.id),
|
|
], limit=1)
|
|
|
|
if not open_previous_closing:
|
|
move.company_id.sudo().tax_lock_date = move.tax_closing_end_date
|
|
|
|
# Add pdf report as attachment to move
|
|
report, options = move._get_report_options_from_tax_closing_entry()
|
|
|
|
attachments = move._get_vat_report_attachments(report, options)
|
|
|
|
# End activity
|
|
activity = move.activity_ids.filtered(lambda m: m.activity_type_id.id == tax_closing_activity_type.id)
|
|
if activity:
|
|
activity.action_done()
|
|
|
|
# Post the message with the PDF
|
|
subject = _(
|
|
"Vat closing from %s to %s",
|
|
format_date(self.env, options['date']['date_from']),
|
|
format_date(self.env, options['date']['date_to']),
|
|
)
|
|
move.with_context(no_new_invoice=True).message_post(body=move.ref, subject=subject, attachments=attachments)
|
|
|
|
# Create the recurring entry (new draft move and new activity)
|
|
if move.fiscal_position_id.foreign_vat:
|
|
next_closing_params = {'fiscal_positions': move.fiscal_position_id}
|
|
else:
|
|
next_closing_params = {'include_domestic': True}
|
|
move.company_id._get_and_update_tax_closing_moves(move.tax_closing_end_date + relativedelta(days=1), **next_closing_params)
|
|
|
|
def refresh_tax_entry(self):
|
|
for move in self.filtered(lambda m: m.tax_closing_end_date and m.state == 'draft'):
|
|
report, options = move._get_report_options_from_tax_closing_entry()
|
|
self.env[report.custom_handler_model_name]._generate_tax_closing_entries(report, options, closing_moves=move)
|
|
|
|
def _get_report_options_from_tax_closing_entry(self):
|
|
self.ensure_one()
|
|
date_to = self.tax_closing_end_date
|
|
# Take the periodicity of tax report from the company and compute the starting period date.
|
|
delay = self.company_id._get_tax_periodicity_months_delay() - 1
|
|
date_from = date_utils.start_of(date_to + relativedelta(months=-delay), 'month')
|
|
|
|
# In case the company submits its report in different regions, a closing entry
|
|
# is made for each fiscal position defining a foreign VAT.
|
|
# We hence need to make sure to select a tax report in the right country when opening
|
|
# the report (in case there are many, we pick the first one available; it doesn't impact the closing)
|
|
if self.fiscal_position_id.foreign_vat:
|
|
fpos_option = self.fiscal_position_id.id
|
|
report_country = self.fiscal_position_id.country_id
|
|
else:
|
|
fpos_option = 'domestic'
|
|
report_country = self.company_id.account_fiscal_country_id
|
|
|
|
generic_tax_report = self.env.ref('account.generic_tax_report')
|
|
tax_report = self.env['account.report'].search([
|
|
('availability_condition', '=', 'country'),
|
|
('country_id', '=', report_country.id),
|
|
('root_report_id', '=', generic_tax_report.id),
|
|
], limit=1)
|
|
|
|
if not tax_report:
|
|
tax_report = generic_tax_report
|
|
|
|
options = {
|
|
'date': {
|
|
'date_from': fields.Date.to_string(date_from),
|
|
'date_to': fields.Date.to_string(date_to),
|
|
'filter': 'custom',
|
|
'mode': 'range',
|
|
},
|
|
'fiscal_position': fpos_option,
|
|
'tax_unit': 'company_only',
|
|
}
|
|
|
|
if tax_report.country_id and tax_report.filter_multi_company == 'tax_units':
|
|
# Enforce multicompany if the closing is done for a tax unit
|
|
candidate_tax_unit = self.company_id.account_tax_unit_ids.filtered(lambda x: x.country_id == report_country)
|
|
if candidate_tax_unit:
|
|
options['tax_unit'] = candidate_tax_unit.id
|
|
company_ids = [company.id for company in candidate_tax_unit.sudo().company_ids]
|
|
else:
|
|
company_ids = self.env.company.ids
|
|
else:
|
|
company_ids = self.env.company.ids
|
|
|
|
report_options = tax_report.with_context(allowed_company_ids=company_ids)._get_options(previous_options=options)
|
|
if 'tax_report_control_error' in report_options:
|
|
# This key will be set to False in the options by a custom init_options for reports adding control lines to themselves.
|
|
# Its presence indicate that we need to compute the report in order to run the actual checks. The options dictionary will then be
|
|
# modified in place (by a dynamic lines function) to contain the right check value under that key (see l10n_be_reports for an example).
|
|
tax_report._get_lines(report_options)
|
|
|
|
return tax_report, report_options
|
|
|
|
def _get_vat_report_attachments(self, report, options):
|
|
# Fetch pdf
|
|
pdf_data = report.export_to_pdf(options)
|
|
return [(pdf_data['file_name'], pdf_data['file_content'])]
|