Skip to content

Commit

Permalink
[IMP] l10n_ar_account_tax_settlement: txt retenciones sire aplicadas
Browse files Browse the repository at this point in the history
Tarea: 40906
  • Loading branch information
pablohmontenegro committed Jan 6, 2025
1 parent 85cb4b9 commit 9e51dd3
Show file tree
Hide file tree
Showing 12 changed files with 399 additions and 0 deletions.
18 changes: 18 additions & 0 deletions l10n_ar_account_tax_settlement/views/account_payment_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_account_payment_form" model="ir.ui.view">
<field name="name">account.payment.form.inherited</field>
<field name="model">account.payment</field>
<field name="inherit_id" ref="account.view_account_payment_form"/>
<field name="arch" type="xml">
<field name="ref" position="after">
<field name="es_sire" invisible="1"/>
<group name="sire_data" attrs="{'invisible': [('es_sire', '!=', True)]}">
<field name="sire_aplica_cdi"/>
<field name="sire_aplica_acrecentamiento"/>
<field name="sire_codigo_alicuota"/>
</group>
</field>
</field>
</record>
</odoo>
66 changes: 66 additions & 0 deletions l10n_ar_txt_sire/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
.. |company| replace:: ADHOC SA

.. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png
:alt: ADHOC SA
:target: https://www.adhoc.com.ar

.. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png

.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3

========================
Tax Settlements For Sire
========================

Este módulo imlementa:

* archivos para declaración de impuesto sire de retenciones aplicadas a sujetos domiciliados en el exterior

Archivos para declaración de impuestos
======================================

* SIRE: https://www.afip.gob.ar/sire/documentos/SIRE-especificacion-para-emision-por-lote.pdf apartado 3. F2003 CERTIFICADOS SUJETOS DOMICILIADOS EN EL EXTERIOR
https://www.afip.gob.ar/sire/documentos/SIRE-especificacion-para-emision-por-lote.pdf apartado 5. F2005 CERTIFICADOS DE RETENCIÓN IMPOSITIVA (beta: nunca fue testeado).

Installation
============

To install this module, you need to:

#. Only need to install the module

Configuration
=============

To configure this module, you need to:

#. Nothing to configure

Usage
=====

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: http://runbot.adhoc.com.ar/

Credits
=======

Images
------

* |company| |icon|

Contributors
------------

Maintainer
----------

|company_logo|

This module is maintained by the |company|.

To contribute to this module, please visit https://www.adhoc.com.ar.
6 changes: 6 additions & 0 deletions l10n_ar_txt_sire/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from . import models
from . import wizards
20 changes: 20 additions & 0 deletions l10n_ar_txt_sire/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
'name': 'Txt SIRE',
'version': "16.0.1.0.0",
'category': 'Accounting',
'website': 'www.adhoc.com.ar',
'license': 'LGPL-3',
'depends': [
'l10n_ar_account_tax_settlement',
],
'data': [
'data/account_account_tag_data.xml',
'views/res_partner_view.xml',
'views/account_payment_view.xml',
],
'demo': [
],
'installable': True,
'auto_install': False,
'application': False,
}
8 changes: 8 additions & 0 deletions l10n_ar_txt_sire/data/account_account_tag_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="tag_tax_sire" model="account.account.tag">
<field name="name">Sire</field>
<field name="applicability">taxes</field>
<field name="country_id" ref="base.ar"/>
</record>
</odoo>
Binary file not shown.
3 changes: 3 additions & 0 deletions l10n_ar_txt_sire/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import account_journal
from . import res_partner
from . import account_payment
205 changes: 205 additions & 0 deletions l10n_ar_txt_sire/models/account_journal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
from odoo import models, fields, _
from odoo.exceptions import RedirectWarning, UserError


class AccountJournal(models.Model):
_inherit = 'account.journal'

# TODO ver como queremos separar la de santa fe, arba y demás. usamos uno
# solo y luego logica adentro? un diario para cada una?
settlement_tax = fields.Selection(selection_add=[
('sire', 'TXT Retenciones SIRE'),
])

