566 lines
20 KiB
Python
566 lines
20 KiB
Python
# This file is part of the account_retencion_ar module for Tryton.
|
|
# The COPYRIGHT file at the top level of this repository contains
|
|
# the full copyright notices and license terms.
|
|
from decimal import Decimal
|
|
from sql import Null
|
|
|
|
from trytond import backend
|
|
from trytond.model import ModelView, ModelSQL, fields
|
|
from trytond.wizard import Wizard, StateView, StateReport, Button
|
|
from trytond.report import Report
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Eval, Bool, Not, Id
|
|
from trytond.transaction import Transaction
|
|
from trytond.exceptions import UserError
|
|
from trytond.i18n import gettext
|
|
from trytond.tools.multivalue import migrate_property
|
|
from trytond.modules.company.model import (
|
|
CompanyMultiValueMixin, CompanyValueMixin)
|
|
|
|
|
|
class TaxWithholdingType(ModelSQL, ModelView, CompanyMultiValueMixin):
|
|
'Tax Withholding Type'
|
|
__name__ = 'account.retencion'
|
|
|
|
name = fields.Char('Name', required=True)
|
|
type = fields.Selection([
|
|
('efectuada', 'Submitted'),
|
|
('soportada', 'Received'),
|
|
], 'Type', required=True)
|
|
tax = fields.Selection('get_tax', 'Tax', required=True, sort=False)
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
domain=[
|
|
('type', '!=', None),
|
|
('closed', '!=', True),
|
|
])
|
|
sequence = fields.MultiValue(fields.Many2One(
|
|
'ir.sequence', 'Sequence',
|
|
domain=[
|
|
('sequence_type', '=',
|
|
Id('account_retencion_ar', 'seq_type_account_retencion')),
|
|
('company', 'in',
|
|
[Eval('context', {}).get('company', -1), None]),
|
|
],
|
|
states={'invisible': Eval('type') != 'efectuada'}))
|
|
sequences = fields.One2Many('account.retencion.sequence',
|
|
'retencion', 'Sequences')
|
|
regime_code = fields.Char('Regime code')
|
|
regime_name = fields.Char('Regime name')
|
|
subdivision = fields.Many2One('country.subdivision', 'Subdivision',
|
|
domain=[('country.code', '=', 'AR')],
|
|
states={'invisible': Eval('tax') != 'iibb'})
|
|
minimum_non_taxable_amount = fields.Numeric('Minimum Non-Taxable Amount',
|
|
digits=(16, 2))
|
|
rate_registered = fields.Numeric('% Withholding to Registered',
|
|
digits=(14, 10))
|
|
rate_non_registered = fields.Numeric('% Withholding to Non-Registered',
|
|
digits=(14, 10))
|
|
minimum_withholdable_amount = fields.Numeric('Minimum Amount to Withhold',
|
|
digits=(16, 2))
|
|
scales = fields.One2Many('account.retencion.scale', 'retencion', 'Scales')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._order.insert(0, ('type', 'ASC'))
|
|
cls._order.insert(1, ('name', 'ASC'))
|
|
cls._order.insert(2, ('regime_code', 'ASC'))
|
|
|
|
@classmethod
|
|
def get_tax(cls):
|
|
selection = [
|
|
('iva', 'IVA'),
|
|
('gana', 'Ganancias'),
|
|
('suss', 'SUSS'),
|
|
('iibb', 'Ingresos Brutos'),
|
|
('otro', 'Otro'),
|
|
]
|
|
return selection
|
|
|
|
@classmethod
|
|
def multivalue_model(cls, field):
|
|
pool = Pool()
|
|
if field == 'sequence':
|
|
return pool.get('account.retencion.sequence')
|
|
return super().multivalue_model(field)
|
|
|
|
def get_rec_name(self, name):
|
|
if self.regime_name:
|
|
return '%s - %s' % (self.name, self.regime_name)
|
|
return self.name
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return super().view_attributes() + [
|
|
('//group[@id="calculation"]', 'states',
|
|
{'invisible': Eval('type') != 'efectuada'}),
|
|
]
|
|
|
|
|
|
class TaxWithholdingTypeSequence(ModelSQL, CompanyValueMixin):
|
|
'Tax Withholding Type Sequence'
|
|
__name__ = 'account.retencion.sequence'
|
|
|
|
retencion = fields.Many2One('account.retencion', 'Tax Withholding Type',
|
|
ondelete='CASCADE', context={'company': Eval('company', -1)},
|
|
depends={'company'})
|
|
sequence = fields.Many2One('ir.sequence',
|
|
'Sequence', domain=[
|
|
('sequence_type', '=',
|
|
Id('account_retencion_ar', 'seq_type_account_retencion')),
|
|
('company', 'in', [Eval('company', -1), None]),
|
|
])
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
exist = backend.TableHandler.table_exist(cls._table)
|
|
super().__register__(module_name)
|
|
if not exist:
|
|
cls._migrate_property([], [], [])
|
|
|
|
@classmethod
|
|
def _migrate_property(cls, field_names, value_names, fields):
|
|
field_names.append('sequence')
|
|
value_names.append('sequence')
|
|
fields.append('company')
|
|
migrate_property(
|
|
'account.retencion', field_names, cls, value_names,
|
|
parent='retencion', fields=fields)
|
|
|
|
|
|
class TaxWithholdingTypeScale(ModelSQL, ModelView):
|
|
'Tax Withholding Type Scale'
|
|
__name__ = 'account.retencion.scale'
|
|
|
|
retencion = fields.Many2One('account.retencion', 'Tax Withholding Type',
|
|
ondelete='CASCADE')
|
|
start_amount = fields.Numeric('Amount from', digits=(16, 2))
|
|
end_amount = fields.Numeric('Amount up to', digits=(16, 2))
|
|
fixed_withholdable_amount = fields.Numeric('Fixed Amount to Withhold',
|
|
digits=(16, 2))
|
|
rate = fields.Numeric('% Withholding', digits=(14, 10))
|
|
minimum_non_taxable_amount = fields.Numeric('Non-Taxable Base',
|
|
digits=(16, 2))
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._order.insert(0, ('start_amount', 'ASC'))
|
|
|
|
|
|
class TaxWithholdingSubmitted(ModelSQL, ModelView):
|
|
'Tax Withholding Submitted'
|
|
__name__ = 'account.retencion.efectuada'
|
|
|
|
_states = {'readonly': Eval('state') != 'draft'}
|
|
|
|
name = fields.Char('Number',
|
|
states={
|
|
'required': Bool(Eval('name_required')),
|
|
'readonly': Not(Bool(Eval('name_required'))),
|
|
})
|
|
name_required = fields.Function(fields.Boolean('Name Required'),
|
|
'on_change_with_name_required')
|
|
tax = fields.Many2One('account.retencion', 'Type',
|
|
domain=[('type', '=', 'efectuada')], states=_states)
|
|
regime_code = fields.Function(fields.Char('Regime code'),
|
|
'get_tax_field')
|
|
regime_name = fields.Function(fields.Char('Regime name'),
|
|
'get_tax_field')
|
|
date = fields.Date('Date', required=True, states=_states)
|
|
voucher = fields.Many2One('account.voucher', 'Voucher', readonly=True)
|
|
party = fields.Many2One('party.party', 'Party', states=_states)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('issued', 'Issued'),
|
|
('cancelled', 'Cancelled'),
|
|
], 'State', readonly=True)
|
|
payment_amount = fields.Numeric('Payment Amount',
|
|
digits=(16, 2), readonly=True)
|
|
accumulated_amount = fields.Numeric('Accumulated Amount',
|
|
digits=(16, 2), readonly=True)
|
|
minimum_non_taxable_amount = fields.Numeric('Minimum Non-Taxable Amount',
|
|
digits=(16, 2), readonly=True)
|
|
scale_non_taxable_amount = fields.Numeric('Non-Taxable Base (Scale)',
|
|
digits=(16, 2), readonly=True)
|
|
taxable_amount = fields.Numeric('Taxable Amount',
|
|
digits=(16, 2), readonly=True)
|
|
rate = fields.Numeric('% Withholding',
|
|
digits=(14, 10), readonly=True)
|
|
scale_fixed_amount = fields.Numeric('Fixed Amount to Withhold (Scale)',
|
|
digits=(16, 2), readonly=True)
|
|
computed_amount = fields.Numeric('Computed Amount',
|
|
digits=(16, 2), readonly=True)
|
|
minimum_withholdable_amount = fields.Numeric('Minimum Amount to Withhold',
|
|
digits=(16, 2), readonly=True)
|
|
accumulated_withheld = fields.Numeric('Accumulated Withheld',
|
|
digits=(16, 2), readonly=True)
|
|
amount = fields.Numeric('Amount', digits=(16, 2), required=True,
|
|
states=_states)
|
|
|
|
del _states
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
cursor = Transaction().connection.cursor()
|
|
sql_table = cls.__table__()
|
|
table_h = cls.__table_handler__(module_name)
|
|
|
|
aliquot_exist = table_h.column_exist('aliquot')
|
|
|
|
super().__register__(module_name)
|
|
cursor.execute(*sql_table.update(
|
|
[sql_table.state], ['cancelled'],
|
|
where=sql_table.state == 'canceled'))
|
|
if aliquot_exist:
|
|
cursor.execute(*sql_table.update(
|
|
[sql_table.rate], [sql_table.aliquot.cast('NUMERIC')],
|
|
where=sql_table.aliquot != Null))
|
|
table_h.drop_column('aliquot')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._buttons.update({
|
|
'execute_report': {
|
|
'invisible': Eval('state') != 'issued',
|
|
'depends': ['state'],
|
|
},
|
|
})
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
Date = Pool().get('ir.date')
|
|
return Date.today()
|
|
|
|
@staticmethod
|
|
def default_amount():
|
|
return Decimal('0.00')
|
|
|
|
@fields.depends('tax')
|
|
def on_change_with_name_required(self, name=None):
|
|
if self.tax and self.tax.sequence:
|
|
return False
|
|
return True
|
|
|
|
@classmethod
|
|
def delete(cls, retenciones):
|
|
cls.check_delete(retenciones)
|
|
super().delete(retenciones)
|
|
|
|
@classmethod
|
|
def check_delete(cls, retenciones):
|
|
if Transaction().context.get('delete_calculated', False):
|
|
return
|
|
for retencion in retenciones:
|
|
if retencion.voucher:
|
|
raise UserError(gettext(
|
|
'account_retencion_ar.msg_not_delete',
|
|
retencion=retencion.name))
|
|
|
|
@classmethod
|
|
def copy(cls, retenciones, default=None):
|
|
if default is None:
|
|
default = {}
|
|
current_default = default.copy()
|
|
current_default['state'] = 'draft'
|
|
current_default['name'] = None
|
|
current_default['voucher'] = None
|
|
return super().copy(retenciones, default=current_default)
|
|
|
|
@classmethod
|
|
def get_tax_field(cls, retenciones, names):
|
|
result = {}
|
|
for name in names:
|
|
result[name] = {}
|
|
if cls._fields[name]._type == 'many2one':
|
|
for r in retenciones:
|
|
field = getattr(r.tax, name, None)
|
|
result[name][r.id] = field.id if field else None
|
|
elif cls._fields[name]._type == 'boolean':
|
|
for r in retenciones:
|
|
result[name][r.id] = getattr(r.tax, name, False)
|
|
else:
|
|
for r in retenciones:
|
|
result[name][r.id] = getattr(r.tax, name, None)
|
|
return result
|
|
|
|
@classmethod
|
|
def search_tax_field(cls, name, clause):
|
|
return [('tax.' + name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
@ModelView.button_action(
|
|
'account_retencion_ar.report_account_retencion_efectuada')
|
|
def execute_report(cls, retenciones):
|
|
pass
|
|
|
|
|
|
class TaxWithholdingReceived(ModelSQL, ModelView):
|
|
'Tax Withholding Received'
|
|
__name__ = 'account.retencion.soportada'
|
|
|
|
_states = {'readonly': Eval('state') != 'draft'}
|
|
|
|
name = fields.Char('Number', required=True, states=_states)
|
|
tax = fields.Many2One('account.retencion', 'Type',
|
|
domain=[('type', '=', 'soportada')], states=_states)
|
|
date = fields.Date('Date', required=True, states=_states)
|
|
voucher = fields.Many2One('account.voucher', 'Voucher', readonly=True)
|
|
party = fields.Many2One('party.party', 'Party', states=_states)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('held', 'Held'),
|
|
('cancelled', 'Cancelled'),
|
|
], 'State', readonly=True)
|
|
amount = fields.Numeric('Amount', digits=(16, 2), required=True,
|
|
states=_states)
|
|
|
|
del _states
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
cursor = Transaction().connection.cursor()
|
|
sql_table = cls.__table__()
|
|
super().__register__(module_name)
|
|
cursor.execute(*sql_table.update(
|
|
[sql_table.state], ['cancelled'],
|
|
where=sql_table.state == 'canceled'))
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
Date = Pool().get('ir.date')
|
|
return Date.today()
|
|
|
|
@staticmethod
|
|
def default_amount():
|
|
return Decimal('0.00')
|
|
|
|
@classmethod
|
|
def delete(cls, retenciones):
|
|
cls.check_delete(retenciones)
|
|
super().delete(retenciones)
|
|
|
|
@classmethod
|
|
def check_delete(cls, retenciones):
|
|
for retencion in retenciones:
|
|
if retencion.voucher:
|
|
raise UserError(gettext(
|
|
'account_retencion_ar.msg_not_delete',
|
|
retencion=retencion.name))
|
|
|
|
@classmethod
|
|
def copy(cls, retenciones, default=None):
|
|
if default is None:
|
|
default = {}
|
|
current_default = default.copy()
|
|
current_default['state'] = 'draft'
|
|
current_default['name'] = None
|
|
current_default['voucher'] = None
|
|
return super().copy(retenciones, default=current_default)
|
|
|
|
|
|
class TaxWithholdingSubmittedReport(Report):
|
|
__name__ = 'account.retencion.efectuada.report'
|
|
|
|
@classmethod
|
|
def execute(cls, ids, data):
|
|
pool = Pool()
|
|
TaxWithholdingSubmitted = pool.get('account.retencion.efectuada')
|
|
|
|
for retencion in TaxWithholdingSubmitted.browse(ids):
|
|
if retencion.state != 'issued':
|
|
raise UserError(gettext(
|
|
'account_retencion_ar.msg_print_not_issued',
|
|
retencion=retencion.name))
|
|
return super().execute(ids, data)
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
|
|
report_context = super().get_context(records, header, data)
|
|
|
|
company = Company(Transaction().context.get('company'))
|
|
report_context['company'] = company
|
|
report_context['format_vat_number'] = cls.format_vat_number
|
|
return report_context
|
|
|
|
@classmethod
|
|
def format_vat_number(cls, vat_number=''):
|
|
if not vat_number:
|
|
return ''
|
|
return '%s-%s-%s' % (vat_number[:2], vat_number[2:-1], vat_number[-1])
|
|
|
|
|
|
class Perception(metaclass=PoolMeta):
|
|
__name__ = 'account.tax'
|
|
|
|
subdivision = fields.Many2One('country.subdivision', 'Subdivision',
|
|
domain=[('country.code', '=', 'AR')],
|
|
states={'invisible': Eval('afip_kind') != 'provincial'})
|
|
minimum_non_taxable_amount = fields.Numeric('Minimum Non-Taxable Amount',
|
|
digits=(16, 2))
|
|
rate_registered = fields.Numeric('% Perception to Registered',
|
|
digits=(14, 10))
|
|
rate_non_registered = fields.Numeric('% Perception to Non-Registered',
|
|
digits=(14, 10))
|
|
minimum_perceivable_amount = fields.Numeric(
|
|
'Minimum Amount to be Perceived', digits=(16, 2))
|
|
|
|
@classmethod
|
|
def view_attributes(cls):
|
|
return super().view_attributes() + [
|
|
('//group[@id="calculation"]', 'states',
|
|
{'invisible': ~Eval('afip_kind').in_(
|
|
['nacional', 'provincial', 'municipal'])}),
|
|
]
|
|
|
|
|
|
class PrintIIBBSubdivisionStart(ModelView):
|
|
'Retenciones y Percepciones de Ingresos Brutos por Jurisdicción'
|
|
__name__ = 'account.print_iibb_subdivision.start'
|
|
|
|
start_date = fields.Date('Start date', required=True)
|
|
end_date = fields.Date('End date', required=True)
|
|
subdivision = fields.Many2One('country.subdivision', 'Subdivision',
|
|
domain=[('country.code', '=', 'AR')], required=True)
|
|
|
|
|
|
class PrintIIBBSubdivision(Wizard):
|
|
'Retenciones y Percepciones de Ingresos Brutos por Jurisdicción'
|
|
__name__ = 'account.print_iibb_subdivision'
|
|
|
|
start = StateView('account.print_iibb_subdivision.start',
|
|
'flexar_silix.print_iibb_subdivision_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateReport('account.iibb_subdivision.report')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'start_date': self.start.start_date,
|
|
'end_date': self.start.end_date,
|
|
'subdivision': self.start.subdivision.id,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class IIBBSubdivisionReport(Report):
|
|
'Retenciones y Percepciones de Ingresos Brutos por Jurisdicción'
|
|
__name__ = 'account.iibb_subdivision.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
pool = Pool()
|
|
Subdivision = pool.get('country.subdivision')
|
|
|
|
report_context = super().get_context(records, header, data)
|
|
|
|
company = report_context['user'].company
|
|
report_context['company'] = company
|
|
report_context['start_date'] = data['start_date']
|
|
report_context['end_date'] = data['end_date']
|
|
report_context['subdivision'] = Subdivision(
|
|
data['subdivision']).rec_name
|
|
report_context['retenciones'] = cls._get_retenciones(
|
|
company, data['start_date'], data['end_date'], data['subdivision'])
|
|
report_context['percepciones'] = cls._get_percepciones(
|
|
company, data['start_date'], data['end_date'], data['subdivision'])
|
|
return report_context
|
|
|
|
@classmethod
|
|
def _get_retenciones(cls, company, start_date, end_date, subdivision):
|
|
pool = Pool()
|
|
WithholdingType = pool.get('account.retencion')
|
|
TaxWithholdingSubmitted = pool.get('account.retencion.efectuada')
|
|
|
|
withholding_type = WithholdingType.search([
|
|
('tax', '=', 'iibb'),
|
|
('type', '=', 'efectuada'),
|
|
('subdivision', '=', subdivision),
|
|
])
|
|
if not withholding_type:
|
|
return []
|
|
withholding_type = withholding_type[0]
|
|
|
|
res = []
|
|
retenciones = TaxWithholdingSubmitted.search([
|
|
('tax', '=', withholding_type),
|
|
('date', '>=', start_date),
|
|
('date', '<=', end_date),
|
|
('state', '=', 'issued'),
|
|
], order=[
|
|
('date', 'ASC'),
|
|
('name', 'ASC'),
|
|
])
|
|
for retencion in retenciones:
|
|
record = {
|
|
'date': retencion.date,
|
|
'vat_number': retencion.party.vat_number,
|
|
'party_name': retencion.party.rec_name,
|
|
'base': retencion.payment_amount,
|
|
'amount': retencion.amount,
|
|
'number': retencion.name,
|
|
}
|
|
res.append(record)
|
|
|
|
return res
|
|
|
|
@classmethod
|
|
def _get_percepciones(cls, company, start_date, end_date, subdivision):
|
|
pool = Pool()
|
|
PerceptionType = pool.get('account.tax')
|
|
Invoice = pool.get('account.invoice')
|
|
|
|
perception_type = PerceptionType.search([
|
|
('group.afip_kind', '=', 'provincial'),
|
|
('group.kind', '=', 'sale'),
|
|
('company', '=', company),
|
|
('subdivision', '=', subdivision),
|
|
])
|
|
if not perception_type:
|
|
return []
|
|
perception_type = perception_type[0]
|
|
|
|
res = []
|
|
invoices = Invoice.search([
|
|
('type', '=', 'out'),
|
|
['OR', ('state', 'in', ['posted', 'paid']),
|
|
[('state', '=', 'cancelled'), ('number', '!=', None)]],
|
|
('move.date', '>=', start_date),
|
|
('move.date', '<=', end_date),
|
|
#('pos.pos_do_not_report', '=', False),
|
|
], order=[
|
|
('number', 'ASC'),
|
|
('invoice_date', 'ASC'),
|
|
])
|
|
for invoice in invoices:
|
|
for percepcion in invoice.taxes:
|
|
if percepcion.tax != perception_type:
|
|
continue
|
|
record = {
|
|
'date': invoice.invoice_date,
|
|
'vat_number': invoice.party.vat_number,
|
|
'party_name': invoice.party.rec_name,
|
|
'base': invoice.untaxed_amount,
|
|
'amount': percepcion.amount,
|
|
'number': invoice.number,
|
|
}
|
|
res.append(record)
|
|
|
|
return res
|