Add support for automatic withholdings
This commit is contained in:
parent
4c97b3b965
commit
21f03eaf85
|
@ -1,3 +1,4 @@
|
|||
* Add support for automatic withholdings
|
||||
* Improves voucher form
|
||||
|
||||
Version 6.0.0 - 2021-09-10
|
||||
|
|
16
__init__.py
16
__init__.py
|
@ -5,13 +5,29 @@
|
|||
from trytond.pool import Pool
|
||||
from . import account_retencion_ar
|
||||
from . import account_voucher_ar
|
||||
from . import party
|
||||
from . import company
|
||||
from . import product
|
||||
|
||||
|
||||
def register():
|
||||
Pool.register(
|
||||
account_retencion_ar.TaxWithholdingType,
|
||||
account_retencion_ar.TaxWithholdingTypeSequence,
|
||||
account_retencion_ar.TaxWithholdingTypeScale,
|
||||
account_retencion_ar.TaxWithholdingSubmitted,
|
||||
account_retencion_ar.TaxWithholdingReceived,
|
||||
account_voucher_ar.AccountVoucher,
|
||||
account_voucher_ar.RecalculateWithholdingsStart,
|
||||
party.Party,
|
||||
party.PartyExemption,
|
||||
party.PartyWithholdingIIBB,
|
||||
company.Company,
|
||||
company.CompanyWithholdingIIBB,
|
||||
product.Category,
|
||||
product.Product,
|
||||
invoice.InvoiceLine,
|
||||
module='account_retencion_ar', type_='model')
|
||||
Pool.register(
|
||||
account_voucher_ar.RecalculateWithholdings,
|
||||
module='account_retencion_ar', type_='wizard')
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
# 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.pool import Pool
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Eval, Bool, Not, Id
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.exceptions import UserError
|
||||
|
@ -24,6 +25,7 @@ class TaxWithholdingType(ModelSQL, ModelView, CompanyMultiValueMixin):
|
|||
('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),
|
||||
|
@ -40,6 +42,38 @@ class TaxWithholdingType(ModelSQL, ModelView, CompanyMultiValueMixin):
|
|||
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):
|
||||
|
@ -48,6 +82,18 @@ class TaxWithholdingType(ModelSQL, ModelView, CompanyMultiValueMixin):
|
|||
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'
|
||||
|
@ -80,6 +126,26 @@ class TaxWithholdingTypeSequence(ModelSQL, CompanyValueMixin):
|
|||
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'
|
||||
|
@ -95,7 +161,10 @@ class TaxWithholdingSubmitted(ModelSQL, ModelView):
|
|||
'on_change_with_name_required')
|
||||
tax = fields.Many2One('account.retencion', 'Type',
|
||||
domain=[('type', '=', 'efectuada')], states=_states)
|
||||
aliquot = fields.Float('Aliquot')
|
||||
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)
|
||||
|
@ -104,6 +173,26 @@ class TaxWithholdingSubmitted(ModelSQL, ModelView):
|
|||
('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)
|
||||
|
||||
|
@ -113,10 +202,19 @@ class TaxWithholdingSubmitted(ModelSQL, ModelView):
|
|||
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')
|
||||
|
||||
@staticmethod
|
||||
def default_state():
|
||||
|
@ -144,11 +242,13 @@ class TaxWithholdingSubmitted(ModelSQL, ModelView):
|
|||
|
||||
@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',
|
||||
retention=retencion.name))
|
||||
retencion=retencion.name))
|
||||
|
||||
@classmethod
|
||||
def copy(cls, retenciones, default=None):
|
||||
|
@ -160,6 +260,27 @@ class TaxWithholdingSubmitted(ModelSQL, ModelView):
|
|||
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:])]
|
||||
|
||||
|
||||
class TaxWithholdingReceived(ModelSQL, ModelView):
|
||||
'Tax Withholding Received'
|
||||
|
@ -216,7 +337,7 @@ class TaxWithholdingReceived(ModelSQL, ModelView):
|
|||
if retencion.voucher:
|
||||
raise UserError(gettext(
|
||||
'account_retencion_ar.msg_not_delete',
|
||||
retention=retencion.name))
|
||||
retencion=retencion.name))
|
||||
|
||||
@classmethod
|
||||
def copy(cls, retenciones, default=None):
|
||||
|
|
|
@ -55,6 +55,19 @@
|
|||
<menuitem name="Tax Withholdings" parent="account.menu_account"
|
||||
id="menu_retenciones" sequence="22"/>
|
||||
|
||||
<!-- Tax Withholding Type Scales -->
|
||||
|
||||
<record model="ir.ui.view" id="account_retencion_scale_form">
|
||||
<field name="model">account.retencion.scale</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">account_retencion_scale_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="account_retencion_scale_tree">
|
||||
<field name="model">account.retencion.scale</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">account_retencion_scale_tree</field>
|
||||
</record>
|
||||
|
||||
<!-- Tax Withholdings Submitted -->
|
||||
|
||||
<record model="ir.ui.view" id="account_retencion_efectuada_form">
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
# The COPYRIGHT file at the top level of this repository contains
|
||||
# the full copyright notices and license terms.
|
||||
from decimal import Decimal
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from trytond.model import ModelView, fields
|
||||
from trytond.model import Workflow, ModelView, fields
|
||||
from trytond.wizard import Wizard, StateView, StateTransition, Button
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Eval, Or
|
||||
from trytond.pyson import Eval, Or, And
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.exceptions import UserError
|
||||
from trytond.i18n import gettext
|
||||
|
||||
|
@ -30,6 +33,48 @@ class AccountVoucher(metaclass=PoolMeta):
|
|||
Eval('currency_code') != 'ARS'),
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
calculated_state = ('calculated', 'Calculated')
|
||||
if calculated_state not in cls.state.selection:
|
||||
cls.state.selection.append(calculated_state)
|
||||
cls._transitions |= set((
|
||||
('draft', 'calculated'),
|
||||
('calculated', 'draft'),
|
||||
('calculated', 'posted'),
|
||||
))
|
||||
cls._buttons.update({
|
||||
'calculate': {
|
||||
'invisible': Or(
|
||||
Eval('voucher_type') != 'payment',
|
||||
Eval('state') != 'draft'),
|
||||
'depends': ['voucher_type', 'state'],
|
||||
},
|
||||
'recalculate': {
|
||||
'invisible': And(
|
||||
Eval('state') != 'calculated',
|
||||
Eval('amount_to_pay', 0) > Eval('amount', 0)),
|
||||
'depends': ['state', 'amount_to_pay', 'amount'],
|
||||
},
|
||||
'draft': {
|
||||
'invisible': Eval('state') != 'calculated',
|
||||
'depends': ['state'],
|
||||
},
|
||||
'post': {
|
||||
'invisible': Or(
|
||||
And(Eval('voucher_type') == 'payment',
|
||||
Eval('state') != 'calculated'),
|
||||
And(Eval('voucher_type') == 'receipt',
|
||||
Eval('state') != 'draft')),
|
||||
'depends': ['voucher_type', 'state'],
|
||||
},
|
||||
'cancel': {
|
||||
'invisible': Eval('state') != 'posted',
|
||||
'depends': ['state'],
|
||||
},
|
||||
})
|
||||
|
||||
@fields.depends('retenciones_efectuadas', 'retenciones_soportadas')
|
||||
def on_change_with_amount(self, name=None):
|
||||
amount = super().on_change_with_amount(name)
|
||||
|
@ -43,9 +88,421 @@ class AccountVoucher(metaclass=PoolMeta):
|
|||
amount += retencion.amount
|
||||
return amount
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('draft')
|
||||
def draft(cls, vouchers):
|
||||
for voucher in vouchers:
|
||||
voucher.delete_withholding()
|
||||
|
||||
def delete_withholding(self):
|
||||
pool = Pool()
|
||||
TaxWithholdingSubmitted = pool.get('account.retencion.efectuada')
|
||||
if self.retenciones_efectuadas:
|
||||
with Transaction().set_context(delete_calculated=True):
|
||||
TaxWithholdingSubmitted.delete(self.retenciones_efectuadas)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('calculated')
|
||||
def calculate(cls, vouchers):
|
||||
for voucher in vouchers:
|
||||
voucher.calculate_withholdings()
|
||||
|
||||
@classmethod
|
||||
@ModelView.button_action(
|
||||
'account_retencion_ar.wizard_recalculate_withholdings')
|
||||
def recalculate(cls, vouchers):
|
||||
pass
|
||||
|
||||
def calculate_withholdings(self, context={}):
|
||||
if self.company.ganancias_agente_retencion:
|
||||
self._calculate_withholding_ganancias(context)
|
||||
if self.company.iibb_agente_retencion:
|
||||
self._calculate_withholding_iibb(context)
|
||||
|
||||
def _calculate_withholding_ganancias(self, context={}):
|
||||
pool = Pool()
|
||||
TaxWithholdingSubmitted = pool.get('account.retencion.efectuada')
|
||||
|
||||
quantize = Decimal(10) ** -Decimal(2)
|
||||
|
||||
withholding_data = self._get_withholding_data_ganancias(context)
|
||||
for data in withholding_data.values():
|
||||
|
||||
withholding_type = data['tax']
|
||||
|
||||
payment_amount = data['payment_amount']
|
||||
accumulated_amount = data['accumulated_amount'] + payment_amount
|
||||
minimum_non_taxable_amount = (
|
||||
withholding_type.minimum_non_taxable_amount or Decimal(0))
|
||||
if accumulated_amount < minimum_non_taxable_amount:
|
||||
continue
|
||||
|
||||
taxable_amount = accumulated_amount - minimum_non_taxable_amount
|
||||
rate = data['rate']
|
||||
if not rate:
|
||||
continue
|
||||
|
||||
scale_non_taxable_amount = data['scale_non_taxable_amount']
|
||||
computed_amount = ((taxable_amount - scale_non_taxable_amount) *
|
||||
rate / Decimal(100))
|
||||
computed_amount = computed_amount.quantize(quantize)
|
||||
|
||||
scale_fixed_amount = data['scale_fixed_amount']
|
||||
computed_amount += scale_fixed_amount
|
||||
|
||||
minimum_withholdable_amount = (
|
||||
withholding_type.minimum_withholdable_amount or Decimal(0))
|
||||
if computed_amount < minimum_withholdable_amount:
|
||||
continue
|
||||
|
||||
accumulated_withheld = data['accumulated_withheld']
|
||||
amount = computed_amount - accumulated_withheld
|
||||
|
||||
withholding = TaxWithholdingSubmitted()
|
||||
withholding.tax = withholding_type
|
||||
withholding.voucher = self
|
||||
withholding.party = self.party
|
||||
withholding.date = self.date
|
||||
withholding.payment_amount = payment_amount
|
||||
withholding.accumulated_amount = accumulated_amount
|
||||
withholding.minimum_non_taxable_amount = minimum_non_taxable_amount
|
||||
withholding.scale_non_taxable_amount = scale_non_taxable_amount
|
||||
withholding.taxable_amount = taxable_amount
|
||||
withholding.rate = rate
|
||||
withholding.scale_fixed_amount = scale_fixed_amount
|
||||
withholding.computed_amount = computed_amount
|
||||
withholding.minimum_withholdable_amount = (
|
||||
minimum_withholdable_amount)
|
||||
withholding.accumulated_withheld = accumulated_withheld
|
||||
withholding.amount = amount
|
||||
withholding.save()
|
||||
|
||||
def _get_withholding_data_ganancias(self, context={}):
|
||||
pool = Pool()
|
||||
Invoice = pool.get('account.invoice')
|
||||
AccountVoucher = pool.get('account.voucher')
|
||||
TaxWithholdingSubmitted = pool.get('account.retencion.efectuada')
|
||||
|
||||
# Verify conditions
|
||||
if not self.party.ganancias_condition:
|
||||
raise UserError(gettext(
|
||||
'account_retencion_ar.msg_party_ganancias_condition'))
|
||||
if self.party.ganancias_condition == 'ex':
|
||||
return {}
|
||||
|
||||
quantize = Decimal(10) ** -Decimal(2)
|
||||
res = {}
|
||||
|
||||
default_regimen = self.party.ganancias_regimen
|
||||
if not default_regimen:
|
||||
default_regimen = self.company.ganancias_regimen_retencion
|
||||
if not default_regimen:
|
||||
return {}
|
||||
|
||||
# Payment Amount
|
||||
vat_rate = context.get('vat_rate', Decimal(0.21))
|
||||
if context:
|
||||
amount = context.get('amount', Decimal(0))
|
||||
amount_option = context.get('amount_option', 'add')
|
||||
|
||||
if amount_option == 'add':
|
||||
tax = default_regimen
|
||||
if tax.id not in res:
|
||||
res[tax.id] = {
|
||||
'tax': tax,
|
||||
'payment_amount': Decimal(0),
|
||||
'accumulated_amount': Decimal(0),
|
||||
'accumulated_withheld': Decimal(0),
|
||||
}
|
||||
payment_amount = amount / (1 + vat_rate)
|
||||
res[tax.id]['payment_amount'] += (
|
||||
payment_amount.quantize(quantize))
|
||||
|
||||
else: # amount_option == 'included'
|
||||
pass
|
||||
|
||||
else:
|
||||
for line in self.lines:
|
||||
origin = str(line.move_line.move_origin)
|
||||
if origin[:origin.find(',')] != 'account.invoice':
|
||||
continue
|
||||
if not line.amount:
|
||||
continue
|
||||
|
||||
invoice = Invoice(line.move_line.move_origin.id)
|
||||
payment_rate = Decimal(line.amount / invoice.total_amount)
|
||||
|
||||
for invoice_line in invoice.lines:
|
||||
if invoice_line.type != 'line':
|
||||
continue
|
||||
tax = invoice_line.ganancias_regimen or default_regimen
|
||||
if tax.id not in res:
|
||||
res[tax.id] = {
|
||||
'tax': tax,
|
||||
'payment_amount': Decimal(0),
|
||||
'accumulated_amount': Decimal(0),
|
||||
'accumulated_withheld': Decimal(0),
|
||||
}
|
||||
payment_amount = invoice_line.amount * payment_rate
|
||||
res[tax.id]['payment_amount'] += (
|
||||
payment_amount.quantize(quantize))
|
||||
|
||||
# Verify exemptions
|
||||
taxes = [x for x in res.keys()]
|
||||
for exemption in self.party.exemptions:
|
||||
for tax_id in taxes:
|
||||
reference = 'account.retencion,%s' % str(tax_id)
|
||||
if (str(exemption.tax) == reference and
|
||||
exemption.end_date >= self.date):
|
||||
del res[tax_id]
|
||||
|
||||
# Accumulated Amount
|
||||
period_first_date = self.date + relativedelta(day=1)
|
||||
period_last_date = self.date + relativedelta(day=31)
|
||||
vouchers = AccountVoucher.search([
|
||||
('party', '=', self.party),
|
||||
('date', '>=', period_first_date),
|
||||
('date', '<=', period_last_date),
|
||||
('state', '=', 'posted'),
|
||||
])
|
||||
for voucher in vouchers:
|
||||
for line in voucher.lines:
|
||||
origin = str(line.move_line.move_origin)
|
||||
if origin[:origin.find(',')] != 'account.invoice':
|
||||
continue
|
||||
if not line.amount:
|
||||
continue
|
||||
|
||||
invoice = Invoice(line.move_line.move_origin.id)
|
||||
payment_rate = Decimal(line.amount / invoice.total_amount)
|
||||
|
||||
for invoice_line in invoice.lines:
|
||||
if invoice_line.type != 'line':
|
||||
continue
|
||||
tax = invoice_line.ganancias_regimen or default_regimen
|
||||
if tax.id not in res:
|
||||
continue
|
||||
accumulated_amount = invoice_line.amount * payment_rate
|
||||
res[tax.id]['accumulated_amount'] += (
|
||||
accumulated_amount.quantize(quantize))
|
||||
|
||||
if voucher.amount > voucher.amount_to_pay:
|
||||
difference = ((voucher.amount - voucher.amount_to_pay) /
|
||||
(1 + vat_rate))
|
||||
if default_regimen.id in res:
|
||||
res[default_regimen.id]['accumulated_amount'] += (
|
||||
difference.quantize(quantize))
|
||||
|
||||
# Accumulated Withheld
|
||||
for tax_id in res.keys():
|
||||
withholdings = TaxWithholdingSubmitted.search([
|
||||
('tax', '=', tax_id),
|
||||
('party', '=', self.party),
|
||||
('date', '>=', period_first_date),
|
||||
('date', '<=', period_last_date),
|
||||
('state', '=', 'issued'),
|
||||
])
|
||||
for withholding in withholdings:
|
||||
res[tax_id]['accumulated_withheld'] += withholding.amount
|
||||
|
||||
# Rate and extra data
|
||||
for tax_id, tax in res.items():
|
||||
tax.update(self._get_withholding_extra_data_ganancias(tax))
|
||||
|
||||
return res
|
||||
|
||||
def _get_withholding_extra_data_ganancias(self, tax_data):
|
||||
res = {
|
||||
'rate': Decimal(0),
|
||||
'scale_non_taxable_amount': Decimal(0),
|
||||
'scale_fixed_amount': Decimal(0),
|
||||
}
|
||||
regimen = tax_data['tax']
|
||||
if regimen.scales:
|
||||
taxable_amount = (tax_data['payment_amount'] +
|
||||
tax_data['accumulated_amount'] -
|
||||
regimen.minimum_non_taxable_amount)
|
||||
for scale in regimen.scales:
|
||||
if (taxable_amount >= scale.start_amount and
|
||||
taxable_amount <= scale.end_amount):
|
||||
res['rate'] = scale.rate
|
||||
res['scale_non_taxable_amount'] = (
|
||||
scale.minimum_non_taxable_amount)
|
||||
res['scale_fixed_amount'] = (
|
||||
scale.fixed_withholdable_amount)
|
||||
return res
|
||||
if self.party.ganancias_condition == 'in':
|
||||
res['rate'] = regimen.rate_registered
|
||||
else:
|
||||
res['rate'] = regimen.rate_non_registered
|
||||
return res
|
||||
|
||||
def _calculate_withholding_iibb(self, context={}):
|
||||
pool = Pool()
|
||||
TaxWithholdingSubmitted = pool.get('account.retencion.efectuada')
|
||||
|
||||
quantize = Decimal(10) ** -Decimal(2)
|
||||
|
||||
withholding_data = self._get_withholding_data_iibb(context)
|
||||
for data in withholding_data.values():
|
||||
|
||||
withholding_type = data['tax']
|
||||
|
||||
payment_amount = data['payment_amount']
|
||||
accumulated_amount = data['accumulated_amount'] + payment_amount
|
||||
minimum_non_taxable_amount = (
|
||||
withholding_type.minimum_non_taxable_amount or Decimal(0))
|
||||
if accumulated_amount < minimum_non_taxable_amount:
|
||||
continue
|
||||
|
||||
taxable_amount = accumulated_amount - minimum_non_taxable_amount
|
||||
rate = data['rate']
|
||||
if not rate:
|
||||
continue
|
||||
|
||||
computed_amount = taxable_amount * rate / Decimal(100)
|
||||
computed_amount = computed_amount.quantize(quantize)
|
||||
|
||||
minimum_withholdable_amount = (
|
||||
withholding_type.minimum_withholdable_amount or Decimal(0))
|
||||
if computed_amount < minimum_withholdable_amount:
|
||||
continue
|
||||
|
||||
accumulated_withheld = data['accumulated_withheld']
|
||||
amount = computed_amount - accumulated_withheld
|
||||
|
||||
withholding = TaxWithholdingSubmitted()
|
||||
withholding.tax = withholding_type
|
||||
withholding.voucher = self
|
||||
withholding.party = self.party
|
||||
withholding.date = self.date
|
||||
withholding.payment_amount = payment_amount
|
||||
withholding.accumulated_amount = accumulated_amount
|
||||
withholding.minimum_non_taxable_amount = minimum_non_taxable_amount
|
||||
withholding.taxable_amount = taxable_amount
|
||||
withholding.rate = rate
|
||||
withholding.computed_amount = computed_amount
|
||||
withholding.minimum_withholdable_amount = (
|
||||
minimum_withholdable_amount)
|
||||
withholding.accumulated_withheld = accumulated_withheld
|
||||
withholding.amount = amount
|
||||
withholding.save()
|
||||
|
||||
def _get_withholding_data_iibb(self, context={}):
|
||||
pool = Pool()
|
||||
Invoice = pool.get('account.invoice')
|
||||
|
||||
# Verify conditions
|
||||
if not self.party.iibb_condition:
|
||||
raise UserError(gettext(
|
||||
'account_retencion_ar.msg_party_iibb_condition'))
|
||||
if self.party.iibb_condition in ['ex', 'rs', 'na', 'cs']:
|
||||
return {}
|
||||
company_address = self.company.party.address_get('invoice')
|
||||
if not company_address or not company_address.subdivision:
|
||||
raise UserError(gettext(
|
||||
'account_retencion_ar.msg_company_subdivision'))
|
||||
company_subdivision = company_address.subdivision
|
||||
|
||||
quantize = Decimal(10) ** -Decimal(2)
|
||||
res = {}
|
||||
|
||||
vat_rate = context.get('vat_rate', Decimal(0.21))
|
||||
if context:
|
||||
amount = context.get('amount', Decimal(0))
|
||||
amount_option = context.get('amount_option', 'add')
|
||||
|
||||
for tax in self.company.iibb_regimenes_retencion:
|
||||
ok = False
|
||||
if tax.subdivision == company_subdivision:
|
||||
ok = True
|
||||
elif self.party.iibb_condition == 'cm':
|
||||
for x in self.party.iibb_regimenes:
|
||||
if x.regimen_retencion == tax:
|
||||
ok = True
|
||||
if not ok:
|
||||
continue
|
||||
|
||||
# Payment Amount
|
||||
if context:
|
||||
if amount_option == 'add':
|
||||
if tax.id not in res:
|
||||
res[tax.id] = {
|
||||
'tax': tax,
|
||||
'payment_amount': Decimal(0),
|
||||
'accumulated_amount': Decimal(0),
|
||||
'accumulated_withheld': Decimal(0),
|
||||
}
|
||||
payment_amount = amount / (1 + vat_rate)
|
||||
res[tax.id]['payment_amount'] += (
|
||||
payment_amount.quantize(quantize))
|
||||
|
||||
else: # amount_option == 'included'
|
||||
pass
|
||||
|
||||
else:
|
||||
for line in self.lines:
|
||||
origin = str(line.move_line.move_origin)
|
||||
if origin[:origin.find(',')] != 'account.invoice':
|
||||
continue
|
||||
if not line.amount:
|
||||
continue
|
||||
|
||||
if tax.id not in res:
|
||||
res[tax.id] = {
|
||||
'tax': tax,
|
||||
'payment_amount': Decimal(0),
|
||||
'accumulated_amount': Decimal(0),
|
||||
'accumulated_withheld': Decimal(0),
|
||||
}
|
||||
|
||||
invoice = Invoice(line.move_line.move_origin.id)
|
||||
if line.amount == invoice.total_amount:
|
||||
payment_amount = invoice.untaxed_amount
|
||||
else:
|
||||
payment_amount = (line.amount *
|
||||
invoice.untaxed_amount / invoice.total_amount)
|
||||
res[tax.id]['payment_amount'] += payment_amount.quantize(
|
||||
quantize)
|
||||
|
||||
# Verify exemptions
|
||||
taxes = [x for x in res.keys()]
|
||||
for exemption in self.party.exemptions:
|
||||
for tax_id in taxes:
|
||||
reference = 'account.retencion,%s' % str(tax_id)
|
||||
if (str(exemption.tax) == reference and
|
||||
exemption.end_date >= self.date):
|
||||
del res[tax_id]
|
||||
|
||||
# Rate and extra data
|
||||
for tax_id, tax in res.items():
|
||||
tax.update(self._get_withholding_extra_data_iibb(tax))
|
||||
|
||||
return res
|
||||
|
||||
def _get_withholding_extra_data_iibb(self, tax_data):
|
||||
res = {
|
||||
'rate': Decimal(0),
|
||||
}
|
||||
regimen = tax_data['tax']
|
||||
for x in self.party.iibb_regimenes:
|
||||
if x.regimen_retencion == regimen and x.rate_retencion:
|
||||
res['rate'] = x.rate_retencion
|
||||
return res
|
||||
if self.party.iibb_condition in ['in', 'cm']:
|
||||
res['rate'] = regimen.rate_registered
|
||||
else:
|
||||
res['rate'] = regimen.rate_non_registered
|
||||
return res
|
||||
|
||||
def prepare_move_lines(self):
|
||||
pool = Pool()
|
||||
Period = pool.get('account.period')
|
||||
|
||||
move_lines = super().prepare_move_lines()
|
||||
Period = Pool().get('account.period')
|
||||
|
||||
if self.voucher_type == 'receipt':
|
||||
if self.retenciones_soportadas:
|
||||
for retencion in self.retenciones_soportadas:
|
||||
|
@ -124,3 +581,49 @@ class AccountVoucher(metaclass=PoolMeta):
|
|||
'party': None,
|
||||
'state': 'cancelled',
|
||||
})
|
||||
|
||||
|
||||
class RecalculateWithholdingsStart(ModelView):
|
||||
'Recalculate withholdings'
|
||||
__name__ = 'account.voucher.recalculate_withholdings.start'
|
||||
|
||||
amount = fields.Numeric('Payment Amount', digits=(16, 2), required=True)
|
||||
amount_option = fields.Selection([
|
||||
('add', 'Add withholdings to the amount'),
|
||||
#('included', 'Withholdings included in the amount'),
|
||||
], 'Option', required=True, sort=False)
|
||||
|
||||
@staticmethod
|
||||
def default_amount_option():
|
||||
return 'add'
|
||||
|
||||
|
||||
class RecalculateWithholdings(Wizard):
|
||||
'Recalculate withholdings'
|
||||
__name__ = 'account.voucher.recalculate_withholdings'
|
||||
|
||||
start = StateView(
|
||||
'account.voucher.recalculate_withholdings.start',
|
||||
'account_retencion_ar.recalculate_withholdings_start_view', [
|
||||
Button('Cancelar', 'end', 'tryton-cancel'),
|
||||
Button('Recalculate', 'recalculate', 'tryton-ok', default=True),
|
||||
])
|
||||
recalculate = StateTransition()
|
||||
|
||||
def transition_recalculate(self):
|
||||
AccountVoucher = Pool().get('account.voucher')
|
||||
|
||||
voucher = AccountVoucher(Transaction().context['active_id'])
|
||||
if not voucher:
|
||||
return {}
|
||||
|
||||
voucher.delete_withholding()
|
||||
voucher.calculate_withholdings(context={
|
||||
'amount': self.start.amount,
|
||||
'vat_rate': Decimal(0.21),
|
||||
'amount_option': self.start.amount_option,
|
||||
})
|
||||
return 'end'
|
||||
|
||||
def end(self):
|
||||
return 'reload'
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
<tryton>
|
||||
<data>
|
||||
|
||||
<!-- Account Voucher -->
|
||||
|
||||
<record model="ir.ui.view" id="view_voucher_retencion_form">
|
||||
<field name="model">account.voucher</field>
|
||||
<field name="inherit" ref="account_voucher_ar.account_voucher_form"/>
|
||||
|
@ -20,5 +22,50 @@
|
|||
<field name="name">retenciones_soportadas_tree</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.button" id="voucher_calculate_button">
|
||||
<field name="name">calculate</field>
|
||||
<field name="string">Calculate withholdings</field>
|
||||
<field name="model" search="[('model', '=', 'account.voucher')]"/>
|
||||
</record>
|
||||
<record model="ir.model.button-res.group"
|
||||
id="voucher_calculate_button_group_account">
|
||||
<field name="button" ref="voucher_calculate_button"/>
|
||||
<field name="group" ref="account.group_account"/>
|
||||
</record>
|
||||
<record model="ir.model.button" id="voucher_draft_button">
|
||||
<field name="name">draft</field>
|
||||
<field name="string">Draft</field>
|
||||
<field name="model" search="[('model', '=', 'account.voucher')]"/>
|
||||
</record>
|
||||
<record model="ir.model.button-res.group"
|
||||
id="voucher_draft_button_group_account">
|
||||
<field name="button" ref="voucher_draft_button"/>
|
||||
<field name="group" ref="account.group_account"/>
|
||||
</record>
|
||||
<record model="ir.model.button" id="voucher_recalculate_button">
|
||||
<field name="name">recalculate</field>
|
||||
<field name="string">Recalculate withholdings</field>
|
||||
<field name="model" search="[('model', '=', 'account.voucher')]"/>
|
||||
</record>
|
||||
<record model="ir.model.button-res.group"
|
||||
id="voucher_recalculate_button_group_account">
|
||||
<field name="button" ref="voucher_recalculate_button"/>
|
||||
<field name="group" ref="account.group_account"/>
|
||||
</record>
|
||||
|
||||
<!-- Wizard: Recalculate withholdings -->
|
||||
|
||||
<record model="ir.ui.view" id="recalculate_withholdings_start_view">
|
||||
<field name="model">account.voucher.recalculate_withholdings.start</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">recalculate_withholdings_start_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="wizard_recalculate_withholdings">
|
||||
<field name="name">Recalculate withholdings</field>
|
||||
<field name="wiz_name">account.voucher.recalculate_withholdings</field>
|
||||
<field name="model">account.voucher</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# 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 trytond.model import ModelSQL, fields
|
||||
from trytond.pool import PoolMeta
|
||||
from trytond.pyson import Eval
|
||||
|
||||
|
||||
class Company(metaclass=PoolMeta):
|
||||
__name__ = 'company.company'
|
||||
|
||||
ganancias_agente_retencion = fields.Boolean(
|
||||
'Agente de Retención de Impuesto a las Ganancias')
|
||||
ganancias_regimen_retencion = fields.Many2One('account.retencion',
|
||||
'Régimen de Ganancias',
|
||||
domain=[('type', '=', 'efectuada'), ('tax', '=', 'gana')])
|
||||
iibb_agente_retencion = fields.Boolean(
|
||||
'Agente de Retención de Impuesto a los Ingresos Brutos')
|
||||
iibb_regimenes_retencion = fields.Many2Many('company.retencion.iibb',
|
||||
'company', 'regimen', 'Jurisdicciones de Ingresos Brutos',
|
||||
domain=[('type', '=', 'efectuada'), ('tax', '=', 'iibb')])
|
||||
|
||||
|
||||
class CompanyWithholdingIIBB(ModelSQL):
|
||||
'Régimen de Ingresos Brutos de Empresa'
|
||||
__name__ = 'company.retencion.iibb'
|
||||
|
||||
company = fields.Many2One('company.company', 'Company',
|
||||
ondelete='CASCADE', required=True)
|
||||
regimen = fields.Many2One('account.retencion', 'Régimen',
|
||||
ondelete='CASCADE', required=True)
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="company_view_form">
|
||||
<field name="model">company.company</field>
|
||||
<field name="inherit" ref="company.company_view_form"/>
|
||||
<field name="name">company_form</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
|
@ -0,0 +1,33 @@
|
|||
# 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 trytond.model import fields
|
||||
from trytond.pool import PoolMeta
|
||||
from trytond.pyson import Eval
|
||||
from trytond.exceptions import UserError
|
||||
from trytond.i18n import gettext
|
||||
|
||||
|
||||
class InvoiceLine(metaclass=PoolMeta):
|
||||
__name__ = 'account.invoice.line'
|
||||
|
||||
ganancias_regimen = fields.Many2One('account.retencion',
|
||||
'Régimen Ganancias',
|
||||
domain=[('type', '=', 'efectuada'), ('tax', '=', 'gana')],
|
||||
states={'invisible': Eval('invoice_type') != 'in'})
|
||||
|
||||
@fields.depends('product', 'invoice', '_parent_invoice.type',
|
||||
'invoice_type')
|
||||
def on_change_product(self):
|
||||
super().on_change_product()
|
||||
if not self.product:
|
||||
return
|
||||
if self.invoice and self.invoice.type:
|
||||
type_ = self.invoice.type
|
||||
else:
|
||||
type_ = self.invoice_type
|
||||
if type_ != 'in':
|
||||
return
|
||||
self.ganancias_regimen = self.product.ganancias_regimen_used
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<!-- Invoice Line -->
|
||||
|
||||
<record model="ir.ui.view" id="invoice_line_view_form">
|
||||
<field name="model">account.invoice.line</field>
|
||||
<field name="inherit" ref="account_invoice.invoice_line_view_form"/>
|
||||
<field name="name">invoice_line_form</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
25
message.xml
25
message.xml
|
@ -2,10 +2,31 @@
|
|||
<tryton>
|
||||
<data grouped="1">
|
||||
<record model="ir.message" id="msg_not_delete">
|
||||
<field name="text">You cannot delete retention "%(retention)s" because it is associated to a voucher</field>
|
||||
<field name="text">You cannot delete Tax Withholding "%(retencion)s" because it is associated with a Voucher</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_missing_retencion_seq">
|
||||
<field name="text">You must set a sequence to Retencion efecutada.</field>
|
||||
<field name="text">You must define the Sequence for the Tax Withholding</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_print_not_issued">
|
||||
<field name="text">You cannot print Tax Withholding "%(retencion)s" because it is not issued</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_party_iva_condition">
|
||||
<field name="text">El Tercero no tiene definida su Condición ante IVA</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_party_iibb_condition">
|
||||
<field name="text">El Tercero no tiene definida su Condición ante Ingresos Brutos</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_party_ganancias_condition">
|
||||
<field name="text">El Tercero no tiene definida su Condición ante Ganancias</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_party_subdivision">
|
||||
<field name="text">El Tercero no tiene definida una Provincia/Jurisdicción</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_party_iibb_regimenes">
|
||||
<field name="text">The Party does not have any Regime defined for Ingresos Brutos</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_company_subdivision">
|
||||
<field name="text">La empresa no tiene definida una Provincia/Jurisdicción</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
# 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 trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.pool import PoolMeta
|
||||
|
||||
|
||||
class Party(metaclass=PoolMeta):
|
||||
__name__ = 'party.party'
|
||||
|
||||
exemptions = fields.One2Many('party.exemption',
|
||||
'party', 'Exenciones de Retención y Percepción')
|
||||
ganancias_regimen = fields.Many2One('account.retencion',
|
||||
'Régimen de Ganancias',
|
||||
domain=[('type', '=', 'efectuada'), ('tax', '=', 'gana')])
|
||||
iibb_regimenes = fields.One2Many('party.retencion.iibb',
|
||||
'party', 'Jurisdicciones de Ingresos Brutos')
|
||||
|
||||
|
||||
class PartyExemption(ModelSQL, ModelView):
|
||||
'Exención de Retención/Percepción de Tercero'
|
||||
__name__ = 'party.exemption'
|
||||
|
||||
party = fields.Many2One('party.party', 'Party',
|
||||
ondelete='CASCADE', required=True)
|
||||
tax = fields.Reference('Type', [
|
||||
('account.retencion', 'Retención'),
|
||||
('account.tax', 'Percepción'),
|
||||
],
|
||||
required=True,
|
||||
domain={
|
||||
'account.retencion': [
|
||||
('type', '=', 'efectuada')
|
||||
],
|
||||
'account.tax': [
|
||||
('group.afip_kind', 'in',
|
||||
['nacional', 'provincial', 'municipal']),
|
||||
('group.kind', '=', 'sale'),
|
||||
],
|
||||
})
|
||||
end_date = fields.Date('Valid until', required=True)
|
||||
|
||||
|
||||
class PartyWithholdingIIBB(ModelSQL, ModelView):
|
||||
'Régimen de Ingresos Brutos de Tercero'
|
||||
__name__ = 'party.retencion.iibb'
|
||||
|
||||
party = fields.Many2One('party.party', 'Party',
|
||||
ondelete='CASCADE', required=True)
|
||||
regimen_retencion = fields.Many2One('account.retencion',
|
||||
'Retención', ondelete='CASCADE',
|
||||
domain=[('type', '=', 'efectuada'), ('tax', '=', 'iibb')])
|
||||
rate_retencion = fields.Numeric('% Retención', digits=(14, 10))
|
||||
regimen_percepcion = fields.Many2One('account.tax',
|
||||
'Percepción', ondelete='CASCADE',
|
||||
domain=[
|
||||
('group.afip_kind', '=', 'provincial'),
|
||||
('group.kind', '=', 'sale'),
|
||||
])
|
||||
rate_percepcion = fields.Numeric('% Percepción', digits=(14, 10))
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<!-- Party -->
|
||||
|
||||
<record model="ir.ui.view" id="party_view_form">
|
||||
<field name="model">party.party</field>
|
||||
<field name="inherit" ref="party.party_view_form"/>
|
||||
<field name="name">party_form</field>
|
||||
</record>
|
||||
|
||||
<!-- Exención de Retención/Percepción de Tercero -->
|
||||
|
||||
<record model="ir.ui.view" id="party_exemption_view_form">
|
||||
<field name="model">party.exemption</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">party_exemption_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="party_exemption_view_tree">
|
||||
<field name="model">party.exemption</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">party_exemption_tree</field>
|
||||
</record>
|
||||
|
||||
<!-- Régimen de Ingresos Brutos de Tercero -->
|
||||
|
||||
<record model="ir.ui.view" id="party_retencion_iibb_view_form">
|
||||
<field name="model">party.retencion.iibb</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">party_retencion_iibb_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="party_retencion_iibb_view_tree">
|
||||
<field name="model">party.retencion.iibb</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">party_retencion_iibb_tree</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
|
@ -0,0 +1,27 @@
|
|||
# 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 trytond.model import fields
|
||||
from trytond.pool import PoolMeta
|
||||
|
||||
|
||||
class Category(metaclass=PoolMeta):
|
||||
__name__ = 'product.category'
|
||||
|
||||
ganancias_regimen = fields.Many2One('account.retencion',
|
||||
'Régimen de Ganancias',
|
||||
domain=[('type', '=', 'efectuada'), ('tax', '=', 'gana')])
|
||||
|
||||
|
||||
class Product(metaclass=PoolMeta):
|
||||
__name__ = 'product.product'
|
||||
|
||||
ganancias_regimen_used = fields.Function(fields.Many2One(
|
||||
'account.retencion', 'Régimen Ganancias'),
|
||||
'get_ganancias_regimen_used')
|
||||
|
||||
def get_ganancias_regimen_used(self, name):
|
||||
if self.account_category:
|
||||
if self.account_category.ganancias_regimen:
|
||||
return self.account_category.ganancias_regimen.id
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<!-- Product Category -->
|
||||
|
||||
<record model="ir.ui.view" id="category_view_form">
|
||||
<field name="model">product.category</field>
|
||||
<field name="inherit" ref="product.category_view_form"/>
|
||||
<field name="name">category_form</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
|
@ -5,4 +5,7 @@ depends:
|
|||
xml:
|
||||
account_retencion_ar.xml
|
||||
account_voucher_ar.xml
|
||||
party.xml
|
||||
company.xml
|
||||
product.xml
|
||||
message.xml
|
||||
|
|
|
@ -1,17 +1,41 @@
|
|||
<?xml version="1.0"?>
|
||||
<form>
|
||||
<label name="tax"/>
|
||||
<field name="tax" widget="selection"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="amount"/>
|
||||
<field name="amount"/>
|
||||
<label name="aliquot"/>
|
||||
<field name="aliquot"/>
|
||||
<label name="date"/>
|
||||
<field name="date"/>
|
||||
<label name="tax"/>
|
||||
<field name="tax" widget="selection"/>
|
||||
<label name="party"/>
|
||||
<field name="party"/>
|
||||
<label name="state"/>
|
||||
<field name="state"/>
|
||||
<group id="withholding" colspan="4">
|
||||
<label name="regime_name"/>
|
||||
<field name="regime_name"/>
|
||||
<label name="voucher"/>
|
||||
<field name="voucher"/>
|
||||
<label name="payment_amount"/>
|
||||
<field name="payment_amount"/>
|
||||
<label name="accumulated_amount"/>
|
||||
<field name="accumulated_amount"/>
|
||||
<label name="minimum_non_taxable_amount"/>
|
||||
<field name="minimum_non_taxable_amount"/>
|
||||
<label name="taxable_amount"/>
|
||||
<field name="taxable_amount"/>
|
||||
<label name="scale_non_taxable_amount"/>
|
||||
<field name="scale_non_taxable_amount"/>
|
||||
<label name="rate"/>
|
||||
<field name="rate"/>
|
||||
<label name="scale_fixed_amount"/>
|
||||
<field name="scale_fixed_amount"/>
|
||||
<label name="computed_amount"/>
|
||||
<field name="computed_amount"/>
|
||||
<label name="minimum_withholdable_amount"/>
|
||||
<field name="minimum_withholdable_amount"/>
|
||||
<label name="accumulated_withheld"/>
|
||||
<field name="accumulated_withheld"/>
|
||||
</group>
|
||||
</form>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<?xml version="1.0"?>
|
||||
<tree>
|
||||
<field name="tax"/>
|
||||
<field name="name"/>
|
||||
<field name="amount"/>
|
||||
<field name="aliquot"/>
|
||||
<field name="date"/>
|
||||
<field name="tax"/>
|
||||
<field name="party"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
|
|
|
@ -2,10 +2,29 @@
|
|||
<form>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="account"/>
|
||||
<field name="account"/>
|
||||
<label name="tax"/>
|
||||
<field name="tax"/>
|
||||
<label name="type"/>
|
||||
<field name="type"/>
|
||||
<label name="account"/>
|
||||
<field name="account"/>
|
||||
<label name="sequence"/>
|
||||
<field name="sequence"/>
|
||||
<label name="subdivision"/>
|
||||
<field name="subdivision"/>
|
||||
<label name="regime_code"/>
|
||||
<field name="regime_code"/>
|
||||
<label name="regime_name"/>
|
||||
<field name="regime_name"/>
|
||||
<group id="calculation" colspan="4" string="Automatic Calculation">
|
||||
<label name="minimum_non_taxable_amount"/>
|
||||
<field name="minimum_non_taxable_amount"/>
|
||||
<label name="minimum_withholdable_amount"/>
|
||||
<field name="minimum_withholdable_amount"/>
|
||||
<label name="rate_registered"/>
|
||||
<field name="rate_registered"/>
|
||||
<label name="rate_non_registered"/>
|
||||
<field name="rate_non_registered"/>
|
||||
<field name="scales" colspan="4"/>
|
||||
</group>
|
||||
</form>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0"?>
|
||||
<form>
|
||||
<label name="start_amount"/>
|
||||
<field name="start_amount"/>
|
||||
<label name="end_amount"/>
|
||||
<field name="end_amount"/>
|
||||
<label name="fixed_withholdable_amount"/>
|
||||
<field name="fixed_withholdable_amount"/>
|
||||
<label name="rate"/>
|
||||
<field name="rate"/>
|
||||
<label name="minimum_non_taxable_amount"/>
|
||||
<field name="minimum_non_taxable_amount"/>
|
||||
</form>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0"?>
|
||||
<tree>
|
||||
<field name="start_amount"/>
|
||||
<field name="end_amount"/>
|
||||
<field name="fixed_withholdable_amount"/>
|
||||
<field name="rate"/>
|
||||
<field name="minimum_non_taxable_amount"/>
|
||||
</tree>
|
|
@ -1,13 +1,13 @@
|
|||
<?xml version="1.0"?>
|
||||
<form>
|
||||
<label name="tax"/>
|
||||
<field name="tax" widget="selection"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="amount"/>
|
||||
<field name="amount"/>
|
||||
<label name="date"/>
|
||||
<field name="date"/>
|
||||
<label name="tax"/>
|
||||
<field name="tax" widget="selection"/>
|
||||
<label name="party"/>
|
||||
<field name="party"/>
|
||||
<label name="state"/>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0"?>
|
||||
<tree>
|
||||
<field name="tax"/>
|
||||
<field name="name"/>
|
||||
<field name="amount"/>
|
||||
<field name="date"/>
|
||||
<field name="tax"/>
|
||||
<field name="party"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<?xml version="1.0"?>
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="account"/>
|
||||
<field name="tax"/>
|
||||
<field name="type"/>
|
||||
<field name="sequence"/>
|
||||
<field name="account"/>
|
||||
<field name="regime_name"/>
|
||||
</tree>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0"?>
|
||||
<data>
|
||||
<xpath
|
||||
expr="/form/notebook/page[@id='accounting']/field[@name='supplier_taxes']"
|
||||
position="after">
|
||||
<separator id="withholding" string="Withholding" colspan="4"/>
|
||||
<label name="ganancias_regimen"/>
|
||||
<field name="ganancias_regimen"/>
|
||||
</xpath>
|
||||
</data>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0"?>
|
||||
<data>
|
||||
<xpath expr="/form/notebook/page[@id='afip']" position="after">
|
||||
<page id="taxes" string="Withholding and Perception of Taxes">
|
||||
<label name="ganancias_agente_retencion"/>
|
||||
<field name="ganancias_agente_retencion"/>
|
||||
<label name="ganancias_regimen_retencion"/>
|
||||
<field name="ganancias_regimen_retencion"/>
|
||||
<label name="iibb_agente_retencion"/>
|
||||
<field name="iibb_agente_retencion"/>
|
||||
<field name="iibb_regimenes_retencion" colspan="4"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0"?>
|
||||
<data>
|
||||
<xpath
|
||||
expr="/form/notebook/page[@id='general']/group[@id='taxes_deductible_rate']"
|
||||
position="after">
|
||||
<label name="ganancias_regimen"/>
|
||||
<field name="ganancias_regimen"/>
|
||||
</xpath>
|
||||
</data>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0"?>
|
||||
<form>
|
||||
<label name="tax"/>
|
||||
<field name="tax"/>
|
||||
<label name="end_date"/>
|
||||
<field name="end_date"/>
|
||||
</form>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<tree>
|
||||
<field name="tax"/>
|
||||
<field name="end_date"/>
|
||||
</tree>
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0"?>
|
||||
<data>
|
||||
<xpath expr="/form/notebook/page[@id='empresa']" position="after">
|
||||
<page id="taxes" string="Withholding and Perception of Taxes">
|
||||
<label name="ganancias_condition"/>
|
||||
<field name="ganancias_condition"/>
|
||||
<label name="ganancias_regimen"/>
|
||||
<field name="ganancias_regimen"/>
|
||||
<label name="iibb_condition"/>
|
||||
<field name="iibb_condition"/>
|
||||
<field name="iibb_regimenes" colspan="4"/>
|
||||
<field name="exemptions" colspan="4"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0"?>
|
||||
<form>
|
||||
<label name="regimen_retencion"/>
|
||||
<field name="regimen_retencion"/>
|
||||
<label name="rate_retencion"/>
|
||||
<field name="rate_retencion"/>
|
||||
<label name="regimen_percepcion"/>
|
||||
<field name="regimen_percepcion"/>
|
||||
<label name="rate_percepcion"/>
|
||||
<field name="rate_percepcion"/>
|
||||
</form>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0"?>
|
||||
<tree>
|
||||
<field name="regimen_retencion"/>
|
||||
<field name="rate_retencion"/>
|
||||
<field name="regimen_percepcion"/>
|
||||
<field name="rate_percepcion"/>
|
||||
</tree>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0"?>
|
||||
<form>
|
||||
<label name="amount"/>
|
||||
<field name="amount"/>
|
||||
<label name="amount_option"/>
|
||||
<field name="amount_option"/>
|
||||
</form>
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0"?>
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="tax"/>
|
||||
<field name="name"/>
|
||||
<field name="amount"/>
|
||||
</tree>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0"?>
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="tax"/>
|
||||
<field name="name"/>
|
||||
<field name="amount"/>
|
||||
</tree>
|
||||
|
|
|
@ -6,4 +6,9 @@
|
|||
<field name="retenciones_soportadas" colspan="4"
|
||||
view_ids="account_retencion_ar.retenciones_soportadas_view_tree"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/group[@id='buttons']/button[@name='post']" position="before">
|
||||
<button name="draft"/>
|
||||
<button name="calculate"/>
|
||||
<button name="recalculate"/>
|
||||
</xpath>
|
||||
</data>
|
||||
|
|
Loading…
Reference in New Issue