def sire_files_values(self, move_lines):
""" Retenciones de sire. Implementado según especificación de tarea 40906 """
self.ensure_one()
content = ''
for line in move_lines.sorted(key=lambda r: (r.date, r.id)):
payment = line.payment_id
# VALIDACIONES

# Validamos que el impuesto SIRE tenga código de régimen establecido
if not payment.tax_withholding_id.codigo_regimen:
raise RedirectWarning(
message=_("El impuesto '%s' no tiene código de régimen establecido. Es obligatorio para generar el archivo txt Sire. Editar "
"campo 'Codigo de regimen IVA' en solapa 'Opciones avanzadas' en la vista formulario", payment.tax_withholding_id.name),
action={
'type': 'ir.actions.act_window',
'res_model': 'account.tax',
'views': [(False, 'form')],
'res_id': payment.tax_withholding_id.id,
'name': _('Tax'),
'view_mode': 'form',
},
button_text=_('Editar impuesto'),
)

# Validamos que el partner sea Cliente / Proveedor del Exterior
if line.partner_id.l10n_ar_afip_responsibility_type_id.id != self.env.ref('l10n_ar.res_EXT').id:
raise UserError(_("Solo puede generar el archivo de retenciones SIRE para contactos con responsabilidad"
"AFIP: 'Cliente / Proveedor del Exterior'. Contacto: %s (id: %s)"
% (line.partner_id.name, line.partner_id.id)))

# Validamos que el contacto tenga país establecido
if not line.partner_id.country_id:
raise RedirectWarning(
message=_("El contacto '%s' debe tener país establecido", payment.partner_id.name),
action={
'type': 'ir.actions.act_window',
'res_model': 'res.partner',
'views': [(False, 'form')],
'res_id': payment.partner_id.id,
'name': _('Tax'),
'view_mode': 'form',
},
button_text=_('Editar contacto.'),
)

# Validamos que el país del contacto tenga el cuit correspondiente
pais = line.partner_id.country_id
es_persona = line.partner_id.company_type == 'person'
if es_persona:
if not pais.l10n_ar_natural_vat:
raise RedirectWarning(
message=_("El país '%s' no tiene IVA Persona Física establecido.", pais.name),
action={
'type': 'ir.actions.act_window',
'res_model': 'res.country',
'views': [(False, 'form')],
'res_id': pais.id,
'name': _('País'),
'view_mode': 'form',
},
button_text=_('Editar País'),
)
if not es_persona:
if not pais.l10n_ar_legal_entity_vat:
raise RedirectWarning(
message=_("El país '%s' no tiene cuit persona jurídica establecido.", pais.name),
action={
'type': 'ir.actions.act_window',
'res_model': 'res.country',
'views': [(False, 'form')],
'res_id': pais.id,
'name': _('País'),
'view_mode': 'form',
},
button_text=_('Editar País'),
)

# Validamos que el cuit del agente sea de 11 dígitos
partner_vat = payment.partner_id.vat
if len(partner_vat) != 11:
raise RedirectWarning(
message=_("El cuit '%s' del agente debe ser de 11 dígitos de longitud", payment.partner_id.name),
action={
'type': 'ir.actions.act_window',
'res_model': 'res.partner',
'views': [(False, 'form')],
'res_id': payment.partner_id.id,
'name': _('Tax'),
'view_mode': 'form',
},
button_text=_('Editar contacto.'),
)

# Validamos que el contacto tenga país y fecha de nacimiento si es persona física
if es_persona and (not line.partner_id.born_country_id or not line.partner_id.birthdate):
raise RedirectWarning(
message=_("El contacto por ser un individuo '%s'"
" debe tener país y fecha de nacimiento establecido.", line.partner_id.name),
action={
'type': 'ir.actions.act_window',
'res_model': 'res.partner',
'views': [(False, 'form')],
'res_id': line.partner_id.id,
'name': _('Contacto'),
'view_mode': 'form',
},
button_text=_('Editar Contacto'),
)

