399 lines
14 KiB
Python
399 lines
14 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
from decimal import Decimal
|
|
from collections import OrderedDict
|
|
|
|
from trytond.model import fields, ModelView
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Eval
|
|
from trytond.wizard import Wizard, StateView, Button, StateReport
|
|
from trytond.transaction import Transaction
|
|
from trytond.report import Report
|
|
from trytond.exceptions import UserError
|
|
|
|
_ZERO = Decimal('0.0')
|
|
|
|
CLASIFICATION = [
|
|
('', ''),
|
|
('01', 'IVA'),
|
|
('02', 'IC'),
|
|
('03', 'ICA'),
|
|
('04', 'INC'),
|
|
('05', 'ReteIVA'),
|
|
('06', 'ReteFuente'),
|
|
('07', 'ReteICA'),
|
|
('20', 'FtoHorticultura'),
|
|
('21', 'Timbre'),
|
|
('22', 'Bolsas'),
|
|
('23', 'INCarbono'),
|
|
('24', 'INCombustibles'),
|
|
('25', 'Sobretasa Combustibles'),
|
|
('26', 'Sordicom'),
|
|
('30', 'IC Datos'),
|
|
('32', 'ICL'),
|
|
('33', 'INPP'),
|
|
('34', 'IBUA'),
|
|
('35', 'ICUI'),
|
|
('ZZ', 'Otro'),
|
|
('NA', 'No Aceptada'),
|
|
]
|
|
|
|
HELP_CLASIFICATION = {
|
|
'01': 'IVA (impuesto sobre las Ventas)',
|
|
'02': 'IC (Impuesto al Consumo Departamental Nominal)',
|
|
'03': 'ICA (Impuesto de Industria, Comercio y Aviso)',
|
|
'04': 'INC (Impuesto Nacional al Consumo)',
|
|
'05': 'ReteIVA (Retención sobre el IVA)',
|
|
'06': 'ReteFuente (Retención sobre Renta)',
|
|
'07': 'ReteICA (Retención sobre el ICA)',
|
|
'08': 'IC% (Impuesto al Consumo Departamental Porcentual)',
|
|
'20': 'FtoHorticultura (Cuota de Fomento Hortifruticula)',
|
|
'21': 'Timbre (Impuesto de Timbre)',
|
|
'22': 'INC Bolsas (Impuesto Nacional al consumo de Bolsa Plástica)',
|
|
'23': 'INCarbono (Impuesto Nacional del Carbono)',
|
|
'24': 'INCombustibles (Impuesto Nacional a los Combustibles)',
|
|
'25': 'Sobretasa Combustibles',
|
|
'26': 'Sordicom (Contribución minoristas(Combustibles))',
|
|
'30': 'IC Datos (Impuesto al Consumo de Datos)',
|
|
'32': 'ICL (Impuesto al Comsumo de Licores)',
|
|
'33': 'INPP (Impuesto Nacional Productos Plasticos)',
|
|
'34': 'IBUA (Impuesto a las bebidas ultraprocesadas azucaradas)',
|
|
'35': 'ICUI (Impuesto a los productos comestibles ultraprocesados \n industrialmente y/o con alto contenido de azúcares añadidos, sodio o grasas saturadas)',
|
|
'ZZ': 'Otro',
|
|
'NA': 'No Aceptada',
|
|
}
|
|
|
|
|
|
class Tax(metaclass=PoolMeta):
|
|
__name__ = 'account.tax'
|
|
classification = fields.Selection([
|
|
('', ''),
|
|
('renta', 'Renta'),
|
|
('ret', 'ReteFuente'),
|
|
('iva', 'IVA'),
|
|
('ica', 'ICA'),
|
|
('cree', 'Cree'),
|
|
('ic', 'Impuesto al Consumo'),
|
|
('bomberil', 'Impuesto Bomberil'),
|
|
('avtableros', 'Impuesto Avisos y Tableros'),
|
|
], 'Classification For Reports')
|
|
classification_tax = fields.Selection(CLASIFICATION, 'Classification Tax Electronic Invoice',
|
|
help_selection=HELP_CLASIFICATION)
|
|
not_printable = fields.Boolean('Not Printable')
|
|
iva_costo = fields.Boolean('IVA Mayor Valor Costo', states={
|
|
'invisible': Eval('classification') != 'iva'
|
|
})
|
|
taxable_base = fields.Float('Taxable Base', digits=(2, 2), help='Marcar si solo desea aplicar el impuesto sobre un \
|
|
porcentaje de la base del producto, ejm: AIU')
|
|
base = fields.Numeric('Base', digits=(16, 2),
|
|
help='Defina este valor si desea controlar la base \
|
|
minima para aplicar el impuesto, especialmente en retenciones.')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Tax, cls).__setup__()
|
|
cls.invoice_account.domain = [
|
|
('company', '=', Eval('company')),
|
|
('type', '!=', None),
|
|
('closed', '!=', True),
|
|
]
|
|
cls.credit_note_account.domain = [
|
|
('company', '=', Eval('company')),
|
|
('type', '!=', None),
|
|
('closed', '!=', True),
|
|
]
|
|
|
|
def _process_tax(self, price_unit):
|
|
res = super(Tax, self)._process_tax(price_unit)
|
|
# Compute tax from taxable base
|
|
if self.taxable_base and res and self.type == 'percentage':
|
|
new_base = price_unit * Decimal(self.taxable_base / 100)
|
|
amount = new_base * self.rate
|
|
res['base'] = new_base
|
|
res['amount'] = amount
|
|
return res
|
|
|
|
|
|
class TaxLine(metaclass=PoolMeta):
|
|
__name__ = 'account.tax.line'
|
|
|
|
@fields.depends('_parent_move_line.account', 'move_line')
|
|
def on_change_with_company(self, name=None):
|
|
if self.move_line:
|
|
return self.move_line.account.company.id
|
|
|
|
@fields.depends('tax', 'move_line')
|
|
def on_change_with_amount(self, name=None):
|
|
if hasattr(self.move_line, 'credit') and hasattr(self.move_line, 'debit'):
|
|
return (self.move_line.credit - self.move_line.debit)
|
|
|
|
|
|
class TaxesByInvoiceStart(ModelView):
|
|
'Taxes By Invoice Start'
|
|
__name__ = 'account_col.print_taxes_by_invoice.start'
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True)
|
|
start_period = fields.Many2One('account.period', 'Start Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
('start_date', '<=', (Eval('end_period'), 'start_date')),
|
|
], depends=['fiscalyear', 'end_period'], required=True)
|
|
end_period = fields.Many2One('account.period', 'End Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
('start_date', '>=', (Eval('start_period'), 'start_date'))
|
|
],
|
|
depends=['fiscalyear', 'start_period'], required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
|
|
@staticmethod
|
|
def default_fiscalyear():
|
|
FiscalYear = Pool().get('account.fiscalyear')
|
|
return FiscalYear.find(
|
|
Transaction().context.get('company'), exception=False)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@fields.depends('fiscalyear')
|
|
def on_change_fiscalyear(self):
|
|
self.start_period = None
|
|
self.end_period = None
|
|
|
|
|
|
class PrintTaxesByInvoice(Wizard):
|
|
'Print Taxes By Invoice'
|
|
__name__ = 'account_col.print_taxes_by_invoice'
|
|
start = StateView(
|
|
'account_col.print_taxes_by_invoice.start',
|
|
'account_col.print_taxes_by_invoice_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateReport('account_col.taxes_by_invoice_report')
|
|
|
|
def do_print_(self, action):
|
|
start_period = self.start.start_period.id
|
|
end_period = self.start.end_period.id
|
|
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'fiscalyear': self.start.fiscalyear.id,
|
|
'start_period': start_period,
|
|
'end_period': end_period,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class TaxesByInvoice(Report):
|
|
__name__ = 'account_col.taxes_by_invoice_report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Period = pool.get('account.period')
|
|
Company = pool.get('company.company')
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
Invoice = pool.get('account.invoice')
|
|
company = Company(data['company'])
|
|
|
|
start_period = Period(data['start_period'])
|
|
end_period = Period(data['end_period'])
|
|
|
|
dom_invoices = []
|
|
dom_invoices.append([
|
|
('invoice_date', '>=', start_period.start_date),
|
|
('invoice_date', '<=', end_period.end_date),
|
|
('state', 'in', ['posted', 'paid']),
|
|
])
|
|
|
|
invoices = Invoice.search(dom_invoices, order=[('number', 'ASC')])
|
|
|
|
taxes = {}
|
|
for invoice in invoices:
|
|
for itax in invoice.taxes:
|
|
if not itax.tax:
|
|
msg = f'Falta impuestos en la factura {invoice.number}'
|
|
raise UserError(msg)
|
|
tax_id = itax.tax.id
|
|
if tax_id not in taxes.keys():
|
|
taxes[tax_id] = {
|
|
'name': (itax.account.code or '') + ' - ' + itax.account.name,
|
|
'lines': [],
|
|
'sum_base': [],
|
|
'sum_amount': [],
|
|
}
|
|
taxes[tax_id]['lines'].append(itax)
|
|
taxes[tax_id]['sum_base'].append(itax.base)
|
|
taxes[tax_id]['sum_amount'].append(itax.amount)
|
|
|
|
report_context['records'] = taxes.values()
|
|
report_context['start_period'] = start_period.name
|
|
report_context['end_period'] = end_period.name
|
|
report_context['company'] = company
|
|
report_context['fiscalyear'] = FiscalYear(data['fiscalyear']).name
|
|
return report_context
|
|
|
|
|
|
class TaxesPostedStart(ModelView):
|
|
'Taxes Posted Start'
|
|
__name__ = 'account_col.print_taxes_posted.start'
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True)
|
|
start_period = fields.Many2One('account.period', 'Start Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
('start_date', '<=', (Eval('end_period'), 'start_date')),
|
|
], depends=['fiscalyear', 'end_period'], required=True)
|
|
end_period = fields.Many2One('account.period', 'End Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
('start_date', '>=', (Eval('start_period'), 'start_date'))
|
|
],
|
|
depends=['fiscalyear', 'start_period'], required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
taxes = fields.Many2Many('account.tax', None, None, 'Tax')
|
|
|
|
@staticmethod
|
|
def default_fiscalyear():
|
|
FiscalYear = Pool().get('account.fiscalyear')
|
|
return FiscalYear.find(
|
|
Transaction().context.get('company'), exception=False)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@fields.depends('fiscalyear')
|
|
def on_change_fiscalyear(self):
|
|
self.start_period = None
|
|
self.end_period = None
|
|
|
|
|
|
class PrintTaxesPosted(Wizard):
|
|
'Print Taxes Posted'
|
|
__name__ = 'account_col.print_taxes_posted'
|
|
start = StateView(
|
|
'account_col.print_taxes_posted.start',
|
|
'account_col.print_taxes_posted_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateReport('account_col.taxes_posted_report')
|
|
|
|
def do_print_(self, action):
|
|
taxes = None
|
|
if self.start.taxes:
|
|
taxes = [tax.id for tax in self.start.taxes]
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'fiscalyear': self.start.fiscalyear.id,
|
|
'start_period': self.start.start_period.id,
|
|
'end_period': self.start.end_period.id,
|
|
'taxes': taxes,
|
|
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class TaxesPosted(Report):
|
|
__name__ = 'account_col.taxes_posted_report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Period = pool.get('account.period')
|
|
Company = pool.get('company.company')
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
Tax = pool.get('account.tax')
|
|
MoveLine = pool.get('account.move.line')
|
|
company = Company(data['company'])
|
|
|
|
start_period = Period(data['start_period'])
|
|
end_period = Period(data['end_period'])
|
|
|
|
dom = []
|
|
if data['taxes']:
|
|
dom.append(('id', 'in', data['taxes']))
|
|
|
|
taxes = Tax.search(dom, order=[('invoice_account.code', 'ASC')])
|
|
taxes_accounts = {}
|
|
|
|
def _get_data_tax(acc):
|
|
val = {
|
|
'code': acc.code,
|
|
'name': acc.rec_name,
|
|
'rate': '',
|
|
'lines': [],
|
|
'sum_base': [],
|
|
'sum_amount': [],
|
|
}
|
|
return val
|
|
|
|
for t in taxes:
|
|
for tax_i_acc in (t.invoice_account, t.credit_note_account):
|
|
if tax_i_acc and tax_i_acc.id not in taxes_accounts.keys():
|
|
taxes_accounts[tax_i_acc.id] = _get_data_tax(tax_i_acc)
|
|
|
|
periods = Period.search([
|
|
('fiscalyear', '=', data['fiscalyear']),
|
|
('start_date', '>=', start_period.start_date),
|
|
('end_date', '<=', end_period.end_date),
|
|
])
|
|
period_ids = [p.id for p in periods]
|
|
|
|
lines = MoveLine.search([
|
|
('account', 'in', set(taxes_accounts.keys())),
|
|
('move.period', 'in', period_ids),
|
|
], order=[('date', 'ASC')])
|
|
|
|
targets = {}
|
|
for line in lines:
|
|
line_id = line.account.id
|
|
if line_id not in targets.keys():
|
|
targets[line_id] = taxes_accounts[line_id]
|
|
line_ = cls.get_tax_reversed(line)
|
|
targets[line_id]['lines'].append(line_)
|
|
targets[line_id]['sum_base'].append(line_['base'])
|
|
targets[line_id]['sum_amount'].append(line_['tax_amount'])
|
|
|
|
ordered_targets = sorted(((v['code'], v) for v in targets.values()), key = lambda tup: tup[0])
|
|
ordered_targets = OrderedDict(ordered_targets)
|
|
|
|
report_context['records'] = ordered_targets.values()
|
|
report_context['start_period'] = start_period.name
|
|
report_context['end_period'] = end_period.name
|
|
report_context['company'] = company
|
|
report_context['fiscalyear'] = FiscalYear(data['fiscalyear']).name
|
|
return report_context
|
|
|
|
@classmethod
|
|
def get_tax_reversed(cls, line):
|
|
line2 = {'line': line}
|
|
rate = None
|
|
base = _ZERO
|
|
amount = line.debit - line.credit
|
|
if line.tax_lines:
|
|
for tax_line in line.tax_lines:
|
|
if tax_line.tax and tax_line.type == 'tax':
|
|
rate = tax_line.tax.rate
|
|
elif tax_line.type == 'base':
|
|
base += tax_line.amount
|
|
if rate:
|
|
base = amount / abs(rate)
|
|
rate = rate * 100
|
|
|
|
|
|
#TODO: Add smart compute deduced from taxes model
|
|
line2.update({'base': base, 'tax_amount': amount, 'rate': rate})
|
|
return line2
|