trytonpsk-account_col/tax.py

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