fecha_impuesto = fields.Date.from_string(line.date).strftime('%d/%m/%Y')
# 1 Formulario (integer long 4, 1-4, obligatorio) --> '2003' comentado por agi en tarea 40050, en nota del 23/10/2024 a las 10:00hs
# Además en la sección "3.2. F2003 - Validaciones", pàgina 5 del pdf de la especificación se indica que debe ser fijo "2003"
content += '2003'
# 2 Versión (integer long 4, 5-8, obligatorio) --> '0100' comentado por agi en tarea 40050, en nota del 23/10/2024 a las 10:00hs
# Además en la sección "3.2. F2003 - Validaciones", pàgina 5 del pdf de la especificación se indica que debe ser fijo "0100"
content += '0100'
# 3 Código de trazabilidad (string long 10, 9-18, no obligatorio) --> texto libre o en blanco -->
# comentado por agi en tarea 40050, en nota del 23/10/2024 a las 10:00hs
content += ' ' * 10
# 4 Cuit agente (integer, long 11, 19-29, obligatorio)
content += partner_vat
# 5 Impuesto (integer long 3, 30-32, no obligatorio) -->
# sección "3.2. F2003 - Validaciones", pàgina 5 del pdf de la especificación se indica que debe ser fijo "218"
content += '218'
# 6 Régimen (integer long 3, 33-35, obligatorio)
content += payment.tax_withholding_id.codigo_regimen
# 7 Cuit ordenante (integer 11, 36-46, obligatorio)
content += partner_vat
# 8 Fecha retención (date long 10, 59-68, obligatorio)
content += fecha_impuesto
# 9 Tipo comprobante (integer 2, 57-58, obligatorio)
# por el momento lo dejamos fijo '06' que es el tipo de comprobante para retenciones
# pero en un futuro para percepciones puede tomar otros valrores (tomar como referencia lo desarrollado para sicore) --> ver espeficiación vieja tarea 40906
content += '06'
# 10 Fecha comprobante (date 10, 59-68, obligatorio)
content += fecha_impuesto
# 11 Nro comprobante (string 16, 69-84, obligatorio)
# Número de Orden de Pago sin guiones ni tipo de identificación (ej: 000100008220) (prefijo sin - + número de documento) (no es obligatorio) --> ver espeficiación vieja tarea 40906
content += re.sub('[^0-9]', '', payment.name).ljust(16)
# 12 Importe comprobante (decimal 14, 85-98, obligatorio)
content += '%14.2f' % payment.payment_group_id.payments_amount
# 13 Filler (filler 14, 99-112, obligatorio)
content += ' ' * 14
# 14 Certificado original nro (string 25, 113-137, no obligatorio)
content += ' ' * 25
# 15 Certificado original fecha reten (date 10, 138-147, no obligatorio)
content += ' ' * 10
# 16 Certificado original importe (decimal 14, 148-161, no obligatorio)
content += ' ' * 14
# 17 Motivo emisión nota de créditon(string 30, 162-191, no obligatorio)
content += ' ' * 30
# 18 No retención (boolean 1, 192-192) --> ver especificación vieja tarea 40906
content += '0'
# 19 No retención motivo (string 30, 193-222, no obligatorio)
content += '0' * 30
# 20 Aplica CDI (boolean 1, 223-223, obligatorio) --> especificación 40906, mt 11/12/24
content += '1' if payment.sire_aplica_cdi else '0'
# 21 Código de alícuota (integer, 4, 224-227, obligatorio)
content += payment.sire_codigo_alicuota.zfill(4)
# 22 Aplica acrecentamiento (boolean, 1, 228-228)
content += '1' if payment.sire_aplica_acrecentamiento else '0'
# 23 Retenido clave nif (string 50, 229-278, obligatorio)
# Cuit del pais del sujeto retenido s/ especificación tarea 40906, mt 11/12/24
content += pais.l10n_ar_natural_vat if es_persona else pais.l10n_ar_legal_entity_vat
content += ' ' * 39
# 24 Retenido Apellido Nombre Denominacion (string, 60, 279-338, obligatorio)
content += line.partner_id.name[:60].ljust(60)
# 25 Retenido domicilio actual en exterior (string, 60, 339-398, obligatorio)
# domicilio completo --> comentado por agi en tarea 40050, en nota del 23/10/2024 a las 10:00hs
domicilio = ''
if payment.partner_id.street:
domicilio += payment.partner_id.street
if payment.partner_id.street2:
domicilio += ' ' + payment.partner_id.street2
if payment.partner_id.city:
domicilio += ' ' + payment.partner_id.city
if payment.partner_id.state_id:
domicilio += ' ' + payment.partner_id.state_id.name
content += domicilio[:60].ljust(60) if domicilio else ' ' * 60
# 26 Retenido domicilio actual en exterior pais (integer, 3, 399-401, obligatorio)
content += line.partner_id.country_id.l10n_ar_afip_code or ' ' * 3
# 27 Retenido tipo de persona (string, 1, 402-402, obligatorio)
content += 'F' if es_persona else 'J'
# 28 Retenido nacimiento constitucion pais (integer, 3, 403-405, no obligatorio)
content += line.partner_id.born_country_id.l10n_ar_afip_code if es_persona else ' ' * 3
# 29 Retenido nacimiento constitucion fecha (date 10, 406-415, no obligatorio)
content += line.partner_id.birthdate.strftime('%d/%m/%Y') if es_persona else ' ' * 10
content += '\r\n'
return [{
'txt_filename': ('Retenciones') + '_sire.txt',
'txt_content': content,
}]
25 changes: 25 additions & 0 deletions l10n_ar_txt_sire/models/account_payment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from odoo import models, fields, api


