868 lines
34 KiB
Python
868 lines
34 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 sql import Table
|
|
from decimal import Decimal
|
|
from datetime import date, timedelta
|
|
|
|
from trytond.i18n import gettext
|
|
from trytond.model import ModelView, ModelSQL, fields, dualmethod
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Eval, If, Bool
|
|
from trytond.wizard import (
|
|
Wizard, StateView, Button, StateReport, StateTransition
|
|
)
|
|
from trytond.transaction import Transaction
|
|
from trytond.modules.company import CompanyReport
|
|
from trytond.exceptions import UserError
|
|
|
|
from .exceptions import (
|
|
NotificationAuthError, NotificationAuthWarning, InvalidTypeInvoiceError,
|
|
NotificationAdvanceWarning,
|
|
)
|
|
|
|
conversor = None
|
|
try:
|
|
from numword import numword_es
|
|
conversor = numword_es.NumWordES()
|
|
except:
|
|
print("Warning: Does not possible import numword module, please install it...!")
|
|
|
|
_ZERO = Decimal('0.00')
|
|
|
|
# Wilson necesitamos que la actualizacion por electronic_invoice de este modulo
|
|
# no rompa la retrocompatibilidad, esta variable debe seguir disponible hasta
|
|
# que no se migren todos los usuarios
|
|
TYPE_INVOICE = [
|
|
('', ''),
|
|
('C', 'Computador'),
|
|
('P', 'POS'),
|
|
('M', 'Manual'),
|
|
('1', 'Venta Electronica'),
|
|
('2', 'Exportacion'),
|
|
('3', 'Factura por Contingencia Facturador'),
|
|
('4', 'Factura por Contingencia DIAN'),
|
|
('91', 'Nota Crédito Eléctronica'),
|
|
('92', 'Nota Débito Eléctronica'),
|
|
('05', 'Documento soporte en adquisiciones'),
|
|
('95', 'Nota de ajuste al documento soporte'),
|
|
]
|
|
|
|
TYPE_INVOICE_OUT = [
|
|
('', ''),
|
|
('C', 'Computador'),
|
|
('P', 'POS'),
|
|
('M', 'Manual'),
|
|
('1', 'Venta Electronica'),
|
|
('2', 'Exportacion'),
|
|
('3', 'Factura por Contingencia Facturador'),
|
|
('4', 'Factura por Contingencia DIAN'),
|
|
('91', 'Nota Crédito Eléctronica'),
|
|
('92', 'Nota Débito Eléctronica'),
|
|
]
|
|
|
|
TYPE_INVOICE_IN = [
|
|
('', ''),
|
|
('05', 'Documento soporte en adquisiciones'),
|
|
('95', 'Nota de ajuste al documento soporte'),
|
|
]
|
|
|
|
|
|
class Invoice(metaclass=PoolMeta):
|
|
__name__ = 'account.invoice'
|
|
estimate_pay_date = fields.Function(fields.Date('Estimate Pay Date'),
|
|
'get_estimate_pay_date'
|
|
)
|
|
equivalent_invoice = fields.Boolean('Equivalent Invoice', states={
|
|
'invisible': Eval('type') != 'in',
|
|
})
|
|
resolution = fields.Char('Resolution', states={
|
|
'readonly': Eval('state') != 'draft',
|
|
})
|
|
payment_method = fields.Text('Payment Method',
|
|
states={'readonly': Eval('state') != 'draft'},
|
|
depends=['payment_term'],
|
|
)
|
|
number_alternate = fields.Char('Number Alternate',
|
|
states={
|
|
'readonly': Eval('state') != 'draft',
|
|
'invisible': Eval('type') != 'in',
|
|
})
|
|
delay_amount = fields.Numeric('Delay Amount',
|
|
states={'readonly': Eval('state') != 'draft'})
|
|
total_amount_words = fields.Function(fields.Char('Total Amount Words'),
|
|
'get_total_amount_words')
|
|
authorization = fields.Many2One('account.invoice.authorization',
|
|
'Authorization', domain=[
|
|
('state', '=', 'active'),
|
|
('company', '=', Eval('company')),
|
|
], states={'invisible': False}
|
|
)
|
|
invoice_type = fields.Selection(TYPE_INVOICE, 'Type Invoice',
|
|
states={
|
|
# 'invisible': And(Eval('type') != 'out', ~Eval('equivalent_invoice')),
|
|
'readonly': Eval('state').in_(['validated', 'posted', 'paid'])
|
|
})
|
|
invoice_type_string = invoice_type.translated('invoice_type')
|
|
untaxed_amount_cache = fields.Numeric('Untaxed Cache',
|
|
digits=(16, Eval('currency_digits', 2)),
|
|
depends=['currency_digits'])
|
|
petty_cash = fields.Boolean('Petty Cash', states={
|
|
'invisible': Eval('type') != 'out',
|
|
})
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Invoice, cls).__setup__()
|
|
if 'untaxed_amount_cache' not in cls._check_modify_exclude:
|
|
cls._check_modify_exclude.add('untaxed_amount_cache')
|
|
if 'number' not in cls._check_modify_exclude:
|
|
cls._check_modify_exclude.add('number')
|
|
cls.state_string = super(Invoice, cls).state.translated('state')
|
|
cls.payment_term.states['required'] = True
|
|
cls.payment_term.states['readonly'] = Eval('state') == 'posted'
|
|
cls._buttons.update({
|
|
'validate_taxes_wizard': {}
|
|
})
|
|
|
|
def print_invoice(self):
|
|
# Ignore cache for invoice because high demand of resources
|
|
return
|
|
|
|
@classmethod
|
|
def copy(cls, invoices, default=None):
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
default['number_alternate'] = None
|
|
default['estimate_pay_date'] = None
|
|
default['delay_amount'] = None
|
|
default['authorization'] = None
|
|
return super(Invoice, cls).copy(invoices, default=default)
|
|
|
|
def get_estimate_pay_date(self, name=None):
|
|
if not self.move:
|
|
return
|
|
for l in self.move.lines:
|
|
if l.account.id == self.account.id and l.reconciliation is None:
|
|
return l.maturity_date
|
|
|
|
def get_total_amount_words(self, name=None):
|
|
if conversor and self.total_amount:
|
|
# if self.second_currency.code == 'USD':
|
|
# value = str(self.total_amount).split('.')
|
|
# num = (conversor.currency(
|
|
# val=tuple(int(n) for n in value),
|
|
# old=True,
|
|
# hightxt='dolar/s',
|
|
# lowtxt='centavo/s',
|
|
# jointxt='con')).upper()
|
|
# else:
|
|
digits = self.company.currency.digits
|
|
total_amount = (round(self.total_amount, digits))
|
|
num = (conversor.cardinal(int(total_amount))).upper()
|
|
return num
|
|
|
|
@fields.depends('payment_method', 'payment_term')
|
|
def on_change_payment_term(self, name=None):
|
|
if self.payment_term:
|
|
self.payment_method = self.payment_term.description
|
|
|
|
@fields.depends('invoice_type', 'authorization', 'type')
|
|
def on_change_invoice_type(self, name=None):
|
|
Authorization = Pool().get('account.invoice.authorization')
|
|
if self.invoice_type:
|
|
authorizations = Authorization.search([
|
|
('kind', '=', self.invoice_type),
|
|
('state', '=', 'active'),
|
|
])
|
|
if len(authorizations) == 1 and authorizations[0].type == self.type:
|
|
authorization = authorizations[0]
|
|
self.authorization = authorization.id
|
|
self.resolution = self.authorization.number
|
|
else:
|
|
self.authorization = None
|
|
self.resolution = None
|
|
|
|
@fields.depends('equivalent_invoice', 'authorization', 'invoice_type')
|
|
def on_change_equivalent_invoice(self, name=None):
|
|
if self.equivalent_invoice:
|
|
if not self.invoice_type:
|
|
self.invoice_type = '05'
|
|
self.on_change_invoice_type()
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def validate_invoice(cls, invoices):
|
|
for inv in invoices:
|
|
if inv.type == 'out':
|
|
if inv.invoice_type in ['', None] or (inv.total_amount < 0 and inv.invoice_type == '1'):
|
|
raise InvalidTypeInvoiceError(
|
|
gettext('account_col.msg_invalid_type_invoice')
|
|
)
|
|
else:
|
|
cls.validate_tax(inv)
|
|
cls.set_number([inv])
|
|
super(Invoice, cls).validate_invoice(invoices)
|
|
|
|
@classmethod
|
|
def validate_tax(cls, invoice):
|
|
pool = Pool()
|
|
Config = pool.get('account.configuration')
|
|
Line = pool.get('account.invoice.line')
|
|
InvoiceTax = Pool().get('account.invoice.tax')
|
|
config = Config(1)
|
|
taxes_validate = [t for t in invoice.taxes if t.base and t.tax.base and t.tax.base > t.base]
|
|
if taxes_validate and config.remove_tax:
|
|
lines_to_change = [l for l in invoice.lines if l.type == 'line']
|
|
Line.write(lines_to_change, {'taxes': [
|
|
('remove', [t.tax.id for t in taxes_validate])]})
|
|
InvoiceTax.delete(taxes_validate)
|
|
invoice.save()
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def post(cls, invoices):
|
|
super(Invoice, cls).post(invoices)
|
|
for inv in invoices:
|
|
inv.write([inv], {
|
|
'untaxed_amount_cache': inv.untaxed_amount
|
|
})
|
|
if inv.type == 'out':
|
|
if inv.invoice_type in ['', None]:
|
|
raise InvalidTypeInvoiceError(
|
|
gettext('account_col.msg_invalid_type_invoice')
|
|
)
|
|
elif inv.invoice_type == '91' and inv.original_invoice \
|
|
and inv.original_invoice.state == 'posted':
|
|
writeoff = None
|
|
pool = Pool()
|
|
config = pool.get('account.configuration')(1)
|
|
if inv.currency != inv.company.currency:
|
|
writeoff = config.difference_in_exchange
|
|
cls.reconcile_invoice(inv, writeoff=writeoff)
|
|
if inv.type == 'in':
|
|
cls.check_duplicated_reference(inv)
|
|
cls.check_advance(inv)
|
|
|
|
@classmethod
|
|
def reconcile_invoice(cls, invoice, writeoff=None):
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
origin = invoice.original_invoice
|
|
if origin.amount_to_pay + invoice.amount_to_pay == 0:
|
|
to_reconcile_lines = []
|
|
for inv in (invoice, origin):
|
|
for ml in inv.move.lines:
|
|
if inv.account == ml.account:
|
|
to_reconcile_lines.append(ml)
|
|
to_reconcile_lines.extend([l for l in origin.payment_lines])
|
|
MoveLine.reconcile(to_reconcile_lines, writeoff=writeoff)
|
|
|
|
@classmethod
|
|
def set_number(cls, invoices):
|
|
for invoice in invoices:
|
|
if not invoice.number and invoice.type == 'out' \
|
|
and invoice.authorization:
|
|
invoice.number = invoice.authorization.sequence.get()
|
|
if invoice.type == 'in' and invoice.equivalent_invoice \
|
|
and not invoice.number_alternate:
|
|
invoice.check_authorization()
|
|
invoice.number_alternate = invoice.authorization.sequence.get()
|
|
invoice.save()
|
|
super(Invoice, cls).set_number(invoices)
|
|
|
|
def check_authorization(self):
|
|
today_ = Pool().get('ir.date').today()
|
|
Warning = Pool().get('res.user.warning')
|
|
if self.authorization:
|
|
next_number = self.authorization.sequence.number_next_internal
|
|
auth = self.authorization
|
|
res_days = (auth.end_date_auth - today_).days
|
|
msg_error = None
|
|
msg_warn = None
|
|
if res_days <= 0:
|
|
msg_error = 'Error, la autorización está vencida'
|
|
elif next_number > self.authorization.to_auth:
|
|
msg_error = 'Error, la operación supera el rango de numeración de la autorización'
|
|
elif res_days <= 10:
|
|
msg_warn = 'La autorización esta próxima a vencer, quedan "%s" días' % str(res_days)
|
|
elif (int(self.authorization.to_auth) - int(next_number)) <= 10:
|
|
msg_warn = 'Los rangos de numeración de la autorización estan proximos a vencer'
|
|
|
|
warning_key = 'notification_auth_%d' % auth.id
|
|
if msg_error:
|
|
raise NotificationAuthError(gettext(
|
|
'account_col.msg_notification_auth', msg=msg_error)
|
|
)
|
|
elif msg_warn and Warning.check(warning_key):
|
|
raise NotificationAuthWarning(warning_key,
|
|
gettext('account_col.msg_notification_auth', msg=msg_warn)
|
|
)
|
|
|
|
def check_advance(self):
|
|
Line = Pool().get('account.move.line')
|
|
lines = Line.search_count([
|
|
('account.type.receivable', '=', True),
|
|
('account.reconcile', '=', True),
|
|
('party', '=', self.party.id),
|
|
('reconciliation', '=', None),
|
|
('debit', '>', 0),
|
|
])
|
|
Warning = Pool().get('res.user.warning')
|
|
warning_key = 'notification_advance_%s' % (str(self.party.id) + self.invoice_date.strftime("%y-%m-%d"))
|
|
if lines and Warning.check(warning_key):
|
|
raise NotificationAdvanceWarning(warning_key,
|
|
gettext('account_col.msg_notification_advance', party=self.party.name, msg=lines)
|
|
)
|
|
|
|
def get_move(self):
|
|
move = super(Invoice, self).get_move()
|
|
if self.description:
|
|
move.description = self.description.replace('\n', ' ')
|
|
if self.reference:
|
|
for line in move.lines:
|
|
line.reference = self.reference
|
|
# line.description = description.replace('\n', ' ')
|
|
return move
|
|
|
|
@dualmethod
|
|
def update_taxes(cls, invoices, exception=False):
|
|
super(Invoice, cls).update_taxes(invoices, exception=False)
|
|
InvoiceTax = Pool().get('account.invoice.tax')
|
|
Tax = Pool().get('account.tax')
|
|
taxes_update = Tax.search([
|
|
('type', '=', 'fixed'),
|
|
('amount', '=', 0),
|
|
])
|
|
taxes_update_ids = [t.id for t in taxes_update]
|
|
for invoice in invoices:
|
|
value = 0
|
|
for ln in invoice.lines:
|
|
if not ln.product or ln.unit_price == 0 or not ln.product.extra_tax:
|
|
continue
|
|
value += float(ln.product.extra_tax) * ln.quantity
|
|
extra_tax_amount = Decimal(str(round(value, 4)))
|
|
|
|
if invoice.state == 'draft' and extra_tax_amount != 0:
|
|
for line_tax in invoice.taxes:
|
|
if line_tax.tax.id in taxes_update_ids:
|
|
InvoiceTax.write([line_tax], {
|
|
'amount': extra_tax_amount
|
|
})
|
|
invoice.save()
|
|
|
|
def get_lines_to_pay_cache(self):
|
|
Line = Pool().get('account.move.line')
|
|
if self.lines_to_pay:
|
|
return self.lines_to_pay
|
|
Date = Pool().get('ir.date')
|
|
move_lines = []
|
|
for line in self.lines:
|
|
move_lines += line.get_move_lines()
|
|
for tax in self.taxes:
|
|
move_lines += tax.get_move_lines()
|
|
total = Decimal('0.0')
|
|
for line in move_lines:
|
|
total += line.debit - line.credit
|
|
term_lines = [(Date.today(), total)]
|
|
if self.payment_term:
|
|
term_lines = self.payment_term.compute(
|
|
total, self.company.currency, self.invoice_date)
|
|
lines_to_pay = []
|
|
seq = 0
|
|
for date_, amount in term_lines:
|
|
seq += 1
|
|
line = Line(
|
|
id=seq,
|
|
maturity_date=date_,
|
|
amount_second_currency=0,
|
|
debit=abs(amount),
|
|
credit=0,
|
|
)
|
|
lines_to_pay.append(line)
|
|
return lines_to_pay
|
|
|
|
@classmethod
|
|
def check_duplicated_reference(cls, invoice):
|
|
today = date.today()
|
|
target_date = today - timedelta(days=90)
|
|
if invoice.reference:
|
|
duplicates = cls.search_read([
|
|
('reference', '=', invoice.reference),
|
|
('party', '=', invoice.party.id),
|
|
('invoice_date', '>=', target_date),
|
|
], fields_names=['reference'])
|
|
if len(duplicates) >= 2:
|
|
raise UserError(gettext(
|
|
'account_col.msg_duplicated_reference_invoice')
|
|
)
|
|
|
|
|
|
class InvoiceLine(ModelSQL, ModelView):
|
|
__name__ = 'account.invoice.line'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(InvoiceLine, cls).__setup__()
|
|
|
|
# Set account domain dynamically for kind
|
|
cls.account.domain = [
|
|
('closed', '!=', True),
|
|
('company', '=', Eval('company', -1)),
|
|
If(Bool(Eval('_parent_invoice')),
|
|
If(Eval('_parent_invoice', {}).get('type') == 'out',
|
|
cls._account_domain_('out'),
|
|
If(Eval('_parent_invoice', {}).get('type') == 'in',
|
|
cls._account_domain_('in'),
|
|
['OR',
|
|
cls._account_domain_('out'),
|
|
cls._account_domain_('in')])),
|
|
If(Eval('invoice_type') == 'out',
|
|
cls._account_domain_('out'),
|
|
If(Eval('invoice_type') == 'in',
|
|
cls._account_domain_('in'),
|
|
['OR',
|
|
cls._account_domain_('out'),
|
|
cls._account_domain_('in')]))),
|
|
]
|
|
|
|
@staticmethod
|
|
def _account_domain_(type_):
|
|
if type_ == 'out':
|
|
return ['OR',
|
|
('type.revenue', '=', True),
|
|
('type.payable', '=', True)
|
|
]
|
|
elif type_ == 'in':
|
|
return ['OR',
|
|
('type.expense', '=', True),
|
|
('type.debt', '=', True),
|
|
('type.stock', '=', True),
|
|
]
|
|
|
|
@classmethod
|
|
def trigger_create(cls, records):
|
|
for line in records:
|
|
if line.type != 'line':
|
|
continue
|
|
if line.product and line.product.account_category and line.quantity < 0:
|
|
category = line.product.account_category
|
|
account_id = None
|
|
if line.invoice.type == 'in':
|
|
if category.account_return_purchase:
|
|
account_id = category.account_return_purchase.id
|
|
else:
|
|
if category.account_return_sale:
|
|
account_id = category.account_return_sale.id
|
|
if not account_id:
|
|
continue
|
|
|
|
line.write([line], {'account': account_id})
|
|
|
|
@fields.depends('description')
|
|
def on_change_product(self):
|
|
super(InvoiceLine, self).on_change_product()
|
|
if self.product:
|
|
self.description = self.product.name
|
|
else:
|
|
self.description = None
|
|
|
|
def get_move_lines(self):
|
|
lines = super(InvoiceLine, self).get_move_lines()
|
|
if self.product:
|
|
for line in lines:
|
|
if not self.description:
|
|
self.description = self.product.name
|
|
self.save()
|
|
line.description = self.description.replace('\n', ' ')
|
|
return lines
|
|
|
|
def compute_price_w_tax(self, line):
|
|
Tax = Pool().get('account.tax')
|
|
res = line.unit_price * Decimal(line.quantity)
|
|
if line.taxes and line.unit_price:
|
|
tax_list = Tax.compute(line.taxes,
|
|
line.unit_price or Decimal('0.0'), 1)
|
|
res = sum([t['amount'] for t in tax_list], Decimal('0.0'))
|
|
res = (res + line.unit_price) * Decimal(line.quantity)
|
|
if line.product.extra_tax:
|
|
res += line.product.extra_tax * Decimal(line.quantity)
|
|
res = res.quantize(
|
|
Decimal(1) / 10 ** self.__class__.unit_price.digits[1])
|
|
return res
|
|
|
|
|
|
class InvoiceForceDraft(Wizard):
|
|
'Invoice Force Draft'
|
|
__name__ = 'account.invoice.force_draft'
|
|
start_state = 'force_draft'
|
|
force_draft = StateTransition()
|
|
|
|
def transition_force_draft(self):
|
|
pool = Pool()
|
|
Invoice = pool.get('account.invoice')
|
|
MoveLine = pool.get('account.move.line')
|
|
account_invoice = Table('account_invoice')
|
|
account_move = Table('account_move')
|
|
id_ = Transaction().context['active_id']
|
|
invoice = Invoice(id_)
|
|
if invoice.electronic_state == 'authorized':
|
|
return 'end'
|
|
if invoice.move:
|
|
move = invoice.move
|
|
MoveLine.check_journal_period_modify(move.period, move.journal)
|
|
cursor = Transaction().connection.cursor()
|
|
if id_:
|
|
cursor.execute(*account_invoice.update(
|
|
columns=[
|
|
account_invoice.state,
|
|
account_invoice.invoice_report_cache,
|
|
account_invoice.accounting_date,
|
|
],
|
|
values=["validated", None, None],
|
|
where=account_invoice.id == id_)
|
|
)
|
|
if invoice.move:
|
|
cursor.execute(*account_move.update(
|
|
columns=[account_move.state],
|
|
values=["draft"],
|
|
where=account_move.id == invoice.move.id)
|
|
)
|
|
return 'end'
|
|
|
|
|
|
class EquivalentInvoice(CompanyReport):
|
|
'Equivalent Invoice'
|
|
__name__ = 'account.invoice.equivalent'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
report_context['company'] = Transaction().context.get('company')
|
|
return report_context
|
|
|
|
|
|
class MovesInvoicesStart(ModelView):
|
|
'Moves Invoices Start'
|
|
__name__ = 'account_invoice.moves.start'
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
start_date = fields.Date('Start Date')
|
|
end_date = fields.Date('End Date', required=True)
|
|
type_inv = fields.Selection([
|
|
('out', 'Invoice'),
|
|
('out_credit_note', 'Out Credit Note'),
|
|
('in', 'Supplier Invoice'),
|
|
('in_credit_note', 'In Credit Note'),
|
|
], 'Type', required=True)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_end_date():
|
|
Date = Pool().get('ir.date')
|
|
return Date.today()
|
|
|
|
|
|
class MovesInvoices(Wizard):
|
|
'Moves Invoices'
|
|
__name__ = 'account_invoice.moves'
|
|
start = StateView('account_invoice.moves.start',
|
|
'account_col.moves_invoices_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-ok', default=True),
|
|
])
|
|
print_ = StateReport('account_invoice.moves_report')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'start_date': self.start.start_date,
|
|
'end_date': self.start.end_date,
|
|
'type_invoice': self.start.type_inv,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class MovesInvoicesReport(CompanyReport):
|
|
'Moves Invoices Report'
|
|
__name__ = 'account_invoice.moves_report'
|
|
|
|
@classmethod
|
|
def get_domain_invoice(cls, data):
|
|
if data['type_invoice'] == "out_credit_note":
|
|
type_invoice = 'out'
|
|
elif data['type_invoice'] == "in_credit_note":
|
|
type_invoice = 'in'
|
|
else:
|
|
type_invoice = data['type_invoice']
|
|
|
|
dom_invoices = [
|
|
('company', '=', data['company']),
|
|
('type', '=', type_invoice),
|
|
('state', 'in', ['validated', 'posted', 'paid']),
|
|
('move', '!=', None),
|
|
]
|
|
return dom_invoices
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
Invoice = pool.get('account.invoice')
|
|
company = Company(data['company'])
|
|
start = data['start_date']
|
|
end = data['end_date']
|
|
|
|
dom_invoices = cls.get_domain_invoice(data)
|
|
|
|
if end != None:
|
|
dom_invoices.append(
|
|
('invoice_date', '<=', end),
|
|
)
|
|
if start != None:
|
|
dom_invoices.append(
|
|
('invoice_date', '>=', start),
|
|
)
|
|
|
|
invoices = Invoice.search(dom_invoices,
|
|
order=[('invoice_date', 'ASC')])
|
|
|
|
moves_filtered = []
|
|
|
|
for invoice in invoices:
|
|
if invoice.untaxed_amount < _ZERO:
|
|
if "credit" in data['type_invoice']:
|
|
moves_filtered.append(invoice.move)
|
|
else:
|
|
continue
|
|
else:
|
|
if "credit" not in data['type_invoice']:
|
|
moves_filtered.append(invoice.move)
|
|
else:
|
|
continue
|
|
|
|
report_context['records'] = moves_filtered
|
|
report_context['moves_filtered'] = moves_filtered
|
|
report_context['company'] = company
|
|
report_context['start'] = start
|
|
report_context['end'] = end
|
|
return report_context
|
|
|
|
|
|
class InvoiceUpdateStart(ModelView):
|
|
'Invoice Update Start'
|
|
__name__ = 'account_invoice.update.start'
|
|
date = fields.Date('Date')
|
|
description = fields.Char('Description')
|
|
tax_add = fields.Many2One('account.tax', 'Add Tax', domain=[
|
|
('group.kind', '=', Eval('group_tax'))
|
|
], depends=['group_tax'])
|
|
tax_remove = fields.Many2One('account.tax', 'Remove Tax', domain=[
|
|
('group.kind', '=', Eval('group_tax'))
|
|
], depends=['group_tax'])
|
|
group_tax = fields.Char('Group Tax')
|
|
number_alternate = fields.Char('New Number Inv. Equi')
|
|
number = fields.Char('New Number')
|
|
party = fields.Many2One('party.party', 'Party')
|
|
|
|
@staticmethod
|
|
def default_group_tax():
|
|
Invoice = Pool().get('account.invoice')
|
|
invoice_ids = Transaction().context['active_ids']
|
|
invoice = Invoice(invoice_ids[0])
|
|
if invoice.type == 'in':
|
|
return 'purchase'
|
|
else:
|
|
return 'sale'
|
|
|
|
|
|
class InvoiceUpdate(Wizard):
|
|
'Invoice Update'
|
|
__name__ = 'account_invoice.update'
|
|
start = StateView('account_invoice.update.start',
|
|
'account_col.invoice_update_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Ok', 'accept', 'tryton-ok', default=True),
|
|
])
|
|
accept = StateTransition()
|
|
|
|
def transition_accept(self):
|
|
Invoice = Pool().get('account.invoice')
|
|
Line = Pool().get('account.invoice.line')
|
|
InvoiceTax = Pool().get('account.invoice.tax')
|
|
invoice, = Invoice.browse([Transaction().context['active_id']])
|
|
|
|
values = {}
|
|
|
|
if self.start.party:
|
|
values['party'] = self.start.party.id
|
|
values['invoice_address'] = self.start.party.addresses[0].id
|
|
if self.start.date:
|
|
values['invoice_date'] = self.start.date
|
|
if self.start.description:
|
|
values['description'] = self.start.description
|
|
if self.start.number:
|
|
account_invoice = Table('account_invoice')
|
|
id_ = Transaction().context['active_id']
|
|
cursor = Transaction().connection.cursor()
|
|
if id_:
|
|
cursor.execute(*account_invoice.update(
|
|
columns=[account_invoice.number],
|
|
values=[self.start.number],
|
|
where=account_invoice.id == id_)
|
|
)
|
|
if self.start.number_alternate:
|
|
account_invoice = Table('account_invoice')
|
|
id_ = Transaction().context['active_id']
|
|
cursor = Transaction().connection.cursor()
|
|
if id_:
|
|
cursor.execute(*account_invoice.update(
|
|
columns=[account_invoice.number_alternate],
|
|
values=[self.start.number],
|
|
where=account_invoice.id == id_)
|
|
)
|
|
|
|
if invoice.state == 'draft' and not invoice.cufe and values:
|
|
if self.start.party:
|
|
for line in invoice.lines:
|
|
line.party = self.start.party.id
|
|
Line.write(list(invoice.lines), {'party': self.start.party.id})
|
|
Invoice.write([invoice], values)
|
|
|
|
if (self.start.tax_add or self.start.tax_remove) and invoice:
|
|
lines_to_change = []
|
|
for line in invoice.lines:
|
|
if line.type != 'line':
|
|
continue
|
|
lines_to_change.append(line)
|
|
|
|
if lines_to_change:
|
|
if self.start.tax_add:
|
|
Line.write(lines_to_change, {'taxes': [
|
|
('add', [self.start.tax_add.id])]})
|
|
if self.start.tax_remove:
|
|
Line.write(lines_to_change, {'taxes': [
|
|
('remove', [self.start.tax_remove.id])]})
|
|
if self.start.tax_add:
|
|
invoice.on_change_taxes()
|
|
if self.start.tax_remove:
|
|
for itax in invoice.taxes:
|
|
if itax.tax.id == self.start.tax_remove.id:
|
|
InvoiceTax.delete([itax])
|
|
|
|
invoice.save()
|
|
return 'end'
|
|
|
|
|
|
class InvoiceReport(metaclass=PoolMeta):
|
|
__name__ = 'account.invoice'
|
|
|
|
@classmethod
|
|
def _execute(cls, records, header, data, action):
|
|
# Ensure to restore original context
|
|
# set_lang may modify it
|
|
with Transaction().set_context(Transaction().context):
|
|
report_context = cls.get_context(records, header, data)
|
|
return cls.convert(action, cls.render(action, report_context))
|
|
|
|
|
|
class InvoiceTax(ModelSQL):
|
|
'Invoice Tax'
|
|
__name__ = 'account.invoice.tax'
|
|
|
|
def get_move_lines(self):
|
|
'''
|
|
Return a list of move lines instances for invoice tax
|
|
'''
|
|
lines = super(InvoiceTax, self).get_move_lines()
|
|
lines_ = []
|
|
Module = Pool().get('ir.module')
|
|
MoveLine = Pool().get('account.move.line')
|
|
Currency = Pool().get('currency.currency')
|
|
module, = Module.search(['name', '=', 'analytic_account'])
|
|
if module.state == 'activated':
|
|
AnalyticLine = Pool().get('analytic_account.line')
|
|
|
|
op = self.invoice
|
|
for ln in lines:
|
|
ln.reference = self.invoice.reference
|
|
if hasattr(self.invoice, 'operation_center') and self.invoice.operation_center:
|
|
ln.operation_center = self.invoice.operation_center
|
|
|
|
if hasattr(self.invoice, 'analytic_account') and self.invoice.analytic_account:
|
|
analytic_ = self.invoice.analytic_account
|
|
|
|
# Add analytic when IVA mayor valor del costo gasto
|
|
if self.tax.iva_costo and self.invoice.type == 'in' and self.tax.classification == 'iva':
|
|
grouped = {}
|
|
for line in self.invoice.lines:
|
|
|
|
if self.tax in line.taxes:
|
|
op = line.operation_center.id if hasattr(line, 'operation_center') and line.operation_center else None
|
|
analytic = line.analytic_accounts[0].account.id if hasattr(line, 'analytic_accounts') and line.analytic_accounts and line.analytic_accounts[0].account else None
|
|
key = str(op) + '-' +str(analytic)
|
|
try:
|
|
grouped[key]['base'] += line.amount
|
|
except:
|
|
new_line = MoveLine()
|
|
for field in [
|
|
'debit', 'credit', 'tax_lines', 'reference', 'second_currency',
|
|
'amount_second_currency', 'description', 'account', 'origin', 'party']:
|
|
setattr(new_line, field, getattr(ln, field))
|
|
grouped[key] = {
|
|
'key': key,
|
|
'line': new_line,
|
|
'base': abs(line.amount),
|
|
'analytic_account': analytic if analytic else analytic_,
|
|
}
|
|
if op:
|
|
grouped[key]['line'].operation_center = op
|
|
for m in grouped.values():
|
|
amount_ = m['base']* self.tax.rate * Decimal(self.tax.taxable_base/100) \
|
|
if self.tax.taxable_base else m['base']* self.tax.rate
|
|
amount_ = amount_.quantize(Decimal(1) / 10 ** 2)
|
|
if self.invoice.currency != self.invoice.company.currency:
|
|
with Transaction().set_context(date=self.invoice.currency_date):
|
|
amount = Currency.compute(self.invoice.currency,
|
|
amount_, self.invoice.company.currency)
|
|
m['line'].amount_second_currency = amount_
|
|
else:
|
|
amount = amount_
|
|
|
|
for t in m['line'].tax_lines:
|
|
t.amount = amount
|
|
|
|
if m['line'].debit == 0:
|
|
m['line'].credit = abs(amount)
|
|
else:
|
|
m['line'].debit = abs(amount)
|
|
|
|
analytic_account_id = m['analytic_account']
|
|
if not analytic_account_id and hasattr(self.invoice, 'analytic_account') and self.invoice.analytic_account:
|
|
analytic_account_id = self.invoice.analytic_account.id
|
|
|
|
if not m['analytic_account']:
|
|
raise UserError(gettext('account_col.msg_analytic_required'))
|
|
analytic_line = AnalyticLine(
|
|
account=m['analytic_account'],
|
|
debit=m['line'].debit,
|
|
credit=m['line'].credit,
|
|
date=self.invoice.invoice_date,
|
|
)
|
|
m['line'].analytic_lines = [analytic_line]
|
|
lines_.append(m['line'])
|
|
if lines_:
|
|
lines = lines_
|
|
return lines
|
|
|
|
|
|
# class WriteOff():
|
|
# # Don't remove this class is neccesary for writeoff account move
|
|
# pass
|