Files
account_invoice_do/invoice.py
T

309 lines
10 KiB
Python
Executable File

# 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 dateutil.relativedelta import relativedelta
from decimal import Decimal
from datetime import date
from trytond.model import fields
from trytond.pool import PoolMeta
from trytond.modules.product.product import TYPES
from trytond.pyson import Eval
from trytond.modules.currency.fields import Monetary
_ZERO = Decimal('0.00')
class Invoice(metaclass=PoolMeta):
__name__ = 'account.invoice'
employee = fields.Many2One('company.employee', 'Salesman',
domain=[
('company', '=', Eval('company', -1)),
],
states={
'readonly': Eval('state') != 'draft',
},
depends=['state', 'company'])
due_date = fields.Function(fields.Date('Due Date'),
'get_remainder_time')
amount_paid = fields.Function(Monetary(
"Amount Paid", currency='currency', digits='currency'),
'get_amount_paid')
days_elapsed = fields.Function(fields.Integer('Days Elapsed'),
'get_days_elapsed')
days_overdue = fields.Function(fields.Integer('Days Overdue'),
'get_days_overdue')
party_reimbursement = fields.Many2One(
'party.party', 'Party Reimbursement',
states={
'invisible': Eval('type') != 'in',
'readonly': Eval('state') != 'draft',
},
context={
'company': Eval('company', -1),
},
depends=['company'])
@classmethod
def search_rec_name(cls, name, clause):
domain = super().search_rec_name(name, clause)
return domain + [
('description', *clause[1:]),
]
def get_days_elapsed(self, name=None):
if self.state == 'paid' or not self.due_date or not self.invoice_date:
return None
days = (date.today() - self.invoice_date).days
return days if days > 0 else None
def get_days_overdue(self, name=None):
if self.state == 'paid' or not self.due_date or not self.invoice_date:
return None
days = (date.today() - self.due_date).days
return days if days > 0 else None
def get_remainder_time(self, name):
term = self.payment_term
if term and term.lines:
for line in term.lines:
if (not line.relativedeltas
or not self.invoice_date):
break
delta = line.relativedeltas[0]
return self.invoice_date + relativedelta(
months=delta.months,
weeks=delta.weeks,
days=delta.days)
return None
@classmethod
def get_amount_paid(cls, invoices, name):
res = {i.id: _ZERO for i in invoices}
for invoice in invoices:
if invoice.state not in ('posted', 'paid'):
continue
total = invoice.total_amount or _ZERO
to_pay = invoice.amount_to_pay or _ZERO
paid = abs(total - to_pay)
if invoice.currency:
paid = invoice.currency.round(paid)
res[invoice.id] = paid
return res
@classmethod
def __setup__(cls):
super().__setup__()
cls._order = [
('invoice_date', 'DESC'),
('id', 'DESC'),
]
_LINE_DEPENDS = [
'type', 'unit_price', 'quantity', 'taxes',
'invoice', '_parent_invoice.currency', 'currency', 'product',
]
_LINE_STATES = {
'invisible': ~Eval('type').in_(['line']),
}
_LINE_TOTAL_STATES = {
'invisible': ~Eval('type').in_(['line', 'subtotal']),
}
class InvoiceLine(metaclass=PoolMeta):
'Invoice Line'
__name__ = 'account.invoice.line'
product_type = fields.Function(
fields.Selection([('', '')] + TYPES, "Product Type"),
'on_change_with_product_type'
)
itbis_invoiced_line = fields.Function(Monetary(
"ITBIS Invoiced", currency='currency', digits='currency',
states=_LINE_STATES),
'get_itbis_invoiced_line'
)
itbis_line = fields.Function(Monetary(
"ITBIS", currency='currency', digits='currency',
states=_LINE_STATES),
'get_itbis_line'
)
itbis_withholding_line = fields.Function(Monetary(
"ITBIS Withholding", currency='currency', digits='currency',
states=_LINE_STATES),
'get_itbis_withholding_line'
)
isc_line = fields.Function(Monetary(
"ISC", currency='currency', digits='currency',
states=_LINE_STATES),
'get_isc_line'
)
cdt_line = fields.Function(Monetary(
"CDT", currency='currency', digits='currency',
states=_LINE_STATES),
'get_cdt_line'
)
isr_withholding_line = fields.Function(Monetary(
"ISR Withholding", currency='currency', digits='currency',
states=_LINE_STATES),
'get_isr_withholding_line'
)
tip_line = fields.Function(Monetary(
"Tip", currency='currency', digits='currency',
states=_LINE_STATES),
'get_tip_line'
)
line_total = fields.Function(Monetary(
"Total", currency='currency', digits='currency',
states=_LINE_TOTAL_STATES),
'get_line_total'
)
@fields.depends('type', 'product')
def on_change_with_product_type(self, name=None):
if self.type == 'line' and self.product:
return self.product.type
return ''
@classmethod
def _get_line_currency(cls, line):
invoice = getattr(line, 'invoice', None)
if invoice and hasattr(invoice, 'currency') and invoice.currency:
return invoice.currency
return getattr(line, 'currency', None)
@classmethod
def _round_amount(cls, line, amount):
currency = cls._get_line_currency(line)
if currency:
return currency.round(amount)
return amount
@classmethod
def _get_base_line_amount(cls, line):
if line.type != 'line':
return Decimal('0.0')
amount = None
if hasattr(line, 'on_change_with_amount'):
amount = line.on_change_with_amount()
if amount is None:
amount = getattr(line, 'amount', None)
return amount or Decimal('0.0')
@classmethod
def _compute_tax_kind_amount(cls, line, tax_kind):
if line.type != 'line':
return Decimal('0.0')
taxes = getattr(line, 'taxes', None)
if not taxes:
return Decimal('0.0')
base = cls._get_base_line_amount(line)
total = Decimal('0.0')
for tax in taxes:
if getattr(tax, 'tax_kind', None) == tax_kind:
rate = getattr(tax, 'rate', None) or Decimal('0.0')
total += base * Decimal(str(rate))
return cls._round_amount(line, abs(total))
@classmethod
def _compute_line_total_amount(cls, line):
if line.type == 'line':
base = cls._get_base_line_amount(line)
tax_total = Decimal('0.0')
for tax in (getattr(line, 'taxes', None) or []):
rate = getattr(tax, 'rate', None) or Decimal('0.0')
tax_total += base * Decimal(str(rate))
return cls._round_amount(line, base + tax_total)
if line.type == 'subtotal':
subtotal = Decimal('0.0')
invoice = getattr(line, 'invoice', None)
if not invoice or not hasattr(invoice, 'lines'):
return subtotal
for line2 in invoice.lines:
if line2.type == 'line':
subtotal += cls._compute_line_total_amount(line2)
elif line2.type == 'subtotal':
if line == line2:
break
subtotal = Decimal('0.0')
return cls._round_amount(line, subtotal)
return Decimal('0.0')
@classmethod
def get_itbis_invoiced_line(cls, lines, name):
return {l.id: cls._compute_tax_kind_amount(l, 'itbis') for l in lines}
@classmethod
def get_itbis_line(cls, lines, name):
return cls.get_itbis_invoiced_line(lines, name)
@classmethod
def get_itbis_withholding_line(cls, lines, name):
return {l.id: cls._compute_tax_kind_amount(l, 'itbis_withholding') for l in lines}
@classmethod
def get_isc_line(cls, lines, name):
return {l.id: cls._compute_tax_kind_amount(l, 'isc') for l in lines}
@classmethod
def get_cdt_line(cls, lines, name):
return {l.id: cls._compute_tax_kind_amount(l, 'cdt') for l in lines}
@classmethod
def get_isr_withholding_line(cls, lines, name):
return {l.id: cls._compute_tax_kind_amount(l, 'isr_withholding') for l in lines}
@classmethod
def get_tip_line(cls, lines, name):
return {l.id: cls._compute_tax_kind_amount(l, 'tip') for l in lines}
@classmethod
def get_line_total(cls, lines, name):
return {l.id: cls._compute_line_total_amount(l) for l in lines}
@fields.depends(*_LINE_DEPENDS, methods=['on_change_with_amount'])
def on_change_with_itbis_invoiced_line(self, name=None):
return InvoiceLine._compute_tax_kind_amount(self, 'itbis')
@fields.depends(*_LINE_DEPENDS, methods=['on_change_with_amount'])
def on_change_with_itbis_line(self, name=None):
return InvoiceLine._compute_tax_kind_amount(self, 'itbis')
@fields.depends(*_LINE_DEPENDS, methods=['on_change_with_amount'])
def on_change_with_itbis_withholding_line(self, name=None):
return InvoiceLine._compute_tax_kind_amount(self, 'itbis_withholding')
@fields.depends(*_LINE_DEPENDS, methods=['on_change_with_amount'])
def on_change_with_isc_line(self, name=None):
return InvoiceLine._compute_tax_kind_amount(self, 'isc')
@fields.depends(*_LINE_DEPENDS, methods=['on_change_with_amount'])
def on_change_with_cdt_line(self, name=None):
return InvoiceLine._compute_tax_kind_amount(self, 'cdt')
@fields.depends(*_LINE_DEPENDS, methods=['on_change_with_amount'])
def on_change_with_isr_withholding_line(self, name=None):
return InvoiceLine._compute_tax_kind_amount(self, 'isr_withholding')
@fields.depends(*_LINE_DEPENDS, methods=['on_change_with_amount'])
def on_change_with_tip_line(self, name=None):
return InvoiceLine._compute_tax_kind_amount(self, 'tip')
@fields.depends(*_LINE_DEPENDS, methods=['on_change_with_amount'])
def on_change_with_line_total(self, name=None):
return InvoiceLine._compute_line_total_amount(self)