class AccountPayment(models.Model):

_inherit = "account.payment"

sire_aplica_cdi = fields.Boolean(related='partner_id.sire_aplica_cdi',
readonly=False,
help="Campo para archivo txt Ganancias SIRE. Marcar si aplica CDI")
sire_aplica_acrecentamiento = fields.Boolean(related='partner_id.sire_aplica_acrecentamiento',
readonly=False,
help="Campo para archivo txt Ganancias SIRE. Marcar si aplica CDI")
sire_codigo_alicuota = fields.Char(related='partner_id.sire_codigo_alicuota', readonly=False, size=4)
# Este campo se usa para hacer invisibles los campos anteriores en el pago si no se trata de una retención
# de sire
es_sire = fields.Boolean(compute='_compute_es_sire')

@api.onchange('tax_withholding_id')
def _compute_es_sire(self):
tag_tax_sire = self.env.ref('l10n_ar_account_tax_settlement.tag_tax_sire')
payments_with_sire = self.filtered(lambda pay: tag_tax_sire in
pay.tax_withholding_id.invoice_repartition_line_ids.tag_ids)
payments_with_sire.es_sire = True
(self - payments_with_sire).es_sire = False
13 changes: 13 additions & 0 deletions l10n_ar_txt_sire/models/res_partner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from odoo import models, fields


class ResPartner(models.Model):
_inherit = 'res.partner'

sire_aplica_cdi = fields.Boolean(string='Aplica CDI',
help="Campo para archivo txt Ganancias SIRE. Marcar si aplica CDI")
sire_aplica_acrecentamiento = fields.Boolean(string='Aplica acrecentamiento',
help="Campo para archivo txt Ganancias SIRE. Marcar si aplica CDI")
sire_codigo_alicuota = fields.Char(size=4)
born_country_id = fields.Many2one('res.country', string='País de Nacimiento', ondelete='restrict')
birthdate = fields.Date(string='Fecha de Nacimiento')
18 changes: 18 additions & 0 deletions l10n_ar_txt_sire/views/account_payment_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_account_payment_form" model="ir.ui.view">
<field name="name">account.payment.form.inherited</field>
<field name="model">account.payment</field>
<field name="inherit_id" ref="account.view_account_payment_form"/>
<field name="arch" type="xml">
<field name="ref" position="after">
<field name="es_sire" invisible="1"/>
<group name="sire_data" attrs="{'invisible': [('es_sire', '!=', True)]}">
<field name="sire_aplica_cdi"/>
<field name="sire_aplica_acrecentamiento"/>
<field name="sire_codigo_alicuota"/>
</group>
</field>
</field>
</record>
</odoo>
Loading

0 comments on commit 9e51dd3

Please sign in to comment.