Files
tryton/invoice.py
T

2799 lines
104 KiB
Python
Raw Normal View History

2014-12-02 12:54:39 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2010-08-19 22:02:49 +02:00
from decimal import Decimal
2016-03-04 00:23:47 +01:00
from collections import defaultdict, namedtuple
from itertools import combinations
2018-08-09 20:43:51 +02:00
from sql import Null
from sql.aggregate import Sum
from sql.conditionals import Coalesce, Case
from sql.functions import Round
2013-09-06 22:33:52 +02:00
2016-09-13 14:03:55 +02:00
from trytond.model import Workflow, ModelView, ModelSQL, fields, Check, \
2018-09-01 11:21:06 +02:00
sequence_ordered, Unique, DeactivableMixin
2008-04-14 14:37:04 +02:00
from trytond.report import Report
2012-01-02 17:57:03 +01:00
from trytond.wizard import Wizard, StateView, StateTransition, StateAction, \
2017-09-03 11:17:09 +02:00
Button
2013-09-06 22:33:52 +02:00
from trytond import backend
2016-07-18 14:03:30 +02:00
from trytond.pyson import If, Eval, Bool
2014-07-17 18:57:29 +02:00
from trytond.tools import reduce_ids, grouped_slice
2010-08-19 22:02:49 +02:00
from trytond.transaction import Transaction
2011-07-03 13:18:43 +02:00
from trytond.pool import Pool
2012-09-24 22:17:32 +02:00
from trytond.rpc import RPC
from trytond.config import config
2012-09-24 22:17:32 +02:00
2014-12-19 19:41:49 +01:00
from trytond.modules.account.tax import TaxableMixin
2015-01-06 23:30:09 +01:00
from trytond.modules.product import price_digits
2014-12-19 19:41:49 +01:00
2012-09-24 22:17:32 +02:00
__all__ = ['Invoice', 'InvoicePaymentLine', 'InvoiceLine',
2018-09-01 11:21:06 +02:00
'InvoiceLineTax', 'InvoiceTax', 'PaymentMethod',
2017-09-03 11:17:09 +02:00
'InvoiceReport',
2012-09-24 22:17:32 +02:00
'PayInvoiceStart', 'PayInvoiceAsk', 'PayInvoice',
'CreditInvoiceStart', 'CreditInvoice']
2008-04-10 17:21:44 +02:00
_STATES = {
2011-08-21 11:20:30 +02:00
'readonly': Eval('state') != 'draft',
2008-04-10 17:21:44 +02:00
}
2011-08-04 16:57:16 +02:00
_DEPENDS = ['state']
2008-04-10 17:21:44 +02:00
2008-11-28 18:29:40 +01:00
_TYPE = [
2016-03-20 22:58:13 +01:00
('out', 'Customer'),
('in', 'Supplier'),
2008-11-28 18:29:40 +01:00
]
2008-04-10 17:21:44 +02:00
_TYPE2JOURNAL = {
2016-03-20 22:58:13 +01:00
'out': 'revenue',
'in': 'expense',
2008-04-10 17:21:44 +02:00
}
2010-01-20 15:25:32 +01:00
_ZERO = Decimal('0.0')
STATES = [
('draft', 'Draft'),
('validated', 'Validated'),
('posted', 'Posted'),
('paid', 'Paid'),
('cancel', 'Canceled'),
]
2010-01-20 15:25:32 +01:00
if config.getboolean('account_invoice', 'filestore', default=False):
file_id = 'invoice_report_cache_id'
store_prefix = config.get('account_invoice', 'store_prefix', default=None)
else:
file_id = None
store_prefix = None
2008-04-10 17:21:44 +02:00
2014-12-19 19:41:49 +01:00
class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
2008-04-10 17:21:44 +02:00
'Invoice'
2012-09-24 22:17:32 +02:00
__name__ = 'account.invoice'
2008-12-18 18:15:58 +01:00
_order_name = 'number'
2008-04-10 17:21:44 +02:00
company = fields.Many2One('company.company', 'Company', required=True,
2012-03-05 10:52:57 +01:00
states=_STATES, select=True, domain=[
2011-08-21 11:20:30 +02:00
('id', If(Eval('context', {}).contains('company'), '=', '!='),
2013-10-09 18:25:10 +02:00
Eval('context', {}).get('company', -1)),
2011-08-04 16:57:16 +02:00
],
depends=_DEPENDS)
2016-10-31 17:29:23 +01:00
company_party = fields.Function(
fields.Many2One('party.party', "Company Party"),
'on_change_with_company_party')
tax_identifier = fields.Many2One(
'party.identifier', "Tax Identifier",
states=_STATES,
domain=[
('party', '=', Eval('company_party', -1)),
],
depends=_DEPENDS + ['company_party'])
2014-01-26 14:28:52 +01:00
type = fields.Selection(_TYPE, 'Type', select=True,
2011-08-04 16:57:16 +02:00
required=True, states={
2011-08-21 11:20:30 +02:00
'readonly': ((Eval('state') != 'draft')
| Eval('context', {}).get('type')
| (Eval('lines', [0]) & Eval('type'))),
}, depends=['state'])
2010-03-11 16:45:42 +01:00
type_name = fields.Function(fields.Char('Type'), 'get_type_name')
2012-03-05 10:52:57 +01:00
number = fields.Char('Number', size=None, readonly=True, select=True)
2011-08-04 16:57:16 +02:00
reference = fields.Char('Reference', size=None, states=_STATES,
depends=_DEPENDS)
description = fields.Char('Description', size=None, states=_STATES,
depends=_DEPENDS)
state = fields.Selection(STATES, 'State', readonly=True)
2009-07-18 14:27:14 +02:00
invoice_date = fields.Date('Invoice Date',
states={
2012-12-23 22:28:41 +01:00
'readonly': Eval('state').in_(['posted', 'paid', 'cancel']),
'required': Eval('state').in_(
2016-03-20 22:58:13 +01:00
If(Eval('type') == 'in',
2012-12-23 22:28:41 +01:00
['validated', 'posted', 'paid'],
['posted', 'paid'])),
2011-08-04 16:57:16 +02:00
},
depends=['state'])
accounting_date = fields.Date('Accounting Date', states=_STATES,
depends=_DEPENDS)
2011-12-22 22:04:29 +01:00
party = fields.Many2One('party.party', 'Party',
2014-01-26 14:28:52 +01:00
required=True, states=_STATES, depends=_DEPENDS)
2017-09-26 01:23:42 +02:00
party_tax_identifier = fields.Many2One(
'party.identifier', "Party Tax Identifier",
states=_STATES,
domain=[
('party', '=', Eval('party', -1)),
],
depends=_DEPENDS + ['party'])
2014-01-26 14:28:52 +01:00
party_lang = fields.Function(fields.Char('Party Language'),
'on_change_with_party_lang')
2008-10-22 20:08:25 +02:00
invoice_address = fields.Many2One('party.address', 'Invoice Address',
2011-08-04 16:57:16 +02:00
required=True, states=_STATES, depends=['state', 'party'],
domain=[('party', '=', Eval('party'))])
2008-05-09 17:26:41 +02:00
currency = fields.Many2One('currency.currency', 'Currency', required=True,
states=_STATES, depends=_DEPENDS)
2014-01-26 14:28:52 +01:00
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'on_change_with_currency_digits')
currency_date = fields.Function(fields.Date('Currency Date'),
'on_change_with_currency_date')
2008-04-10 17:21:44 +02:00
journal = fields.Many2One('account.journal', 'Journal', required=True,
2013-05-21 19:12:48 +02:00
states=_STATES, depends=_DEPENDS)
2014-12-03 16:13:16 +01:00
move = fields.Many2One('account.move', 'Move', readonly=True,
domain=[
('company', '=', Eval('company', -1)),
],
depends=['company'])
2012-12-23 22:28:41 +01:00
cancel_move = fields.Many2One('account.move', 'Cancel Move', readonly=True,
2014-12-03 16:13:16 +01:00
domain=[
('company', '=', Eval('company', -1)),
],
2012-12-23 22:28:41 +01:00
states={
2016-03-20 22:58:13 +01:00
'invisible': Eval('type') == 'out',
2014-12-03 16:13:16 +01:00
},
depends=['company'])
2008-04-10 17:21:44 +02:00
account = fields.Many2One('account.account', 'Account', required=True,
2014-12-03 16:13:16 +01:00
states=_STATES, depends=_DEPENDS + ['type', 'company'],
2010-01-20 10:57:08 +01:00
domain=[
2014-12-03 16:13:16 +01:00
('company', '=', Eval('company', -1)),
2016-03-20 22:58:13 +01:00
If(Eval('type') == 'out',
2012-03-30 14:19:39 +02:00
('kind', '=', 'receivable'),
('kind', '=', 'payable')),
2011-08-04 16:57:16 +02:00
])
2008-04-10 17:21:44 +02:00
payment_term = fields.Many2One('account.invoice.payment_term',
2016-05-17 09:49:24 +02:00
'Payment Term', states=_STATES, depends=_DEPENDS)
2008-04-10 17:21:44 +02:00
lines = fields.One2Many('account.invoice.line', 'invoice', 'Lines',
2014-12-03 16:13:16 +01:00
domain=[
('company', '=', Eval('company', -1)),
],
states={
'readonly': (Eval('state') != 'draft') | ~Eval('company'),
},
depends=['state', 'company'])
2008-04-10 17:21:44 +02:00
taxes = fields.One2Many('account.invoice.tax', 'invoice', 'Tax Lines',
2014-01-26 14:28:52 +01:00
states=_STATES, depends=_DEPENDS)
2011-08-04 16:57:16 +02:00
comment = fields.Text('Comment', states=_STATES, depends=_DEPENDS)
2013-09-19 17:33:27 +02:00
origins = fields.Function(fields.Char('Origins'), 'get_origins')
2010-03-11 16:45:42 +01:00
untaxed_amount = fields.Function(fields.Numeric('Untaxed',
2011-08-04 16:57:16 +02:00
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits']),
'get_amount', searcher='search_untaxed_amount')
2011-08-04 16:57:16 +02:00
tax_amount = fields.Function(fields.Numeric('Tax', digits=(16,
Eval('currency_digits', 2)), depends=['currency_digits']),
'get_amount', searcher='search_tax_amount')
2011-08-04 16:57:16 +02:00
total_amount = fields.Function(fields.Numeric('Total', digits=(16,
Eval('currency_digits', 2)), depends=['currency_digits']),
'get_amount', searcher='search_total_amount')
2017-12-20 19:25:26 +01:00
reconciled = fields.Function(fields.Date('Reconciled',
states={
'invisible': ~Eval('reconciled'),
}),
2010-03-11 16:45:42 +01:00
'get_reconciled')
lines_to_pay = fields.Function(fields.One2Many('account.move.line', None,
'Lines to Pay'), 'get_lines_to_pay')
2009-03-05 14:51:35 +01:00
payment_lines = fields.Many2Many('account.invoice-account.move.line',
2018-03-24 15:29:47 +01:00
'invoice', 'line', string='Payment Lines',
2014-12-03 16:13:16 +01:00
domain=[
2018-03-24 15:29:47 +01:00
('account', '=', Eval('account', -1)),
('party', 'in', [None, Eval('party', -1)]),
['OR',
('invoice_payment', '=', None),
('invoice_payment', '=', Eval('id', -1)),
],
If(Eval('type') == 'out',
If(Eval('total_amount', 0) >= 0,
('debit', '=', 0),
('credit', '=', 0)),
If(Eval('total_amount', 0) >= 0,
('credit', '=', 0),
('debit', '=', 0))),
2014-12-03 16:13:16 +01:00
],
2013-09-10 11:38:13 +02:00
states={
2018-03-24 15:29:47 +01:00
'invisible': Eval('state') == 'paid',
'readonly': Eval('state') != 'posted',
2013-09-10 11:38:13 +02:00
},
2018-03-24 15:29:47 +01:00
depends=['state', 'account', 'party', 'id', 'type', 'total_amount'])
2010-03-11 16:45:42 +01:00
amount_to_pay_today = fields.Function(fields.Numeric('Amount to Pay Today',
2011-08-04 16:57:16 +02:00
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits']), 'get_amount_to_pay')
2010-03-11 16:45:42 +01:00
amount_to_pay = fields.Function(fields.Numeric('Amount to Pay',
2011-08-04 16:57:16 +02:00
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits']), 'get_amount_to_pay')
invoice_report_cache = fields.Binary('Invoice Report', readonly=True,
file_id=file_id, store_prefix=store_prefix)
invoice_report_cache_id = fields.Char('Invoice Report ID', readonly=True)
invoice_report_format = fields.Char('Invoice Report Format', readonly=True)
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def __setup__(cls):
super(Invoice, cls).__setup__()
2012-12-23 22:28:41 +01:00
cls._check_modify_exclude = ['state', 'payment_lines', 'cancel_move',
2011-09-17 14:43:48 +02:00
'invoice_report_cache', 'invoice_report_format']
cls._order = [
('number', 'DESC'),
('id', 'DESC'),
]
2016-10-31 17:29:23 +01:00
cls.tax_identifier.domain = [
cls.tax_identifier.domain,
('type', 'in', cls._tax_identifier_types()),
]
2012-09-24 22:17:32 +02:00
cls._error_messages.update({
'missing_tax_line': ('Invoice "%s" has taxes defined but not '
'on invoice lines.\nRe-compute the invoice.'),
'diff_tax_line': ('Invoice "%s" tax bases are different from '
'invoice lines.\nRe-compute the invoice.'),
2013-10-05 12:16:31 +02:00
'missing_tax_line2': (
'Invoice "%s" has taxes on invoice lines '
'that are not in the invoice.\nRe-compute the invoice.'),
2017-03-30 09:45:15 +02:00
'no_invoice_sequence': ('Missing invoice sequence for '
'invoice "%(invoice)s" on fiscalyear "%(fiscalyear)s".'),
'modify_invoice': ('You can not modify invoice "%s" because '
'it is posted, paid or cancelled.'),
'same_account_on_line': ('Invoice "%(invoice)s" uses the same '
'account "%(account)s" for the invoice and in line '
'"%(line)s".'),
'delete_cancel': ('Invoice "%s" must be cancelled before '
'deletion.'),
'delete_numbered': ('The numbered invoice "%s" can not be '
'deleted.'),
2013-10-05 12:16:31 +02:00
'customer_invoice_cancel_move': (
'Customer invoice/credit note '
'"%s" can not be cancelled once posted.'),
2018-03-24 15:29:47 +01:00
'payment_lines_greater_amount': (
'The payment lines on invoice "%(invoice)s" can not be '
'greater than the invoice amount.'),
'modify_payment_lines_invoice_paid': (
'Payment lines can not be modified '
'on a paid invoice "%(invoice)s"'),
2013-02-16 22:06:26 +01:00
})
2012-09-24 22:17:32 +02:00
cls._transitions |= set((
2012-12-23 22:28:41 +01:00
('draft', 'validated'),
('validated', 'posted'),
('draft', 'posted'),
('posted', 'paid'),
('validated', 'draft'),
('paid', 'posted'),
2012-03-25 00:17:27 +01:00
('draft', 'cancel'),
2012-12-23 22:28:41 +01:00
('validated', 'cancel'),
('posted', 'cancel'),
2012-04-03 12:20:28 +02:00
('cancel', 'draft'),
2012-03-25 00:17:27 +01:00
))
2012-09-24 22:17:32 +02:00
cls._buttons.update({
2012-03-25 00:17:27 +01:00
'cancel': {
2012-12-23 22:28:41 +01:00
'invisible': (~Eval('state').in_(['draft', 'validated'])
& ~((Eval('state') == 'posted')
2016-03-20 22:58:13 +01:00
& (Eval('type') == 'in'))),
2017-03-30 09:45:15 +02:00
'help': 'Cancel the invoice',
2018-02-19 11:37:06 +01:00
'depends': ['state', 'type'],
2012-03-25 00:17:27 +01:00
},
'draft': {
2012-12-23 22:28:41 +01:00
'invisible': (~Eval('state').in_(['cancel', 'validated'])
| ((Eval('state') == 'cancel')
& Eval('cancel_move', -1))),
2018-08-19 20:02:58 +02:00
'icon': If(Eval('state') == 'cancel', 'tryton-undo',
'tryton-back'),
'depends': ['state'],
2012-03-25 00:17:27 +01:00
},
'validate_invoice': {
'pre_validate':
['OR',
('invoice_date', '!=', None),
2016-03-20 22:58:13 +01:00
('type', '!=', 'in'),
],
2012-03-25 00:17:27 +01:00
'invisible': Eval('state') != 'draft',
2018-02-19 11:37:06 +01:00
'depends': ['state'],
2012-03-25 00:17:27 +01:00
},
2012-12-23 22:28:41 +01:00
'post': {
'pre_validate':
['OR',
('invoice_date', '!=', None),
2016-03-20 22:58:13 +01:00
('type', '!=', 'in'),
],
2012-12-23 22:28:41 +01:00
'invisible': ~Eval('state').in_(['draft', 'validated']),
2018-02-19 11:37:06 +01:00
'depends': ['state'],
2012-03-25 00:17:27 +01:00
},
2012-09-10 15:27:42 +02:00
'pay': {
2012-12-23 22:28:41 +01:00
'invisible': Eval('state') != 'posted',
2018-02-19 11:37:06 +01:00
'depends': ['state'],
2012-09-10 15:27:42 +02:00
},
2012-03-25 00:17:27 +01:00
})
2018-08-26 18:06:56 +02:00
cls.__rpc__.update({
'post': RPC(
readonly=False, instantiate=0, fresh_session=True),
})
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def __register__(cls, module_name):
2016-03-20 22:58:13 +01:00
pool = Pool()
Line = pool.get('account.invoice.line')
Tax = pool.get('account.invoice.tax')
2013-09-06 22:33:52 +02:00
sql_table = cls.__table__()
2016-03-20 22:58:13 +01:00
line = Line.__table__()
tax = Tax.__table__()
2012-09-24 22:17:32 +02:00
super(Invoice, cls).__register__(module_name)
transaction = Transaction()
cursor = transaction.connection.cursor()
2018-07-24 22:01:07 +02:00
table = cls.__table_handler__(module_name)
2009-07-18 14:27:14 +02:00
2016-03-20 22:58:13 +01:00
# Migration from 3.8: remove invoice/credit note type
cursor.execute(*sql_table.select(sql_table.id,
where=sql_table.type.like('%_invoice')
| sql_table.type.like('%_credit_note'),
limit=1))
if cursor.fetchone():
for type_ in ['out', 'in']:
cursor.execute(*sql_table.update(
columns=[sql_table.type],
values=[type_],
where=sql_table.type == '%s_invoice' % type_))
cursor.execute(*line.update(
columns=[line.invoice_type],
values=[type_],
where=line.invoice_type == '%s_invoice' % type_))
cursor.execute(*line.update(
columns=[line.quantity, line.invoice_type],
values=[-line.quantity, type_],
where=(line.invoice_type == '%s_credit_note' % type_)
& (line.invoice == Null)
))
# Don't use UPDATE FROM because SQLite nor MySQL support it
cursor.execute(*line.update(
columns=[line.quantity, line.invoice_type],
values=[-line.quantity, type_],
where=line.invoice.in_(sql_table.select(sql_table.id,
where=(
sql_table.type == '%s_credit_note' % type_)
))))
cursor.execute(*tax.update(
columns=[tax.base, tax.amount, tax.base_sign],
values=[-tax.base, -tax.amount, -tax.base_sign],
where=tax.invoice.in_(sql_table.select(sql_table.id,
where=(
sql_table.type == '%s_credit_note' % type_)
))))
cursor.execute(*sql_table.update(
columns=[sql_table.type],
values=[type_],
where=sql_table.type == '%s_credit_note' % type_))
2016-05-17 09:49:24 +02:00
# Migration from 4.0: Drop not null on payment_term
table.not_null_action('payment_term', 'remove')
2018-08-09 20:43:51 +02:00
# Add index on create_date
table.index_action('create_date', action='add')
2012-09-24 22:17:32 +02:00
@staticmethod
def default_type():
2016-03-20 22:58:13 +01:00
return Transaction().context.get('type', 'out')
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@staticmethod
def default_state():
2008-04-10 17:21:44 +02:00
return 'draft'
2012-09-24 22:17:32 +02:00
@staticmethod
def default_currency():
Company = Pool().get('company.company')
2010-08-19 22:02:49 +02:00
if Transaction().context.get('company'):
2012-09-24 22:17:32 +02:00
company = Company(Transaction().context['company'])
return company.currency.id
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@staticmethod
def default_currency_digits():
Company = Pool().get('company.company')
2010-08-19 22:02:49 +02:00
if Transaction().context.get('company'):
2012-09-24 22:17:32 +02:00
company = Company(Transaction().context['company'])
return company.currency.digits
return 2
2012-09-24 22:17:32 +02:00
@staticmethod
def default_company():
2012-03-27 10:43:07 +02:00
return Transaction().context.get('company')
2008-04-10 17:21:44 +02:00
2016-10-31 17:29:23 +01:00
@fields.depends('company')
def on_change_with_company_party(self, name=None):
if self.company:
return self.company.party.id
2012-09-24 22:17:32 +02:00
@classmethod
def default_payment_term(cls):
PaymentTerm = Pool().get('account.invoice.payment_term')
payment_terms = PaymentTerm.search(cls.payment_term.domain)
if len(payment_terms) == 1:
return payment_terms[0].id
2018-08-31 14:58:05 +02:00
@fields.depends('party', 'type', 'accounting_date', 'invoice_date')
def _get_account_payment_term(self):
'''
Return default account and payment term
'''
2014-10-29 20:39:13 +01:00
self.account = None
2012-09-24 22:17:32 +02:00
if self.party:
2018-08-31 14:58:05 +02:00
with Transaction().set_context(
date=self.accounting_date or self.invoice_date):
if self.type == 'out':
self.account = self.party.account_receivable_used
if self.party.customer_payment_term:
self.payment_term = self.party.customer_payment_term
elif self.type == 'in':
self.account = self.party.account_payable_used
if self.party.supplier_payment_term:
self.payment_term = self.party.supplier_payment_term
2018-08-31 14:58:05 +02:00
@fields.depends('type', methods=['_get_account_payment_term'])
2012-09-24 22:17:32 +02:00
def on_change_type(self):
Journal = Pool().get('account.journal')
journals = Journal.search([
2016-03-20 22:58:13 +01:00
('type', '=', _TYPE2JOURNAL.get(self.type or 'out',
2012-09-24 22:17:32 +02:00
'revenue')),
], limit=1)
if journals:
2014-10-29 20:39:13 +01:00
self.journal, = journals
2018-08-31 14:58:05 +02:00
self._get_account_payment_term()
2008-04-10 17:21:44 +02:00
2018-08-31 14:58:05 +02:00
@fields.depends('party', methods=['_get_account_payment_term'])
2012-09-24 22:17:32 +02:00
def on_change_party(self):
2014-10-29 20:39:13 +01:00
self.invoice_address = None
2018-08-31 14:58:05 +02:00
self._get_account_payment_term()
2012-09-24 22:17:32 +02:00
if self.party:
2014-10-29 20:39:13 +01:00
self.invoice_address = self.party.address_get(type='invoice')
2017-09-26 01:23:42 +02:00
self.party_tax_identifier = self.party.tax_identifier
2008-04-10 17:21:44 +02:00
2014-01-26 14:28:52 +01:00
@fields.depends('currency')
2012-09-24 22:17:32 +02:00
def on_change_with_currency_digits(self, name=None):
if self.currency:
return self.currency.digits
2008-07-28 20:16:08 +02:00
return 2
2014-01-26 14:28:52 +01:00
@fields.depends('invoice_date')
2012-09-24 22:17:32 +02:00
def on_change_with_currency_date(self, name=None):
Date = Pool().get('ir.date')
return self.invoice_date or Date.today()
2008-07-28 20:16:08 +02:00
2014-01-26 14:28:52 +01:00
@fields.depends('party')
2012-09-24 22:17:32 +02:00
def on_change_with_party_lang(self, name=None):
2012-11-23 18:00:20 +01:00
Config = Pool().get('ir.configuration')
2012-09-24 22:17:32 +02:00
if self.party:
if self.party.lang:
return self.party.lang.code
2012-11-23 18:00:20 +01:00
return Config.get_language()
2009-01-20 00:04:31 +01:00
2012-09-24 22:17:32 +02:00
@classmethod
def get_type_name(self, invoices, name):
type_names = {}
2008-04-14 14:37:04 +02:00
type2name = {}
2010-08-19 22:02:49 +02:00
for type, name in self.fields_get(fields_names=['type']
)['type']['selection']:
2008-04-14 14:37:04 +02:00
type2name[type] = name
2012-09-24 22:17:32 +02:00
for invoice in invoices:
type_names[invoice.id] = type2name[invoice.type]
return type_names
2008-04-14 14:37:04 +02:00
2014-01-31 22:31:31 +01:00
@fields.depends('lines', 'taxes', 'currency', 'party', 'type',
'accounting_date', 'invoice_date')
2012-09-24 22:17:32 +02:00
def on_change_lines(self):
2015-03-16 17:58:43 +01:00
self._on_change_lines_taxes()
2008-08-04 19:17:48 +02:00
2014-01-31 22:31:31 +01:00
@fields.depends('lines', 'taxes', 'currency', 'party', 'type',
'accounting_date', 'invoice_date')
2012-09-24 22:17:32 +02:00
def on_change_taxes(self):
2015-03-16 17:58:43 +01:00
self._on_change_lines_taxes()
2008-08-04 19:17:48 +02:00
2012-09-24 22:17:32 +02:00
def _on_change_lines_taxes(self):
pool = Pool()
2013-02-23 17:40:40 +01:00
InvoiceTax = pool.get('account.invoice.tax')
2014-09-17 22:19:37 +02:00
2014-10-29 20:39:13 +01:00
self.untaxed_amount = Decimal('0.0')
self.tax_amount = Decimal('0.0')
self.total_amount = Decimal('0.0')
2008-08-04 19:17:48 +02:00
computed_taxes = {}
2014-09-17 22:19:37 +02:00
2012-09-24 22:17:32 +02:00
if self.lines:
for line in self.lines:
2014-10-29 20:39:13 +01:00
self.untaxed_amount += getattr(line, 'amount', None) or 0
2014-12-19 19:41:49 +01:00
computed_taxes = self._get_taxes()
2014-09-17 22:19:37 +02:00
2014-10-29 20:39:13 +01:00
def is_zero(amount):
if self.currency:
return self.currency.is_zero(amount)
else:
return amount != Decimal('0.0')
2008-08-04 19:17:48 +02:00
tax_keys = []
2014-10-29 20:39:13 +01:00
taxes = list(self.taxes or [])
2012-09-24 22:17:32 +02:00
for tax in (self.taxes or []):
if tax.manual:
2014-10-29 20:39:13 +01:00
self.tax_amount += tax.amount or Decimal('0.0')
2008-08-04 19:17:48 +02:00
continue
2018-03-25 23:34:44 +02:00
tax_id = tax.tax.id if tax.tax else None
key = (tax.account.id, tax_id)
if (key not in computed_taxes) or (key in tax_keys):
2014-10-29 20:39:13 +01:00
taxes.remove(tax)
2008-08-04 19:17:48 +02:00
continue
tax_keys.append(key)
2014-10-29 20:39:13 +01:00
if not is_zero(computed_taxes[key]['base']
- (tax.base or Decimal('0.0'))):
self.tax_amount += computed_taxes[key]['amount']
tax.amount = computed_taxes[key]['amount']
tax.base = computed_taxes[key]['base']
2008-08-04 19:17:48 +02:00
else:
2014-10-29 20:39:13 +01:00
self.tax_amount += tax.amount or Decimal('0.0')
2008-08-04 19:17:48 +02:00
for key in computed_taxes:
if key not in tax_keys:
2014-10-29 20:39:13 +01:00
self.tax_amount += computed_taxes[key]['amount']
2018-06-26 19:11:42 +02:00
value = InvoiceTax.default_get(list(InvoiceTax._fields.keys()))
value.update(computed_taxes[key])
invoice_tax = InvoiceTax(**value)
if invoice_tax.tax:
invoice_tax.sequence = invoice_tax.tax.sequence
taxes.append(invoice_tax)
2014-10-29 20:39:13 +01:00
self.taxes = taxes
2012-09-24 22:17:32 +02:00
if self.currency:
2014-10-29 20:39:13 +01:00
self.untaxed_amount = self.currency.round(self.untaxed_amount)
self.tax_amount = self.currency.round(self.tax_amount)
self.total_amount = self.untaxed_amount + self.tax_amount
2012-09-24 22:17:32 +02:00
if self.currency:
2014-10-29 20:39:13 +01:00
self.total_amount = self.currency.round(self.total_amount)
2008-08-04 19:17:48 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def get_amount(cls, invoices, names):
pool = Pool()
InvoiceTax = pool.get('account.invoice.tax')
Move = pool.get('account.move')
MoveLine = pool.get('account.move.line')
cursor = Transaction().connection.cursor()
untaxed_amount = dict((i.id, _ZERO) for i in invoices)
tax_amount = dict((i.id, _ZERO) for i in invoices)
total_amount = dict((i.id, _ZERO) for i in invoices)
2013-09-06 22:33:52 +02:00
type_name = cls.tax_amount._field.sql_type().base
tax = InvoiceTax.__table__()
2014-01-31 00:03:05 +01:00
to_round = False
2014-07-17 18:57:29 +02:00
for sub_ids in grouped_slice(invoices):
2013-09-06 22:33:52 +02:00
red_sql = reduce_ids(tax.invoice, sub_ids)
cursor.execute(*tax.select(tax.invoice,
Coalesce(Sum(tax.amount), 0).as_(type_name),
where=red_sql,
group_by=tax.invoice))
for invoice_id, sum_ in cursor.fetchall():
# SQLite uses float for SUM
if not isinstance(sum_, Decimal):
sum_ = Decimal(str(sum_))
2014-01-31 00:03:05 +01:00
to_round = True
tax_amount[invoice_id] = sum_
2014-01-31 00:03:05 +01:00
# Float amount must be rounded to get the right precision
if to_round:
for invoice in invoices:
tax_amount[invoice.id] = invoice.currency.round(
tax_amount[invoice.id])
2008-04-10 17:21:44 +02:00
invoices_move = set()
invoices_no_move = set()
2012-09-24 22:17:32 +02:00
for invoice in invoices:
if invoice.move:
invoices_move.add(invoice.id)
else:
invoices_no_move.add(invoice.id)
2013-12-18 23:23:20 +01:00
invoices_move = cls.browse(invoices_move)
invoices_no_move = cls.browse(invoices_no_move)
2008-04-10 17:21:44 +02:00
2013-09-06 22:33:52 +02:00
type_name = cls.total_amount._field.sql_type().base
invoice = cls.__table__()
move = Move.__table__()
line = MoveLine.__table__()
2014-01-31 00:03:05 +01:00
to_round = False
2014-07-17 18:57:29 +02:00
for sub_ids in grouped_slice(invoices_move):
2013-09-06 22:33:52 +02:00
red_sql = reduce_ids(invoice.id, sub_ids)
cursor.execute(*invoice.join(move,
condition=invoice.move == move.id
).join(line, condition=move.id == line.move
).select(invoice.id,
Coalesce(Sum(
Case((line.second_currency == invoice.currency,
line.amount_second_currency),
else_=line.debit - line.credit)),
0).cast(type_name),
2013-09-06 22:33:52 +02:00
where=(invoice.account == line.account) & red_sql,
group_by=invoice.id))
for invoice_id, sum_ in cursor.fetchall():
# SQLite uses float for SUM
if not isinstance(sum_, Decimal):
sum_ = Decimal(str(sum_))
2014-01-31 00:03:05 +01:00
to_round = True
total_amount[invoice_id] = sum_
for invoice in invoices_move:
2016-03-20 22:58:13 +01:00
if invoice.type == 'in':
total_amount[invoice.id] *= -1
2014-01-31 00:03:05 +01:00
# Float amount must be rounded to get the right precision
if to_round:
total_amount[invoice.id] = invoice.currency.round(
total_amount[invoice.id])
untaxed_amount[invoice.id] = (
total_amount[invoice.id] - tax_amount[invoice.id])
for invoice in invoices_no_move:
untaxed_amount[invoice.id] = sum(
(line.amount for line in invoice.lines
if line.type == 'line'), _ZERO)
total_amount[invoice.id] = (
untaxed_amount[invoice.id] + tax_amount[invoice.id])
result = {
'untaxed_amount': untaxed_amount,
'tax_amount': tax_amount,
'total_amount': total_amount,
}
2018-06-26 19:11:42 +02:00
for key in list(result.keys()):
if key not in names:
del result[key]
return result
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
def get_reconciled(self, name):
2017-12-20 19:25:26 +01:00
reconciliations = [l.reconciliation for l in self.lines_to_pay]
if not reconciliations:
return None
elif not all(reconciliations):
return None
else:
return max(r.date for r in reconciliations)
2008-04-10 17:21:44 +02:00
2014-06-06 13:00:19 +02:00
@classmethod
def get_lines_to_pay(cls, invoices, name):
pool = Pool()
MoveLine = pool.get('account.move.line')
line = MoveLine.__table__()
invoice = cls.__table__()
cursor = Transaction().connection.cursor()
2014-06-06 13:00:19 +02:00
lines = defaultdict(list)
2014-11-06 16:46:07 +01:00
for sub_ids in grouped_slice(invoices):
2014-06-06 13:00:19 +02:00
red_sql = reduce_ids(invoice.id, sub_ids)
cursor.execute(*invoice.join(line,
condition=((invoice.move == line.move)
& (invoice.account == line.account))).select(
invoice.id, line.id,
where=(line.maturity_date != Null) & red_sql,
2014-06-06 13:00:19 +02:00
order_by=(invoice.id, line.maturity_date)))
for invoice_id, line_id in cursor.fetchall():
lines[invoice_id].append(line_id)
return lines
2012-09-24 22:17:32 +02:00
@classmethod
def get_amount_to_pay(cls, invoices, name):
pool = Pool()
Currency = pool.get('currency.currency')
Date = pool.get('ir.date')
2008-10-08 12:18:05 +02:00
2012-09-24 22:17:32 +02:00
today = Date.today()
res = dict((x.id, _ZERO) for x in invoices)
2013-06-26 22:46:51 +02:00
for invoice in invoices:
if invoice.state != 'posted':
continue
2010-01-20 15:25:32 +01:00
amount = _ZERO
amount_currency = _ZERO
for line in invoice.lines_to_pay:
if line.reconciliation:
continue
2012-09-24 22:17:32 +02:00
if (name == 'amount_to_pay_today'
and line.maturity_date > today):
continue
2012-09-24 22:17:32 +02:00
if (line.second_currency
and line.second_currency == invoice.currency):
amount_currency += line.amount_second_currency
else:
amount += line.debit - line.credit
for line in invoice.payment_lines:
if line.reconciliation:
continue
2012-09-24 22:17:32 +02:00
if (line.second_currency
and line.second_currency == invoice.currency):
amount_currency += line.amount_second_currency
else:
amount += line.debit - line.credit
2010-01-20 15:25:32 +01:00
if amount != _ZERO:
with Transaction().set_context(date=invoice.currency_date):
2012-09-24 22:17:32 +02:00
amount_currency += Currency.compute(
invoice.company.currency, amount, invoice.currency)
2016-03-20 22:58:13 +01:00
if invoice.type == 'in':
amount_currency *= -1
res[invoice.id] = amount_currency
2008-04-10 17:21:44 +02:00
return res
2012-09-24 22:17:32 +02:00
@classmethod
def search_total_amount(cls, name, clause):
2011-07-03 13:18:43 +02:00
pool = Pool()
2012-09-24 22:17:32 +02:00
Rule = pool.get('ir.rule')
Line = pool.get('account.invoice.line')
Tax = pool.get('account.invoice.tax')
Invoice = pool.get('account.invoice')
Currency = pool.get('currency.currency')
2013-09-06 22:33:52 +02:00
type_name = cls.total_amount._field.sql_type().base
line = Line.__table__()
invoice = Invoice.__table__()
currency = Currency.__table__()
2013-09-06 22:33:52 +02:00
tax = Tax.__table__()
_, operator, value = clause
2015-06-15 01:15:46 +02:00
invoice_query = Rule.query_get('account.invoice')
Operator = fields.SQL_OPERATORS[operator]
# SQLite uses float for sum
if backend.name() == 'sqlite':
value = float(value)
2009-03-23 21:01:52 +01:00
union = (line.join(invoice, condition=(invoice.id == line.invoice)
).join(currency, condition=(currency.id == invoice.currency)
).select(line.invoice.as_('invoice'),
Coalesce(Sum(Round((line.quantity * line.unit_price).cast(
type_name),
currency.digits)), 0).as_('total_amount'),
2013-09-06 22:33:52 +02:00
where=line.invoice.in_(invoice_query),
group_by=line.invoice)
| tax.select(tax.invoice.as_('invoice'),
Coalesce(Sum(tax.amount), 0).as_('total_amount'),
where=tax.invoice.in_(invoice_query),
group_by=tax.invoice))
query = union.select(union.invoice, group_by=union.invoice,
having=Operator(Sum(union.total_amount).cast(type_name),
value))
2013-09-06 22:33:52 +02:00
return [('id', 'in', query)]
2012-09-24 22:17:32 +02:00
@classmethod
def search_untaxed_amount(cls, name, clause):
2011-07-03 13:18:43 +02:00
pool = Pool()
2012-09-24 22:17:32 +02:00
Rule = pool.get('ir.rule')
Line = pool.get('account.invoice.line')
Invoice = pool.get('account.invoice')
Currency = pool.get('currency.currency')
2013-09-06 22:33:52 +02:00
type_name = cls.untaxed_amount._field.sql_type().base
line = Line.__table__()
invoice = Invoice.__table__()
currency = Currency.__table__()
_, operator, value = clause
2015-06-15 01:15:46 +02:00
invoice_query = Rule.query_get('account.invoice')
Operator = fields.SQL_OPERATORS[operator]
# SQLite uses float for sum
if backend.name() == 'sqlite':
value = float(value)
query = line.join(invoice,
condition=(invoice.id == line.invoice)
).join(currency,
condition=(currency.id == invoice.currency)
).select(line.invoice,
where=line.invoice.in_(invoice_query),
group_by=line.invoice,
having=Operator(Coalesce(Sum(
Round((line.quantity * line.unit_price).cast(
type_name),
currency.digits)), 0).cast(type_name),
value))
2013-09-06 22:33:52 +02:00
return [('id', 'in', query)]
2012-09-24 22:17:32 +02:00
@classmethod
def search_tax_amount(cls, name, clause):
2011-07-03 13:18:43 +02:00
pool = Pool()
2012-09-24 22:17:32 +02:00
Rule = pool.get('ir.rule')
Tax = pool.get('account.invoice.tax')
2013-09-06 22:33:52 +02:00
type_name = cls.tax_amount._field.sql_type().base
tax = Tax.__table__()
_, operator, value = clause
2015-06-15 01:15:46 +02:00
invoice_query = Rule.query_get('account.invoice')
Operator = fields.SQL_OPERATORS[operator]
# SQLite uses float for sum
if backend.name() == 'sqlite':
value = float(value)
2013-09-06 22:33:52 +02:00
query = tax.select(tax.invoice,
where=tax.invoice.in_(invoice_query),
group_by=tax.invoice,
having=Operator(Coalesce(Sum(tax.amount), 0).cast(type_name),
value))
2013-09-06 22:33:52 +02:00
return [('id', 'in', query)]
2009-03-23 21:01:52 +01:00
2014-12-19 19:41:49 +01:00
@property
def taxable_lines(self):
taxable_lines = []
# In case we're called from an on_change we have to use some sensible
# defaults
for line in self.lines:
if getattr(line, 'type', None) != 'line':
continue
taxable_lines.append(tuple())
for attribute, default_value in [
('taxes', []),
('unit_price', Decimal(0)),
('quantity', 0.),
]:
value = getattr(line, attribute, None)
taxable_lines[-1] += (
value if value is not None else default_value,)
return taxable_lines
2014-12-19 19:41:49 +01:00
@property
def tax_date(self):
return self.accounting_date or self.invoice_date
def _get_tax_context(self):
2012-09-24 22:17:32 +02:00
context = {}
if self.party and self.party.lang:
context['language'] = self.party.lang.code
return context
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
def _compute_taxes(self):
2014-12-19 19:41:49 +01:00
taxes = self._get_taxes()
2018-06-26 19:11:42 +02:00
for tax in taxes.values():
2014-12-19 19:41:49 +01:00
tax['invoice'] = self.id
2014-09-17 22:19:37 +02:00
return taxes
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def update_taxes(cls, invoices, exception=False):
Tax = Pool().get('account.invoice.tax')
to_create = []
to_delete = []
to_write = []
2012-09-24 22:17:32 +02:00
for invoice in invoices:
2012-12-23 22:28:41 +01:00
if invoice.state in ('posted', 'paid', 'cancel'):
continue
2012-09-24 22:17:32 +02:00
computed_taxes = invoice._compute_taxes()
2008-04-10 17:21:44 +02:00
if not invoice.taxes:
2018-06-26 19:11:42 +02:00
to_create.extend(computed_taxes.values())
2008-04-10 17:21:44 +02:00
else:
tax_keys = []
for tax in invoice.taxes:
if tax.manual:
continue
2012-09-24 22:17:32 +02:00
tax_id = tax.tax.id if tax.tax else None
2018-03-25 23:34:44 +02:00
key = (tax.account.id, tax_id)
2014-12-02 12:54:39 +01:00
if (key not in computed_taxes) or (key in tax_keys):
2008-04-10 17:21:44 +02:00
if exception:
cls.raise_user_error('missing_tax_line',
(invoice.rec_name,))
to_delete.append(tax)
2008-04-10 17:21:44 +02:00
continue
tax_keys.append(key)
2012-09-24 22:17:32 +02:00
if not invoice.currency.is_zero(
2008-04-10 17:21:44 +02:00
computed_taxes[key]['base'] - tax.base):
if exception:
cls.raise_user_error('diff_tax_line',
(invoice.rec_name,))
to_write.extend(([tax], computed_taxes[key]))
2008-04-10 17:21:44 +02:00
for key in computed_taxes:
2014-12-02 12:54:39 +01:00
if key not in tax_keys:
2008-04-10 17:21:44 +02:00
if exception:
cls.raise_user_error('missing_tax_line2',
(invoice.rec_name,))
to_create.append(computed_taxes[key])
if to_create:
Tax.create(to_create)
if to_delete:
Tax.delete(to_delete)
if to_write:
Tax.write(*to_write)
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
def _get_move_line(self, date, amount):
2008-04-10 17:21:44 +02:00
'''
Return move line
'''
pool = Pool()
Currency = pool.get('currency.currency')
MoveLine = pool.get('account.move.line')
line = MoveLine()
if self.currency != self.company.currency:
2012-09-24 22:17:32 +02:00
with Transaction().set_context(date=self.currency_date):
line.amount_second_currency = Currency.compute(
2013-01-19 17:11:54 +01:00
self.company.currency, amount, self.currency)
line.second_currency = self.currency
2008-04-10 17:21:44 +02:00
else:
line.amount_second_currency = None
line.second_currency = None
2016-03-20 22:58:13 +01:00
if amount <= 0:
line.debit, line.credit = -amount, 0
2008-04-10 17:21:44 +02:00
else:
line.debit, line.credit = 0, amount
if line.amount_second_currency:
line.amount_second_currency = (
line.amount_second_currency.copy_sign(
line.debit - line.credit))
line.account = self.account
if self.account.party_required:
line.party = self.party
line.maturity_date = date
line.description = self.description
return line
2008-04-10 17:21:44 +02:00
def get_move(self):
2012-03-25 00:17:27 +01:00
'''
Compute account move for the invoice and return the created move
2012-03-25 00:17:27 +01:00
'''
2011-07-03 13:18:43 +02:00
pool = Pool()
2012-09-24 22:17:32 +02:00
Move = pool.get('account.move')
Period = pool.get('account.period')
Date = pool.get('ir.date')
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
if self.move:
2012-12-23 22:28:41 +01:00
return self.move
2012-09-24 22:17:32 +02:00
self.update_taxes([self], exception=True)
move_lines = []
for line in self.lines:
move_lines += line.get_move_lines()
for tax in self.taxes:
move_lines += tax.get_move_lines()
2008-04-10 17:21:44 +02:00
total = Decimal('0.0')
total_currency = Decimal('0.0')
for line in move_lines:
total += line.debit - line.credit
if line.amount_second_currency:
total_currency += line.amount_second_currency
2008-04-10 17:21:44 +02:00
2016-05-17 09:49:24 +02:00
term_lines = [(Date.today(), total)]
if self.payment_term:
term_lines = self.payment_term.compute(
total, self.company.currency, self.invoice_date)
2008-04-10 17:21:44 +02:00
remainder_total_currency = total_currency
for date, amount in term_lines:
line = self._get_move_line(date, amount)
if line.amount_second_currency:
remainder_total_currency += line.amount_second_currency
move_lines.append(line)
2012-09-24 22:17:32 +02:00
if not self.currency.is_zero(remainder_total_currency):
move_lines[-1].amount_second_currency -= \
2012-04-03 20:06:19 +02:00
remainder_total_currency
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
accounting_date = self.accounting_date or self.invoice_date
period_id = Period.find(self.company.id, date=accounting_date)
2008-04-10 17:21:44 +02:00
move = Move()
move.journal = self.journal
move.period = period_id
move.date = accounting_date
move.origin = self
move.company = self.company
move.lines = move_lines
2012-09-24 22:17:32 +02:00
return move
2008-04-10 17:21:44 +02:00
@classmethod
def set_number(cls, invoices):
2012-03-25 00:17:27 +01:00
'''
Set number to the invoice
'''
2011-07-03 13:18:43 +02:00
pool = Pool()
2012-09-24 22:17:32 +02:00
Date = pool.get('ir.date')
2008-04-10 17:21:44 +02:00
for invoice in invoices:
2016-11-12 14:23:22 +01:00
# Posted and paid invoices are tested by check_modify so we can
# not modify tax_identifier nor number
if invoice.state in {'posted', 'paid'}:
continue
2016-10-31 17:29:23 +01:00
if not invoice.tax_identifier:
invoice.tax_identifier = invoice.get_tax_identifier()
2017-09-26 01:23:42 +02:00
# Generated invoice may not fill the party tax identifier
if not invoice.party_tax_identifier:
invoice.party_tax_identifier = invoice.party.tax_identifier
2016-10-31 17:29:23 +01:00
if invoice.number:
continue
2008-04-10 17:21:44 +02:00
2017-03-30 09:45:15 +02:00
if not invoice.invoice_date and invoice.type == 'out':
invoice.invoice_date = Date.today()
invoice.number = invoice.get_next_number()
cls.save(invoices)
2008-04-10 17:21:44 +02:00
2017-03-30 09:45:15 +02:00
def get_next_number(self, pattern=None):
pool = Pool()
Sequence = pool.get('ir.sequence.strict')
Period = pool.get('account.period')
if pattern is None:
pattern = {}
else:
pattern = pattern.copy()
period_id = Period.find(
self.company.id, date=self.accounting_date or self.invoice_date,
test_state=self.type != 'in')
period = Period(period_id)
fiscalyear = period.fiscalyear
pattern.setdefault('company', self.company.id)
pattern.setdefault('fiscalyear', fiscalyear.id)
pattern.setdefault('period', period.id)
invoice_type = self.type
if (all(l.amount < 0 for l in self.lines if l.product)
and self.total_amount < 0):
invoice_type += '_credit_note'
else:
invoice_type += '_invoice'
for invoice_sequence in fiscalyear.invoice_sequences:
if invoice_sequence.match(pattern):
sequence = getattr(
invoice_sequence, '%s_sequence' % invoice_type)
break
else:
self.raise_user_error('no_invoice_sequence', {
'invoice': self.rec_name,
'fiscalyear': fiscalyear.rec_name,
})
with Transaction().set_context(date=self.invoice_date):
return Sequence.get_id(sequence.id)
2016-10-31 17:29:23 +01:00
@classmethod
def _tax_identifier_types(cls):
return ['eu_vat']
def get_tax_identifier(self):
"Return the default computed tax identifier"
types = self._tax_identifier_types()
for identifier in self.company.party.identifiers:
if identifier.type in types:
return identifier.id
2012-09-24 22:17:32 +02:00
@classmethod
def check_modify(cls, invoices):
2008-04-10 17:21:44 +02:00
'''
Check if the invoices can be modified
'''
2012-09-24 22:17:32 +02:00
for invoice in invoices:
2012-12-23 22:28:41 +01:00
if (invoice.state in ('posted', 'paid')
or (invoice.state == 'cancel'
and invoice.cancel_move)):
cls.raise_user_error('modify_invoice', (invoice.rec_name,))
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
def get_rec_name(self, name):
items = []
2016-07-04 12:05:46 +02:00
if self.number:
items.append(self.number)
if self.reference:
items.append('[%s]' % self.reference)
if not items:
items.append('(%s)' % self.id)
return ' '.join(items)
2008-04-14 11:16:24 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def search_rec_name(cls, name, clause):
2015-04-05 23:35:37 +02:00
if clause[1].startswith('!') or clause[1].startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
return [bool_op,
('number',) + tuple(clause[1:]),
('reference',) + tuple(clause[1:]),
('party',) + tuple(clause[1:]),
]
2008-04-14 11:16:24 +02:00
2014-09-17 21:44:59 +02:00
def get_origins(self, name):
2018-06-26 19:11:42 +02:00
return ', '.join(set(filter(None,
2014-09-17 21:44:59 +02:00
(l.origin_name for l in self.lines))))
2013-09-19 17:33:27 +02:00
2015-03-09 19:41:10 +01:00
@classmethod
def view_attributes(cls):
2015-09-04 12:02:55 +02:00
return [('/form//field[@name="comment"]', 'spell', Eval('party_lang'))]
2015-03-09 19:41:10 +01:00
2012-09-24 22:17:32 +02:00
@classmethod
def delete(cls, invoices):
cls.check_modify(invoices)
# Cancel before delete
2012-09-24 22:17:32 +02:00
cls.cancel(invoices)
for invoice in invoices:
if invoice.state != 'cancel':
cls.raise_user_error('delete_cancel', (invoice.rec_name,))
2012-12-23 22:28:41 +01:00
if invoice.number:
cls.raise_user_error('delete_numbered', (invoice.rec_name,))
2012-09-24 22:17:32 +02:00
super(Invoice, cls).delete(invoices)
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
2013-12-19 14:44:39 +01:00
def write(cls, *args):
actions = iter(args)
all_invoices = []
for invoices, values in zip(actions, actions):
if set(values) - set(cls._check_modify_exclude):
cls.check_modify(invoices)
all_invoices += invoices
update_tax = [i for i in all_invoices if i.state == 'draft']
super(Invoice, cls).write(*args)
2012-09-24 22:17:32 +02:00
if update_tax:
cls.update_taxes(update_tax)
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def copy(cls, invoices, default=None):
2008-04-16 19:21:05 +02:00
if default is None:
default = {}
2018-09-01 01:02:03 +02:00
else:
default = default.copy()
default.setdefault('number', None)
default.setdefault('move', None)
default.setdefault('cancel_move', None)
default.setdefault('invoice_report_cache', None)
default.setdefault('invoice_report_format', None)
default.setdefault('payment_lines', None)
2012-03-27 10:43:07 +02:00
default.setdefault('invoice_date', None)
default.setdefault('accounting_date', None)
2018-09-01 01:02:03 +02:00
default.setdefault('lines_to_pay', None)
2013-12-19 00:55:47 +01:00
return super(Invoice, cls).copy(invoices, default=default)
2008-12-05 13:25:54 +01:00
@classmethod
def validate(cls, invoices):
super(Invoice, cls).validate(invoices)
for invoice in invoices:
invoice.check_same_account()
invoice.check_cancel_move()
2018-03-24 15:29:47 +01:00
invoice.check_payment_lines()
2008-04-16 19:21:05 +02:00
def check_same_account(self):
2012-09-24 22:17:32 +02:00
for line in self.lines:
if (line.type == 'line'
and line.account == self.account):
self.raise_user_error('same_account_on_line', {
2013-03-25 18:49:29 +00:00
'invoice': self.rec_name,
'account': self.account.rec_name,
'line': line.rec_name,
})
2008-04-10 17:21:44 +02:00
2012-12-23 22:28:41 +01:00
def check_cancel_move(self):
2016-03-20 22:58:13 +01:00
if self.type == 'out' and self.cancel_move:
self.raise_user_error('customer_invoice_cancel_move',
self.rec_name)
2012-12-23 22:28:41 +01:00
2018-03-24 15:29:47 +01:00
def check_payment_lines(self):
amount = sum(l.debit - l.credit for l in self.lines_to_pay)
payment_amount = sum(l.debit - l.credit for l in self.payment_lines)
if abs(amount) < abs(payment_amount):
self.raise_user_error('payment_lines_greater_amount', {
'invoice': self.rec_name,
})
2016-03-04 00:23:47 +01:00
def get_reconcile_lines_for_amount(self, amount):
'''
2012-09-24 22:17:32 +02:00
Return list of lines and the remainder to make reconciliation.
'''
2016-03-04 00:23:47 +01:00
Result = namedtuple('Result', ['lines', 'remainder'])
2016-03-04 00:23:47 +01:00
lines = [l for l in self.payment_lines + self.lines_to_pay
if not l.reconciliation]
2016-03-04 00:23:47 +01:00
best = Result([], self.total_amount)
2018-06-26 19:11:42 +02:00
for n in range(len(lines), 0, -1):
2016-03-04 00:23:47 +01:00
for comb_lines in combinations(lines, n):
remainder = sum((l.debit - l.credit) for l in comb_lines)
remainder -= amount
result = Result(list(comb_lines), remainder)
if self.currency.is_zero(remainder):
return result
if abs(remainder) < abs(best.remainder):
best = result
return best
2018-09-01 11:21:06 +02:00
def pay_invoice(self, amount, payment_method, date, description,
2012-03-27 10:43:07 +02:00
amount_second_currency=None, second_currency=None):
2009-03-18 17:55:15 +01:00
'''
2012-09-24 22:17:32 +02:00
Adds a payment of amount to an invoice using the journal, date and
description.
Returns the payment line.
2009-03-18 17:55:15 +01:00
'''
2011-07-03 13:18:43 +02:00
pool = Pool()
2012-09-24 22:17:32 +02:00
Move = pool.get('account.move')
2016-03-20 22:58:13 +01:00
Line = pool.get('account.move.line')
2012-09-24 22:17:32 +02:00
Period = pool.get('account.period')
2016-03-20 22:58:13 +01:00
line1 = Line(description=description, account=self.account)
line2 = Line(description=description)
lines = [line1, line2]
2016-03-20 22:58:13 +01:00
if amount >= 0:
if self.type == 'out':
line1.debit, line1.credit = 0, amount
else:
line1.debit, line1.credit = amount, 0
else:
2016-03-20 22:58:13 +01:00
if self.type == 'out':
line1.debit, line1.credit = -amount, 0
else:
2016-07-11 18:22:47 +02:00
line1.debit, line1.credit = 0, -amount
2012-10-10 17:54:13 +02:00
2016-03-20 22:58:13 +01:00
line2.debit, line2.credit = line1.credit, line1.debit
if line2.debit:
2018-09-01 11:21:06 +02:00
payment_acccount = 'debit_account'
2016-03-20 22:58:13 +01:00
else:
2018-09-01 11:21:06 +02:00
payment_acccount = 'credit_account'
line2.account = getattr(payment_method, payment_acccount)
2016-03-20 22:58:13 +01:00
for line in lines:
if line.account.party_required:
line.party = self.party
if amount_second_currency:
line.amount_second_currency = amount_second_currency.copy_sign(
line.debit - line.credit)
line.second_currency = second_currency
2012-09-24 22:17:32 +02:00
period_id = Period.find(self.company.id, date=date)
2018-09-01 11:21:06 +02:00
move = Move(
journal=payment_method.journal, period=period_id, date=date,
2016-03-20 22:58:13 +01:00
company=self.company, lines=lines)
move.save()
2012-09-24 22:17:32 +02:00
Move.post([move])
for line in move.lines:
2012-09-24 22:17:32 +02:00
if line.account == self.account:
2018-03-24 15:29:47 +01:00
self.add_payment_lines({self: [line]})
2012-09-24 22:17:32 +02:00
return line
raise Exception('Missing account')
2018-03-24 15:29:47 +01:00
@classmethod
def add_payment_lines(cls, payments):
"Add value lines to the key invoice from the payment dictionary."
to_write = []
2018-06-26 19:11:42 +02:00
for invoice, lines in payments.items():
2018-03-24 15:29:47 +01:00
if invoice.state == 'paid':
cls.raise_user_error('modify_payment_lines_invoice_paid', {
'invoice': invoice.rec_name,
})
to_write.append([invoice])
to_write.append({'payment_lines': [('add', lines)]})
if to_write:
cls.write(*to_write)
@classmethod
def remove_payment_lines(cls, lines):
"Remove payment lines from their invoices."
pool = Pool()
PaymentLine = pool.get('account.invoice-account.move.line')
payments = defaultdict(list)
2018-06-26 19:11:42 +02:00
ids = list(map(int, lines))
2018-03-24 15:29:47 +01:00
for sub_ids in grouped_slice(ids):
payment_lines = PaymentLine.search([
('line', 'in', list(sub_ids)),
])
for payment_line in payment_lines:
payments[payment_line.invoice].append(payment_line.line)
to_write = []
2018-06-26 19:11:42 +02:00
for invoice, lines in payments.items():
2018-03-24 15:29:47 +01:00
if invoice.state == 'paid':
cls.raise_user_error('modify_payment_lines_invoice_paid', {
'invoice': invoice.rec_name,
})
to_write.append([invoice])
to_write.append({'payment_lines': [('remove', lines)]})
if to_write:
cls.write(*to_write)
2012-09-24 22:17:32 +02:00
def print_invoice(self):
'''
Generate invoice report and store it in invoice_report field.
'''
2012-09-24 22:17:32 +02:00
if self.invoice_report_cache:
2012-03-25 00:17:27 +01:00
return
2012-09-24 22:17:32 +02:00
InvoiceReport = Pool().get('account.invoice', type='report')
InvoiceReport.execute([self.id], {})
2012-09-24 22:17:32 +02:00
def _credit(self):
2008-04-16 23:56:33 +02:00
'''
2008-10-03 17:12:23 +02:00
Return values to credit invoice.
2008-04-16 23:56:33 +02:00
'''
credit = self.__class__()
2008-04-16 23:56:33 +02:00
2008-11-14 18:59:02 +01:00
for field in ('company', 'party', 'invoice_address', 'currency',
'journal', 'account', 'payment_term', 'description',
'comment', 'type'):
setattr(credit, field, getattr(self, field))
2008-04-16 23:56:33 +02:00
credit.lines = [line._credit() for line in self.lines]
credit.taxes = [tax._credit() for tax in self.taxes if tax.manual]
return credit
2008-04-16 23:56:33 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def credit(cls, invoices, refund=False):
2008-04-16 23:56:33 +02:00
'''
2008-10-03 17:12:23 +02:00
Credit invoices and return ids of new invoices.
2012-09-24 22:17:32 +02:00
Return the list of new invoice
2008-04-16 23:56:33 +02:00
'''
2012-09-24 22:17:32 +02:00
MoveLine = Pool().get('account.move.line')
new_invoices = [i._credit() for i in invoices]
cls.save(new_invoices)
cls.update_taxes(new_invoices)
if refund:
cls.post(new_invoices)
2018-06-26 19:11:42 +02:00
for invoice, new_invoice in zip(invoices, new_invoices):
2012-12-23 22:28:41 +01:00
if new_invoice.state == 'posted':
2012-09-24 22:17:32 +02:00
MoveLine.reconcile([l for l in invoice.lines_to_pay
if not l.reconciliation] +
[l for l in new_invoice.lines_to_pay
if not l.reconciliation])
return new_invoices
2008-04-16 23:56:33 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
2012-03-25 00:17:27 +01:00
@ModelView.button
@Workflow.transition('draft')
2012-09-24 22:17:32 +02:00
def draft(cls, invoices):
2012-12-23 22:28:41 +01:00
Move = Pool().get('account.move')
moves = []
for invoice in invoices:
if invoice.move:
moves.append(invoice.move)
if moves:
2014-09-17 21:44:59 +02:00
Move.delete(moves)
2012-09-24 22:17:32 +02:00
@classmethod
2012-03-25 00:17:27 +01:00
@ModelView.button
2012-12-23 22:28:41 +01:00
@Workflow.transition('validated')
def validate_invoice(cls, invoices):
pool = Pool()
Move = pool.get('account.move')
invoices_in = cls.browse([i for i in invoices if i.type == 'in'])
cls.set_number(invoices_in)
moves = []
for invoice in invoices_in:
move = invoice.get_move()
if move != invoice.move:
invoice.move = move
moves.append(move)
if moves:
Move.save(moves)
cls.save(invoices_in)
2012-09-24 22:17:32 +02:00
@classmethod
2012-03-25 00:17:27 +01:00
@ModelView.button
2012-12-23 22:28:41 +01:00
@Workflow.transition('posted')
def post(cls, invoices):
Move = Pool().get('account.move')
cls.set_number(invoices)
2012-12-23 22:28:41 +01:00
moves = []
2012-09-24 22:17:32 +02:00
for invoice in invoices:
move = invoice.get_move()
if move != invoice.move:
invoice.move = move
moves.append(move)
if invoice.state != 'posted':
invoice.state = 'posted'
if moves:
Move.save(moves)
cls.save(invoices)
Move.post([i.move for i in invoices if i.move.state != 'posted'])
for invoice in invoices:
2016-03-20 22:58:13 +01:00
if invoice.type == 'out':
2012-12-23 22:28:41 +01:00
invoice.print_invoice()
2012-09-24 22:17:32 +02:00
@classmethod
2012-09-10 15:27:42 +02:00
@ModelView.button_action('account_invoice.wizard_pay')
2012-09-24 22:17:32 +02:00
def pay(cls, invoices):
2012-09-10 15:27:42 +02:00
pass
2012-09-24 22:17:32 +02:00
@classmethod
def process(cls, invoices):
2012-03-25 00:17:27 +01:00
paid = []
2012-12-23 22:28:41 +01:00
posted = []
2012-09-24 22:17:32 +02:00
for invoice in invoices:
2012-12-23 22:28:41 +01:00
if invoice.state not in ('posted', 'paid'):
2012-03-25 00:17:27 +01:00
continue
if invoice.reconciled:
2012-09-24 22:17:32 +02:00
paid.append(invoice)
2012-03-25 00:17:27 +01:00
else:
2012-12-23 22:28:41 +01:00
posted.append(invoice)
2012-09-24 22:17:32 +02:00
cls.paid(paid)
2012-12-23 22:28:41 +01:00
cls.post(posted)
2012-09-24 22:17:32 +02:00
@classmethod
2012-03-25 00:17:27 +01:00
@Workflow.transition('paid')
2012-09-24 22:17:32 +02:00
def paid(cls, invoices):
2018-03-24 15:29:47 +01:00
# Remove links to lines which actually do not pay the invoice
to_write = []
for invoice in invoices:
to_remove = []
reconciliations = [l.reconciliation for l in invoice.lines_to_pay]
for payment_line in invoice.payment_lines:
if payment_line.reconciliation not in reconciliations:
to_remove.append(payment_line)
if to_remove:
to_write.append([invoice])
to_write.append([('remove', to_remove)])
if to_write:
cls.write(*to_write)
2012-09-24 22:17:32 +02:00
@classmethod
2012-03-25 00:17:27 +01:00
@ModelView.button
@Workflow.transition('cancel')
2012-09-24 22:17:32 +02:00
def cancel(cls, invoices):
pool = Pool()
Move = pool.get('account.move')
Line = pool.get('account.move.line')
2012-12-23 22:28:41 +01:00
cancel_moves = []
delete_moves = []
to_save = []
2012-12-23 22:28:41 +01:00
for invoice in invoices:
if invoice.move:
if invoice.move.state == 'draft':
delete_moves.append(invoice.move)
elif not invoice.cancel_move:
2014-05-09 23:33:02 +02:00
invoice.cancel_move = invoice.move.cancel()
to_save.append(invoice)
2012-12-23 22:28:41 +01:00
cancel_moves.append(invoice.cancel_move)
if cancel_moves:
Move.save(cancel_moves)
cls.save(to_save)
2012-12-23 22:28:41 +01:00
if delete_moves:
2014-09-17 21:44:59 +02:00
Move.delete(delete_moves)
2012-12-23 22:28:41 +01:00
if cancel_moves:
Move.post(cancel_moves)
# Write state before reconcile to prevent invoice to go to paid state
cls.write(invoices, {
'state': 'cancel',
})
# Reconcile lines to pay with the cancellation ones if possible
for invoice in invoices:
if not invoice.move or not invoice.cancel_move:
continue
to_reconcile = []
for line in invoice.move.lines + invoice.cancel_move.lines:
if line.account == invoice.account:
if line.reconciliation:
break
to_reconcile.append(line)
else:
if to_reconcile:
Line.reconcile(to_reconcile)
2008-04-10 17:21:44 +02:00
2009-03-05 14:51:35 +01:00
class InvoicePaymentLine(ModelSQL):
'Invoice - Payment Line'
2012-09-24 22:17:32 +02:00
__name__ = 'account.invoice-account.move.line'
2009-03-05 14:51:35 +01:00
invoice = fields.Many2One('account.invoice', 'Invoice', ondelete='CASCADE',
2012-03-05 10:52:57 +01:00
select=True, required=True)
2018-03-24 15:29:47 +01:00
invoice_account = fields.Function(
fields.Many2One('account.account', "Invoice Account"),
'get_invoice')
invoice_party = fields.Function(
fields.Many2One('party.party', "Invoice Party"), 'get_invoice')
line = fields.Many2One(
'account.move.line', 'Payment Line', ondelete='CASCADE',
select=True, required=True,
domain=[
('account', '=', Eval('invoice_account')),
('party', '=', Eval('invoice_party')),
],
depends=['invoice_account', 'invoice_party'])
@classmethod
def __setup__(cls):
super(InvoicePaymentLine, cls).__setup__()
t = cls.__table__()
cls._sql_constraints = [
('line_unique', Unique(t, t.line),
"A payment line can be linked to only one invoice."),
]
@classmethod
def get_invoice(cls, records, names):
result = {}
for name in names:
result[name] = {}
invoice_account = 'invoice_account' in result
invoice_party = 'invoice_party' in result
for record in records:
if invoice_account:
result['invoice_account'][record.id] = (
record.invoice.account.id)
if invoice_party:
if record.invoice.account.party_required:
party = record.invoice.party.id
else:
party = None
result['invoice_party'][record.id] = party
return result
2009-03-05 14:51:35 +01:00
2016-09-13 14:03:55 +02:00
class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
2008-04-10 17:21:44 +02:00
'Invoice Line'
2012-09-24 22:17:32 +02:00
__name__ = 'account.invoice.line'
_states = {
'readonly': Eval('invoice_state') != 'draft',
}
_depends = ['invoice_state']
2008-04-10 17:21:44 +02:00
invoice = fields.Many2One('account.invoice', 'Invoice', ondelete='CASCADE',
2012-03-05 10:52:57 +01:00
select=True, states={
2011-08-21 11:20:30 +02:00
'required': (~Eval('invoice_type') & Eval('party')
& Eval('currency') & Eval('company')),
'invisible': Bool(Eval('context', {}).get('standalone')),
'readonly': _states['readonly'] & Bool(Eval('invoice')),
2011-08-04 16:57:16 +02:00
},
depends=['invoice_type', 'party', 'company', 'currency'] + _depends)
invoice_state = fields.Function(fields.Selection(STATES, 'Invoice State'),
'on_change_with_invoice_state')
invoice_type = fields.Selection(_TYPE + [(None, '')], 'Invoice Type',
select=True,
2011-08-04 16:57:16 +02:00
states={
2011-08-21 11:20:30 +02:00
'readonly': Eval('context', {}).get('type') | Eval('type'),
'required': ~Eval('invoice'),
2011-08-04 16:57:16 +02:00
},
depends=['invoice', 'type'])
2012-03-05 10:52:57 +01:00
party = fields.Many2One('party.party', 'Party', select=True,
2011-08-04 16:57:16 +02:00
states={
2011-08-21 11:20:30 +02:00
'required': ~Eval('invoice'),
'readonly': _states['readonly'],
2011-08-04 16:57:16 +02:00
},
depends=['invoice'] + _depends)
2014-01-26 14:28:52 +01:00
party_lang = fields.Function(fields.Char('Party Language'),
'on_change_with_party_lang')
2010-01-20 10:57:08 +01:00
currency = fields.Many2One('currency.currency', 'Currency',
2011-08-04 16:57:16 +02:00
states={
2011-08-21 11:20:30 +02:00
'required': ~Eval('invoice'),
'readonly': _states['readonly'],
2011-08-04 16:57:16 +02:00
},
depends=['invoice'] + _depends)
2014-01-26 14:28:52 +01:00
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'on_change_with_currency_digits')
2014-12-03 16:13:16 +01:00
company = fields.Many2One('company.company', 'Company', required=True,
2013-02-16 22:06:26 +01:00
domain=[
2011-08-21 11:20:30 +02:00
('id', If(Eval('context', {}).contains('company'), '=', '!='),
2013-10-09 18:25:10 +02:00
Eval('context', {}).get('company', -1)),
], select=True, states=_states, depends=_depends)
2008-04-10 17:21:44 +02:00
type = fields.Selection([
('line', 'Line'),
('subtotal', 'Subtotal'),
('title', 'Title'),
2008-05-21 10:54:56 +02:00
('comment', 'Comment'),
2012-03-05 10:52:57 +01:00
], 'Type', select=True, required=True, states={
2011-08-21 11:20:30 +02:00
'invisible': Bool(Eval('context', {}).get('standalone')),
'readonly': _states['readonly'],
}, depends=_depends)
2008-04-10 17:21:44 +02:00
quantity = fields.Float('Quantity',
2011-08-04 16:57:16 +02:00
digits=(16, Eval('unit_digits', 2)),
states={
2011-08-21 11:20:30 +02:00
'invisible': Eval('type') != 'line',
'required': Eval('type') == 'line',
'readonly': _states['readonly'],
2011-08-04 16:57:16 +02:00
},
depends=['type', 'unit_digits'] + _depends)
unit = fields.Many2One('product.uom', 'Unit', ondelete='RESTRICT',
2011-08-04 16:57:16 +02:00
states={
'required': Bool(Eval('product')),
2011-08-21 11:20:30 +02:00
'invisible': Eval('type') != 'line',
'readonly': _states['readonly'],
2011-08-04 16:57:16 +02:00
},
domain=[
2012-03-25 15:41:01 +02:00
If(Bool(Eval('product_uom_category')),
('category', '=', Eval('product_uom_category')),
('category', '!=', -1)),
2010-01-20 10:57:08 +01:00
],
depends=['product', 'type', 'product_uom_category'] + _depends)
2014-01-26 14:28:52 +01:00
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'on_change_with_unit_digits')
2008-04-10 17:21:44 +02:00
product = fields.Many2One('product.product', 'Product',
ondelete='RESTRICT',
2010-03-11 16:45:42 +01:00
states={
2011-08-21 11:20:30 +02:00
'invisible': Eval('type') != 'line',
'readonly': _states['readonly'],
2011-08-04 16:57:16 +02:00
},
depends=['type'] + _depends)
2012-03-25 15:41:01 +02:00
product_uom_category = fields.Function(
2014-01-26 14:28:52 +01:00
fields.Many2One('product.uom.category', 'Product Uom Category'),
2012-09-24 22:17:32 +02:00
'on_change_with_product_uom_category')
2011-08-04 16:57:16 +02:00
account = fields.Many2One('account.account', 'Account',
ondelete='RESTRICT',
2011-08-04 16:57:16 +02:00
states={
2011-08-21 11:20:30 +02:00
'invisible': Eval('type') != 'line',
'required': Eval('type') == 'line',
'readonly': _states['readonly'],
2011-08-04 16:57:16 +02:00
},
2018-08-31 14:58:05 +02:00
context={
'date': If(Bool(Eval('_parent_invoice.accounting_date')),
Eval('_parent_invoice.accounting_date'),
Eval('_parent_invoice.invoice_date')),
},
depends=['type', 'invoice_type', 'company'] + _depends)
2015-01-06 23:30:09 +01:00
unit_price = fields.Numeric('Unit Price', digits=price_digits,
2011-08-04 16:57:16 +02:00
states={
2011-08-21 11:20:30 +02:00
'invisible': Eval('type') != 'line',
'required': Eval('type') == 'line',
'readonly': _states['readonly'],
2011-08-04 16:57:16 +02:00
},
depends=['type'] + _depends)
2011-08-04 16:57:16 +02:00
amount = fields.Function(fields.Numeric('Amount',
2011-08-21 11:20:30 +02:00
digits=(16, Eval('_parent_invoice', {}).get('currency_digits',
2011-08-04 16:57:16 +02:00
Eval('currency_digits', 2))),
states={
2011-08-21 11:20:30 +02:00
'invisible': ~Eval('type').in_(['line', 'subtotal']),
2011-08-04 16:57:16 +02:00
},
depends=['type', 'currency_digits']), 'get_amount')
2017-12-18 19:02:44 +01:00
description = fields.Text('Description', size=None,
states=_states, depends=_depends)
2009-01-20 00:04:31 +01:00
note = fields.Text('Note')
2009-03-05 14:51:35 +01:00
taxes = fields.Many2Many('account.invoice.line-account.tax',
'line', 'tax', 'Taxes',
2015-03-16 15:40:37 +01:00
order=[('tax.sequence', 'ASC'), ('tax.id', 'ASC')],
domain=[('parent', '=', None), ['OR',
('group', '=', None),
('group.kind', 'in',
If(Bool(Eval('_parent_invoice')),
2016-03-20 22:58:13 +01:00
If(Eval('_parent_invoice', {}).get('type') == 'out',
['sale', 'both'],
['purchase', 'both']),
2016-03-20 22:58:13 +01:00
If(Eval('invoice_type') == 'out',
['sale', 'both'],
['purchase', 'both']))
)],
2014-12-03 16:13:16 +01:00
('company', '=', Eval('company', -1)),
],
2011-08-04 16:57:16 +02:00
states={
2011-08-21 11:20:30 +02:00
'invisible': Eval('type') != 'line',
2016-07-12 09:15:42 +02:00
'readonly': _states['readonly'] | ~Bool(Eval('account')),
2011-08-04 16:57:16 +02:00
},
2016-07-12 09:15:42 +02:00
depends=['type', 'invoice_type', 'company', 'account'] + _depends)
2010-03-11 16:45:42 +01:00
invoice_taxes = fields.Function(fields.One2Many('account.invoice.tax',
None, 'Invoice Taxes'), 'get_invoice_taxes')
2013-03-23 15:37:44 +01:00
origin = fields.Reference('Origin', selection='get_origin', select=True,
states=_states, depends=_depends)
del _states, _depends
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def __setup__(cls):
super(InvoiceLine, cls).__setup__()
t = cls.__table__()
2012-09-24 22:17:32 +02:00
cls._sql_constraints += [
2008-04-10 17:21:44 +02:00
('type_account',
Check(t, ((t.type == 'line') & (t.account != Null))
| (t.type != 'line')),
'Line with "line" type must have an account.'),
2008-11-28 18:29:40 +01:00
('type_invoice',
Check(t, ((t.type != 'line') & (t.invoice != Null))
| (t.type == 'line')),
'Line without "line" type must have an invoice.'),
2012-09-24 22:17:32 +02:00
]
cls._error_messages.update({
'modify': ('You can not modify line "%(line)s" from invoice '
'"%(invoice)s" that is posted or paid.'),
'create': ('You can not add a line to invoice "%(invoice)s" '
'that is posted, paid or cancelled.'),
'same_account_on_invoice': ('You can not create invoice line '
'"%(line)s" on invoice "%(invoice)s" because the invoice '
'uses the same account (%(account)s).'),
2013-02-16 22:06:26 +01:00
})
2008-04-10 17:21:44 +02:00
cls._check_modify_exclude = {'note', 'origin'}
# Set account domain dynamically for kind
cls.account.domain = [
('company', '=', Eval('company', -1)),
If(Bool(Eval('_parent_invoice')),
2016-03-20 22:58:13 +01:00
If(Eval('_parent_invoice', {}).get('type') == 'out',
('kind', 'in', cls._account_domain('out')),
2016-03-20 22:58:13 +01:00
If(Eval('_parent_invoice', {}).get('type') == 'in',
('kind', 'in', cls._account_domain('in')),
('kind', 'in',
cls._account_domain('out')
+ cls._account_domain('in')))),
2016-03-20 22:58:13 +01:00
If(Eval('invoice_type') == 'out',
('kind', 'in', cls._account_domain('out')),
2016-03-20 22:58:13 +01:00
If(Eval('invoice_type') == 'in',
('kind', 'in', cls._account_domain('in')),
('kind', 'in',
cls._account_domain('out')
+ cls._account_domain('in'))))),
]
cls.account.depends += ['company', 'invoice_type']
2016-09-13 14:03:55 +02:00
cls.sequence.states.update({
'invisible': Bool(Eval('context', {}).get('standalone')),
})
@staticmethod
def _account_domain(type_):
if type_ == 'out':
return ['revenue']
elif type_ == 'in':
return ['expense']
2012-09-24 22:17:32 +02:00
@classmethod
def __register__(cls, module_name):
2014-12-03 16:13:16 +01:00
pool = Pool()
Invoice = pool.get('account.invoice')
invoice = Invoice.__table__()
sql_table = cls.__table__()
2012-09-24 22:17:32 +02:00
super(InvoiceLine, cls).__register__(module_name)
cursor = Transaction().connection.cursor()
2018-07-24 22:01:07 +02:00
table = cls.__table_handler__(module_name)
2008-11-28 18:29:40 +01:00
2014-12-03 16:13:16 +01:00
# Migration from 3.4: company is required
cursor.execute(*sql_table.join(invoice,
condition=sql_table.invoice == invoice.id
).select(sql_table.id, invoice.company,
where=sql_table.company == Null))
2014-12-03 16:13:16 +01:00
for line_id, company_id in cursor.fetchall():
cursor.execute(*sql_table.update([sql_table.company], [company_id],
where=sql_table.id == line_id))
2017-12-18 19:02:44 +01:00
# Migration from 4.6: drop required on description
table.not_null_action('description', action='remove')
2012-09-24 22:17:32 +02:00
@staticmethod
def default_currency():
Company = Pool().get('company.company')
2010-08-19 22:02:49 +02:00
if Transaction().context.get('company'):
2012-09-24 22:17:32 +02:00
company = Company(Transaction().context['company'])
return company.currency.id
2008-11-28 18:29:40 +01:00
2012-09-24 22:17:32 +02:00
@staticmethod
def default_currency_digits():
Company = Pool().get('company.company')
2010-08-19 22:02:49 +02:00
if Transaction().context.get('company'):
2012-09-24 22:17:32 +02:00
company = Company(Transaction().context['company'])
2008-11-28 18:29:40 +01:00
return company.currency.digits
return 2
2012-09-24 22:17:32 +02:00
@staticmethod
def default_unit_digits():
return 2
2012-09-24 22:17:32 +02:00
@staticmethod
def default_company():
2012-03-27 10:43:07 +02:00
return Transaction().context.get('company')
2008-11-28 18:29:40 +01:00
2012-09-24 22:17:32 +02:00
@staticmethod
def default_type():
2008-04-10 17:21:44 +02:00
return 'line'
2017-02-01 10:24:50 +01:00
@classmethod
def default_invoice_state(cls):
return 'draft'
@fields.depends('invoice', '_parent_invoice.state')
def on_change_with_invoice_state(self, name=None):
if self.invoice:
return self.invoice.state
return 'draft'
2014-01-26 14:28:52 +01:00
@fields.depends('party')
2012-09-24 22:17:32 +02:00
def on_change_with_party_lang(self, name=None):
2012-11-23 18:00:20 +01:00
Config = Pool().get('ir.configuration')
2012-09-24 22:17:32 +02:00
if self.party and self.party.lang:
return self.party.lang.code
2012-11-23 18:00:20 +01:00
return Config.get_language()
2014-01-26 14:28:52 +01:00
@fields.depends('unit')
2012-09-24 22:17:32 +02:00
def on_change_with_unit_digits(self, name=None):
if self.unit:
return self.unit.digits
return 2
2014-01-26 14:28:52 +01:00
@fields.depends('currency')
2012-09-24 22:17:32 +02:00
def on_change_with_currency_digits(self, name=None):
if self.currency:
return self.currency.digits
return 2
2018-03-24 13:46:10 +01:00
@fields.depends('type', 'quantity', 'unit_price', 'invoice',
2014-01-26 14:28:52 +01:00
'_parent_invoice.currency', 'currency')
2012-09-24 22:17:32 +02:00
def on_change_with_amount(self):
if self.type == 'line':
currency = (self.invoice.currency if self.invoice
else self.currency)
amount = (Decimal(str(self.quantity or '0.0'))
* (self.unit_price or Decimal('0.0')))
if currency:
2012-09-24 22:17:32 +02:00
return currency.round(amount)
return amount
2008-07-28 20:16:08 +02:00
return Decimal('0.0')
2012-09-24 22:17:32 +02:00
def get_amount(self, name):
if self.type == 'line':
return self.on_change_with_amount()
elif self.type == 'subtotal':
subtotal = _ZERO
for line2 in self.invoice.lines:
if line2.type == 'line':
subtotal += line2.invoice.currency.round(
Decimal(str(line2.quantity)) * line2.unit_price)
elif line2.type == 'subtotal':
if self == line2:
break
subtotal = _ZERO
return subtotal
else:
return _ZERO
2008-08-26 18:34:14 +02:00
2013-09-19 17:33:27 +02:00
@property
def origin_name(self):
2013-09-22 14:30:02 +02:00
if isinstance(self.origin, self.__class__):
return self.origin.invoice.rec_name
2013-09-19 17:33:27 +02:00
return self.origin.rec_name if self.origin else None
2014-12-19 19:41:49 +01:00
@property
def taxable_lines(self):
return [(self.taxes, self.unit_price, self.quantity)]
@property
def tax_date(self):
return self.invoice.tax_date
def _get_tax_context(self):
if self.invoice:
return self.invoice._get_tax_context()
else:
return {}
2012-09-24 22:17:32 +02:00
def get_invoice_taxes(self, name):
if not self.invoice:
return
2018-06-26 19:11:42 +02:00
taxes_keys = list(self._get_taxes().keys())
2012-09-24 22:17:32 +02:00
taxes = []
for tax in self.invoice.taxes:
if tax.manual:
continue
2012-09-24 22:17:32 +02:00
tax_id = tax.tax.id if tax.tax else None
2018-03-25 23:34:44 +02:00
key = (tax.account.id, tax_id)
2012-09-24 22:17:32 +02:00
if key in taxes_keys:
taxes.append(tax.id)
return taxes
2012-09-24 22:17:32 +02:00
def _get_tax_rule_pattern(self):
2009-03-28 19:32:35 +01:00
'''
Get tax rule pattern
'''
2012-09-24 22:17:32 +02:00
return {}
2009-03-28 19:32:35 +01:00
2017-12-18 19:02:44 +01:00
@fields.depends('product', 'unit', '_parent_invoice.type',
2018-05-31 13:24:28 +02:00
'_parent_invoice.party', 'party', 'invoice', 'invoice_type',
2018-08-31 14:58:05 +02:00
'_parent_invoice.invoice_date', '_parent_invoice.accounting_date',
2018-05-31 13:24:28 +02:00
methods=['_get_tax_rule_pattern'])
2012-09-24 22:17:32 +02:00
def on_change_product(self):
if not self.product:
2014-10-29 20:39:13 +01:00
return
2008-04-10 17:21:44 +02:00
2008-05-21 18:14:05 +02:00
party = None
2012-09-24 22:17:32 +02:00
if self.invoice and self.invoice.party:
party = self.invoice.party
elif self.party:
party = self.party
2008-04-10 17:21:44 +02:00
2018-08-31 14:58:05 +02:00
date = (self.invoice.accounting_date or self.invoice.invoice_date
if self.invoice else None)
2012-09-24 22:17:32 +02:00
if self.invoice and self.invoice.type:
type_ = self.invoice.type
2014-10-10 10:07:02 +02:00
else:
2012-09-24 22:17:32 +02:00
type_ = self.invoice_type
2016-03-20 22:58:13 +01:00
if type_ == 'in':
2018-08-31 14:58:05 +02:00
with Transaction().set_context(date=date):
self.account = self.product.account_expense_used
2014-10-29 20:39:13 +01:00
taxes = []
2012-09-24 22:17:32 +02:00
pattern = self._get_tax_rule_pattern()
for tax in self.product.supplier_taxes_used:
2009-03-28 19:32:35 +01:00
if party and party.supplier_tax_rule:
2012-09-24 22:17:32 +02:00
tax_ids = party.supplier_tax_rule.apply(tax, pattern)
2009-04-06 10:32:08 +02:00
if tax_ids:
2014-10-29 20:39:13 +01:00
taxes.extend(tax_ids)
2009-03-28 19:32:35 +01:00
continue
2014-10-29 20:39:13 +01:00
taxes.append(tax)
2009-04-06 10:32:08 +02:00
if party and party.supplier_tax_rule:
2012-09-24 22:17:32 +02:00
tax_ids = party.supplier_tax_rule.apply(None, pattern)
2009-04-06 10:32:08 +02:00
if tax_ids:
2014-10-29 20:39:13 +01:00
taxes.extend(tax_ids)
self.taxes = taxes
2008-04-10 17:21:44 +02:00
else:
2018-08-31 14:58:05 +02:00
with Transaction().set_context(date=date):
self.account = self.product.account_revenue_used
2014-10-29 20:39:13 +01:00
taxes = []
2012-09-24 22:17:32 +02:00
pattern = self._get_tax_rule_pattern()
for tax in self.product.customer_taxes_used:
2009-03-28 19:32:35 +01:00
if party and party.customer_tax_rule:
2012-09-24 22:17:32 +02:00
tax_ids = party.customer_tax_rule.apply(tax, pattern)
2009-04-06 10:32:08 +02:00
if tax_ids:
2014-10-29 20:39:13 +01:00
taxes.extend(tax_ids)
2009-03-28 19:32:35 +01:00
continue
2014-10-29 20:39:13 +01:00
taxes.append(tax.id)
2009-04-06 10:32:08 +02:00
if party and party.customer_tax_rule:
2012-09-24 22:17:32 +02:00
tax_ids = party.customer_tax_rule.apply(None, pattern)
2009-04-06 10:32:08 +02:00
if tax_ids:
2014-10-29 20:39:13 +01:00
taxes.extend(tax_ids)
self.taxes = taxes
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
category = self.product.default_uom.category
if not self.unit or self.unit.category != category:
2014-10-29 20:39:13 +01:00
self.unit = self.product.default_uom.id
self.unit_digits = self.product.default_uom.digits
2014-01-26 14:28:52 +01:00
@fields.depends('product')
2012-09-24 22:17:32 +02:00
def on_change_with_product_uom_category(self, name=None):
if self.product:
return self.product.default_uom_category.id
2012-03-25 15:41:01 +02:00
2018-03-24 13:46:10 +01:00
@fields.depends('account', 'product', 'invoice',
'_parent_invoice.party', '_parent_invoice.type',
2018-05-31 13:24:28 +02:00
'party', 'invoice', 'invoice_type',
methods=['_get_tax_rule_pattern'])
2012-09-24 22:17:32 +02:00
def on_change_account(self):
if self.product:
2015-05-06 18:22:33 +02:00
return
taxes = []
party = None
if self.invoice and self.invoice.party:
2012-09-24 22:17:32 +02:00
party = self.invoice.party
elif self.party:
party = self.party
if self.invoice and self.invoice.type:
type_ = self.invoice.type
else:
type_ = self.invoice_type
if party and type_:
if type_ == 'in':
2012-09-06 15:12:40 +02:00
tax_rule = party.supplier_tax_rule
else:
tax_rule = party.customer_tax_rule
else:
tax_rule = None
2012-09-24 22:17:32 +02:00
if self.account:
pattern = self._get_tax_rule_pattern()
for tax in self.account.taxes:
2012-09-06 15:12:40 +02:00
if tax_rule:
2012-09-24 22:17:32 +02:00
tax_ids = tax_rule.apply(tax, pattern)
2012-09-06 15:12:40 +02:00
if tax_ids:
taxes.extend(tax_ids)
continue
2014-10-29 20:39:13 +01:00
taxes.append(tax)
if tax_rule:
tax_ids = tax_rule.apply(None, pattern)
if tax_ids:
taxes.extend(tax_ids)
2014-10-29 20:39:13 +01:00
self.taxes = taxes
2013-09-22 14:30:02 +02:00
@classmethod
def _get_origin(cls):
2013-03-23 15:37:44 +01:00
'Return list of Model names for origin Reference'
2013-09-22 14:30:02 +02:00
return [cls.__name__]
2013-03-23 15:37:44 +01:00
@classmethod
def get_origin(cls):
Model = Pool().get('ir.model')
models = cls._get_origin()
models = Model.search([
('model', 'in', models),
])
return [(None, '')] + [(m.model, m.name) for m in models]
2012-09-24 22:17:32 +02:00
@classmethod
def check_modify(cls, lines, fields=None):
2008-04-10 17:21:44 +02:00
'''
Check if the lines can be modified
'''
if fields is None or fields - cls._check_modify_exclude:
for line in lines:
if (line.invoice
and line.invoice.state in ('posted', 'paid')):
cls.raise_user_error('modify', {
'line': line.rec_name,
'invoice': line.invoice.rec_name
})
2008-04-10 17:21:44 +02:00
2015-03-09 19:41:10 +01:00
@classmethod
def view_attributes(cls):
2015-09-04 12:02:55 +02:00
return [
('/form//field[@name="note"]|/form//field[@name="description"]',
'spell', If(Bool(Eval('_parent_invoice')),
2015-03-09 19:41:10 +01:00
Eval('_parent_invoice', {}).get('party_lang'),
Eval('party_lang')))]
2012-09-24 22:17:32 +02:00
@classmethod
def delete(cls, lines):
cls.check_modify(lines)
super(InvoiceLine, cls).delete(lines)
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
2013-12-19 14:44:39 +01:00
def write(cls, *args):
actions = iter(args)
for lines, values in zip(actions, actions):
cls.check_modify(lines, set(values))
2013-12-19 14:44:39 +01:00
super(InvoiceLine, cls).write(*args)
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2012-09-24 22:17:32 +02:00
Invoice = Pool().get('account.invoice')
2013-01-06 22:18:13 +01:00
invoice_ids = []
for vals in vlist:
if vals.get('invoice'):
invoice_ids.append(vals.get('invoice'))
for invoice in Invoice.browse(invoice_ids):
2012-12-23 22:28:41 +01:00
if invoice.state in ('posted', 'paid', 'cancel'):
cls.raise_user_error('create', (invoice.rec_name,))
2013-01-06 22:18:13 +01:00
return super(InvoiceLine, cls).create(vlist)
2008-04-10 17:21:44 +02:00
2013-12-23 21:16:56 +01:00
@classmethod
def copy(cls, lines, default=None):
if default is None:
default = {}
2018-09-01 01:02:03 +02:00
else:
default = default.copy()
2013-12-23 21:16:56 +01:00
default.setdefault('origin', None)
return super(InvoiceLine, cls).copy(lines, default=default)
@classmethod
def validate(cls, lines):
super(InvoiceLine, cls).validate(lines)
for line in lines:
line.check_same_account()
def check_same_account(self):
2012-09-24 22:17:32 +02:00
if self.type == 'line':
if (self.invoice
and self.account == self.invoice.account):
self.raise_user_error('same_account_on_invoice', {
'line': self.rec_name,
'invoice': self.invoice.rec_name,
'account': self.account.rec_name,
})
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
def _compute_taxes(self):
pool = Pool()
Currency = pool.get('currency.currency')
TaxLine = pool.get('account.tax.line')
2012-09-24 22:17:32 +02:00
tax_lines = []
2012-09-24 22:17:32 +02:00
if self.type != 'line':
return tax_lines
2014-12-19 19:41:49 +01:00
taxes = self._get_taxes().values()
2010-08-19 22:02:49 +02:00
for tax in taxes:
2018-03-25 23:34:44 +02:00
amount = tax['base']
with Transaction().set_context(
date=self.invoice.currency_date):
amount = Currency.compute(
self.invoice.currency, amount,
self.invoice.company.currency)
tax_line = TaxLine()
tax_line.amount = amount
tax_line.type = 'base'
tax_line.tax = tax['tax']
tax_lines.append(tax_line)
return tax_lines
2008-04-10 17:21:44 +02:00
def get_move_lines(self):
2008-04-10 17:21:44 +02:00
'''
Return a list of move lines instances for invoice line
2008-04-10 17:21:44 +02:00
'''
pool = Pool()
Currency = pool.get('currency.currency')
MoveLine = pool.get('account.move.line')
2012-09-24 22:17:32 +02:00
if self.type != 'line':
return []
line = MoveLine()
2012-09-24 22:17:32 +02:00
if self.invoice.currency != self.invoice.company.currency:
with Transaction().set_context(date=self.invoice.currency_date):
amount = Currency.compute(self.invoice.currency,
self.amount, self.invoice.company.currency)
line.amount_second_currency = self.amount
line.second_currency = self.invoice.currency
2008-04-10 17:21:44 +02:00
else:
2012-09-24 22:17:32 +02:00
amount = self.amount
line.amount_second_currency = None
line.second_currency = None
2016-03-20 22:58:13 +01:00
if amount >= 0:
if self.invoice.type == 'out':
line.debit, line.credit = 0, amount
else:
line.debit, line.credit = amount, 0
2008-04-10 17:21:44 +02:00
else:
2016-03-20 22:58:13 +01:00
if self.invoice.type == 'out':
line.debit, line.credit = -amount, 0
else:
line.debit, line.credit = 0, -amount
if line.amount_second_currency:
line.amount_second_currency = (
line.amount_second_currency.copy_sign(
line.debit - line.credit))
line.account = self.account
if self.account.party_required:
line.party = self.invoice.party
line.tax_lines = self._compute_taxes()
return [line]
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
def _credit(self):
2008-04-16 23:56:33 +02:00
'''
Return credit line.
2008-04-16 23:56:33 +02:00
'''
line = self.__class__()
line.origin = self
if self.quantity:
line.quantity = -self.quantity
else:
line.quantity = self.quantity
2008-04-16 23:56:33 +02:00
2016-03-20 22:58:13 +01:00
for field in ('sequence', 'type', 'invoice_type', 'unit_price',
'description', 'unit', 'product', 'account'):
setattr(line, field, getattr(self, field))
line.taxes = self.taxes
return line
2008-04-16 23:56:33 +02:00
2008-04-10 17:21:44 +02:00
2009-03-05 14:51:35 +01:00
class InvoiceLineTax(ModelSQL):
'Invoice Line - Tax'
2012-09-24 22:17:32 +02:00
__name__ = 'account.invoice.line-account.tax'
2009-03-05 14:51:35 +01:00
_table = 'account_invoice_line_account_tax'
line = fields.Many2One('account.invoice.line', 'Invoice Line',
2012-03-05 10:52:57 +01:00
ondelete='CASCADE', select=True, required=True)
2009-03-05 14:51:35 +01:00
tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
required=True)
2016-09-13 14:03:55 +02:00
class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
2008-04-10 17:21:44 +02:00
'Invoice Tax'
2012-09-24 22:17:32 +02:00
__name__ = 'account.invoice.tax'
2008-04-10 17:21:44 +02:00
_rec_name = 'description'
_states = {
'readonly': Eval('invoice_state') != 'draft',
}
_depends = ['invoice_state']
2008-04-10 17:21:44 +02:00
invoice = fields.Many2One('account.invoice', 'Invoice', ondelete='CASCADE',
select=True,
states={
'readonly': _states['readonly'] & Bool(Eval('invoice')),
},
depends=_depends)
invoice_state = fields.Function(fields.Selection(STATES, 'Invoice State'),
'on_change_with_invoice_state')
description = fields.Char('Description', size=None, required=True,
states=_states, depends=_depends)
2010-03-11 16:45:42 +01:00
sequence_number = fields.Function(fields.Integer('Sequence Number'),
'get_sequence_number')
2008-04-10 17:21:44 +02:00
account = fields.Many2One('account.account', 'Account', required=True,
2011-08-21 11:20:30 +02:00
domain=[
('kind', '!=', 'view'),
('company', '=', Eval('_parent_invoice', {}).get('company', 0)),
],
states=_states, depends=_depends)
base = fields.Numeric('Base', required=True,
digits=(16, Eval('_parent_invoice', {}).get('currency_digits', 2)),
states=_states, depends=_depends)
amount = fields.Numeric('Amount', required=True,
2012-06-28 15:04:57 +02:00
digits=(16, Eval('_parent_invoice', {}).get('currency_digits', 2)),
states=_states,
depends=['tax', 'base', 'manual'] + _depends)
manual = fields.Boolean('Manual', states=_states, depends=_depends)
2012-06-28 15:04:57 +02:00
tax = fields.Many2One('account.tax', 'Tax',
ondelete='RESTRICT',
2014-12-03 16:13:16 +01:00
domain=[
('company', '=', Eval('_parent_invoice', {}).get('company', 0)),
],
2012-06-28 15:04:57 +02:00
states={
'readonly': ~Eval('manual', False) | _states['readonly'],
2012-06-28 15:04:57 +02:00
},
depends=['manual'] + _depends)
legal_notice = fields.Text("Legal Notice", states=_states,
depends=_depends)
del _states, _depends
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def __setup__(cls):
super(InvoiceTax, cls).__setup__()
cls._error_messages.update({
'modify': ('You can not modify tax "%(tax)s" from invoice '
'"%(invoice)s" because it is posted or paid.'),
'create': ('You can not add tax to invoice '
'"%(invoice)s" because it is posted, paid or canceled.'),
2013-02-16 22:06:26 +01:00
})
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def __register__(cls, module_name):
super(InvoiceTax, cls).__register__(module_name)
2012-05-14 15:36:16 +02:00
2018-07-24 22:01:07 +02:00
table = cls.__table_handler__(module_name)
2018-03-25 23:34:44 +02:00
# Migration from 4.6: drop base_sign and tax_sign
table.not_null_action('base_sign', action='remove')
table.not_null_action('tax_sign', action='remove')
2012-09-24 22:17:32 +02:00
@staticmethod
def default_base():
2008-04-10 17:21:44 +02:00
return Decimal('0.0')
2012-09-24 22:17:32 +02:00
@staticmethod
def default_amount():
2008-04-10 17:21:44 +02:00
return Decimal('0.0')
2012-09-24 22:17:32 +02:00
@staticmethod
def default_manual():
2008-04-10 17:21:44 +02:00
return True
@fields.depends('invoice', '_parent_invoice.state')
def on_change_with_invoice_state(self, name=None):
if self.invoice:
return self.invoice.state
2018-03-24 13:46:10 +01:00
@fields.depends('tax', 'invoice', '_parent_invoice.party', 'base')
2012-09-24 22:17:32 +02:00
def on_change_tax(self):
Tax = Pool().get('account.tax')
2014-10-29 20:39:13 +01:00
if not self.tax:
return
if self.invoice:
2015-12-31 13:29:08 +01:00
context = self.invoice._get_tax_context()
2014-10-29 20:39:13 +01:00
else:
context = {}
with Transaction().set_context(**context):
tax = Tax(self.tax.id)
self.description = tax.description
2016-03-20 22:58:13 +01:00
if self.base >= 0:
2014-10-29 20:39:13 +01:00
self.account = tax.invoice_account
else:
self.account = tax.credit_note_account
2012-06-28 15:04:57 +02:00
2018-03-24 13:46:10 +01:00
@fields.depends('tax', 'base', 'amount', 'manual', 'invoice',
2016-03-16 22:06:16 +01:00
'_parent_invoice.currency')
2012-09-24 22:17:32 +02:00
def on_change_with_amount(self):
Tax = Pool().get('account.tax')
if self.tax and self.manual:
tax = self.tax
base = self.base or Decimal(0)
for values in Tax.compute([tax], base, 1):
if (values['tax'] == tax
and values['base'] == base):
2016-03-16 22:06:16 +01:00
amount = values['amount']
if self.invoice.currency:
amount = self.invoice.currency.round(amount)
return amount
2012-09-24 22:17:32 +02:00
return self.amount
2012-06-28 15:04:57 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def check_modify(cls, taxes):
2008-04-10 17:21:44 +02:00
'''
Check if the taxes can be modified
'''
2012-09-24 22:17:32 +02:00
for tax in taxes:
2012-12-23 22:28:41 +01:00
if tax.invoice.state in ('posted', 'paid'):
cls.raise_user_error('modify', {
'tax': tax.rec_name,
'invoice': tax.invoice.rec_name,
})
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
def get_sequence_number(self, name):
i = 1
for tax in self.invoice.taxes:
if tax == self:
return i
i += 1
return 0
2012-09-24 22:17:32 +02:00
@classmethod
def delete(cls, taxes):
cls.check_modify(taxes)
super(InvoiceTax, cls).delete(taxes)
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
2013-12-19 14:44:39 +01:00
def write(cls, *args):
taxes = sum(args[0::2], [])
2012-09-24 22:17:32 +02:00
cls.check_modify(taxes)
2013-12-19 14:44:39 +01:00
super(InvoiceTax, cls).write(*args)
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2012-09-24 22:17:32 +02:00
Invoice = Pool().get('account.invoice')
2013-01-06 22:18:13 +01:00
invoice_ids = []
for vals in vlist:
if vals.get('invoice'):
invoice_ids.append(vals['invoice'])
for invoice in Invoice.browse(invoice_ids):
2012-12-23 22:28:41 +01:00
if invoice.state in ('posted', 'paid', 'cancel'):
cls.raise_user_error('create', {
'invoice': invoice.rec_name,
})
2013-01-06 22:18:13 +01:00
return super(InvoiceTax, cls).create(vlist)
2008-04-10 17:21:44 +02:00
def get_move_lines(self):
2008-04-10 17:21:44 +02:00
'''
Return a list of move lines instances for invoice tax
2008-04-10 17:21:44 +02:00
'''
2012-09-24 22:17:32 +02:00
Currency = Pool().get('currency.currency')
pool = Pool()
Currency = pool.get('currency.currency')
MoveLine = pool.get('account.move.line')
TaxLine = pool.get('account.tax.line')
line = MoveLine()
2012-09-24 22:17:32 +02:00
if not self.amount:
return []
line.description = self.description
2012-09-24 22:17:32 +02:00
if self.invoice.currency != self.invoice.company.currency:
with Transaction().set_context(date=self.invoice.currency_date):
amount = Currency.compute(self.invoice.currency, self.amount,
self.invoice.company.currency)
line.amount_second_currency = self.amount
line.second_currency = self.invoice.currency
2008-04-10 17:21:44 +02:00
else:
2012-09-24 22:17:32 +02:00
amount = self.amount
line.amount_second_currency = None
line.second_currency = None
2016-03-20 22:58:13 +01:00
if amount >= 0:
if self.invoice.type == 'out':
line.debit, line.credit = 0, amount
2008-04-10 17:21:44 +02:00
else:
line.debit, line.credit = amount, 0
2008-04-10 17:21:44 +02:00
else:
2016-03-20 22:58:13 +01:00
if self.invoice.type == 'out':
line.debit, line.credit = -amount, 0
2008-04-10 17:21:44 +02:00
else:
line.debit, line.credit = 0, -amount
if line.amount_second_currency:
line.amount_second_currency = (
line.amount_second_currency.copy_sign(
line.debit - line.credit))
line.account = self.account
if self.account.party_required:
line.party = self.invoice.party
2018-03-25 23:34:44 +02:00
tax_line = TaxLine()
tax_line.amount = amount
tax_line.type = 'tax'
tax_line.tax = self.tax
line.tax_lines = [tax_line]
return [line]
2008-04-10 17:21:44 +02:00
2012-09-24 22:17:32 +02:00
def _credit(self):
2008-04-16 23:56:33 +02:00
'''
Return credit tax.
2008-04-16 23:56:33 +02:00
'''
line = self.__class__()
line.base = -self.base
line.amount = -self.amount
2008-04-16 23:56:33 +02:00
2018-03-25 23:34:44 +02:00
for field in ['description', 'sequence', 'manual', 'account', 'tax']:
setattr(line, field, getattr(self, field))
return line
2008-04-16 23:56:33 +02:00
2008-04-25 20:34:17 +02:00
2018-09-01 11:21:06 +02:00
class PaymentMethod(DeactivableMixin, ModelSQL, ModelView):
'Payment Method'
__name__ = 'account.invoice.payment.method'
company = fields.Many2One('company.company', "Company", required=True)
name = fields.Char("Name", required=True, translate=True)
journal = fields.Many2One('account.journal', "Journal", required=True,
domain=[('type', '=', 'cash')])
credit_account = fields.Many2One('account.account', "Credit Account",
required=True,
domain=[
('kind', '!=', 'view'),
('company', '=', Eval('company')),
],
depends=['company'])
debit_account = fields.Many2One('account.account', "Debit Account",
required=True,
domain=[
('kind', '!=', 'view'),
('company', '=', Eval('company')),
],
depends=['company'])
@classmethod
def default_company(cls):
return Transaction().context.get('company')
2008-04-14 19:05:52 +02:00
class InvoiceReport(Report):
2012-09-24 22:17:32 +02:00
__name__ = 'account.invoice'
2008-04-14 19:05:52 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def __setup__(cls):
super(InvoiceReport, cls).__setup__()
cls.__rpc__['execute'] = RPC(False)
2012-09-24 22:17:32 +02:00
@classmethod
2017-09-03 11:17:09 +02:00
def _execute(cls, records, data, action):
pool = Pool()
Invoice = pool.get('account.invoice')
# Re-instantiate because records are TranslateModel
invoice, = Invoice.browse(records)
if invoice.invoice_report_cache:
2017-09-03 11:17:09 +02:00
return (
invoice.invoice_report_format,
bytes(invoice.invoice_report_cache))
else:
2017-09-03 11:17:09 +02:00
result = super(InvoiceReport, cls)._execute(records, data, action)
# If the invoice is posted or paid and the report not saved in
# invoice_report_cache there was an error somewhere. So we save it
# now in invoice_report_cache
2016-03-20 22:58:13 +01:00
if invoice.state in {'posted', 'paid'} and invoice.type == 'out':
2017-09-03 11:17:09 +02:00
format_, data = result
invoice.invoice_report_format = format_
invoice.invoice_report_cache = \
Invoice.invoice_report_cache.cast(data)
invoice.save()
2017-09-03 11:17:09 +02:00
return result
@classmethod
def execute(cls, ids, data):
pool = Pool()
Invoice = pool.get('account.invoice')
with Transaction().set_context(
language=False,
address_with_party=True):
2017-12-26 17:27:03 +01:00
result = super(InvoiceReport, cls).execute(ids, data)
if len(ids) == 1:
invoice, = Invoice.browse(ids)
if invoice.number:
result = result[:3] + (result[3] + ' - ' + invoice.number,)
return result
2012-09-24 22:17:32 +02:00
@classmethod
2015-01-20 18:49:54 +01:00
def get_context(cls, records, data):
pool = Pool()
Date = pool.get('ir.date')
2017-09-03 11:17:09 +02:00
context = super(InvoiceReport, cls).get_context(records, data)
context['invoice'] = context['record']
context['today'] = Date.today()
2017-09-03 11:17:09 +02:00
return context
2015-01-20 18:49:54 +01:00
2008-04-14 19:05:52 +02:00
2012-01-02 17:57:03 +01:00
class PayInvoiceStart(ModelView):
'Pay Invoice'
2012-09-24 22:17:32 +02:00
__name__ = 'account.invoice.pay.start'
2011-08-04 16:57:16 +02:00
amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'], required=True)
2008-05-09 17:26:41 +02:00
currency = fields.Many2One('currency.currency', 'Currency', required=True)
2014-01-26 14:28:52 +01:00
currency_digits = fields.Integer('Currency Digits', readonly=True)
2012-09-29 15:34:34 +02:00
description = fields.Char('Description', size=None)
2018-09-01 11:21:06 +02:00
company = fields.Many2One('company.company', "Company", readonly=True)
invoice_account = fields.Many2One(
'account.account', "Invoice Account", readonly=True)
payment_method = fields.Many2One(
'account.invoice.payment.method', "Payment Method", required=True,
domain=[
('company', '=', Eval('company')),
('debit_account', '!=', Eval('invoice_account')),
('credit_account', '!=', Eval('invoice_account')),
],
depends=['company', 'amount', 'invoice_account'])
date = fields.Date('Date', required=True)
2012-09-24 22:17:32 +02:00
@staticmethod
def default_date():
Date = Pool().get('ir.date')
return Date.today()
2012-09-24 22:17:32 +02:00
@staticmethod
def default_currency_digits():
return 2
2014-01-26 14:28:52 +01:00
@fields.depends('currency')
2012-09-24 22:17:32 +02:00
def on_change_with_currency_digits(self):
if self.currency:
return self.currency.digits
2008-11-04 18:10:39 +01:00
return 2
2009-02-28 16:57:26 +01:00
class PayInvoiceAsk(ModelView):
2012-01-02 17:57:03 +01:00
'Pay Invoice'
2012-09-24 22:17:32 +02:00
__name__ = 'account.invoice.pay.ask'
type = fields.Selection([
('writeoff', 'Write-Off'),
('partial', 'Partial Payment'),
], 'Type', required=True)
2018-09-01 11:21:06 +02:00
writeoff = fields.Many2One(
'account.move.reconcile.write_off', "Write Off",
2011-08-04 16:57:16 +02:00
domain=[
2018-09-01 11:21:06 +02:00
('company', '=', Eval('company')),
2010-01-20 10:57:08 +01:00
],
2011-08-04 16:57:16 +02:00
states={
2011-08-21 11:20:30 +02:00
'invisible': Eval('type') != 'writeoff',
'required': Eval('type') == 'writeoff',
2018-09-01 11:21:06 +02:00
},
depends=['company', 'type'])
2010-01-20 10:57:08 +01:00
amount = fields.Numeric('Payment Amount',
digits=(16, Eval('currency_digits', 2)),
2009-03-18 17:56:58 +01:00
readonly=True, depends=['currency_digits'])
currency = fields.Many2One('currency.currency', 'Payment Currency',
readonly=True)
currency_digits = fields.Integer('Payment Currency Digits', readonly=True)
amount_writeoff = fields.Numeric('Write-Off Amount',
2011-08-21 11:20:30 +02:00
digits=(16, Eval('currency_digits_writeoff', 2)), readonly=True,
depends=['currency_digits_writeoff', 'type'], states={
'invisible': Eval('type') != 'writeoff',
2009-03-18 17:56:58 +01:00
})
currency_writeoff = fields.Many2One('currency.currency',
2011-08-04 16:57:16 +02:00
'Write-Off Currency', readonly=True, states={
2011-08-21 11:20:30 +02:00
'invisible': Eval('type') != 'writeoff',
2011-08-04 16:57:16 +02:00
}, depends=['type'])
2009-03-18 17:56:58 +01:00
currency_digits_writeoff = fields.Integer('Write-Off Currency Digits',
required=True, readonly=True)
lines_to_pay = fields.Many2Many('account.move.line', None, None,
'Lines to Pay', readonly=True)
2009-03-05 14:51:35 +01:00
lines = fields.Many2Many('account.move.line', None, None, 'Lines',
2011-08-04 16:57:16 +02:00
domain=[
('id', 'in', Eval('lines_to_pay')),
2014-02-10 20:32:37 +01:00
('reconciliation', '=', None),
2010-01-20 10:57:08 +01:00
],
2011-08-04 16:57:16 +02:00
states={
2011-08-21 11:20:30 +02:00
'invisible': Eval('type') != 'writeoff',
2014-01-26 14:28:52 +01:00
},
2011-08-04 16:57:16 +02:00
depends=['lines_to_pay', 'type'])
payment_lines = fields.Many2Many('account.move.line', None, None,
2011-08-04 16:57:16 +02:00
'Payment Lines', readonly=True,
states={
2011-08-21 11:20:30 +02:00
'invisible': Eval('type') != 'writeoff',
2011-08-04 16:57:16 +02:00
}, depends=['type'])
company = fields.Many2One('company.company', 'Company', readonly=True)
2009-03-18 17:56:58 +01:00
invoice = fields.Many2One('account.invoice', 'Invoice', readonly=True)
date = fields.Date('Date', readonly=True)
2012-09-24 22:17:32 +02:00
@staticmethod
def default_type():
2009-03-18 17:56:58 +01:00
return 'partial'
2014-01-26 14:28:52 +01:00
@fields.depends('lines', 'amount', 'currency', 'currency_writeoff',
'invoice', 'payment_lines', 'date')
2012-09-24 22:17:32 +02:00
def on_change_lines(self):
Currency = Pool().get('currency.currency')
2009-03-18 17:56:58 +01:00
if self.currency and self.currency_writeoff:
with Transaction().set_context(date=self.date):
amount = Currency.compute(self.currency, self.amount,
self.currency_writeoff)
else:
amount = self.amount
2009-03-18 17:56:58 +01:00
2014-10-29 20:39:13 +01:00
self.amount_writeoff = Decimal('0.0')
if not self.invoice:
return
2012-09-24 22:17:32 +02:00
for line in self.lines:
2014-10-29 20:39:13 +01:00
self.amount_writeoff += line.debit - line.credit
for line in self.payment_lines:
self.amount_writeoff += line.debit - line.credit
2016-03-20 22:58:13 +01:00
if self.invoice.type == 'in':
2014-10-29 20:39:13 +01:00
self.amount_writeoff = - self.amount_writeoff - amount
2009-03-18 17:56:58 +01:00
else:
2014-10-29 20:39:13 +01:00
self.amount_writeoff = self.amount_writeoff - amount
2008-11-04 18:10:39 +01:00
class PayInvoice(Wizard):
'Pay Invoice'
2012-09-24 22:17:32 +02:00
__name__ = 'account.invoice.pay'
2012-01-02 17:57:03 +01:00
start = StateView('account.invoice.pay.start',
'account_invoice.pay_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2015-03-10 18:48:00 +01:00
Button('OK', 'choice', 'tryton-ok', default=True),
2012-01-02 17:57:03 +01:00
])
choice = StateTransition()
ask = StateView('account.invoice.pay.ask',
'account_invoice.pay_ask_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2015-03-10 18:48:00 +01:00
Button('OK', 'pay', 'tryton-ok', default=True),
2012-01-02 17:57:03 +01:00
])
pay = StateTransition()
2012-09-24 22:17:32 +02:00
@classmethod
def __setup__(cls):
super(PayInvoice, cls).__setup__()
cls._error_messages.update({
'amount_greater_amount_to_pay': ('On invoice "%s" you can not '
'create a partial payment with an amount greater than the '
'amount to pay.'),
2013-02-16 22:06:26 +01:00
})
2018-08-26 18:06:56 +02:00
cls.__rpc__['create'].fresh_session = True
2013-12-20 13:53:55 +01:00
def get_reconcile_lines_for_amount(self, invoice, amount):
2016-03-20 22:58:13 +01:00
if invoice.type == 'in':
amount *= -1
2013-12-20 13:53:55 +01:00
return invoice.get_reconcile_lines_for_amount(amount)
2012-09-24 22:17:32 +02:00
def default_start(self, fields):
Invoice = Pool().get('account.invoice')
2012-01-02 17:57:03 +01:00
default = {}
2012-09-24 22:17:32 +02:00
invoice = Invoice(Transaction().context['active_id'])
2018-09-01 11:21:06 +02:00
default['company'] = invoice.company.id
2012-01-02 17:57:03 +01:00
default['currency'] = invoice.currency.id
default['currency_digits'] = invoice.currency.digits
2012-04-03 20:06:19 +02:00
default['amount'] = (invoice.amount_to_pay_today
or invoice.amount_to_pay)
2012-01-02 17:57:03 +01:00
default['description'] = invoice.number
2018-09-01 11:21:06 +02:00
default['invoice_account'] = invoice.account.id
2012-01-02 17:57:03 +01:00
return default
2012-09-24 22:17:32 +02:00
def transition_choice(self):
pool = Pool()
Invoice = pool.get('account.invoice')
Currency = pool.get('currency.currency')
2012-09-24 22:17:32 +02:00
invoice = Invoice(Transaction().context['active_id'])
2012-09-24 22:17:32 +02:00
with Transaction().set_context(date=self.start.date):
amount = Currency.compute(self.start.currency,
self.start.amount, invoice.company.currency)
amount_invoice = Currency.compute(
self.start.currency, self.start.amount, invoice.currency)
2013-12-20 13:53:55 +01:00
_, remainder = self.get_reconcile_lines_for_amount(invoice, amount)
if (remainder == Decimal('0.0')
and amount_invoice <= invoice.amount_to_pay):
return 'pay'
return 'ask'
2012-09-24 22:17:32 +02:00
def default_ask(self, fields):
2011-07-03 13:18:43 +02:00
pool = Pool()
2012-09-24 22:17:32 +02:00
Invoice = pool.get('account.invoice')
Currency = pool.get('currency.currency')
2012-01-02 17:57:03 +01:00
default = {}
2012-09-24 22:17:32 +02:00
invoice = Invoice(Transaction().context['active_id'])
2012-01-02 17:57:03 +01:00
default['lines_to_pay'] = [x.id for x in invoice.lines_to_pay
if not x.reconciliation]
2012-09-24 22:17:32 +02:00
default['amount'] = self.start.amount
2016-11-24 16:42:48 +01:00
default['date'] = self.start.date
2012-09-24 22:17:32 +02:00
default['currency'] = self.start.currency.id
default['currency_digits'] = self.start.currency_digits
2012-01-02 17:57:03 +01:00
default['company'] = invoice.company.id
2012-09-24 22:17:32 +02:00
with Transaction().set_context(date=self.start.date):
2013-01-19 17:11:54 +01:00
amount = Currency.compute(self.start.currency,
self.start.amount, invoice.company.currency)
amount_invoice = Currency.compute(
self.start.currency, self.start.amount, invoice.currency)
2012-09-24 22:17:32 +02:00
if invoice.company.currency.is_zero(amount):
lines = invoice.lines_to_pay
else:
2013-12-20 13:53:55 +01:00
lines, _ = self.get_reconcile_lines_for_amount(invoice, amount)
default['lines'] = [x.id for x in lines]
2012-01-02 17:57:03 +01:00
for line_id in default['lines'][:]:
if line_id not in default['lines_to_pay']:
default['lines'].remove(line_id)
2016-03-04 00:23:47 +01:00
default['payment_lines'] = [x.id for x in invoice.payment_lines
if not x.reconciliation]
2012-01-02 17:57:03 +01:00
default['currency_writeoff'] = invoice.company.currency.id
default['currency_digits_writeoff'] = invoice.company.currency.digits
default['invoice'] = invoice.id
if (amount_invoice > invoice.amount_to_pay
2012-09-24 22:17:32 +02:00
or invoice.company.currency.is_zero(amount)):
2012-01-02 17:57:03 +01:00
default['type'] = 'writeoff'
return default
2012-09-24 22:17:32 +02:00
def transition_pay(self):
2011-07-03 13:18:43 +02:00
pool = Pool()
2012-09-24 22:17:32 +02:00
Invoice = pool.get('account.invoice')
Currency = pool.get('currency.currency')
MoveLine = pool.get('account.move.line')
2012-09-24 22:17:32 +02:00
invoice = Invoice(Transaction().context['active_id'])
2012-09-24 22:17:32 +02:00
with Transaction().set_context(date=self.start.date):
amount = Currency.compute(self.start.currency,
self.start.amount, invoice.company.currency)
2012-01-02 17:57:03 +01:00
reconcile_lines, remainder = \
2013-12-20 13:53:55 +01:00
self.get_reconcile_lines_for_amount(invoice, amount)
2012-03-27 10:43:07 +02:00
amount_second_currency = None
second_currency = None
2012-09-24 22:17:32 +02:00
if self.start.currency != invoice.company.currency:
amount_second_currency = self.start.amount
second_currency = self.start.currency
2016-03-20 22:58:13 +01:00
if (abs(amount) > abs(invoice.amount_to_pay)
and self.ask.type != 'writeoff'):
self.raise_user_error('amount_greater_amount_to_pay',
(invoice.rec_name,))
2012-09-24 22:17:32 +02:00
line = None
if not invoice.company.currency.is_zero(amount):
line = invoice.pay_invoice(amount,
2018-09-01 11:21:06 +02:00
self.start.payment_method, self.start.date,
2012-09-24 22:17:32 +02:00
self.start.description, amount_second_currency,
2012-01-02 17:57:03 +01:00
second_currency)
2012-01-02 17:57:03 +01:00
if remainder != Decimal('0.0'):
2012-09-24 22:17:32 +02:00
if self.ask.type == 'writeoff':
lines = [l for l in self.ask.lines] + \
[l for l in invoice.payment_lines
if not l.reconciliation]
if line and line not in lines:
# Add new payment line if payment_lines was cached before
# its creation
2012-09-24 22:17:32 +02:00
lines += [line]
if lines:
MoveLine.reconcile(lines,
2018-09-01 11:21:06 +02:00
writeoff=self.ask.writeoff,
2013-12-20 14:00:28 +01:00
date=self.start.date)
else:
2012-09-24 22:17:32 +02:00
if line:
reconcile_lines += [line]
2012-01-02 17:57:03 +01:00
if reconcile_lines:
2012-09-24 22:17:32 +02:00
MoveLine.reconcile(reconcile_lines)
2012-01-02 17:57:03 +01:00
return 'end'
2008-04-16 23:56:33 +02:00
2012-01-02 17:57:03 +01:00
class CreditInvoiceStart(ModelView):
2013-07-12 10:37:12 +02:00
'Credit Invoice'
2012-09-24 22:17:32 +02:00
__name__ = 'account.invoice.credit.start'
2013-02-16 22:06:26 +01:00
with_refund = fields.Boolean('With Refund',
states={
'readonly': ~Eval('with_refund_allowed'),
},
depends=['with_refund_allowed'],
2013-02-16 22:06:26 +01:00
help='If true, the current invoice(s) will be paid.')
with_refund_allowed = fields.Boolean("With Refund Allowed", readonly=True)
2008-04-16 23:56:33 +02:00
2008-10-03 17:12:23 +02:00
class CreditInvoice(Wizard):
'Credit Invoice'
2012-09-24 22:17:32 +02:00
__name__ = 'account.invoice.credit'
2012-01-02 17:57:03 +01:00
start = StateView('account.invoice.credit.start',
'account_invoice.credit_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Credit', 'credit', 'tryton-ok', default=True),
])
credit = StateAction('account_invoice.act_invoice_form')
2008-04-16 23:56:33 +02:00
2012-09-24 22:17:32 +02:00
@classmethod
def __setup__(cls):
super(CreditInvoice, cls).__setup__()
cls._error_messages.update({
2013-02-16 22:06:26 +01:00
'refund_non_posted': ('You can not credit with refund '
'invoice "%s" because it is not posted.'),
2013-02-16 22:06:26 +01:00
'refund_with_payement': ('You can not credit with refund '
'invoice "%s" because it has payments.'),
'refund_supplier': ('You can not credit with refund '
2013-06-03 15:15:17 +02:00
'invoice "%s" because it is a supplier '
'invoice/credit note.'),
2013-02-16 22:06:26 +01:00
})
2012-09-24 22:17:32 +02:00
def default_start(self, fields):
Invoice = Pool().get('account.invoice')
2012-01-02 17:57:03 +01:00
default = {
'with_refund': True,
'with_refund_allowed': True,
2012-09-24 22:17:32 +02:00
}
for invoice in Invoice.browse(Transaction().context['active_ids']):
if (invoice.state != 'posted'
or invoice.payment_lines
2016-03-20 22:58:13 +01:00
or invoice.type == 'in'):
2012-01-02 17:57:03 +01:00
default['with_refund'] = False
default['with_refund_allowed'] = False
break
2012-01-02 17:57:03 +01:00
return default
2012-09-24 22:17:32 +02:00
def do_credit(self, action):
2011-07-03 13:18:43 +02:00
pool = Pool()
2012-09-24 22:17:32 +02:00
Invoice = pool.get('account.invoice')
2008-04-16 23:56:33 +02:00
2012-09-24 22:17:32 +02:00
refund = self.start.with_refund
invoices = Invoice.browse(Transaction().context['active_ids'])
if refund:
2012-09-24 22:17:32 +02:00
for invoice in invoices:
2012-12-23 22:28:41 +01:00
if invoice.state != 'posted':
self.raise_user_error('refund_non_posted',
(invoice.rec_name,))
if invoice.payment_lines:
self.raise_user_error('refund_with_payement',
(invoice.rec_name,))
2016-03-20 22:58:13 +01:00
if invoice.type == 'in':
2013-06-03 15:15:17 +02:00
self.raise_user_error('refund_supplier', invoice.rec_name)
credit_invoices = Invoice.credit(invoices, refund=refund)
2008-04-16 23:56:33 +02:00
data = {'res_id': [i.id for i in credit_invoices]}
if len(credit_invoices) == 1:
2012-01-02 17:57:03 +01:00
action['views'].reverse()
return action, data