Files
enterprise-addons/l10n_ma_reports/models/account_report.py
T

191 lines
9.5 KiB
Python

from odoo import api, fields, models, _
from odoo.tools import date_utils, SQL
L10N_MA_CUSTOMS_VAT_ICE = '20727020'
class L10n_MaTaxReportHandler(models.AbstractModel):
_name = 'l10n_ma.tax.report.handler'
_inherit = ['account.tax.report.handler']
_description = 'Moroccan Tax Report Custom Handler'
def _custom_options_initializer(self, report, options, previous_options):
super()._custom_options_initializer(report, options, previous_options=previous_options)
options.setdefault('buttons', []).append({
'name': _('XML'),
'sequence': 30,
'action': 'export_file',
'action_param': 'l10n_ma_reports_export_vat_to_xml',
'file_export_type': _('XML'),
})
@api.model
def _l10n_ma_prepare_vat_report_header_values(self, company, bill_data_list, period_type, date_from):
template_vals = {
'errors': {},
'year': str(date_from.year),
}
if period_type == 'trimester':
template_vals['period'] = date_utils.get_quarter_number(date_from)
template_vals['regime_code'] = '2'
else:
template_vals['period'] = date_from.month
template_vals['regime_code'] = '1'
template_vals['vat_id'] = company.vat
# Check for different errors in the report
incomplete_vendor_ids = [data['partner_id'] for data in bill_data_list if data['partner_from_ma'] and (not data['partner_vat'] or not data['partner_ice'])]
self._check_l10n_ma_report_errors(self.env['res.partner'].browse(incomplete_vendor_ids), period_type, template_vals, company)
return template_vals
def _check_l10n_ma_report_errors(self, errored_vendors, period_type, template_vals, company):
if errored_vendors:
template_vals['errors']['partner_vat_ice_missing'] = {
'message': _('There are partners located in Morocco without any ICE and/or Tax ID specified.'
' The resulting XML will not contain the associated vendor bills.'),
'action_text': _('View Partner(s)'),
'action': errored_vendors._get_records_action(name=_('Invalid Partner(s)')),
}
if period_type not in {'monthly', 'trimester'}:
template_vals['errors']['period_invalid'] = {
'message': _('This report only supports monthly and quarterly periods.'),
'level': 'danger',
}
if not company.vat:
template_vals['errors']['company_vat_missing'] = {
'message': _('Company %s has no VAT number and it is required to generate the XML file.', company.name),
'action_text': _('View Company/ies'),
'action': company.partner_id._get_records_action(name=_('Invalid Company/ies')),
'level': 'danger',
}
@api.model
def _l10n_ma_prepare_vat_report_bill_values(self, bill_data_list, prorata_value):
template_vals = {
'bills': [],
}
if int(prorata_value):
template_vals['prorata'] = prorata_value
index = 1
for bill_data in bill_data_list:
if bill_data['partner_from_ma'] and not (bill_data['partner_vat'] and bill_data['partner_ice']):
continue
sign = -1 if bill_data['is_inbound'] else 1
payment_methods = [method for method in (bill_data['payment_method'] or []) if method is not None]
base_amount = self.env.company.currency_id.round(bill_data['tax_amount'] / (bill_data['tax_rate'] / 100))
template_vals['bills'].append({
'name': bill_data['move_name'],
'sequence': index,
'base_amount': base_amount * sign,
'tax_amount': bill_data['tax_amount'] * sign,
'total_amount': self.env.company.currency_id.round((bill_data['tax_amount'] + base_amount) * sign),
'partner_vat': bill_data['partner_vat'] or L10N_MA_CUSTOMS_VAT_ICE,
'partner_name': bill_data['partner_name'],
'partner_ice': bill_data['partner_ice'] or L10N_MA_CUSTOMS_VAT_ICE,
'tax_rate': bill_data['tax_rate'] * sign,
'payment_method': payment_methods[0] if len(payment_methods) == 1 else '7',
'payment_date': bill_data['payment_date'] or '',
'invoice_date': bill_data['invoice_date'],
})
index += 1
return template_vals
@api.model
def _l10n_ma_prepare_vat_report_values(self, options):
report = self.env['account.report'].browse(options['report_id'])
deductions_expr = self.env.ref('l10n_ma.tax_report_part_d_tax_sum')
tags = deductions_expr._expand_aggregations()._get_matching_tags()
report_query = report._get_report_query(options, 'strict_range', domain=[('tax_tag_ids', 'in', tags.ids)])
if 'account_move_line__move_id' not in report_query._joins:
report_query.join('account_move_line', 'move_id', 'account_move', 'id', 'move_id')
if 'account_move_line__partner_id' not in report_query._joins:
report_query.left_join('account_move_line', 'partner_id', 'res_partner', 'id', 'partner_id')
self.env.cr.execute(SQL(
"""
SELECT
COALESCE(MIN(caba_origin_move.name), MIN(account_move_line__move_id.name)) AS move_name,
SUM(account_move_line.balance) AS tax_amount,
CASE WHEN account_move_line__partner_id.country_id = %(ma_country_id)s
THEN account_move_line__partner_id.vat
ELSE account_move_line__partner_id.l10n_ma_customs_vat
END AS partner_vat,
account_move_line__partner_id.name as partner_name,
account_move_line__partner_id.company_registry AS partner_ice,
tax.amount AS tax_rate,
CASE WHEN MIN(caba_origin_move.id) IS NULL
THEN NULL
ELSE ARRAY_AGG(DISTINCT caba_payment.l10n_ma_reports_payment_method)
END AS payment_method,
CASE WHEN MIN(caba_origin_move.id) IS NULL
THEN NULL -- We make the assumption it's not important to provide it if not using cash basis
ELSE GREATEST(MAX(account_move_line__move_id.date), MAX(caba_origin_move.date))
END AS payment_date,
COALESCE(MIN(caba_origin_move.invoice_date), MIN(account_move_line__move_id.invoice_date)) AS invoice_date,
COALESCE(MIN(caba_origin_move.move_type), MIN(account_move_line__move_id.move_type)) IN %(inbound_types)s AS is_inbound,
account_move_line__partner_id.country_id = %(ma_country_id)s AS partner_from_ma,
account_move_line__partner_id.id AS partner_id
FROM
%(table_references)s
JOIN account_tax tax
ON tax.id = account_move_line.tax_line_id -- Only tax lines receive tags from the report, so it's fine to do this
AND tax.amount_type = 'percent'
LEFT JOIN account_move caba_origin_move
ON caba_origin_move.id = account_move_line__move_id.tax_cash_basis_origin_move_id
LEFT JOIN account_partial_reconcile caba_partial
ON account_move_line__move_id.tax_cash_basis_rec_id = caba_partial.id
LEFT JOIN account_move_line caba_payment_aml
ON caba_payment_aml.id IN (caba_partial.debit_move_id, caba_partial.credit_move_id)
AND caba_payment_aml.move_id != caba_origin_move.id
LEFT JOIN account_payment caba_payment
ON caba_payment.move_id = caba_payment_aml.move_id
WHERE %(search_condition)s
GROUP BY account_move_line__partner_id.id, COALESCE(caba_origin_move.id, account_move_line__move_id.id), tax.id
HAVING SUM(account_move_line.balance) != 0
""",
table_references=report_query.from_clause,
search_condition=report_query.where_clause,
ma_country_id=self.env.ref('base.ma').id,
inbound_types=tuple(self.env['account.move'].get_inbound_types()),
))
bill_data_list = self.env.cr.dictfetchall()
date_from = fields.Date.from_string(options['date'].get('date_from'))
period_type = options['return_periodicity']['periodicity']
template_vals = self._l10n_ma_prepare_vat_report_header_values(self.env.company, bill_data_list, period_type, date_from)
prorata_expression = self.env.ref('l10n_ma.l10n_ma_vat_d_prorata_pro')
prorata_expr_totals = report._compute_expression_totals_for_each_column_group(prorata_expression, options)
prorata_value = prorata_expr_totals[next(iter(options['column_groups']))][prorata_expression]['value']
template_vals |= self._l10n_ma_prepare_vat_report_bill_values(bill_data_list, prorata_value)
return template_vals
@api.model
def l10n_ma_reports_export_vat_to_xml(self, options):
report = self.env['account.report'].browse(options['report_id'])
template_values = self._l10n_ma_prepare_vat_report_values(options)
return report._generate_file_data_with_error_check(
options,
self.env['ir.qweb']._render,
{'values': template_values, 'template': 'l10n_ma_reports.l10n_ma_tax_report_template', 'file_type': 'xml'},
template_values['errors'],
)