1982 lines
122 KiB
Python
1982 lines
122 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import fields, models, _
|
|
from odoo.exceptions import RedirectWarning, UserError
|
|
import odoo.release
|
|
from odoo.tools import SQL, float_is_zero
|
|
from odoo.tools.float_utils import float_split_str, float_compare
|
|
|
|
from datetime import datetime
|
|
from dateutil.relativedelta import relativedelta
|
|
import re
|
|
import unicodedata
|
|
|
|
SPANISH_PROVINCES_REPORT_CODES = {
|
|
'VI': '01',
|
|
'AB': '02',
|
|
'A': '03',
|
|
'AL': '04',
|
|
'AV': '05',
|
|
'BA': '06',
|
|
'PM': '07',
|
|
'B': '08',
|
|
'BU': '09',
|
|
'CC': '10',
|
|
'CA': '11',
|
|
'CS': '12',
|
|
'CR': '13',
|
|
'CO': '14',
|
|
'C': '15',
|
|
'CU': '16',
|
|
'GI': '17',
|
|
'GR': '18',
|
|
'GU': '19',
|
|
'SS': '20',
|
|
'H': '21',
|
|
'HU': '22',
|
|
'J': '23',
|
|
'LE': '24',
|
|
'L': '25',
|
|
'LO': '26',
|
|
'LU': '27',
|
|
'M': '28',
|
|
'MA': '29',
|
|
'MU': '30',
|
|
'NA': '31',
|
|
'OR': '32',
|
|
'O': '33',
|
|
'P': '34',
|
|
'GC': '35',
|
|
'PO': '36',
|
|
'SA': '37',
|
|
'TF': '38',
|
|
'S': '39',
|
|
'SG': '40',
|
|
'SE': '41',
|
|
'SO': '42',
|
|
'T': '43',
|
|
'TE': '44',
|
|
'TO': '45',
|
|
'V': '46',
|
|
'VA': '47',
|
|
'BI': '48',
|
|
'ZA': '49',
|
|
'Z': '50',
|
|
'CE': '51',
|
|
'ME': '52',
|
|
}
|
|
|
|
# including retention tax lines as we need to get the amounts before retention
|
|
MOD_347_CUSTOM_ENGINES_DOMAINS = {
|
|
'_report_custom_engine_threshold_insurance_bought': [
|
|
('move_id.l10n_es_reports_mod347_invoice_type', '=', 'insurance'),
|
|
('move_id.move_type', 'in', ('in_invoice', 'in_refund', 'in_receipt')),
|
|
'|',
|
|
('account_type', '=', 'liability_payable'),
|
|
('tax_line_id.l10n_es_type', '=', 'retencion'),
|
|
],
|
|
|
|
'_report_custom_engine_threshold_insurance_sold': [
|
|
('move_id.l10n_es_reports_mod347_invoice_type', '=', 'insurance'),
|
|
('move_id.move_type', 'in', ('out_invoice', 'out_refund', 'out_receipt')),
|
|
'|',
|
|
('account_type', '=', 'asset_receivable'),
|
|
('tax_line_id.l10n_es_type', '=', 'retencion'),
|
|
],
|
|
|
|
'_report_custom_engine_threshold_regular_bought': [
|
|
('move_id.l10n_es_reports_mod347_invoice_type', '=', 'regular'),
|
|
('move_id.move_type', 'in', ('in_invoice', 'in_refund', 'in_receipt')),
|
|
'|',
|
|
('account_type', '=', 'liability_payable'),
|
|
('tax_line_id.l10n_es_type', '=', 'retencion'),
|
|
],
|
|
|
|
'_report_custom_engine_threshold_regular_sold': [
|
|
('move_id.l10n_es_reports_mod347_invoice_type', '=', 'regular'),
|
|
('move_id.move_type', 'in', ('out_invoice', 'out_refund', 'out_receipt')),
|
|
'|',
|
|
('account_type', '=', 'asset_receivable'),
|
|
('tax_line_id.l10n_es_type', '=', 'retencion'),
|
|
],
|
|
|
|
'_report_custom_engine_threshold_all_operations': [
|
|
('move_id.l10n_es_reports_mod347_invoice_type', '!=', None),
|
|
'|',
|
|
('account_type', 'in', ('asset_receivable', 'liability_payable')),
|
|
('tax_line_id.l10n_es_type', '=', 'retencion'),
|
|
],
|
|
}
|
|
|
|
MOD_349_KEYS = ('A', 'E', 'T', 'S', 'I', 'M', 'H', 'R', 'D', 'C')
|
|
|
|
MOD_349_CUSTOM_ENGINES_SPLIT_REGEX = re.compile(r'_report_custom_engine_modelo349_(invoice|refund)_([eatsimhrdc])')
|
|
|
|
|
|
class AccountReport(models.Model):
|
|
_inherit = 'account.report'
|
|
|
|
def _get_expression_audit_aml_domain(self, expression, options):
|
|
if expression.engine == 'custom':
|
|
# Allow auditing mod347's threshold lines (for consistency: this way all the lines of the report are audited in the same way)
|
|
if expression.formula in MOD_347_CUSTOM_ENGINES_DOMAINS:
|
|
return MOD_347_CUSTOM_ENGINES_DOMAINS[expression.formula]
|
|
# Allow auditing mod349 summary lines
|
|
matching = MOD_349_CUSTOM_ENGINES_SPLIT_REGEX.fullmatch(expression.formula)
|
|
if matching:
|
|
move_type = matching.group(1)
|
|
key = matching.group(2).capitalize()
|
|
return self.env[self.custom_handler_model_name]._get_modelo349_audit_aml_domain(options, key, move_type)
|
|
return super()._get_expression_audit_aml_domain(expression, options)
|
|
|
|
|
|
class L10n_EsTaxReportHandler(models.AbstractModel):
|
|
_name = 'l10n_es.tax.report.handler'
|
|
_inherit = ['account.tax.report.handler']
|
|
_description = 'Spanish Tax Report Custom Handler'
|
|
|
|
def _append_boe_button(self, options, boe_number):
|
|
options['buttons'].append(
|
|
{
|
|
'name': _('BOE'),
|
|
'sequence': 0,
|
|
'action': 'open_boe_wizard',
|
|
'action_param': boe_number,
|
|
'file_export_type': _('BOE'),
|
|
'branch_allowed': True,
|
|
})
|
|
|
|
def open_boe_wizard(self, options, boe_number):
|
|
""" Triggers the generation of the BOE file for the current mod report.
|
|
In case this BOE file needs some more data to be entered manually by
|
|
the user, it show instead a wizard prompting for them, which will, once
|
|
validated and closed, trigger the generation of the BOE itself.
|
|
"""
|
|
period, _year = self._get_mod_period_and_year(options)
|
|
if boe_number == 390:
|
|
# period will be falsy if a whole year is selected
|
|
if period and not options.get('_running_export_test'):
|
|
raise UserError(_("Wrong report dates for BOE generation : please select a range of a year."))
|
|
elif boe_number != 347 and not period:
|
|
raise UserError(_("Wrong report dates for BOE generation : please select a range of one month or a trimester."))
|
|
|
|
return {
|
|
'name': _('Print BOE'),
|
|
'view_mode': 'form',
|
|
'views': [(False, 'form')],
|
|
'res_model': f'l10n_es_reports.aeat.boe.mod{boe_number}.export.wizard',
|
|
'type': 'ir.actions.act_window',
|
|
'target': 'new',
|
|
'context': {
|
|
**self.env.context,
|
|
'l10n_es_reports_report_options': options,
|
|
'default_report_id': options['sections_source_id'],
|
|
},
|
|
}
|
|
|
|
def _get_mod_period_and_year(self, options):
|
|
""" Returns the period and year (in terms of AEAT modulo reports regulation)
|
|
corresponding to the report options given in parameters, in the form
|
|
of a tuple (period, year). Period will be None if the dates do not fit
|
|
any.
|
|
|
|
A UserError will be raised if the start and end date of given in the
|
|
options do not corresond to the first and last day of their respective
|
|
month, or belong to two different years.
|
|
"""
|
|
date_options = (options or {}).get('date', {})
|
|
if not date_options.get('date_from') or not date_options.get('date_to'):
|
|
raise UserError(_("Cannot generate a BOE file for an open-ended date range."))
|
|
|
|
date_from = datetime.strptime(options['date']['date_from'], "%Y-%m-%d")
|
|
date_to = datetime.strptime(options['date']['date_to'], "%Y-%m-%d")
|
|
|
|
if not date_from.year == date_to.year:
|
|
raise UserError(_("Cannot generate a BOE file for two different years"))
|
|
|
|
if date_from.day != 1 or date_to.day != (date_to + relativedelta(day=31)).day:
|
|
raise UserError(_("Your date range does not cover entire months, please use a start and end date matching respectively the first and last day of a month."))
|
|
|
|
rslt_period = None
|
|
rslt_year = str(date_from.year) # Identical to date_to.year thanks to the previous conditions
|
|
if date_from.month == date_to.month:
|
|
rslt_period = '%02d' % (date_from.month)
|
|
elif date_from.month == date_to.month - 2 and self._retrieve_period_and_year(date_from, trimester=True)[0] == self._retrieve_period_and_year(date_to, trimester=True)[0]:
|
|
rslt_period = '%01dT' % (date_to.month / 3)
|
|
# Period stays None otherwize, so we can use rslt_period == None to check if a trimester or year is selected
|
|
|
|
return rslt_period, rslt_year
|
|
|
|
def _retrieve_period_and_year(self, date, trimester=False):
|
|
""" Retrieves the period and year (in the form of a tuple) corresponding
|
|
to a given date.
|
|
|
|
:param trimester: whether or not we use trimesters as periods.
|
|
"""
|
|
if trimester:
|
|
return '%01dT' % (1 + ((date.month - 1) // 3)), str(date.year)
|
|
else:
|
|
return '%02d' % date.month, str(date.year)
|
|
|
|
def _convert_period_to_dates(self, period, year):
|
|
""" Converts a period and a year to a tuple of two dates, respectively its
|
|
start and end date.
|
|
"""
|
|
if period[-1] == 'T':
|
|
quarter = int(period[:-1])
|
|
return datetime(day=1, month=1 + (quarter - 1) * 3, year=int(year)), (datetime(day=1, month=quarter * 3, year=int(year)) + relativedelta(day=31)) # relativedelta used to force last day of the month without triggering ValueError for months with less than 31 days
|
|
else:
|
|
return datetime(day=1, month=int(period), year=int(year)), datetime(day=1, month=int(period), year=int(year)) + relativedelta(day=31)
|
|
|
|
def _l10n_es_boe_format_string(self, string, length=-1, align='left', fill_char=b' '):
|
|
""" Formats a string so that it is BOE-compatible.
|
|
|
|
:param string: the string to format
|
|
:param length: the desired length of the resulting string, or -1 if there is not
|
|
:param align: 'left' or 'right', depending on the side of the result string where string must placed (no effect if no length is given)
|
|
:param fill_char: the character that will be used to bring the result string to a size of length (no effect if length is not specified)
|
|
"""
|
|
string = string and string.upper() or ''
|
|
rslt = b''
|
|
for char in unicodedata.normalize('NFKC', string): # We use a normalized version of the string here so that we are sure accentuated charcaters are each time encoded with only one character (and not a regular character followed by a combining one)
|
|
if not char in ('Ñ', 'Ç'):
|
|
normalized_char = unicodedata.normalize('NFD', char) # Not NFKD, as the NFKC normalization in the loop already ensures good treatment of compatibility characters. NFD splits accentuated characters in two parts: the original character, and the accent to combine it with
|
|
rslt += normalized_char.encode('ISO-8859-1', 'ignore') # Combinable accentuation characters are not supported by this encoding, and disappear when transcoding.
|
|
else:
|
|
rslt += char.encode('ISO-8859-1')
|
|
|
|
if length > -1:
|
|
rslt = rslt[:length]
|
|
if align == 'left':
|
|
rslt = rslt.ljust(length, fill_char)
|
|
elif align == 'right':
|
|
rslt = rslt.rjust(length, fill_char)
|
|
|
|
return rslt
|
|
|
|
def _l10n_es_boe_format_number(self, options, number, length=-1, decimal_places=0, signed=False, sign_neg='N', sign_pos='', in_currency=False):
|
|
""" Formats a number to a BOE-compatible string.
|
|
|
|
:param number: the number to format
|
|
:param length: the desired length for the resulting string, or -1, to just use the number of characters of the number.
|
|
:param decimal_places: the number of decimal places to use (these characters are part of the length limit)
|
|
:param signed: whether or not the number must be signed in the resulting string
|
|
:param sign_neg: the character to use as the first character of the resulting string if signed is True and
|
|
the number was negative (the resulting string will contain no additional - sign)
|
|
:param sign_pos: same as sign_neg, but if number is positive
|
|
:param in_currency: True iff number is expressed in company currency (and thus needs to be converted in €)
|
|
"""
|
|
company = self.env.company
|
|
|
|
if in_currency:
|
|
# If number is an amount expressed in company currency, we ensure that it
|
|
# is written in € in BOE file
|
|
conversion_date = options['date']['date_to']
|
|
curr_eur = self.env["res.currency"].search([('name', '=', 'EUR')], limit=1)
|
|
number = company.currency_id._convert(number, curr_eur, company, conversion_date)
|
|
|
|
if isinstance(number, float):
|
|
split_number = float_split_str(abs(number), decimal_places)
|
|
str_number = split_number[0] + split_number[1]
|
|
else:
|
|
str_number = str(abs(number)) + '0' * decimal_places
|
|
|
|
negative_amount = in_currency and company.currency_id.compare_amounts(number, 0.0) == -1 or number < 0
|
|
sign_str = signed and (negative_amount and sign_neg or sign_pos) or ''
|
|
|
|
# Done in two parts, so that sign str is always in front of the filling characters
|
|
return self._l10n_es_boe_format_string(sign_str) + self._l10n_es_boe_format_string(str_number, length=length - len(sign_str), align='right', fill_char=b'0')
|
|
|
|
def _retrieve_casilla_lines(self, report_lines):
|
|
""" Retrieves the values of the casillas contained in report_lines, using
|
|
the fact that these lines' names are prefixed by their number between [] to
|
|
identify them. Returns a dictionnary, with casillas as keys and their values
|
|
as values.
|
|
"""
|
|
casilla_pattern = re.compile(r'\[(?P<casilla>.*)\]')
|
|
rslt = {}
|
|
for line in report_lines:
|
|
matcher = casilla_pattern.match(line['name'])
|
|
if matcher:
|
|
casilla = matcher.group('casilla')
|
|
casilla_value = line['columns'][0]['no_format'] # Element [0] is the current period, in case we are comparing
|
|
|
|
rslt[casilla] = casilla_value
|
|
|
|
return rslt
|
|
|
|
def _retrieve_report_expression(self, options, xmlid):
|
|
""" Retrieves the data of the report line denoted by xmlid, with respect
|
|
to the given options.
|
|
"""
|
|
expression = self.env.ref(xmlid)
|
|
expression_totals = self.env['account.report'].browse(options['report_id'])._compute_expression_totals_for_each_column_group(expression._expand_aggregations(), options)
|
|
# This considers we have but one column group
|
|
return next(expr_total[expression]['value'] for expr_total in expression_totals.values())
|
|
|
|
def _get_bic_and_iban(self, res_partner_bank):
|
|
""" Convenience method returning (bic,iban) of the given account if
|
|
this account exists, or a tuple of empty strings otherwise.
|
|
"""
|
|
if res_partner_bank:
|
|
return res_partner_bank.bank_bic or "", res_partner_bank.sanitized_acc_number
|
|
|
|
return '', ''
|
|
|
|
def _retrieve_boe_manual_wizard(self, options, modelo_number):
|
|
""" Retrieves a BOE manual wizard object from its id, contained within the
|
|
options dict.
|
|
"""
|
|
return self.env[f'l10n_es_reports.aeat.boe.mod{modelo_number}.export.wizard'].browse(options['l10n_es_reports_boe_wizard_id'])
|
|
|
|
def _call_on_partner_sublines(self, report_options, line_xml_id, fun_to_call, required_ids_set=None):
|
|
""" Calls a function on the data of all the sublines generated by a
|
|
groupby parameter for a report line (except the one giving the total).
|
|
|
|
:param report_options: the options to use to generate line data
|
|
:param line_xml_id: the xml id of the report line whose children we want to call our function on
|
|
:param fun_to_call: the function to call on sublines. It must take only one argument, the data dictionary of the subline.
|
|
:param required_ids_set: a set containing ids on which we want fun_to_call to be called.
|
|
This is used to generate data for models that are not present
|
|
in the grouped line displayed on the report. (this can for example
|
|
happen if they have no operation in this year; but
|
|
some data to be added into BOE make in necessary to still include
|
|
them in the file). This set will be modified by the function.
|
|
"""
|
|
if required_ids_set is None:
|
|
required_ids_set = set()
|
|
rslt = self._l10n_es_boe_format_string('')
|
|
report = self.env['account.report'].browse(report_options['report_id'])
|
|
report_line = self.env.ref(line_xml_id)
|
|
line_dict_id = report._get_generic_line_id('account.report.line', report_line.id)
|
|
for subline in report._report_expand_unfoldable_line_with_groupby(line_dict_id, report_line.groupby, report_options, None, 0)['lines']:
|
|
subline_model, subline_model_id = report._get_model_info_from_id(subline['id'])
|
|
|
|
if subline_model == 'res.partner':
|
|
rslt += fun_to_call({'line_data': subline, 'line_xml_id': line_xml_id, 'report_options': report_options})
|
|
if subline_model_id in required_ids_set:
|
|
required_ids_set.remove(subline_model_id)
|
|
|
|
for element in required_ids_set: # These elements are the ones for wich no line was generated, but that were into the original required ids set. So, we still treat them.
|
|
rslt += fun_to_call({
|
|
'line_data': {'id': report._get_generic_line_id('res.partner', element)},
|
|
'line_xml_id': line_xml_id,
|
|
'report_options': report_options
|
|
})
|
|
|
|
return rslt
|
|
|
|
def _get_partner_subline(self, report_options, line_xml_id, partner_id):
|
|
""" Returns the data of a subline generated by a groupby parameter, if its
|
|
'id' (i.e. the actual id of the model denoted by groupby represented by the
|
|
line) is equal to a given value.
|
|
|
|
:param report_options: the options to use to generate data
|
|
:param line_xml_id: the xml id of the parent line
|
|
:param sub_line_id: the id of the "grouped by" model corresponding to the subline we want to retrieve
|
|
"""
|
|
report = self.env['account.report'].browse(report_options['report_id'])
|
|
report_line = self.env.ref(line_xml_id)
|
|
line_dict_id = report._get_generic_line_id('account.report.line', report_line.id)
|
|
for subline in report._report_expand_unfoldable_line_with_groupby(line_dict_id, report_line.groupby, report_options, None, 0)['lines']:
|
|
subline_model, model_id = report._get_model_info_from_id(subline['id'])
|
|
if subline_model == 'res.partner' and model_id == partner_id:
|
|
return subline
|
|
|
|
def _extract_tin(self, partner, error_if_no_tin=True):
|
|
if not partner.vat:
|
|
if error_if_no_tin:
|
|
raise UserError(_(
|
|
"No TIN set for partner %(name)s (id %(id)d). Please define one.",
|
|
name=partner.name,
|
|
id=partner.id,
|
|
))
|
|
else:
|
|
return ''
|
|
|
|
country_code, number = partner._split_vat(partner.vat)
|
|
return country_code.upper() + number
|
|
|
|
def _extract_spanish_tin(self, partner, except_if_foreign=False):
|
|
formatted_tin = self._extract_tin(partner, error_if_no_tin=True)
|
|
is_spanish = formatted_tin[:2] == 'ES' or not partner.country_id or partner.country_code == 'ES'
|
|
if is_spanish:
|
|
return formatted_tin.removeprefix('ES')
|
|
elif except_if_foreign:
|
|
raise UserError(_("Reading a non-Spanish TIN as a Spanish TIN."))
|
|
return ''
|
|
|
|
def _generate_111_115_common_header(self, options, period, year, modelo_number):
|
|
rslt = b''
|
|
|
|
# Wizard with manually-entered data
|
|
boe_wizard = self._retrieve_boe_manual_wizard(options, modelo_number)
|
|
|
|
# Header
|
|
current_company = self.env.company
|
|
rslt += self._l10n_es_boe_format_string(f"<T{modelo_number}0{year}{period}0000>")
|
|
rslt += self._l10n_es_boe_format_string('<AUX>')
|
|
rslt += self._l10n_es_boe_format_string(' ' * 70) # Reserved for AEAT
|
|
odoo_version = odoo.release.version.split('.')
|
|
rslt += self._l10n_es_boe_format_string(str(odoo_version[0]) + str(odoo_version[1]), length=4)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 4) # Reserved for AEAT
|
|
rslt += self._l10n_es_boe_format_string(self._extract_spanish_tin(current_company.partner_id), length=9)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 213) # Reserved for AEAT
|
|
rslt += self._l10n_es_boe_format_string('</AUX>')
|
|
|
|
# Fills in the common fields between mod 111 and 115
|
|
rslt += self._l10n_es_boe_format_string(f"<T{modelo_number}01000>")
|
|
rslt += self._l10n_es_boe_format_string(' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.declaration_type)
|
|
rslt += self._l10n_es_boe_format_string(self._extract_spanish_tin(current_company.partner_id), length=9)
|
|
rslt += self._l10n_es_boe_format_string(current_company.name, length=60)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 20) # We keep the name of the declaring party blank here, as it is a company
|
|
rslt += self._l10n_es_boe_format_string(year)
|
|
rslt += self._l10n_es_boe_format_string(period)
|
|
|
|
return rslt
|
|
|
|
|
|
class L10n_EsMod111TaxReportHandler(models.AbstractModel):
|
|
_name = 'l10n_es.mod111.tax.report.handler'
|
|
_inherit = ['l10n_es.tax.report.handler']
|
|
_description = 'Spanish Tax Report Custom Handler (Mod111)'
|
|
|
|
def _custom_options_initializer(self, report, options, previous_options):
|
|
super()._custom_options_initializer(report, options, previous_options=previous_options)
|
|
super()._append_boe_button(options, 111)
|
|
|
|
def export_boe(self, options):
|
|
period, year = self._get_mod_period_and_year(options)
|
|
|
|
rslt = self._generate_111_115_common_header(options, period, year, 111)
|
|
report = self.env['account.report'].browse(options['report_id'])
|
|
report_lines = report._get_lines(options)
|
|
casilla_lines_map = self._retrieve_casilla_lines(report_lines)
|
|
|
|
# Wizard with manually-entered data
|
|
boe_wizard = self._retrieve_boe_manual_wizard(options, 111)
|
|
|
|
# Content of the report
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['01'], length=8, signed=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['02'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['03'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['04'], length=8, signed=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['05'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['06'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['07'], length=8, signed=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['08'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['09'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['10'], length=8, signed=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['11'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['12'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['13'], length=8, signed=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['14'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['15'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['16'], length=8, signed=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['17'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['18'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['19'], length=8, signed=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['20'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['21'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['22'], length=8, signed=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['23'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['24'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['25'], length=8, signed=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['26'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['27'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['28'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['29'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['30'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and 'X' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and boe_wizard.previous_report_number or '', length=13)
|
|
rslt += self._l10n_es_boe_format_string(' ') # Reserved for AEAT
|
|
dummy, iban = self._get_bic_and_iban(boe_wizard.partner_bank_id)
|
|
rslt += self._l10n_es_boe_format_string(iban, length=34)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 389) # Reserved for AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 13) # Reserved for AEAT
|
|
|
|
# We close the tags... (They have been opened by _generate_111_115_common_header)
|
|
rslt += self._l10n_es_boe_format_string('</T11101000>')
|
|
rslt += self._l10n_es_boe_format_string('</T1110' + year + period + '0000>')
|
|
|
|
return {
|
|
'file_name': report.get_default_report_filename(options, 'txt'),
|
|
'file_content': rslt,
|
|
'file_type': 'txt',
|
|
}
|
|
|
|
|
|
class L10n_EsMod115TaxReportHandler(models.AbstractModel):
|
|
_name = 'l10n_es.mod115.tax.report.handler'
|
|
_inherit = ['l10n_es.tax.report.handler']
|
|
_description = 'Spanish Tax Report Custom Handler (Mod115)'
|
|
|
|
def _custom_options_initializer(self, report, options, previous_options):
|
|
super()._custom_options_initializer(report, options, previous_options=previous_options)
|
|
super()._append_boe_button(options, 115)
|
|
|
|
def export_boe(self, options):
|
|
period, year = self._get_mod_period_and_year(options)
|
|
|
|
rslt = self._generate_111_115_common_header(options, period, year, 115)
|
|
report = self.env['account.report'].browse(options['report_id'])
|
|
report_lines = report._get_lines(options)
|
|
casilla_lines_map = self._retrieve_casilla_lines(report_lines)
|
|
|
|
# Wizard with manually-entered data
|
|
boe_wizard = self._retrieve_boe_manual_wizard(options, 115)
|
|
|
|
# Content of the report
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['01'], length=15, signed=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['02'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['03'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['04'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['05'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and 'X' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and boe_wizard.previous_report_number or '', length=13)
|
|
dummy, iban = self._get_bic_and_iban(boe_wizard.partner_bank_id)
|
|
rslt += self._l10n_es_boe_format_string(iban, length=34)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 236) # Reserved for AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 13) # Reserved for AEAT
|
|
|
|
# We close the tags... (They have been opened by _generate_111_115_common_header)
|
|
rslt += self._l10n_es_boe_format_string('</T11501000>')
|
|
rslt += self._l10n_es_boe_format_string('</T1150' + year + period + '0000>')
|
|
|
|
return {
|
|
'file_name': report.get_default_report_filename(options, 'txt'),
|
|
'file_content': rslt,
|
|
'file_type': 'txt',
|
|
}
|
|
|
|
|
|
class L10n_EsMod130TaxReportHandler(models.AbstractModel):
|
|
_name = 'l10n_es.mod130.tax.report.handler'
|
|
_inherit = ['l10n_es.tax.report.handler']
|
|
_description = 'Spanish Tax Report Custom Handler (Mod130)'
|
|
|
|
def _custom_options_initializer(self, report, options, previous_options=None):
|
|
super()._custom_options_initializer(report, options, previous_options=previous_options)
|
|
super()._append_boe_button(options, 130)
|
|
|
|
def open_boe_wizard(self, options, boe_number):
|
|
result = super().open_boe_wizard(options, boe_number)
|
|
result['res_model'] = 'l10n_es_reports.aeat.boe.mod130.export.wizard'
|
|
return result
|
|
|
|
def export_boe(self, options):
|
|
period, year = self._get_mod_period_and_year(options)
|
|
# Legal requirement for the export of boe file for modelo 130
|
|
boe_modelo_id = 'T13001000'
|
|
|
|
rslt = f"<{boe_modelo_id}>".encode()
|
|
report = self.env['account.report'].browse(options['report_id'])
|
|
report_lines = report._get_lines(options)
|
|
casilla_lines_map = self._retrieve_casilla_lines(report_lines)
|
|
|
|
# Wizard with manually-entered data
|
|
boe_wizard = self._retrieve_boe_manual_wizard(options, 130)
|
|
|
|
rslt += self._l10n_es_boe_format_string(' ' * 1)
|
|
rslt += self._l10n_es_boe_format_string(f'{boe_wizard.declaration_type}')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.taxpayer_id or 'n/a', length=9)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.taxpayer_last_name or 'n/a', length=60)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.taxpayer_first_name or 'n/a', length=20)
|
|
rslt += self._l10n_es_boe_format_string(year, length=4)
|
|
rslt += self._l10n_es_boe_format_string(period, length=2)
|
|
|
|
# Content of the report
|
|
for casilla in casilla_lines_map.values():
|
|
rslt += self._l10n_es_boe_format_number(options, casilla, length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and 'X' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and boe_wizard.previous_report_number or '', length=13)
|
|
_, iban = self._get_bic_and_iban(boe_wizard.partner_bank_id)
|
|
rslt += self._l10n_es_boe_format_string(iban, length=34)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 96)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 13)
|
|
|
|
rslt += self._l10n_es_boe_format_string(f'</{boe_modelo_id}>')
|
|
|
|
return {
|
|
'file_name': report.get_default_report_filename(options, 'txt'),
|
|
'file_content': rslt,
|
|
'file_type': 'txt',
|
|
}
|
|
|
|
|
|
class L10n_EsMod303TaxReportHandler(models.AbstractModel):
|
|
_name = 'l10n_es.mod303.tax.report.handler'
|
|
_inherit = ['l10n_es.tax.report.handler']
|
|
_description = 'Spanish Tax Report Custom Handler (Mod303)'
|
|
|
|
def _custom_options_initializer(self, report, options, previous_options):
|
|
super()._custom_options_initializer(report, options, previous_options=previous_options)
|
|
super()._append_boe_button(options, 303)
|
|
|
|
def export_boe(self, options):
|
|
period, year = self._get_mod_period_and_year(options)
|
|
|
|
report = self.env['account.report'].browse(options['report_id'])
|
|
report_lines = report._get_lines(options)
|
|
casilla_lines_map = self._retrieve_casilla_lines(report_lines)
|
|
current_company = self.env.company
|
|
|
|
# Header
|
|
rslt = self._l10n_es_boe_format_string('<T3030' + year + period + '0000>')
|
|
rslt += self._l10n_es_boe_format_string('<AUX>')
|
|
rslt += self._l10n_es_boe_format_string(' ' * 70)
|
|
odoo_version = odoo.release.version.split('.')
|
|
rslt += self._l10n_es_boe_format_string(str(odoo_version[0]) + str(odoo_version[1]), length=4)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 4)
|
|
rslt += self._l10n_es_boe_format_string(self._extract_spanish_tin(current_company.partner_id), length=9)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 213)
|
|
rslt += self._l10n_es_boe_format_string('</AUX>')
|
|
|
|
rslt += self._generate_page1(report, options, current_company, period, year, casilla_lines_map)
|
|
rslt += self._generate_page3(report, options, current_company, period, year, casilla_lines_map)
|
|
if options['date']['date_from'] >= '2023-01-01':
|
|
rslt += self._generate_page_did(report, options, current_company, period, year, casilla_lines_map)
|
|
# We don't need page 2 and 4 (specified in AEAT doc)
|
|
|
|
# We close the tags...
|
|
rslt += self._l10n_es_boe_format_string('</T3030' + year + period + '0000>')
|
|
|
|
return {
|
|
'file_name': report.get_default_report_filename(options, 'txt'),
|
|
'file_content': rslt,
|
|
'file_type': 'txt',
|
|
}
|
|
|
|
def _generate_page1(self, report, options, current_company, period, year, casilla_lines_map):
|
|
# Wizard with manually-entered data
|
|
boe_wizard = self._retrieve_boe_manual_wizard(options, 303)
|
|
|
|
rslt = self._l10n_es_boe_format_string('<T30301000>')
|
|
rslt += self._l10n_es_boe_format_string(' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.declaration_type)
|
|
rslt += self._l10n_es_boe_format_string(self._extract_spanish_tin(current_company.partner_id), length=9)
|
|
rslt += self._l10n_es_boe_format_string(current_company.name, length=80)
|
|
rslt += self._l10n_es_boe_format_string(year)
|
|
rslt += self._l10n_es_boe_format_string(period)
|
|
|
|
# Identification
|
|
rslt += self._l10n_es_boe_format_number(options, 2) # Tributación exclusivamente foral => Always "no", for simplicity
|
|
rslt += self._l10n_es_boe_format_number(options, boe_wizard.monthly_return and 1 or 2)
|
|
rslt += self._l10n_es_boe_format_number(options, 3)
|
|
rslt += self._l10n_es_boe_format_number(options, 2)
|
|
rslt += self._l10n_es_boe_format_number(options, 2)
|
|
rslt += self._l10n_es_boe_format_number(options, 2)
|
|
rslt += self._l10n_es_boe_format_number(options, 2)
|
|
rslt += self._l10n_es_boe_format_number(options, 2)
|
|
rslt += self._l10n_es_boe_format_number(options, 2)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 8)
|
|
rslt += self._l10n_es_boe_format_string(' ')
|
|
rslt += self._l10n_es_boe_format_number(options, boe_wizard._get_using_sii_2021_value())
|
|
|
|
exonerated_from_mod_390 = boe_wizard._get_exonerated_from_mod_390_2021_value(period)
|
|
rslt += self._l10n_es_boe_format_number(options, exonerated_from_mod_390)
|
|
|
|
if exonerated_from_mod_390 == 1:
|
|
profit_and_loss_report = self.env.ref('l10n_es_reports.financial_report_es_profit_and_loss')
|
|
end_date = fields.Date.from_string(options['date']['date_to'])
|
|
transactions_volume_options = profit_and_loss_report.get_options({
|
|
'date': {
|
|
'date_from': '%s-01-01' % end_date.year,
|
|
'date_to': '%s-12-31' % end_date.year,
|
|
'filter': 'custom',
|
|
'mode': 'range',
|
|
},
|
|
})
|
|
|
|
transactions_volume = self._retrieve_report_expression(transactions_volume_options, 'l10n_es_reports.es_profit_and_loss_line_1_balance')
|
|
annual_volume_indicator = current_company.currency_id.is_zero(transactions_volume) and 2 or 1
|
|
else:
|
|
annual_volume_indicator = 0
|
|
|
|
rslt += self._l10n_es_boe_format_number(options, annual_volume_indicator)
|
|
if int(year) >= 2026:
|
|
number = period not in ('01', '1T', '2T', '3T', '4T') and 2 or 0
|
|
rslt += self._l10n_es_boe_format_number(options, number, length=1)
|
|
|
|
# Casillas
|
|
if options['date']['date_from'] >= '2023-01-01':
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('150', 0), length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, 0, length=5) # Casilla 151 is constant
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('152', 0), length=17, decimal_places=2, in_currency=True)
|
|
|
|
if int(year) >= 2026:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('165', 0), length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, 0, length=5) # 166
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('167', 0), length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['01'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, 400, length=5) # Casilla 02 is constant
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['03'], length=17, decimal_places=2, in_currency=True)
|
|
|
|
if options['date']['date_from'] >= '2023-01-01':
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('153', 0), length=17, decimal_places=2, in_currency=True)
|
|
if options['date']['date_from'] >= '2025-01-01':
|
|
casilla_154 = 0
|
|
else:
|
|
casilla_154 = 750 if options['date']['date_from'] >= '2024-10-01' else 500
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_154, length=5)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('155', 0), length=17, decimal_places=2, in_currency=True)
|
|
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['04'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, 1000, length=5) # Casilla 05 is constant
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['06'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['07'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, 2100, length=5) # Casilla 08 is constant
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['09'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['10'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['11'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['12'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['13'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['14'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['15'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
if options['date']['date_from'] >= '2023-01-01':
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('156', 0), length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, 175, length=5) # Casilla 157 is constant
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('158', 0), length=17, decimal_places=2, in_currency=True)
|
|
|
|
if int(year) >= 2026:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('168', 0), length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, 50, length=5)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('170', 0), length=17, decimal_places=2, in_currency=True)
|
|
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['16'], length=17, decimal_places=2, in_currency=True)
|
|
if options['date']['date_from'] >= '2025-01-01':
|
|
casilla_17 = 0
|
|
else:
|
|
casilla_17 = 100 if options['date']['date_from'] >= '2024-10-01' else 50
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_17, length=5)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['18'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['19'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, 140, length=5) # Casilla 20 is constant
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['21'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['22'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, 520, length=5) # Casilla 23 is constant
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['24'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['25'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['26'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['27'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
for casilla in range(28, 40):
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)], length=17, decimal_places=2, in_currency=True)
|
|
|
|
for casilla in range(40, 47):
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
reserved_empty_chars = 600
|
|
if int(year) < 2026:
|
|
if options['date']['date_from'] >= '2024-10-01':
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('165', 0), length=17, decimal_places=2, in_currency=True)
|
|
if options['date']['date_from'] >= '2025-01-01':
|
|
rslt += self._l10n_es_boe_format_number(options, 0, length=5)
|
|
else:
|
|
rslt += self._l10n_es_boe_format_number(options, 200, length=5)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('167', 0), length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('168', 0), length=17, decimal_places=2, in_currency=True)
|
|
if options['date']['date_from'] >= '2025-01-01':
|
|
rslt += self._l10n_es_boe_format_number(options, 50, length=5)
|
|
else:
|
|
rslt += self._l10n_es_boe_format_number(options, 26, length=5)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('170', 0), length=17, decimal_places=2, in_currency=True)
|
|
reserved_empty_chars = 522
|
|
elif options['date']['date_to'] >= '2024-09-30':
|
|
rslt += self._l10n_es_boe_format_number(options, 0, length=78)
|
|
reserved_empty_chars = 522
|
|
else:
|
|
reserved_empty_chars = 521
|
|
# Footer of page 1
|
|
rslt += self._l10n_es_boe_format_string(' ' * reserved_empty_chars) # Reserved for AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 13) # Reserved for AEAT
|
|
rslt += self._l10n_es_boe_format_string('</T30301000>')
|
|
|
|
return rslt
|
|
|
|
def _generate_page3(self, report, options, current_company, period, year, casilla_lines_map):
|
|
rslt = self._l10n_es_boe_format_string('<T30303000>')
|
|
|
|
# Wizard with manually-entered data
|
|
boe_wizard = self._retrieve_boe_manual_wizard(options, 303)
|
|
boe_wizard_fields = boe_wizard.fields_get()
|
|
|
|
# Casillas
|
|
to_treat = ['59', '60']
|
|
|
|
for casilla in to_treat:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[casilla], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['120'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
if options['date']['date_from'] < '2022-01-01':
|
|
rslt += self._l10n_es_boe_format_number(options, 0, length=17)
|
|
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['122'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['123'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['124'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
# Next casillas
|
|
for casilla in (62, 63, 74, 75):
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, 0, length=17) # Normally casilla 76 (Regularization of quotas art. 80.Cinco.5ª LIVA)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['46'], length=17, decimal_places=2, signed=True, in_currency=True) # Should normally be casilla 64 (= sum of casillas 46, 58 and 76), but only casilla 46 is in our version of the report
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['65'], length=5, decimal_places=2)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['66'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['77'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('110', 0), length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('78', 0), length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('87', 0), length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['68'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
if int(year) >= 2026:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('108', 0), length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['69'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
if options['date']['date_from'] >= '2023-01-01':
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['70'], length=17, decimal_places=2, in_currency=True) # Unsigned from 2023 on
|
|
else:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['70'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
if options['date']['date_from'] >= '2023-01-01':
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('109', 0.0), length=17, decimal_places=2, in_currency=True)
|
|
|
|
if int(year) >= 2026:
|
|
rslt += self._l10n_es_boe_format_number(options, 0, length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['71'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
# Information about declaration
|
|
if options['date']['date_from'] >= '2023-01-01':
|
|
rslt += self._l10n_es_boe_format_string(casilla_lines_map['71'] == 0 and 'X' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and 'X' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and boe_wizard.previous_report_number or '', length=13)
|
|
else:
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and 'X' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and boe_wizard.previous_report_number or '', length=13)
|
|
rslt += self._l10n_es_boe_format_string(casilla_lines_map['71'] == 0 and 'X' or ' ')
|
|
|
|
if options['date']['date_from'] < '2023-01-01':
|
|
gov_giving_back = current_company.currency_id.compare_amounts(casilla_lines_map['71'], 0) == -1
|
|
partner_bank = boe_wizard.partner_bank_id
|
|
|
|
bic, iban = self._get_bic_and_iban(partner_bank)
|
|
|
|
rslt += self._l10n_es_boe_format_string(bic if gov_giving_back and iban and iban[:2] != 'ES' else '', length=11)
|
|
rslt += self._l10n_es_boe_format_string(iban, length=34)
|
|
|
|
# Reserved by AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 17)
|
|
|
|
# Devolución
|
|
if gov_giving_back:
|
|
bank = partner_bank.bank_id
|
|
rslt += self._l10n_es_boe_format_string(bank.name or '', length=70)
|
|
rslt += self._l10n_es_boe_format_string(' '.join([bank.street or '', bank.street2 or '']), length=35)
|
|
rslt += self._l10n_es_boe_format_string(bank.city or '', length=30)
|
|
rslt += self._l10n_es_boe_format_string(bank.country.code or '', length=2)
|
|
|
|
# Marca SEPA
|
|
if iban and boe_wizard.declaration_type != 'N':
|
|
iban_country_code = iban[:2]
|
|
if iban_country_code == 'ES':
|
|
marca = '1'
|
|
elif iban_country_code in self.env.ref('base.sepa_zone').mapped('country_ids.code'):
|
|
marca = '2'
|
|
else:
|
|
marca = '3'
|
|
else:
|
|
marca = '0'
|
|
|
|
rslt += self._l10n_es_boe_format_string(marca, length=1)
|
|
|
|
else:
|
|
# All those fields must be empty if the report for the current period isn't a return (Devolución),
|
|
# the file is rejected if they are not.
|
|
rslt += self._l10n_es_boe_format_string('', length=138)
|
|
|
|
# Reserved by AEAT
|
|
reserved_empty_chars = 600
|
|
if options['date']['date_to'] >= '2024-09-30':
|
|
rslt += self._l10n_es_boe_format_string('rectification_direct_debit' in boe_wizard_fields and boe_wizard.rectification_direct_debit and 'X' or ' ')
|
|
if int(year) < 2026:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('108', 0), length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map.get('111', 0), length=17, decimal_places=2, in_currency=True)
|
|
if int(year) < 2026:
|
|
rslt += self._l10n_es_boe_format_string(' ' * 120)
|
|
rslt += self._l10n_es_boe_format_string('rectification_motive_rectifications' in boe_wizard_fields and boe_wizard.rectification_motive_rectifications and 'X' or ' ')
|
|
rslt += self._l10n_es_boe_format_string('rectification_motive_discrepancy_adm_crit' in boe_wizard_fields and boe_wizard.rectification_motive_discrepancy_adm_crit and 'X' or ' ')
|
|
reserved_empty_chars = 443
|
|
elif options['date']['date_from'] < '2022-01-01':
|
|
reserved_empty_chars = 445
|
|
|
|
if int(year) >= 2026:
|
|
reserved_empty_chars = 546
|
|
|
|
rslt += self._l10n_es_boe_format_string(' ' * reserved_empty_chars)
|
|
|
|
# Footer of page 3
|
|
rslt += self._l10n_es_boe_format_string('</T30303000>')
|
|
|
|
return rslt
|
|
|
|
def _generate_page_did(self, report, options, current_company, period, year, casilla_lines_map):
|
|
rslt = self._l10n_es_boe_format_string('<T303DID00>')
|
|
|
|
# Wizard with manually-entered data
|
|
boe_wizard = self._retrieve_boe_manual_wizard(options, 303)
|
|
|
|
partner_bank = boe_wizard.partner_bank_id
|
|
|
|
bic, iban = self._get_bic_and_iban(partner_bank)
|
|
|
|
rslt += self._l10n_es_boe_format_string(bic if boe_wizard.declaration_type == 'X' else '', length=11)
|
|
rslt += self._l10n_es_boe_format_string(iban, length=34)
|
|
|
|
# Return in foreign bank account
|
|
if boe_wizard.declaration_type == 'X':
|
|
bank = partner_bank.bank_id
|
|
rslt += self._l10n_es_boe_format_string(bank.name or '', length=70)
|
|
rslt += self._l10n_es_boe_format_string(' '.join([bank.street or '', bank.street2 or '']), length=35)
|
|
rslt += self._l10n_es_boe_format_string(bank.city or '', length=30)
|
|
rslt += self._l10n_es_boe_format_string(bank.country.code or '', length=2)
|
|
else:
|
|
rslt += self._l10n_es_boe_format_string('', length=137)
|
|
|
|
# Marca SEPA
|
|
if iban and (boe_wizard.declaration_type in ('D', 'X') or casilla_lines_map.get('111')):
|
|
iban_country_code = iban[:2]
|
|
if iban_country_code == 'ES':
|
|
marca = '1'
|
|
elif iban_country_code in self.env.ref('base.sepa_zone').mapped('country_ids.code'):
|
|
marca = '2'
|
|
else:
|
|
marca = '3'
|
|
else:
|
|
marca = '0'
|
|
|
|
rslt += self._l10n_es_boe_format_string(marca, length=1)
|
|
|
|
# Reserved by AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 617)
|
|
|
|
# Footer of page 3
|
|
rslt += self._l10n_es_boe_format_string('</T303DID00>')
|
|
|
|
return rslt
|
|
|
|
|
|
class L10n_EsMod347TaxReportHandler(models.AbstractModel):
|
|
_name = 'l10n_es.mod347.tax.report.handler'
|
|
_inherit = ['l10n_es.tax.report.handler']
|
|
_description = 'Spanish Tax Report Custom Handler (Mod347)'
|
|
|
|
def _custom_options_initializer(self, report, options, previous_options):
|
|
super()._custom_options_initializer(report, options, previous_options=previous_options)
|
|
super()._append_boe_button(options, 347)
|
|
|
|
def _report_custom_engine_threshold_insurance_sold(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
domain = MOD_347_CUSTOM_ENGINES_DOMAINS['_report_custom_engine_threshold_insurance_sold']
|
|
return self._custom_threshold_common(domain, expressions, options, date_scope, current_groupby, next_groupby, offset=offset, limit=limit)
|
|
|
|
def _report_custom_engine_threshold_insurance_bought(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
domain = MOD_347_CUSTOM_ENGINES_DOMAINS['_report_custom_engine_threshold_insurance_bought']
|
|
return self._custom_threshold_common(domain, expressions, options, date_scope, current_groupby, next_groupby, offset=offset, limit=limit)
|
|
|
|
def _report_custom_engine_threshold_regular_bought(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
domain = MOD_347_CUSTOM_ENGINES_DOMAINS['_report_custom_engine_threshold_regular_bought']
|
|
return self._custom_threshold_common(domain, expressions, options, date_scope, current_groupby, next_groupby, offset=offset, limit=limit)
|
|
|
|
def _report_custom_engine_threshold_regular_sold(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
domain = MOD_347_CUSTOM_ENGINES_DOMAINS['_report_custom_engine_threshold_regular_sold']
|
|
return self._custom_threshold_common(domain, expressions, options, date_scope, current_groupby, next_groupby, offset=offset, limit=limit)
|
|
|
|
def _report_custom_engine_threshold_all_operations(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
domain = MOD_347_CUSTOM_ENGINES_DOMAINS['_report_custom_engine_threshold_all_operations']
|
|
next_groupby = 'move_id' if current_groupby == 'partner_id' else next_groupby
|
|
return self._custom_threshold_common(domain, expressions, options, date_scope, current_groupby, next_groupby, offset=offset, limit=limit)
|
|
|
|
def _custom_threshold_common(self, domain, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None):
|
|
""" Some lines of mod 347 report need to be grouped by partner, only keeping the partners whose balance for the line is above 3005.06€.
|
|
This function serves as a common helper to the custom engines handling these lines.
|
|
"""
|
|
report = self.env['account.report'].browse(options['report_id'])
|
|
report._check_groupby_fields((next_groupby.split(',') if next_groupby else []) + ([current_groupby] if current_groupby else []))
|
|
|
|
from_fy_dates = self.env.company.compute_fiscalyear_dates(fields.Date.from_string(options['date']['date_from']))
|
|
to_fy_dates = self.env.company.compute_fiscalyear_dates(fields.Date.from_string(options['date']['date_to']))
|
|
fy_options = {**options, 'date': options['date'].copy()}
|
|
|
|
# Only adapt dates for the threshold if from and to dates belong to the same fiscal year.
|
|
if from_fy_dates == to_fy_dates:
|
|
fy_options['date'].update({
|
|
'date_from': fields.Date.to_string(from_fy_dates['date_from']),
|
|
'date_to': fields.Date.to_string(from_fy_dates['date_to']),
|
|
'mode': 'range',
|
|
})
|
|
|
|
# First get all the partners that match the domain but don't reach the threshold. We'll have to exclude them
|
|
query = report._get_report_query(fy_options, date_scope, domain=domain + options.get('forced_domain', []))
|
|
threshold_value = self._convert_threshold_to_company_currency(3005.06, options)
|
|
partners_to_exclude_query = """
|
|
SELECT account_move_line.partner_id
|
|
FROM %(table_references)s
|
|
%(currency_table_join)s
|
|
WHERE %(search_condition)s
|
|
AND account_move_line.partner_id IS NOT NULL
|
|
GROUP BY account_move_line.partner_id
|
|
HAVING
|
|
ABS(COALESCE(SUM(%(balance_select)s)
|
|
FILTER (WHERE account_move_line__move_id.move_type IN ('out_invoice', 'out_refund', 'out_receipt')),
|
|
0)) <= %(threshold_value)s
|
|
AND
|
|
ABS(COALESCE(SUM(%(balance_select)s)
|
|
FILTER (WHERE account_move_line__move_id.move_type IN ('in_invoice', 'in_refund', 'in_receipt')),
|
|
0)) <= %(threshold_value)s
|
|
"""
|
|
|
|
# Then, add a forced domain because it could be too long later when ast.literal_eval will be applied on it
|
|
forced_domain = [
|
|
*options.get('forced_domain', []),
|
|
('partner_id', 'not in', SQL(
|
|
f"({partners_to_exclude_query})",
|
|
table_references=query.from_clause,
|
|
currency_table_join=report._currency_table_aml_join(options),
|
|
balance_select=report._currency_table_apply_rate(SQL('account_move_line.balance')),
|
|
search_condition=query.where_clause,
|
|
threshold_value=threshold_value,
|
|
))
|
|
]
|
|
domain_options = {**options, 'forced_domain': forced_domain}
|
|
domain_formulas_dict = {str(domain): expressions}
|
|
domain_result = report._compute_formula_batch_with_engine_domain(domain_options, date_scope, domain_formulas_dict, current_groupby, next_groupby,
|
|
offset=0, limit=None)
|
|
return next(result for result in domain_result.values())
|
|
|
|
def _convert_threshold_to_company_currency(self, threshold, options):
|
|
""" Returns a EUR threshold to company currency, using the options' date_to for conversion
|
|
"""
|
|
threshold_currency = self.env.ref('base.EUR')
|
|
|
|
if not threshold_currency.active:
|
|
raise UserError(_("Currency %s, used for a threshold in this report, is either nonexistent or inactive. Please create or activate it.", threshold_currency.name))
|
|
|
|
company_currency = self.env.company.currency_id
|
|
return threshold_currency._convert(threshold, company_currency, self.env.company, options['date']['date_to'])
|
|
|
|
def _build_boe_report_options(self, options, year):
|
|
return self.env['account.report'].browse(options['report_id']).get_options(
|
|
previous_options={
|
|
**options,
|
|
|
|
'date': {'filter': 'custom', 'string': 'Q4 ' + year, 'date_from': year + '-10-01', 'date_to': year + '-12-31', 'mode': 'range'},
|
|
|
|
'comparison': {
|
|
'date_to': year + '-09-30',
|
|
'periods': [
|
|
{'date_to': year + '-09-30', 'date_from': year + '-07-01', 'string': 'Q3 ' + year, 'mode': 'range'},
|
|
{'date_to': year + '-06-30', 'date_from': year + '-04-01', 'string': 'Q2 ' + year, 'mode': 'range'},
|
|
{'date_to': year + '-03-31', 'date_from': year + '-01-01', 'string': 'Q1 ' + year, 'mode': 'range'}
|
|
],
|
|
'number_period': 3,
|
|
'string': f'Q3 {year}',
|
|
'filter': 'previous_period',
|
|
'date_from': f'{year}-07-01',
|
|
},
|
|
}
|
|
)
|
|
|
|
def _get_required_partner_ids_for_boe(self, mod_invoice_type, date_from, date_to, boe_wizard, operation_key, operation_class):
|
|
cash_basis_manual_data = boe_wizard.cash_basis_mod347_data.filtered(lambda x: x.operation_key == operation_key and x.operation_class == operation_class)
|
|
all_partners = cash_basis_manual_data.mapped('partner_id')
|
|
return set(all_partners.ids)
|
|
|
|
def _write_type2_header_record(self, current_company, boe_wizard, boe_report_options, year=None):
|
|
if not year:
|
|
year = str(fields.Date.today().year)
|
|
|
|
# The header is there once for the whole year. It should use the year as date range and not quarterly. No comparison.
|
|
yearly_options = boe_report_options.copy()
|
|
del yearly_options['comparison']
|
|
yearly_options = self.env['account.report'].browse(boe_report_options['report_id']).get_options(
|
|
previous_options={
|
|
**yearly_options,
|
|
'date': {'date_from': '%s-01-01' % year, 'date_to': '%s-12-31' % year, 'filter': 'custom', 'mode': 'range'},
|
|
}
|
|
)
|
|
|
|
rslt = self._l10n_es_boe_format_number(yearly_options, 1)
|
|
rslt += self._l10n_es_boe_format_number(yearly_options, 347)
|
|
rslt += self._l10n_es_boe_format_string(year, length=4)
|
|
rslt += self._l10n_es_boe_format_string(self._extract_spanish_tin(current_company.partner_id), length=9)
|
|
rslt += self._l10n_es_boe_format_string(current_company.name, length=40)
|
|
rslt += self._l10n_es_boe_format_string('T')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.get_formatted_contact_phone(), length=9)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.contact_person_name, length=40)
|
|
mod_347_boe_sequence = current_company.sudo()._get_mod_boe_sequence("347")
|
|
rslt += self._l10n_es_boe_format_number(yearly_options, 347) + self._l10n_es_boe_format_string(mod_347_boe_sequence.next_by_id(), length=10)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and 'C' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.substitutive_declaration and 'S' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.previous_report_number or '', length=13, fill_char=b'0', align='right')
|
|
|
|
declarados_count = self._retrieve_report_expression(yearly_options, 'l10n_es_reports.mod_347_statistics_operations_count_balance')
|
|
rslt += self._l10n_es_boe_format_number(yearly_options, declarados_count, length=9)
|
|
declarados_total = self._retrieve_report_expression(yearly_options, 'l10n_es_reports.mod_347_operations_title_balance')
|
|
rslt += self._l10n_es_boe_format_number(yearly_options, declarados_total, length=16, decimal_places=2, signed=True, sign_pos=' ', in_currency=True)
|
|
|
|
real_estates_data = self._get_real_estates_data(yearly_options, current_company.currency_id)
|
|
rslt += self._l10n_es_boe_format_number(yearly_options, real_estates_data['count'], length=9)
|
|
|
|
rslt += self._l10n_es_boe_format_number(yearly_options, real_estates_data['total'], length=16, decimal_places=2, signed=True, sign_pos=' ', in_currency=True)
|
|
|
|
rslt += self._l10n_es_boe_format_string(' ' * 205)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 9) # TIN of the legal representant; blank if 14 years or older
|
|
rslt += self._l10n_es_boe_format_string(' ' * 88)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 13) # "Sello Electronico" => for administration
|
|
rslt += b'\r\n'
|
|
|
|
return rslt
|
|
|
|
def _get_real_estates_data(self, boe_report_options, currency_id):
|
|
""" Real estates are not directly supported by l10n_es_reports, but by the
|
|
submodule l10n_es_real_estates. This function is used as a hook, so that we
|
|
don't have to access the result of _write_type2_header_record by indexes
|
|
in order to write the real estates data at the right place in the BOE
|
|
(which is better in case the code of the header function needs to be extended).
|
|
"""
|
|
return {'count': 0, 'total': 0}
|
|
|
|
def _get_invoice_types_xmlids(self):
|
|
return {
|
|
'l10n_es_reports.mod_347_operations_insurance_bought': 'insurance',
|
|
'l10n_es_reports.mod_347_operations_insurance_sold': 'insurance',
|
|
'l10n_es_reports.mod_347_operations_regular_sold': 'regular',
|
|
'l10n_es_reports.mod_347_operations_regular_bought': 'regular',
|
|
}
|
|
|
|
def _write_type2_partner_record(self, options, report_data, year, current_company, operation_key, manual_parameters_map, insurance=False, local_negocio=False):
|
|
currency_id = current_company.currency_id
|
|
line_partner = self.env['res.partner'].browse(self.env['account.report']._get_model_info_from_id(report_data['line_data']['id'])[1])
|
|
|
|
rslt = self._l10n_es_boe_format_number(options, 2)
|
|
rslt += self._l10n_es_boe_format_number(options, 347)
|
|
rslt += self._l10n_es_boe_format_string(year, length=4)
|
|
rslt += self._l10n_es_boe_format_string(self._extract_spanish_tin(current_company.partner_id), length=9)
|
|
rslt += self._l10n_es_boe_format_string(line_partner.country_id.code == 'ES' and self._extract_spanish_tin(line_partner) or '', length=9)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 9) # TIN of the legal representant; blank if 14 years or older
|
|
rslt += self._l10n_es_boe_format_string(line_partner.display_name, length=40)
|
|
rslt += self._l10n_es_boe_format_string('D') # 'Tipo de hoja', constant
|
|
|
|
province_code = line_partner.state_id and SPANISH_PROVINCES_REPORT_CODES.get(line_partner.state_id.code) or '99'
|
|
rslt += self._l10n_es_boe_format_string(province_code, length=2)
|
|
# The country code is only mandatory if there is no province code (hence: no head office in Spain)
|
|
if province_code == '99':
|
|
if not line_partner.country_id or not line_partner.country_id.code:
|
|
raise UserError(_("Partner %(name)s (id %(id)d) is not associated to any Spanish province, and should hence have a country code. For this, fill in its 'country' field.", name=line_partner.name, id=line_partner.id))
|
|
|
|
if line_partner.country_id.code == 'ES':
|
|
raise UserError(_("Partner %(name)s (id %(id)d) is located in Spain but does not have any province. Please set one.", name=line_partner.name, id=line_partner.id))
|
|
|
|
partner_country_code = line_partner.country_id.code
|
|
rslt += self._l10n_es_boe_format_string(partner_country_code if partner_country_code and partner_country_code != 'ES' else '', length=2)
|
|
rslt += self._l10n_es_boe_format_string(' ') # Constant
|
|
rslt += self._l10n_es_boe_format_string(operation_key, length=1)
|
|
|
|
# Total amount of operations over the year
|
|
year_operations_sum = currency_id.round(sum(i['no_format'] for i in report_data['line_data'].get('columns', [])))
|
|
rslt += self._l10n_es_boe_format_number(options, year_operations_sum, length=16, decimal_places=2, signed=True, sign_pos=' ', in_currency=True)
|
|
|
|
rslt += self._l10n_es_boe_format_string(insurance and 'X' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(local_negocio and 'X' or ' ')
|
|
|
|
# En metálico
|
|
invoice_types_by_xmlid = self._get_invoice_types_xmlids()
|
|
current_invoice_type = invoice_types_by_xmlid[report_data['line_xml_id']]
|
|
|
|
account_type = operation_key == 'B' and 'asset_receivable' or 'liability_payable'
|
|
matching_field = operation_key == 'B' and 'debit' or 'credit'
|
|
cash_payments_lines_in_period = self.env['account.move.line'].search([('date', '<=', year + '-12-31'), ('date', '>=', year + '-01-01'), ('journal_id.type', '=', 'cash'), ('payment_id', '!=', False), ('partner_id', '=', line_partner.id), ('account_type', '=', account_type), ('company_id', '=', current_company.id)])
|
|
metalico_amount = 0
|
|
for cash_payment_aml in cash_payments_lines_in_period:
|
|
partial_reconcile_ids = cash_payment_aml['matched_' + matching_field + '_ids']
|
|
partial_rec_on_inv_type = partial_reconcile_ids.filtered(lambda x: x[matching_field + '_move_id'].move_id.l10n_es_reports_mod347_invoice_type == current_invoice_type)
|
|
for partial_rec in partial_rec_on_inv_type:
|
|
metalico_amount += partial_rec.amount
|
|
|
|
# Context key used for conversion date is set in get_txt.
|
|
curr_eur = self.env["res.currency"].search([('name', '=', 'EUR')], limit=1)
|
|
threshold = curr_eur._convert(6000, currency_id, current_company, options['date']['date_to'])
|
|
if currency_id.compare_amounts(metalico_amount, threshold) == 1: # We only must report this amount if it is above 6000 €
|
|
rslt += self._l10n_es_boe_format_number(options, metalico_amount, length=15, decimal_places=2, in_currency=True)
|
|
else:
|
|
rslt += self._l10n_es_boe_format_number(options, 0, length=15)
|
|
|
|
# Inmuebles sujetas a la IVA
|
|
operation_class = insurance and 'seguros' or local_negocio and 'local_negocio' or 'otras'
|
|
real_estates_vat_year_total = 0
|
|
real_estates_vat_by_trimester = []
|
|
for trimester in range(1, 5):
|
|
# This module does not support real estates on its own, but we give the possibility
|
|
# to add a real_estates_vat key to the manual parameters map with the needed data,
|
|
# through anoter module (l10n_es_real_estates does that)
|
|
real_estates_vat_partner_dict = manual_parameters_map.get('real_estates_vat', {}).get(line_partner.id)
|
|
real_estates_vat_amount = real_estates_vat_partner_dict and real_estates_vat_partner_dict[str(trimester)][operation_class][operation_key] or 0
|
|
real_estates_vat_year_total += real_estates_vat_amount
|
|
real_estates_vat_by_trimester.append(real_estates_vat_amount)
|
|
|
|
real_estates_vat_year_total = currency_id.round(real_estates_vat_year_total)
|
|
rslt += self._l10n_es_boe_format_number(options, real_estates_vat_year_total, length=16, decimal_places=2, signed=True, sign_pos=' ', in_currency=True)
|
|
|
|
rslt += self._l10n_es_boe_format_string('0000', length=4) # Ejercicio for metalico operations ; automatic computation not supported
|
|
|
|
for trimester_index in range(3, -1, -1): # 4th trimester is at position 0 ; 1st at position 3
|
|
trimester_total = report_data['line_data'].get('columns', [{} for i in range(0, 4)])[trimester_index].get('no_format', 0)
|
|
rslt += self._l10n_es_boe_format_number(options, trimester_total, length=16, decimal_places=2, signed=True, sign_pos=' ', in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, real_estates_vat_by_trimester[trimester_index], length=16, decimal_places=2, signed=True, sign_pos=' ', in_currency=True)
|
|
|
|
# 'NIF Operador Comunitario'
|
|
europe_countries = self.env.ref('base.europe').country_ids - self.env.ref('base.es')
|
|
intracom_tin = ''
|
|
if line_partner.country_id in europe_countries:
|
|
intracom_tin = self._extract_tin(line_partner, error_if_no_tin=False)
|
|
rslt += self._l10n_es_boe_format_string(intracom_tin.upper(), length=17)
|
|
|
|
# Cash Basis (Regimen Especial de Caja)
|
|
cash_basis_partner = manual_parameters_map['cash_basis'].get(line_partner.id)
|
|
cash_basis_data = cash_basis_partner and cash_basis_partner[operation_class][operation_key] or None
|
|
rslt += self._l10n_es_boe_format_string(cash_basis_data is not None and 'X' or ' ')
|
|
|
|
rslt += self._l10n_es_boe_format_string(line_partner == current_company.partner_id and 'X' or ' ')
|
|
|
|
rslt += self._l10n_es_boe_format_string(' ') # Not supported by Odoo; according to the partners, too few people need this option
|
|
|
|
rslt += self._l10n_es_boe_format_number(options, cash_basis_data or 0, length=16, decimal_places=2, signed=True, sign_pos=' ', in_currency=True)
|
|
|
|
rslt += self._l10n_es_boe_format_string('000000', length=6)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 195)
|
|
rslt += b'\r\n'
|
|
|
|
return rslt
|
|
|
|
def export_boe(self, options):
|
|
self.env.flush_all()
|
|
dummy, year = self._get_mod_period_and_year(options)
|
|
current_company = self.env.company
|
|
report = self.env['account.report'].browse(options['report_id'])
|
|
|
|
# Report options to use to retrieve data for the BOE
|
|
boe_report_options = self._build_boe_report_options(options, year)
|
|
|
|
# Wizard with manually-entered data
|
|
boe_wizard = self._retrieve_boe_manual_wizard(options, 347)
|
|
|
|
manual_params = boe_wizard.l10n_es_get_partners_manual_parameters_map()
|
|
|
|
# Header
|
|
rslt = self._write_type2_header_record(current_company, boe_wizard, boe_report_options, year=year)
|
|
seguros_required_a = self._get_required_partner_ids_for_boe('insurance', year + '-01-01', year + '-12-31', boe_wizard, 'A', 'seguros')
|
|
rslt += self._call_on_partner_sublines(
|
|
boe_report_options,
|
|
'l10n_es_reports.mod_347_operations_insurance_bought',
|
|
lambda report_data: self._write_type2_partner_record(boe_report_options, report_data, year, current_company, 'A',
|
|
manual_parameters_map=manual_params, insurance=True),
|
|
required_ids_set=seguros_required_a
|
|
)
|
|
|
|
# TODO master: remove if. Introduced in a bugfix, so won't necessarily exist.
|
|
if self.env.ref('l10n_es_reports.mod_347_operations_insurance_sold', raise_if_not_found=False):
|
|
seguros_required_b = self._get_required_partner_ids_for_boe('insurance', year + '-01-01', year + '-12-31', boe_wizard, 'B', 'seguros')
|
|
rslt += self._call_on_partner_sublines(
|
|
boe_report_options,
|
|
'l10n_es_reports.mod_347_operations_insurance_sold',
|
|
lambda report_data: self._write_type2_partner_record(boe_report_options, report_data, year, current_company, 'B',
|
|
manual_parameters_map=manual_params, insurance=True),
|
|
required_ids_set=seguros_required_b
|
|
)
|
|
|
|
otras_required_a = self._get_required_partner_ids_for_boe('regular', year + '-01-01', year + '-12-31', boe_wizard, 'B', 'otras')
|
|
rslt += self._call_on_partner_sublines(
|
|
boe_report_options,
|
|
'l10n_es_reports.mod_347_operations_regular_sold',
|
|
lambda report_data: self._write_type2_partner_record(boe_report_options, report_data, year, current_company, 'B',
|
|
manual_parameters_map=manual_params),
|
|
required_ids_set=otras_required_a
|
|
)
|
|
|
|
otras_required_b = self._get_required_partner_ids_for_boe('regular', year + '-01-01', year + '-12-31', boe_wizard, 'A', 'otras')
|
|
rslt += self._call_on_partner_sublines(
|
|
boe_report_options,
|
|
'l10n_es_reports.mod_347_operations_regular_bought',
|
|
lambda report_data: self._write_type2_partner_record(boe_report_options, report_data, year, current_company, 'A',
|
|
manual_parameters_map=manual_params),
|
|
required_ids_set=otras_required_b
|
|
)
|
|
|
|
return {
|
|
'file_name': report.get_default_report_filename(options, 'txt'),
|
|
'file_content': rslt,
|
|
'file_type': 'txt',
|
|
}
|
|
|
|
def action_audit_cell(self, options, params):
|
|
report_line = self.env['account.report.line'].browse(params['report_line_id'])
|
|
action = report_line.report_id.action_audit_cell(options, params)
|
|
action['context'] = {
|
|
**(action.get('context') or {}),
|
|
'group_by': ['move_type', 'invoice_date:quarter'],
|
|
}
|
|
return action
|
|
|
|
|
|
class L10n_EsMod349TaxReportHandler(models.AbstractModel):
|
|
_name = 'l10n_es.mod349.tax.report.handler'
|
|
_inherit = ['l10n_es.tax.report.handler']
|
|
_description = 'Spanish Tax Report Custom Handler (Mod349)'
|
|
|
|
def _custom_options_initializer(self, report, options, previous_options):
|
|
super()._custom_options_initializer(report, options, previous_options=previous_options)
|
|
super()._append_boe_button(options, 349)
|
|
|
|
def _report_custom_engine_modelo349_invoice_e(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='E', move_type='invoice')
|
|
|
|
def _report_custom_engine_modelo349_invoice_a(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='A', move_type='invoice')
|
|
|
|
def _report_custom_engine_modelo349_invoice_t(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='T', move_type='invoice')
|
|
|
|
def _report_custom_engine_modelo349_invoice_s(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='S', move_type='invoice')
|
|
|
|
def _report_custom_engine_modelo349_invoice_i(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='I', move_type='invoice')
|
|
|
|
def _report_custom_engine_modelo349_invoice_m(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='M', move_type='invoice')
|
|
|
|
def _report_custom_engine_modelo349_invoice_h(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='H', move_type='invoice')
|
|
|
|
def _report_custom_engine_modelo349_invoice_r(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='R', move_type='invoice')
|
|
|
|
def _report_custom_engine_modelo349_invoice_d(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='D', move_type='invoice')
|
|
|
|
def _report_custom_engine_modelo349_invoice_c(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='C', move_type='invoice')
|
|
|
|
def _report_custom_engine_modelo349_refund_e(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='E', move_type='refund')
|
|
|
|
def _report_custom_engine_modelo349_refund_a(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='A', move_type='refund')
|
|
|
|
def _report_custom_engine_modelo349_refund_t(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='T', move_type='refund')
|
|
|
|
def _report_custom_engine_modelo349_refund_s(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='S', move_type='refund')
|
|
|
|
def _report_custom_engine_modelo349_refund_i(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='I', move_type='refund')
|
|
|
|
def _report_custom_engine_modelo349_refund_m(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='M', move_type='refund')
|
|
|
|
def _report_custom_engine_modelo349_refund_h(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='H', move_type='refund')
|
|
|
|
def _report_custom_engine_modelo349_refund_r(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='R', move_type='refund')
|
|
|
|
def _report_custom_engine_modelo349_refund_d(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='D', move_type='refund')
|
|
|
|
def _report_custom_engine_modelo349_refund_c(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, key='C', move_type='refund')
|
|
|
|
def _report_custom_engine_modelo349_invoice_totals(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, move_type='invoice')
|
|
|
|
def _report_custom_engine_modelo349_refund_totals(self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None):
|
|
return self._custom_modelo349_common(options, current_groupby, move_type='refund')
|
|
|
|
def _get_modelo349_domain(self, key=False):
|
|
domain = [
|
|
('account_type', 'in', ('asset_receivable', 'liability_payable')),
|
|
('move_id.move_type', 'in', ('in_invoice', 'out_invoice', 'in_refund', 'out_refund')),
|
|
('move_id.l10n_es_reports_mod349_available', '=', True),
|
|
('tax_line_id', '=', False),
|
|
]
|
|
|
|
if key:
|
|
domain += [('move_id.l10n_es_reports_mod349_invoice_type', '=', key)]
|
|
else:
|
|
domain += [
|
|
('move_id.l10n_es_reports_mod349_invoice_type', '!=', False),
|
|
'|', ('move_id.reversed_entry_id', '=', False),
|
|
'|', ('move_id.reversed_entry_id.amount_residual', '>', 0), ('move_id.amount_residual', '>', 0)
|
|
]
|
|
return domain
|
|
|
|
def _get_modelo349_audit_aml_domain(self, options, key, move_type):
|
|
res = self._custom_modelo349_common(options, 'move_id', key=key, move_type=move_type)
|
|
return [
|
|
*self._get_modelo349_domain(key),
|
|
('move_id', 'in', tuple(move_id for move_id, _vals in res)),
|
|
]
|
|
|
|
def _custom_modelo349_common(self, options, current_groupby, key=None, move_type=None):
|
|
def build_result_dict(query_res_lines, reversed_moves_dict):
|
|
result_dict = {'value': 0}
|
|
if key:
|
|
# Manage all the lines with specific keys, either refunds and invoices
|
|
treated_moves = []
|
|
|
|
for res_line in query_res_lines:
|
|
if res_line['move_type'] in ('in_refund', 'out_refund') and res_line['reversed_entry_id']:
|
|
# If we find a refund, we need the move which is linked to the refund
|
|
matching_move = reversed_moves_dict[res_line['reversed_entry_id']]
|
|
|
|
if move_type == 'invoice' and options['date']['date_from'] <= matching_move.date.strftime('%Y-%m-%d') <= options['date']['date_to']:
|
|
# If the linked move invoice date is in the same period as the report, we just need to subtract the refund value
|
|
result_dict['value'] -= res_line['amount_untaxed']
|
|
|
|
elif move_type == 'refund' and matching_move.date.strftime('%Y-%m-%d') < options['date']['date_from']:
|
|
# If the linked move is in a previous period, we need to add to the result the amount_total
|
|
# of the linked move minus the sum of it's credit note
|
|
# To be sure we don't add a move value twice (in case there is multiple refunds for a same move),
|
|
# we add it to a list
|
|
# In the case of the file export, we need the amount_total in order to write the rectification lines
|
|
if matching_move.id not in treated_moves:
|
|
if float_is_zero(matching_move.amount_residual, precision_digits=2) and options.get('export_mode') == 'file':
|
|
result_dict['value'] += matching_move.amount_total
|
|
result_dict['value'] += matching_move.amount_total - sum(refund.amount_total for refund in matching_move.reversal_move_ids)
|
|
treated_moves.append(matching_move.id)
|
|
|
|
elif ((res_line['move_type'] in ('in_invoice', 'out_invoice') and move_type == 'invoice') or
|
|
(res_line['move_type'] in ('in_refund', 'out_refund') and move_type == 'refund')):
|
|
# In case the move is matching the section type we always add the value to the result
|
|
result_dict['value'] += res_line['amount_untaxed']
|
|
|
|
elif current_groupby == 'partner_id':
|
|
# Compute the sublines for the firsts line (1 & 3)
|
|
result_dict['value'] = len([line for line in query_res_lines if (line['move_type'] in ('in_refund', 'out_refund') and move_type == 'refund') or (line['move_type'] in ('in_invoice', 'out_invoice') and move_type == 'invoice')])
|
|
else:
|
|
# Manage the firsts lines (1 & 3) to display the total number of partners
|
|
partner_ids = set()
|
|
treated_moves = set()
|
|
for res_line in query_res_lines:
|
|
if res_line['move_type'] in ('in_invoice', 'out_invoice') and move_type == 'refund':
|
|
matching_move = reversed_moves_dict.get(res_line['move_id'])
|
|
|
|
if matching_move and options['date']['date_from'] <= matching_move.date.strftime('%Y-%m-%d') <= options['date']['date_to']:
|
|
# If the linked move invoice date is in the same period as the report, we won't count it in refund operators
|
|
treated_moves.add(res_line['move_id'])
|
|
|
|
elif ((res_line['move_type'] in ('in_invoice', 'out_invoice') and move_type == 'invoice') or
|
|
(res_line['move_type'] in ('in_refund', 'out_refund') and move_type == 'refund')):
|
|
if res_line['partner_id'] in partner_ids or res_line['reversed_entry_id'] in treated_moves:
|
|
continue
|
|
partner_ids.add(res_line['partner_id'])
|
|
if move_type == 'refund':
|
|
reversed_move = reversed_moves_dict.get(res_line['reversed_entry_id'])
|
|
if reversed_move and reversed_move.date.strftime('%Y-%m-%d') <= options['date']['date_from']:
|
|
result_dict['value'] += 1
|
|
else:
|
|
result_dict['value'] += 1
|
|
|
|
result_dict['has_sublines'] = float_compare(result_dict['value'], 0, precision_digits=2)
|
|
|
|
return result_dict
|
|
|
|
report = self.env['account.report'].browse(options['report_id'])
|
|
report._check_groupby_fields([current_groupby] if current_groupby else [])
|
|
|
|
domain = self._get_modelo349_domain(key)
|
|
|
|
# Build query
|
|
query = report._get_report_query(options, 'strict_range', domain=domain)
|
|
groupby_field_sql = self.env['account.move.line']._field_to_sql('account_move_line', current_groupby, query) if current_groupby else SQL()
|
|
self.env.flush_all()
|
|
query = SQL("""
|
|
SELECT DISTINCT account_move.id AS move_id,
|
|
%(select_from_groupby)s
|
|
account_move.l10n_es_reports_mod349_invoice_type AS invoice_type,
|
|
account_move.move_type AS move_type,
|
|
account_move.reversed_entry_id AS reversed_entry_id,
|
|
account_move.partner_id AS partner_id,
|
|
account_move.amount_untaxed_signed * (CASE WHEN account_move.move_type IN ('in_invoice', 'out_refund') THEN -1 ELSE 1 END) AS amount_untaxed
|
|
FROM %(tables)s
|
|
JOIN account_move on account_move.id = account_move_line.move_id
|
|
WHERE %(where_clause)s
|
|
GROUP BY %(group_by)s
|
|
account_move.id
|
|
""",
|
|
select_from_groupby=SQL("%s AS grouping_key,", groupby_field_sql) if groupby_field_sql else SQL(),
|
|
tables=query.from_clause,
|
|
where_clause=query.where_clause,
|
|
group_by=SQL("%s,", groupby_field_sql) if groupby_field_sql else SQL(),
|
|
)
|
|
|
|
self.env.cr.execute(query)
|
|
query_res_lines = self.env.cr.dictfetchall()
|
|
|
|
reversed_entry_ids = {res_line['reversed_entry_id'] for res_line in query_res_lines if res_line['reversed_entry_id']}
|
|
reversed_moves_dict = self.env['account.move'].browse(reversed_entry_ids).grouped('id')
|
|
|
|
if not current_groupby:
|
|
return build_result_dict(query_res_lines, reversed_moves_dict)
|
|
|
|
rslt = []
|
|
|
|
all_res_per_grouping_key = {}
|
|
for query_res in query_res_lines:
|
|
grouping_key = query_res['grouping_key']
|
|
all_res_per_grouping_key.setdefault(grouping_key, []).append(query_res)
|
|
|
|
for grouping_key, query_res_lines in all_res_per_grouping_key.items():
|
|
result_dict = build_result_dict(query_res_lines, reversed_moves_dict)
|
|
if float_compare(result_dict['value'], 0, precision_digits=2):
|
|
rslt.append((grouping_key, result_dict))
|
|
|
|
return rslt
|
|
|
|
def _write_type1_header_record(self, options, period, year, current_company, boe_wizard):
|
|
rslt = self._l10n_es_boe_format_string('1349')
|
|
rslt += self._l10n_es_boe_format_string(year, length=4)
|
|
rslt += self._l10n_es_boe_format_string(self._extract_spanish_tin(current_company.partner_id), length=9)
|
|
rslt += self._l10n_es_boe_format_string(current_company.name, length=40)
|
|
rslt += self._l10n_es_boe_format_string('T')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.get_formatted_contact_phone(), length=9)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.contact_person_name, length=40)
|
|
mod_349_boe_sequence = current_company.sudo()._get_mod_boe_sequence("349")
|
|
rslt += self._l10n_es_boe_format_number(options, 349) + self._l10n_es_boe_format_string(mod_349_boe_sequence.next_by_id(), length=10)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.complementary_declaration and 'C' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.substitutive_declaration and 'S' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.previous_report_number or '', length=13, fill_char=b'0', align='right')
|
|
rslt += self._l10n_es_boe_format_string(period, length=2)
|
|
rslt += self._l10n_es_boe_format_number(options, self._retrieve_report_expression(options, 'l10n_es_reports.mod_349_statistics_invoices_partners_count_balance'), length=9)
|
|
rslt += self._l10n_es_boe_format_number(options, self._retrieve_report_expression(options, 'l10n_es_reports.mod_349_statistics_invoices_total_amount_balance'), length=15, in_currency=True, decimal_places=2)
|
|
rslt += self._l10n_es_boe_format_number(options, self._retrieve_report_expression(options, 'l10n_es_reports.mod_349_statistics_refunds_partners_count_balance'), length=9)
|
|
rslt += self._l10n_es_boe_format_number(options, self._retrieve_report_expression(options, 'l10n_es_reports.mod_349_statistics_refunds_total_amount_balance'), length=15, in_currency=True, decimal_places=2)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.trimester_2months_report and 'X' or ' ')
|
|
rslt += self._l10n_es_boe_format_string(' ' * 204)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 9) # TIN of the legal representative, if under 14 years old
|
|
rslt += self._l10n_es_boe_format_string(' ' * 101) # Constant
|
|
rslt += b'\r\n'
|
|
return rslt
|
|
|
|
def _write_type2_invoice_record(self, options, report_data, year, key, current_company):
|
|
rslt = b''
|
|
if report_data['line_data']['columns'][0]['no_format'] > 0:
|
|
line_partner = self.env['res.partner'].browse(self.env['account.report']._get_model_info_from_id(report_data['line_data']['id'])[1])
|
|
rslt += self._l10n_es_boe_format_string('2349')
|
|
rslt += self._l10n_es_boe_format_string(year, length=4)
|
|
rslt += self._l10n_es_boe_format_string(self._extract_spanish_tin(current_company.partner_id), length=9)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 58)
|
|
rslt += self._l10n_es_boe_format_string(self._extract_tin(line_partner), length=17)
|
|
rslt += self._l10n_es_boe_format_string(line_partner.name, length=40)
|
|
rslt += self._l10n_es_boe_format_string(key, length=1)
|
|
rslt += self._l10n_es_boe_format_number(options, report_data['line_data']['columns'][0]['no_format'], length=13, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 354)
|
|
rslt += b'\r\n'
|
|
|
|
return rslt
|
|
|
|
def _write_type2_refund_records(self, options, report_data, current_company, mod_349_type, invoice_report_line_xml_id, report_period, report_year):
|
|
line_partner = self.env['res.partner'].browse(self.env['account.report']._get_model_info_from_id(report_data['line_data']['id'])[1])
|
|
report_date_from = options['date']['date_from']
|
|
report_date_to = options['date']['date_to']
|
|
|
|
matched_moves = []
|
|
period_dict = {}
|
|
for refund_invoice in self.env['account.move'].search([
|
|
('date', '<=', report_date_to),
|
|
('date', '>=', report_date_from),
|
|
('move_type', 'in', ['in_refund', 'out_refund']),
|
|
('l10n_es_reports_mod349_invoice_type', '=', mod_349_type),
|
|
('partner_id', '=', line_partner.id),
|
|
('state', '=', 'posted'),
|
|
]):
|
|
original_invoice = refund_invoice.reversed_entry_id
|
|
|
|
if not original_invoice:
|
|
raise UserError(_('Refund Invoice %s was created without a link to the original invoice that was credited, '
|
|
'while we need that information for this report. ', refund_invoice.display_name))
|
|
|
|
invoice_period, invoice_year = self._retrieve_period_and_year(original_invoice.date, trimester=report_period[-1] == 'T')
|
|
|
|
if original_invoice.date <= datetime.strptime(report_date_to, '%Y-%m-%d').date() and original_invoice.date >= datetime.strptime(report_date_from, '%Y-%m-%d').date():
|
|
continue
|
|
|
|
if f"{invoice_period}{invoice_year}" not in period_dict:
|
|
period_dict[f"{invoice_period}{invoice_year}"] = {
|
|
'old_balance': 0,
|
|
'new_balance': 0,
|
|
}
|
|
|
|
if original_invoice.id not in matched_moves:
|
|
period_dict[f"{invoice_period}{invoice_year}"]['old_balance'] += original_invoice.amount_total
|
|
period_dict[f"{invoice_period}{invoice_year}"]['new_balance'] += original_invoice.amount_total - sum(refund.amount_total for refund in original_invoice.reversal_move_ids)
|
|
matched_moves.append(original_invoice.id)
|
|
|
|
rslt = self._l10n_es_boe_format_string('')
|
|
|
|
# Now, we can report the record !
|
|
for period, line_vals in period_dict.items():
|
|
rslt += self._l10n_es_boe_format_string('2349')
|
|
rslt += self._l10n_es_boe_format_string(report_year, length=4)
|
|
rslt += self._l10n_es_boe_format_string(self._extract_spanish_tin(current_company.partner_id), length=9)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 58)
|
|
rslt += self._l10n_es_boe_format_string(self._extract_tin(line_partner), length=17)
|
|
rslt += self._l10n_es_boe_format_string(line_partner.name, length=40)
|
|
rslt += self._l10n_es_boe_format_string(mod_349_type, length=1)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 13) # Constant
|
|
rslt += self._l10n_es_boe_format_string(period[2:6], length=4)
|
|
rslt += self._l10n_es_boe_format_string(period[0:2], length=2)
|
|
rslt += self._l10n_es_boe_format_number(options, line_vals['new_balance'], length=13, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, line_vals['old_balance'], length=13, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 322)
|
|
rslt += b'\r\n'
|
|
|
|
return rslt
|
|
|
|
def export_boe(self, options):
|
|
period, year = self._get_mod_period_and_year(options)
|
|
current_company = self.env.company
|
|
|
|
# Wizard with manually-entered data
|
|
boe_wizard = self._retrieve_boe_manual_wizard(options, 349)
|
|
options['export_mode'] = 'file'
|
|
if boe_wizard.trimester_2months_report:
|
|
if period[-1] == 'T':
|
|
options = options.copy()
|
|
end_date = datetime.strptime(options['date']['date_to'], '%Y-%m-%d')
|
|
options['date']['date_to'] = (end_date + relativedelta(day=31, months=-1)).strftime('%Y-%m-%d')
|
|
else:
|
|
raise UserError(_("You cannot generate a BOE file for the first two months of a trimester if only one month is selected!"))
|
|
|
|
# Header
|
|
rslt = self._write_type1_header_record(options, period, year, current_company, boe_wizard)
|
|
|
|
# Invoices lines
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_supplies', lambda report_data: self._write_type2_invoice_record(options, report_data, year, 'E', current_company))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_acquisitions', lambda report_data: self._write_type2_invoice_record(options, report_data, year, 'A', current_company))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_triangular', lambda report_data: self._write_type2_invoice_record(options, report_data, year, 'T', current_company))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_services_sold', lambda report_data: self._write_type2_invoice_record(options, report_data, year, 'S', current_company))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_services_acquired', lambda report_data: self._write_type2_invoice_record(options, report_data, year, 'I', current_company))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_supplies_without_taxes', lambda report_data: self._write_type2_invoice_record(options, report_data, year, 'M', current_company))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_supplies_without_taxes_legal_representative', lambda report_data: self._write_type2_invoice_record(options, report_data, year, 'H', current_company))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_consignment_sales_agreements', lambda report_data: self._write_type2_invoice_record(options, report_data, year, 'R', current_company))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_returns_goods_consignment', lambda report_data: self._write_type2_invoice_record(options, report_data, year, 'D', current_company))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_substitutions', lambda report_data: self._write_type2_invoice_record(options, report_data, year, 'C', current_company))
|
|
|
|
# Refunds lines
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_supplies_refunds', lambda report_data: self._write_type2_refund_records(options, report_data, current_company, 'E', 'l10n_es_reports.mod_349_supplies', period, year))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_acquisitions_refunds', lambda report_data: self._write_type2_refund_records(options, report_data, current_company, 'A', 'l10n_es_reports.mod_349_acquisitions', period, year))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_triangular_refunds', lambda report_data: self._write_type2_refund_records(options, report_data, current_company, 'T', 'l10n_es_reports.mod_349_triangular', period, year))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_services_sold_refunds', lambda report_data: self._write_type2_refund_records(options, report_data, current_company, 'S', 'l10n_es_reports.mod_349_services_sold', period, year))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_services_acquired_refunds', lambda report_data: self._write_type2_refund_records(options, report_data, current_company, 'I', 'l10n_es_reports.mod_349_services_acquired', period, year))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_supplies_without_taxes_refunds', lambda report_data: self._write_type2_refund_records(options, report_data, current_company, 'M', 'l10n_es_reports.mod_349_supplies_without_taxes', period, year))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_supplies_without_taxes_legal_representative_refunds', lambda report_data: self._write_type2_refund_records(options, report_data, current_company, 'H', 'l10n_es_reports.mod_349_supplies_without_taxes_legal_representative', period, year))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_consignment_sales_agreements_refunds', lambda report_data: self._write_type2_refund_records(options, report_data, current_company, 'R', 'l10n_es_reports.mod_349_consignment_sales_agreements', period, year))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_returns_goods_consignment_refunds', lambda report_data: self._write_type2_refund_records(options, report_data, current_company, 'D', 'l10n_es_reports.mod_349_returns_goods_consignment', period, year))
|
|
rslt += self._call_on_partner_sublines(options, 'l10n_es_reports.mod_349_substitutions_refunds', lambda report_data: self._write_type2_refund_records(options, report_data, current_company, 'C', 'l10n_es_reports.mod_349_substitutions', period, year))
|
|
|
|
return {
|
|
'file_name': self.env['account.report'].browse(options['report_id']).get_default_report_filename(options, 'txt'),
|
|
'file_content': rslt,
|
|
'file_type': 'txt',
|
|
}
|
|
|
|
|
|
class L10n_EsMod390TaxReportHandler(models.AbstractModel):
|
|
_name = 'l10n_es.mod390.tax.report.handler'
|
|
_inherit = ['l10n_es.tax.report.handler']
|
|
_description = 'Spanish Tax Report Custom Handler (Mod390)'
|
|
|
|
def _custom_options_initializer(self, report, options, previous_options):
|
|
super()._custom_options_initializer(report, options, previous_options=previous_options)
|
|
super()._append_boe_button(options, 390)
|
|
|
|
def export_boe(self, options):
|
|
_period, year = self._get_mod_period_and_year(options)
|
|
boe_wizard = self._retrieve_boe_manual_wizard(options, 390)
|
|
current_company = self.env.company
|
|
casilla_lines_map = {}
|
|
for section in options['sections']:
|
|
section_report = self.env['account.report'].browse(section['id'])
|
|
report_lines = section_report._get_lines({**options, 'report_id': section_report.id})
|
|
casilla_lines_map.update(self._retrieve_casilla_lines(report_lines))
|
|
|
|
# Header
|
|
rslt = self._l10n_es_boe_format_string('<T3900' + year + '0A0000>')
|
|
rslt += self._l10n_es_boe_format_string('<AUX>')
|
|
rslt += self._l10n_es_boe_format_string(' ' * 70)
|
|
odoo_version = odoo.release.version.split('.')
|
|
rslt += self._l10n_es_boe_format_string(str(odoo_version[0]) + str(odoo_version[1]), length=4)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 4)
|
|
rslt += self._l10n_es_boe_format_string(self._extract_spanish_tin(current_company.partner_id), length=9)
|
|
rslt += self._l10n_es_boe_format_string(' ' * 213)
|
|
rslt += self._l10n_es_boe_format_string('</AUX>')
|
|
|
|
rslt += self._generate_mod_390_page1(options, current_company, year, boe_wizard)
|
|
rslt += self._generate_mod_390_page2(options, casilla_lines_map)
|
|
rslt += self._generate_mod_390_page2b(options, casilla_lines_map)
|
|
rslt += self._generate_mod_390_page3(options, casilla_lines_map)
|
|
rslt += self._generate_mod_390_page4(options, casilla_lines_map)
|
|
# We don't handle page 5 for now (Simplified regime operations, including agricultural, livestock and forestry)
|
|
rslt += self._generate_mod_390_page6(options, casilla_lines_map)
|
|
rslt += self._generate_mod_390_page7(options, casilla_lines_map)
|
|
rslt += self._generate_mod_390_page8(options, casilla_lines_map)
|
|
|
|
rslt += self._l10n_es_boe_format_string('</T3900' + year + '0A0000>')
|
|
|
|
return {
|
|
'file_name': f'Modelo390 - {year}',
|
|
'file_content': rslt,
|
|
'file_type': 'txt',
|
|
}
|
|
|
|
def _generate_mod_390_page1(self, options, current_company, year, boe_wizard):
|
|
# Main info regarding the company, the representant, the dates and the report itself
|
|
# Header
|
|
rslt = self._l10n_es_boe_format_string('<T39001000> ')
|
|
rslt += self._l10n_es_boe_format_string(self._extract_spanish_tin(current_company.partner_id), length=9)
|
|
rslt += self._l10n_es_boe_format_string(current_company.name, length=60)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.physical_person_name, length=20)
|
|
|
|
rslt += self._l10n_es_boe_format_string(year)
|
|
rslt += self._l10n_es_boe_format_string(' ')
|
|
|
|
rslt += self._l10n_es_boe_format_string('1' if boe_wizard.monthly_return else '0')
|
|
|
|
tax_unit_option = options.get('tax_unit')
|
|
group_of_entities = True if tax_unit_option and tax_unit_option != 'company_only' else False
|
|
|
|
rslt += self._l10n_es_boe_format_string('1' if group_of_entities else '0') # Part of a group of entities
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.group_number, length=7) if boe_wizard.group_number else self._l10n_es_boe_format_string(' ' * 7)
|
|
rslt += self._l10n_es_boe_format_string('1' if group_of_entities else '0') # Dominant --> True if we're in a tax unit
|
|
rslt += self._l10n_es_boe_format_string('0') # Dependant --> always False
|
|
rslt += self._l10n_es_boe_format_string('1' if boe_wizard.special_regime_applicable_163 else '0')
|
|
# "NIF de la entidad dominante" must only be filled if the declarant is not the dominant entity
|
|
# see official documentation : https://sede.agenciatributaria.gob.es/static_files/Sede/Procedimiento_ayuda/G412/instr390.pdf
|
|
rslt += self._l10n_es_boe_format_string(' ' * 9)
|
|
rslt += self._l10n_es_boe_format_string('2') # Bankrupcy
|
|
rslt += self._l10n_es_boe_format_string('1' if boe_wizard.special_cash_basis else '2')
|
|
rslt += self._l10n_es_boe_format_string('1' if boe_wizard.special_cash_basis_beneficiary else '2')
|
|
rslt += self._l10n_es_boe_format_string('1' if boe_wizard.is_substitute_declaration else '0')
|
|
rslt += self._l10n_es_boe_format_string('1' if boe_wizard.is_substitute_decl_by_rectif_of_quotas else '0')
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.previous_decl_number, length=13) if boe_wizard.previous_decl_number else self._l10n_es_boe_format_string(' ' * 13)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.principal_activity, length=40)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.principal_code_activity, length=3)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.principal_iae_epigrafe, length=4)
|
|
|
|
# Other Activities
|
|
for _i in range(0, 5):
|
|
# Activity name (40), activity code (3) & activity epigrafe (4)
|
|
rslt += self._l10n_es_boe_format_string(' ' * (40 + 3 + 4))
|
|
|
|
# Joint Declaration
|
|
rslt += self._l10n_es_boe_format_string('0')
|
|
rslt += self._l10n_es_boe_format_string(' ' * (9 + 37))
|
|
|
|
# Representant
|
|
rslt += self._l10n_es_boe_format_string(' ' * (9 + 80 + 2 + 17 + 5 + 2 + 2 + 2 + 9 + 20 + 15 + 5))
|
|
|
|
# Personas Jurídicas
|
|
# Only one persona juridica is mandatory, the others are left blank
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.judicial_person_name, length=80)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.judicial_person_nif, length=9)
|
|
date = boe_wizard.judicial_person_procuration_date
|
|
rslt += self._l10n_es_boe_format_string(datetime.strftime(date, "%d%m%Y") if date else '00000000', length=8)
|
|
rslt += self._l10n_es_boe_format_string(boe_wizard.judicial_person_notary, length=12)
|
|
rslt += self._l10n_es_boe_format_string(((' ' * (80 + 9)) + '00000000' + (' ' * 12)) * 2)
|
|
|
|
# Footer of page 1
|
|
rslt += self._l10n_es_boe_format_string(' ' * (21 + 13 + 20 + 150)) # Reserved for AEAT
|
|
rslt += self._l10n_es_boe_format_string('</T39001000>')
|
|
|
|
return rslt
|
|
|
|
def _generate_mod_390_page2(self, options, casilla_lines_map):
|
|
# Operations carried out under the general regime : accrued VAT
|
|
# Header
|
|
rslt = self._l10n_es_boe_format_string('<T39002000> ')
|
|
casillas = [700, 701, 667, 668, 1, 2, 702, 703, 669, 670, 3, 4, 5, 6, 704, 705, 671, 672,
|
|
500, 501, 706, 707, 673, 674, 502, 503, 504, 505, 708, 709, 675, 676, 643, 644, 710, 711,
|
|
677, 678, 645, 646, 647, 648, 712, 713, 679, 680, 7, 8, 714, 715, 681, 682, 9, 10, 11, 12,
|
|
13, 14, 716, 717, 683, 684, 21, 22, 718, 719, 685, 686, 23, 24, 25, 26, 720, 721, 687, 688,
|
|
545, 546, 722, 723, 689, 690, 547, 548, 551, 552, 27, 28, 29, 30, 649, 650, 31, 32, 33, 34]
|
|
for casilla in casillas:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[f'{casilla:02d}'],
|
|
length=17, decimal_places=2, signed=True, in_currency=True)
|
|
# Blank space for AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 150)
|
|
# Footer
|
|
rslt += self._l10n_es_boe_format_string('</T39002000>')
|
|
|
|
return rslt
|
|
|
|
def _generate_mod_390_page2b(self, options, casilla_lines_map):
|
|
# Header
|
|
rslt = self._l10n_es_boe_format_string('<T39002B00> ')
|
|
rslt += self._l10n_es_boe_format_string('0' * (17 * 4)) # Reserve space for fields 663, 664, 691, and 692 (4 fixed-width values of 17 characters each)
|
|
casillas = [35, 36]
|
|
for casilla in casillas:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)],
|
|
length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_string('0' * (17 * 4)) # Reserve space for fields 665, 666, 693, and 694 (4 fixed-width values of 17 characters each)
|
|
casillas = [599, 600, 601, 602, 41, 42, 43, 44, 45, 46, 47]
|
|
for casilla in casillas:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)],
|
|
length=17, decimal_places=2, signed=True, in_currency=True)
|
|
# Blank space for AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 150)
|
|
# Footer
|
|
rslt += self._l10n_es_boe_format_string('</T39002B00>')
|
|
|
|
return rslt
|
|
|
|
def _generate_mod_390_page3(self, options, casilla_lines_map):
|
|
# Operations carried out under the general regime : VAT deductible
|
|
# Header
|
|
rslt = self._l10n_es_boe_format_string('<T39003000> ')
|
|
|
|
# Casillas
|
|
casillas = [695, 696, 190, 191, 724, 725, 697, 698, 603, 604, 605, 606, 48, 49, 745, 746,
|
|
506, 507, 726, 727, 747, 748, 607, 608, 609, 610, 512, 513, 749, 750, 196, 197, 728, 729,
|
|
751, 752, 611, 612, 613, 614, 50, 51, 753, 754, 514, 515, 730, 731, 755, 756, 615, 616, 617,
|
|
618, 520, 521, 757, 758, 202, 203, 732, 733, 759, 760, 619, 620, 621, 622, 52, 53, 761, 762,
|
|
208, 209, 734, 735, 763, 764, 623, 624, 625, 626, 54, 55, 765, 766, 214, 215, 736, 737, 767,
|
|
768, 627, 628, 629, 630, 56, 57]
|
|
for casilla in casillas:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)],
|
|
length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
# Blank space for AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 150)
|
|
# Footer
|
|
rslt += self._l10n_es_boe_format_string('</T39003000>')
|
|
|
|
return rslt
|
|
|
|
def _generate_mod_390_page4(self, options, casilla_lines_map):
|
|
#Header
|
|
rslt = self._l10n_es_boe_format_string('<T39004000> ')
|
|
# Casillas
|
|
casillas = [769, 770, 220, 221, 738, 739, 771, 772, 631, 632, 633, 634, 58, 59, 773, 774,
|
|
587,588, 740, 741, 775, 776, 635, 636, 637, 638, 597, 598, 60, 61, 660, 661, 639, 62, 651,
|
|
652, 63, 522, 64, 65]
|
|
for casilla in casillas:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)],
|
|
length=17, decimal_places=2, signed=True, in_currency=True)
|
|
# Blank space for AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 150)
|
|
# Footer
|
|
rslt += self._l10n_es_boe_format_string('</T39004000>')
|
|
|
|
return rslt
|
|
|
|
def _generate_mod_390_page6(self, options, casilla_lines_map):
|
|
# Header
|
|
rslt = self._l10n_es_boe_format_string('<T39006000> ')
|
|
# Section 7 : Annual settlement result (Only for taxpayers who are taxed exclusively in common territory)
|
|
# Casillas
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['658'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['84'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['659'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['85'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['86'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
# We don't cover the section 8 of the modelo 390, the casillas are replaced by zeros.
|
|
# 87 --> 91: Administraciones : Territorio commùn (5), Álava (5), Guipúzcoa(5), Vizcaya(5), Navarra(5)
|
|
rslt += self._l10n_es_boe_format_string('0' * 5 * 5)
|
|
# 658 : Administraciones - Regularización cuotas art. 80.Cinco.5ª LIVA (17)
|
|
# 84 : Administraciones - Suma de resultados (17)
|
|
# 92 : Administraciones - Resultado atribuible a territorio común (17)
|
|
# 659 : Administraciones -IVA a la importación liquidado por la Aduana (17)
|
|
# 93 : Administraciones - Compens. cuotas ej. anterior atrib. territ. com. (17)
|
|
# 94 : Administraciones -Resultado liq. anual atribuible territ. comun (17)
|
|
rslt += self._l10n_es_boe_format_string('0' * 17 * 6)
|
|
# Section 9 : Result of settlements
|
|
# Periods that are not taxed under the Special Regime of the group of entities
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['95'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['96'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['524'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['97'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['98'], length=17, decimal_places=2, in_currency=True)
|
|
# Periods that are taxed under the Special Regime of the group of entities
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['662'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['525'], length=17, decimal_places=2, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['526'], length=17, decimal_places=2, in_currency=True)
|
|
# Section 10 : Trading volume
|
|
casillas = [99, 653, 103, 104, 105, 110, 125, 126, 127, 128, 100, 101, 102, 227, 228, 106, 107, 108]
|
|
for casilla in casillas:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
# Blank space for AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 150)
|
|
# Footer
|
|
rslt += self._l10n_es_boe_format_string('</T39006000>')
|
|
|
|
return rslt
|
|
|
|
def _generate_mod_390_page7(self, options, casilla_lines_map):
|
|
# Header
|
|
rslt = self._l10n_es_boe_format_string('<T39007000> ')
|
|
|
|
# Casillas
|
|
# Section 11: Specific operations in the carried out during the year
|
|
casillas = [230, 109, 231, 232, 111, 113, 523]
|
|
for casilla in casillas:
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
for casilla in range(654, 658):
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
# We don't cover the section 12 of the modelo 390, the casillas are replaced by blank spaces
|
|
for _i in range(0, 5):
|
|
rslt += self._l10n_es_boe_format_string(' ' * 40) # Prorratas - Actividad desarrollada
|
|
rslt += self._l10n_es_boe_format_string(' ' * 3) # 12. Prorratas - Código CNAE [114]
|
|
rslt += self._l10n_es_boe_format_string('0' * 17) # 12. Prorratas - Importe de operaciones [115]
|
|
rslt += self._l10n_es_boe_format_string('0' * 17) # 12. Prorratas - Importe de operaciones con derecho a deducción [116]
|
|
rslt += self._l10n_es_boe_format_string(' ') # 12. Prorratas - Tipo de prorrata [117]
|
|
rslt += self._l10n_es_boe_format_string('0' * 5) # 12. Prorratas - % de prorrata [118]
|
|
|
|
# Blank space for AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 150)
|
|
# Footer
|
|
rslt += self._l10n_es_boe_format_string('</T39007000>')
|
|
|
|
return rslt
|
|
|
|
def _generate_mod_390_page8(self, options, casilla_lines_map):
|
|
# Activities with differentiated deduction regimes
|
|
# Header
|
|
rslt = self._l10n_es_boe_format_string('<T39008000> ')
|
|
|
|
# Casillas
|
|
for casilla in range(139, 153):
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['640'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
for casilla in range(153, 170):
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['641'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
for casilla in range(170, 187):
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map['642'], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
for casilla in range(187, 190):
|
|
rslt += self._l10n_es_boe_format_number(options, casilla_lines_map[str(casilla)], length=17, decimal_places=2, signed=True, in_currency=True)
|
|
|
|
# Blank space for AEAT
|
|
rslt += self._l10n_es_boe_format_string(' ' * 150)
|
|
# Footer
|
|
rslt += self._l10n_es_boe_format_string('</T39008000>')
|
|
|
|
return rslt
|