Files
tryton/invoice.py
T

3119 lines
115 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
2018-12-29 14:20:28 +01:00
from trytond.i18n import gettext
2018-12-23 17:31:21 +01:00
from trytond.model import Workflow, ModelView, ModelSQL, fields, \
sequence_ordered, Unique, DeactivableMixin, dualmethod
2018-12-29 14:20:28 +01:00
from trytond.model.exceptions import AccessError
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
2020-12-25 17:14:57 +01:00
from trytond.tools import reduce_ids, grouped_slice, firstline
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
2018-12-29 14:20:28 +01:00
from .exceptions import (
InvoiceTaxValidationError, InvoiceNumberError, InvoiceValidationError,
2021-01-28 11:01:11 +01:00
InvoiceLineValidationError, PayInvoiceError, InvoicePaymentTermDateWarning)
2018-12-29 14:20:28 +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'
_states = {
'readonly': Eval('state') != 'draft',
}
_depends = ['state']
2021-04-20 09:18:57 +02:00
company = fields.Many2One(
'company.company', 'Company', required=True, select=True,
states={
'readonly': _states['readonly'] | Eval('party', True),
},
depends=_depends + ['party'])
2016-10-31 17:29:23 +01:00
company_party = fields.Function(
fields.Many2One(
'party.party', "Company Party",
context={
'company': Eval('company', -1),
},
depends=['company']),
2016-10-31 17:29:23 +01:00
'on_change_with_company_party')
tax_identifier = fields.Many2One(
'party.identifier', "Tax Identifier",
states=_states, depends=_depends)
type = fields.Selection([
('out', "Customer"),
('in', "Supplier"),
], "Type", select=True, 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)
reference = fields.Char('Reference', size=None, states=_states,
depends=_depends)
description = fields.Char('Description', size=None, states=_states,
depends=_depends)
state = fields.Selection([
('draft', "Draft"),
('validated', "Validated"),
('posted', "Posted"),
('paid', "Paid"),
('cancelled', "Cancelled"),
], "State", readonly=True)
2009-07-18 14:27:14 +02:00
invoice_date = fields.Date('Invoice Date',
states={
'readonly': Eval('state').in_(['posted', 'paid', 'cancelled']),
2012-12-23 22:28:41 +01:00
'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)
2021-01-28 11:01:11 +01:00
payment_term_date = fields.Date(
"Payment Term Date", states=_states, depends=_depends,
help="The date from which the payment term is calculated.\n"
"Leave empty to use the invoice date.")
2021-01-13 00:40:53 +01:00
sequence = fields.Integer("Sequence", readonly=True)
party = fields.Many2One(
'party.party', 'Party', required=True, states=_states,
context={
'company': Eval('company', -1),
},
depends=_depends + ['company'])
2017-09-26 01:23:42 +02:00
party_tax_identifier = fields.Many2One(
'party.identifier', "Party Tax Identifier",
states=_states,
2017-09-26 01:23:42 +02:00
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',
required=True, states=_states, depends=_depends + ['party'],
2011-08-04 16:57:16 +02:00
domain=[('party', '=', Eval('party'))])
2008-05-09 17:26:41 +02:00
currency = fields.Many2One('currency.currency', 'Currency', required=True,
states={
'readonly': (
_states['readonly']
| (Eval('lines', [0]) & Eval('currency'))),
},
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')
journal = fields.Many2One(
'account.journal', 'Journal', required=True, states=_states,
context={
'company': Eval('company', -1),
},
depends=_depends + ['company'])
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={
'invisible': ~Eval('cancel_move'),
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,
states=_states, depends=_depends + [
'type', 'company', 'accounting_date', 'invoice_date'],
2010-01-20 10:57:08 +01:00
domain=[
2019-12-28 17:48:50 +01:00
('closed', '!=', True),
2014-12-03 16:13:16 +01:00
('company', '=', Eval('company', -1)),
2016-03-20 22:58:13 +01:00
If(Eval('type') == 'out',
2019-03-24 18:00:01 +01:00
('type.receivable', '=', True),
('type.payable', '=', True)),
],
context={
'date': If(Eval('accounting_date'),
Eval('accounting_date'),
Eval('invoice_date')),
})
2008-04-10 17:21:44 +02:00
payment_term = fields.Many2One('account.invoice.payment_term',
'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)),
('currency', '=', Eval('currency', -1)),
['OR',
('invoice_type', '=', Eval('type')),
('invoice_type', '=', None),
],
['OR',
('party', '=', Eval('party', -1)),
('party', '=', None),
],
2014-12-03 16:13:16 +01:00
],
states={
'readonly': (
(Eval('state') != 'draft')
| ~Eval('company')
| ~Eval('currency')),
},
depends=['state', 'company', 'currency', 'type', 'party'])
2008-04-10 17:21:44 +02:00
taxes = fields.One2Many('account.invoice.tax', 'invoice', 'Tax Lines',
states=_states, depends=_depends)
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.Many2Many(
'account.move.line', None, 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'])
2019-03-08 23:59:16 +01:00
reconciliation_lines = fields.Function(fields.Many2Many(
'account.move.line', None, None, "Payment Lines",
states={
'invisible': Eval('state') != 'paid',
}),
'get_reconciliation_lines')
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)
allow_cancel = fields.Function(
fields.Boolean("Allow Cancel Invoice"), 'get_allow_cancel')
2008-04-10 17:21:44 +02:00
del _states, _depends
2012-09-24 22:17:32 +02:00
@classmethod
def __setup__(cls):
super(Invoice, cls).__setup__()
2021-02-27 15:23:46 +01:00
cls._check_modify_exclude = {
'state', 'payment_lines', 'move', 'cancel_move',
'invoice_report_cache', 'invoice_report_format', 'lines'}
cls._order = [
2021-02-14 15:48:44 +01:00
('number', 'DESC NULLS FIRST'),
('id', 'DESC'),
]
2020-10-12 13:02:30 +02:00
cls.journal.domain = [
If(Eval('type') == 'out',
('type', 'in', cls._journal_types('out')),
('type', 'in', cls._journal_types('in'))),
]
cls.journal.depends += ['type']
2016-10-31 17:29:23 +01:00
cls.tax_identifier.domain = [
2018-10-08 16:01:35 +02:00
('party', '=', Eval('company_party', -1)),
2019-03-02 22:56:01 +01:00
('type', 'in', cls.tax_identifier_types()),
2016-10-31 17:29:23 +01:00
]
2018-10-08 16:01:35 +02:00
cls.tax_identifier.depends += ['company_party']
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'),
2021-02-27 15:23:46 +01:00
('posted', 'posted'),
2012-12-23 22:28:41 +01:00
('posted', 'paid'),
('validated', 'draft'),
('paid', 'posted'),
('draft', 'cancelled'),
('validated', 'cancelled'),
('posted', 'cancelled'),
('cancelled', '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': {
'invisible': ~Eval('allow_cancel', False),
'depends': ['allow_cancel'],
2012-03-25 00:17:27 +01:00
},
'draft': {
'invisible': (
~Eval('state').in_(['cancelled', 'validated'])
| ((Eval('state') == 'cancelled')
& Eval('cancel_move', -1))),
'icon': If(Eval('state') == 'cancelled', 'tryton-undo',
2018-08-19 20:02:58 +02:00
'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'),
],
2021-02-27 15:23:46 +01:00
'invisible': (~Eval('state').in_(['draft', 'validated'])
| ((Eval('state') == 'posted') & Bool(Eval('move')))),
'depends': ['state', 'move'],
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
},
'process': {
'invisible': ~Eval('state').in_(
['posted', 'paid']),
'depends': ['state'],
},
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')
# Migration from 5.6: rename state cancel to cancelled
cursor.execute(*sql_table.update(
[sql_table.state], ['cancelled'],
where=sql_table.state == 'cancel'))
2021-01-13 00:40:53 +01:00
# Migration from 5.8: drop foreign key for sequence
table.drop_fk('sequence')
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
2018-08-31 14:58:05 +02:00
@fields.depends('party', 'type', 'accounting_date', 'invoice_date')
def on_change_with_account(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':
account = self.party.account_receivable_used
2018-08-31 14:58:05 +02:00
elif self.type == 'in':
account = self.party.account_payable_used
return account.id if account else None
@fields.depends('type')
2012-09-24 22:17:32 +02:00
def on_change_type(self):
Journal = Pool().get('account.journal')
journal_type = {
'out': 'revenue',
'in': 'expense',
}.get(self.type or 'out', 'revenue')
2012-09-24 22:17:32 +02:00
journals = Journal.search([
('type', '=', journal_type),
2012-09-24 22:17:32 +02:00
], limit=1)
if journals:
2014-10-29 20:39:13 +01:00
self.journal, = journals
2008-04-10 17:21:44 +02:00
2020-10-12 13:02:30 +02:00
@classmethod
def _journal_types(cls, invoice_type):
if invoice_type == 'out':
return ['revenue']
else:
return ['expense']
2021-02-14 15:51:51 +01:00
@classmethod
def order_accounting_date(cls, tables):
table, _ = tables[None]
return [Coalesce(table.accounting_date, table.invoice_date)]
@fields.depends('party', 'type')
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
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
if self.type == 'out':
self.payment_term = self.party.customer_payment_term
elif self.type == 'in':
self.payment_term = self.party.supplier_payment_term
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')
if self.party and 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
2018-09-19 00:15:58 +02:00
def get_type_name(cls, invoices, name):
2012-09-24 22:17:32 +02:00
type_names = {}
2008-04-14 14:37:04 +02:00
type2name = {}
2018-09-19 00:15:58 +02:00
for type, name in cls.fields_get(fields_names=['type']
2010-08-19 22:02:49 +02:00
)['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
2020-02-12 00:19:27 +01:00
@fields.depends(methods=['_on_change_lines_taxes'])
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
2020-02-12 00:19:27 +01:00
@fields.depends(methods=['_on_change_lines_taxes'])
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
2020-02-12 00:19:27 +01:00
@fields.depends('lines', 'taxes', 'currency',
'accounting_date', 'invoice_date', # From tax_date
methods=['_get_taxes'])
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')
2014-10-29 20:39:13 +01:00
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
2019-11-01 22:50:21 +01:00
key = tax._key
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']
2019-09-07 23:45:08 +02:00
value = InvoiceTax.default_get(
list(InvoiceTax._fields.keys()), with_rec_name=False)
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, Decimal(0)) for i in invoices)
tax_amount = dict((i.id, Decimal(0)) for i in invoices)
total_amount = dict((i.id, Decimal(0)) 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:
# 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(
2020-03-01 12:33:51 +01:00
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:
# 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'), Decimal(0))
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):
2019-04-12 19:43:53 +02:00
def get_reconciliation(line):
if line.reconciliation and line.reconciliation.delegate_to:
return get_reconciliation(line.reconciliation.delegate_to)
else:
return line.reconciliation
reconciliations = list(map(get_reconciliation, self.lines_to_pay))
2017-12-20 19:25:26 +01:00
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,
2019-12-12 12:49:37 +01:00
where=red_sql,
order_by=(invoice.id, line.maturity_date.nulls_last)))
for invoice_id, line_id in cursor:
2014-06-06 13:00:19 +02:00
lines[invoice_id].append(line_id)
return lines
2019-03-08 23:59:16 +01:00
def get_reconciliation_lines(self, name):
if self.state != 'paid':
return
lines = set()
for line in self.move.lines:
if line.account == self.account and line.reconciliation:
for line in line.reconciliation.lines:
if line not in self.move.lines:
lines.add(line)
return [l.id for l in sorted(lines, key=lambda l: l.date)]
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, Decimal(0)) for x in invoices)
2013-06-26 22:46:51 +02:00
for invoice in invoices:
if invoice.state != 'posted':
continue
amount = Decimal(0)
amount_currency = Decimal(0)
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'
2019-12-12 12:49:37 +01:00
and (not line.maturity_date
or 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
if amount != Decimal(0):
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)
2018-09-17 17:53:24 +02:00
if invoice.type == 'in' and amount_currency:
2016-03-20 22:58:13 +01:00
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
2019-12-06 17:33:46 +01:00
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
2019-12-06 17:33:46 +01:00
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
2019-12-06 17:33:46 +01:00
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
def get_allow_cancel(self, name):
if self.state in {'draft', 'validated'}:
return True
if self.state == 'posted':
return self.type == 'in' or self.company.cancel_invoice_out
return False
2014-12-19 19:41:49 +01:00
@property
def taxable_lines(self):
taxable_lines = []
for line in self.lines:
2020-09-05 23:37:58 +02:00
if getattr(line, 'type', None) == 'line':
taxable_lines.extend(line.taxable_lines)
return taxable_lines
2014-12-19 19:41:49 +01:00
@property
def tax_date(self):
return self.accounting_date or self.invoice_date
@fields.depends('party', 'company')
2014-12-19 19:41:49 +01:00
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
2021-04-03 14:27:10 +02:00
if self.company:
context['company'] = self.company.id
2012-09-24 22:17:32 +02:00
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
@dualmethod
2012-09-24 22:17:32 +02:00
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:
if invoice.state in ('posted', 'paid', 'cancelled'):
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
2019-11-01 22:50:21 +01:00
key = tax._key
2014-12-02 12:54:39 +01:00
if (key not in computed_taxes) or (key in tax_keys):
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):
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:
to_create.append(computed_taxes[key])
2018-12-29 14:20:28 +01:00
if exception and (to_create or to_delete or to_write):
raise InvoiceTaxValidationError(
gettext('account_invoice.msg_invoice_tax_invalid',
invoice=invoice.rec_name))
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')
2021-01-28 11:01:11 +01:00
Warning = pool.get('res.user.warning')
Lang = pool.get('ir.lang')
today = Date.today()
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
self.update_taxes(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
2021-01-28 11:01:11 +01:00
term_lines = [(self.payment_term_date or today, total)]
2016-05-17 09:49:24 +02:00
if self.payment_term:
2021-01-28 11:01:11 +01:00
payment_date = self.payment_term_date or self.invoice_date
2016-05-17 09:49:24 +02:00
term_lines = self.payment_term.compute(
2021-01-28 11:01:11 +01:00
total, self.company.currency, payment_date)
2008-04-10 17:21:44 +02:00
remainder_total_currency = total_currency
for date, amount in term_lines:
2021-01-28 11:01:11 +01:00
if date < today:
lang = Lang.get()
warning_key = 'invoice_payment_term_%d' % self.id
if Warning.check(warning_key):
raise InvoicePaymentTermDateWarning(warning_key,
gettext('account_invoice'
'.msg_invoice_payment_term_date_past',
invoice=self.rec_name,
date=lang.strftime(date)))
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')
Lang = pool.get('ir.lang')
2021-01-13 00:40:53 +01:00
Sequence = pool.get('ir.sequence.strict')
today = Date.today()
def accounting_date(invoice):
return invoice.accounting_date or invoice.invoice_date or today
invoices = sorted(invoices, key=accounting_date)
sequences = set()
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 = today
invoice.number, invoice.sequence = invoice.get_next_number()
if invoice.type == 'out' and invoice.sequence not in sequences:
date = accounting_date(invoice)
# Do not need to lock the table
# because sequence.get_id is sequential
after_invoices = cls.search([
2021-01-13 00:40:53 +01:00
('sequence', '=', invoice.sequence),
['OR',
('accounting_date', '>', date),
[
('accounting_date', '=', None),
('invoice_date', '>', date),
],
],
2021-02-14 15:51:51 +01:00
], limit=1, order=[('accounting_date', 'DESC')])
if after_invoices:
after_invoice, = after_invoices
raise InvoiceNumberError(
gettext('account_invoice.msg_invoice_number_after',
invoice=invoice.rec_name,
2021-01-13 00:40:53 +01:00
sequence=Sequence(invoice.sequence).rec_name,
date=Lang.get().strftime(date),
after_invoice=after_invoice.rec_name))
sequences.add(invoice.sequence)
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):
2021-01-13 00:40:53 +01:00
"Return invoice number and sequence id used"
2017-03-30 09:45:15 +02:00
pool = Pool()
Period = pool.get('account.period')
if pattern is None:
pattern = {}
else:
pattern = pattern.copy()
accounting_date = self.accounting_date or self.invoice_date
2017-03-30 09:45:15 +02:00
period_id = Period.find(
self.company.id, date=accounting_date,
2017-03-30 09:45:15 +02:00
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)
for invoice_sequence in fiscalyear.invoice_sequences:
if invoice_sequence.match(pattern):
sequence = getattr(
invoice_sequence, self._sequence_field)
2017-03-30 09:45:15 +02:00
break
else:
2018-12-29 14:20:28 +01:00
raise InvoiceNumberError(
gettext('account_invoice.msg_invoice_no_sequence',
invoice=self.rec_name,
fiscalyear=fiscalyear.rec_name))
with Transaction().set_context(date=accounting_date):
2021-03-11 23:00:09 +01:00
return sequence.get(), sequence.id
2017-03-30 09:45:15 +02:00
@property
def _sequence_field(self):
"Returns the field name of invoice_sequence to use"
field = self.type
if (all(l.amount <= 0 for l in self.lines if l.product)
and self.total_amount < 0):
field += '_credit_note'
else:
field += '_invoice'
return field + '_sequence'
2016-10-31 17:29:23 +01:00
@classmethod
2019-03-02 22:56:01 +01:00
def tax_identifier_types(cls):
return Pool().get('party.party').tax_identifier_types()
2016-10-31 17:29:23 +01:00
def get_tax_identifier(self):
"Return the default computed tax identifier"
2019-03-02 22:56:01 +01:00
types = self.tax_identifier_types()
2016-10-31 17:29:23 +01:00
for identifier in self.company.party.identifiers:
if identifier.type in types:
return identifier.id
@property
def is_modifiable(self):
return not (self.state in {'posted', 'paid'}
or (self.state == 'cancelled'
and (self.move or self.cancel_move or self.number)))
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:
if not invoice.is_modifiable:
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account_invoice.msg_invoice_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):
2021-04-09 10:52:03 +02:00
return super().view_attributes() + [
2019-09-20 11:47:15 +02:00
('/form//field[@name="comment"]', 'spell', Eval('party_lang')),
('/tree', 'visual',
2020-03-01 12:33:51 +01:00
If((
(Eval('type') == 'out')
2019-09-20 11:47:15 +02:00
& (Eval('amount_to_pay_today', 0) > 0))
| ((Eval('type') == 'in')
& (Eval('amount_to_pay_today', 0) < 0)),
'danger',
If(Eval('state') == 'cancelled', 'muted', ''))),
2019-09-20 11:47:15 +02:00
]
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 != 'cancelled':
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account_invoice.msg_invoice_delete_cancel',
invoice=invoice.rec_name))
2012-12-23 22:28:41 +01:00
if invoice.number:
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account_invoice.msg_invoice_delete_numbered',
invoice=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):
2021-02-27 15:23:46 +01:00
if set(values) - cls._check_modify_exclude:
2013-12-19 14:44:39 +01:00
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)
2019-12-11 18:40:23 +01:00
default.setdefault('sequence')
2018-09-01 01:02:03 +02:00
default.setdefault('move', None)
default.setdefault('cancel_move', None)
default.setdefault('invoice_report_cache', None)
2020-07-13 20:57:00 +02:00
default.setdefault('invoice_report_cache_id', None)
2018-09-01 01:02:03 +02:00
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()
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):
2018-12-29 14:20:28 +01:00
raise InvoiceValidationError(
gettext('account_invoice.msg_invoice_same_account_line',
account=self.account.rec_name,
invoice=self.rec_name,
line=line.rec_name))
2008-04-10 17:21:44 +02: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):
2018-12-29 14:20:28 +01:00
raise InvoiceValidationError(
gettext('account_invoice'
'.msg_invoice_payment_lines_greater_amount',
invoice=self.rec_name))
2018-03-24 15:29:47 +01:00
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
2020-04-10 18:40:26 +02:00
def pay_invoice(self, amount, payment_method, date, description=None,
amount_second_currency=None, second_currency=None, overpayment=0):
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.
2020-04-10 18:40:26 +02:00
If overpayment is set, then only the amount minus the overpayment is
used to pay off the invoice.
Returns the payment lines.
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')
2020-04-10 18:40:26 +02:00
pay_line = Line(account=self.account)
counterpart_line = Line()
lines = [pay_line, counterpart_line]
2020-04-10 18:40:26 +02:00
pay_amount = amount - overpayment
if pay_amount >= 0:
2016-03-20 22:58:13 +01:00
if self.type == 'out':
2020-04-10 18:40:26 +02:00
pay_line.debit, pay_line.credit = 0, pay_amount
2016-03-20 22:58:13 +01:00
else:
2020-04-10 18:40:26 +02:00
pay_line.debit, pay_line.credit = pay_amount, 0
else:
2016-03-20 22:58:13 +01:00
if self.type == 'out':
2020-04-10 18:40:26 +02:00
pay_line.debit, pay_line.credit = -pay_amount, 0
2016-03-20 22:58:13 +01:00
else:
2020-04-10 18:40:26 +02:00
pay_line.debit, pay_line.credit = 0, -pay_amount
if overpayment:
overpayment_line = Line(account=self.account)
lines.insert(1, overpayment_line)
overpayment_line.debit = (
abs(overpayment) if pay_line.debit else 0)
overpayment_line.credit = (
abs(overpayment) if pay_line.credit else 0)
2012-10-10 17:54:13 +02:00
2020-04-10 18:40:26 +02:00
counterpart_line.debit = abs(amount) if pay_line.credit else 0
counterpart_line.credit = abs(amount) if pay_line.debit else 0
if counterpart_line.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'
2020-04-10 18:40:26 +02:00
counterpart_line.account = getattr(
payment_method, payment_acccount).current(date=date)
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,
origin=self, description=description,
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])
2020-04-10 18:40:26 +02:00
payment_lines = [l for l in move.lines if l.account == self.account]
payment_line = [l for l in payment_lines
if (l.debit, l.credit) == (pay_line.debit, pay_line.credit)][0]
self.add_payment_lines({self: [payment_line]})
return payment_lines
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':
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account_invoice'
'.msg_invoice_payment_lines_add_remove_paid',
invoice=invoice.rec_name))
2018-03-24 15:29:47 +01:00
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':
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account_invoice'
'.msg_invoice_payment_lines_add_remove_paid',
invoice=invoice.rec_name))
2018-03-24 15:29:47 +01:00
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], {})
2020-01-24 00:23:28 +01:00
def _credit(self, **values):
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
'''
2020-01-24 00:23:28 +01:00
credit = self.__class__(**values)
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
2020-01-24 00:23:28 +01:00
def credit(cls, invoices, refund=False, **values):
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
'''
2020-01-24 00:23:28 +01:00
new_invoices = [i._credit(**values) 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):
if invoice.state != 'posted':
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account_invoice'
'.msg_invoice_credit_refund_not_posted',
invoice=invoice.rec_name))
invoice.cancel_move = new_invoice.move
cls.save(invoices)
cls.cancel(invoices)
2012-09-24 22:17:32 +02:00
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)
2021-02-27 15:23:46 +01:00
@classmethod
@Workflow.transition('posted')
def post_batch(cls, invoices):
pool = Pool()
Date = pool.get('ir.date')
today = Date.today()
cls.set_number(invoices)
for invoice in invoices:
if not invoice.payment_term_date:
invoice.payment_term_date = today
cls.save(invoices)
with Transaction().set_context(_skip_warnings=True):
cls.__queue__._post(invoices)
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):
2021-02-27 15:23:46 +01:00
cls._post(invoices)
@classmethod
def _post(cls, invoices):
pool = Pool()
Move = pool.get('account.move')
2012-12-23 22:28:41 +01:00
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'])
2019-04-15 22:12:03 +02:00
reconciled = []
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()
2019-04-15 22:12:03 +02:00
if invoice.reconciled:
reconciled.append(invoice)
if reconciled:
cls.__queue__.process(reconciled)
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
@ModelView.button
2012-09-24 22:17:32 +02:00
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
cls._clean_payments(invoices)
2012-09-24 22:17:32 +02:00
@classmethod
2012-03-25 00:17:27 +01:00
@ModelView.button
@Workflow.transition('cancelled')
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:
2021-02-27 15:23:46 +01:00
if invoice.move or invoice.number:
2012-12-23 22:28:41 +01:00
if invoice.move.state == 'draft':
delete_moves.append(invoice.move)
elif not invoice.cancel_move:
2019-07-22 23:52:26 +02:00
if (invoice.type == 'out'
and not invoice.company.cancel_invoice_out):
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account_invoice'
'.msg_invoice_customer_cancel_move',
invoice=invoice.rec_name))
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': 'cancelled',
})
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:
to_reconcile.append(line)
Line.reconcile(to_reconcile)
cls._clean_payments(invoices)
@classmethod
def _clean_payments(cls, invoices):
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.id)
if to_remove:
to_write.append([invoice])
to_write.append({
2019-05-02 12:50:55 +02:00
'payment_lines': [('remove', to_remove)],
})
if to_write:
cls.write(*to_write)
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),
2018-12-29 14:20:28 +01:00
'account_invoice.msg_invoice_payment_line_unique'),
2018-03-24 15:29:47 +01:00
]
@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('get_invoice_states', "Invoice State"),
'on_change_with_invoice_state')
invoice_type = fields.Selection('get_invoice_types', "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
},
context={
'company': Eval('company', -1),
},
depends=['invoice', 'company'] + _depends)
2014-01-26 14:28:52 +01:00
party_lang = fields.Function(fields.Char('Party Language'),
'on_change_with_party_lang')
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')
2021-04-20 09:18:57 +02:00
company = fields.Many2One(
'company.company', "Company", required=True, 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',
domain=[
If(Bool(Eval('product_uom_category')),
('default_uom_category', '=', Eval('product_uom_category')),
()),
],
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
},
2020-04-25 09:18:06 +02:00
context={
'company': Eval('company', None),
},
depends=['type', 'product_uom_category', 'company'] + _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(Eval('_parent_invoice', {}).get('accounting_date'),
Eval('_parent_invoice', {}).get('accounting_date'),
Eval('_parent_invoice', {}).get('invoice_date')),
2018-08-31 14:58:05 +02:00
},
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)
2020-12-25 17:14:57 +01:00
summary = fields.Function(fields.Char('Summary'), 'on_change_with_summary')
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)
taxes_deductible_rate = fields.Numeric(
"Taxes Deductible Rate", digits=(14, 10),
domain=[
('taxes_deductible_rate', '>=', 0),
('taxes_deductible_rate', '<=', 1),
],
states={
'invisible': Eval('invoice_type') != 'in',
},
depends=['invoice_type'])
2020-09-05 23:37:58 +02:00
taxes_date = fields.Date(
"Taxes Date",
states={
'invisible': Eval('type') != 'line',
'readonly': _states['readonly'],
},
depends=['type'] + _depends,
help="The date at which the taxes are computed.\n"
"Leave empty for the accounting date.")
invoice_taxes = fields.Function(fields.Many2Many('account.invoice.tax',
None, 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__()
cls._check_modify_exclude = {'note', 'origin'}
# Set account domain dynamically for kind
cls.account.domain = [
2019-12-28 17:48:50 +01:00
('closed', '!=', True),
('company', '=', Eval('company', -1)),
If(Bool(Eval('_parent_invoice')),
2016-03-20 22:58:13 +01:00
If(Eval('_parent_invoice', {}).get('type') == 'out',
2019-03-24 18:00:01 +01:00
cls._account_domain('out'),
2016-03-20 22:58:13 +01:00
If(Eval('_parent_invoice', {}).get('type') == 'in',
2019-03-24 18:00:01 +01:00
cls._account_domain('in'),
['OR',
cls._account_domain('out'),
cls._account_domain('in')])),
2016-03-20 22:58:13 +01:00
If(Eval('invoice_type') == 'out',
2019-03-24 18:00:01 +01:00
cls._account_domain('out'),
2016-03-20 22:58:13 +01:00
If(Eval('invoice_type') == 'in',
2019-03-24 18:00:01 +01:00
cls._account_domain('in'),
['OR',
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':
2019-03-24 18:00:01 +01:00
return ['OR', ('type.revenue', '=', True)]
elif type_ == 'in':
return ['OR',
('type.expense', '=', True),
('type.debt', '=', True),
]
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__()
transaction = Transaction()
cursor = transaction.connection.cursor()
update = transaction.connection.cursor()
2012-09-24 22:17:32 +02:00
super(InvoiceLine, cls).__register__(module_name)
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))
for line_id, company_id in cursor:
update.execute(*sql_table.update([sql_table.company], [company_id],
2014-12-03 16:13:16 +01:00
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')
2018-12-23 17:31:21 +01:00
# Migration from 5.0: remove check constraints
table.drop_constraint('type_account')
table.drop_constraint('type_invoice')
@classmethod
def get_invoice_types(cls):
pool = Pool()
Invoice = pool.get('account.invoice')
return Invoice.fields_get(['type'])['type']['selection'] + [(None, '')]
@fields.depends(
'invoice', '_parent_invoice.currency', '_parent_invoice.company',
'_parent_invoice.type',
methods=['on_change_company'])
def on_change_invoice(self):
if self.invoice:
self.currency = self.invoice.currency
self.company = self.invoice.company
self.on_change_company()
self.invoice_type = self.invoice.type
@fields.depends('company', 'invoice',
'_parent_invoice.type', 'invoice_type')
def on_change_company(self):
invoice_type = self.invoice.type if self.invoice else self.invoice_type
if (invoice_type == 'in'
and self.company
and self.company.purchase_taxes_expense):
self.taxes_deductible_rate = 0
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'
@classmethod
def get_invoice_states(cls):
pool = Pool()
Invoice = pool.get('account.invoice')
return Invoice.fields_get(['state'])['state']['selection']
@fields.depends('invoice', '_parent_invoice.state')
def on_change_with_invoice_state(self, name=None):
if self.invoice:
return self.invoice.state
return 'draft'
@fields.depends('invoice', '_parent_invoice.party', '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')
if self.invoice and self.invoice.party:
party = self.invoice.party
else:
party = self.party
if party and party.lang:
return party.lang.code
2012-11-23 18:00:20 +01:00
return Config.get_language()
2020-12-25 17:14:57 +01:00
@fields.depends('description')
def on_change_with_summary(self, name=None):
return firstline(self.description or '')
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
@fields.depends(
'type', 'quantity', 'unit_price', 'taxes_deductible_rate', 'invoice',
'_parent_invoice.currency', 'currency', 'taxes',
'_parent_invoice.type', 'invoice_type',
methods=['_get_taxes'])
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')))
invoice_type = (
self.invoice.type if self.invoice else self.invoice_type)
if invoice_type == 'in' and self.taxes_deductible_rate != 1:
with Transaction().set_context(_deductible_rate=1):
tax_amount = sum(
t['amount'] for t in self._get_taxes().values())
non_deductible_amount = (
tax_amount * (1 - self.taxes_deductible_rate))
amount += non_deductible_amount
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 = Decimal(0)
2012-09-24 22:17:32 +02:00
for line2 in self.invoice.lines:
if line2.type == 'line':
subtotal += line2.on_change_with_amount()
2012-09-24 22:17:32 +02:00
elif line2.type == 'subtotal':
if self == line2:
break
subtotal = Decimal(0)
2012-09-24 22:17:32 +02:00
return subtotal
else:
return Decimal(0)
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
@classmethod
def default_taxes_deductible_rate(cls):
return 1
2014-12-19 19:41:49 +01:00
@property
def taxable_lines(self):
2020-09-05 23:37:58 +02:00
# In case we're called from an on_change we have to use some sensible
# defaults
context = Transaction().context
if (getattr(self, 'invoice', None)
and getattr(self.invoice, 'type', None)):
invoice_type = self.invoice.type
else:
invoice_type = getattr(self, 'invoice_type', None)
if invoice_type == 'in':
if context.get('_deductible_rate') is not None:
deductible_rate = context['_deductible_rate']
else:
deductible_rate = getattr(self, 'taxes_deductible_rate', 1)
if not deductible_rate:
return []
else:
deductible_rate = 1
2020-09-05 23:37:58 +02:00
return [(
getattr(self, 'taxes', None) or [],
((getattr(self, 'unit_price', None) or Decimal(0))
* deductible_rate),
2020-09-05 23:37:58 +02:00
getattr(self, 'quantity', None) or 0,
getattr(self, 'tax_date', None),
)]
2014-12-19 19:41:49 +01:00
@property
def tax_date(self):
if getattr(self, 'taxes_date', None):
return self.taxes_date
elif hasattr(self, 'invoice') and hasattr(self.invoice, 'tax_date'):
return self.invoice.tax_date
2014-12-19 19:41:49 +01:00
def _get_tax_context(self):
if self.invoice:
return self.invoice._get_tax_context()
else:
2021-04-03 14:27:10 +02:00
context = {}
if self.company:
context['company'] = self.company.id
return context
2014-12-19 19:41:49 +01:00
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
2019-11-01 22:50:21 +01:00
key = tax._key
2012-09-24 22:17:32 +02:00
if key in taxes_keys:
taxes.append(tax.id)
return taxes
2020-09-05 22:12:11 +02:00
@fields.depends('invoice',
'_parent_invoice.accounting_date', '_parent_invoice.invoice_date')
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
'''
2020-09-05 22:12:11 +02:00
if self.invoice:
date = self.invoice.accounting_date or self.invoice.invoice_date
else:
date = None
return {
'date': date,
}
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',
'company',
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
if self.company and self.company.purchase_taxes_expense:
self.taxes_deductible_rate = 0
else:
self.taxes_deductible_rate = (
self.product.supplier_taxes_deductible_rate_used)
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')
2020-12-27 00:19:30 +01:00
get_name = Model.get_name
2013-03-23 15:37:44 +01:00
models = cls._get_origin()
2020-12-27 00:19:30 +01:00
return [(None, '')] + [(m, get_name(m)) for m in models]
2013-03-23 15:37:44 +01:00
2019-03-04 18:53:11 +01:00
def get_rec_name(self, name):
2019-11-26 22:20:24 +01:00
pool = Pool()
Lang = pool.get('ir.lang')
2019-03-04 18:53:11 +01:00
if self.product:
2019-11-26 22:20:24 +01:00
lang = Lang.get()
prefix = (lang.format(
'%.*f', (self.unit.digits, self.quantity or 0))
+ '%s %s' % (self.unit.symbol, self.product.rec_name))
elif self.account:
2019-03-04 18:53:11 +01:00
prefix = self.account.rec_name
else:
prefix = '(%s)' % self.id
2019-03-04 18:53:11 +01:00
if self.invoice:
return '%s @ %s' % (prefix, self.invoice.rec_name)
else:
return prefix
@classmethod
def search_rec_name(cls, name, clause):
return ['OR',
('invoice.rec_name',) + tuple(clause[1:]),
('product.rec_name',) + tuple(clause[1:]),
('account.rec_name',) + tuple(clause[1:]),
]
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 not line.invoice.is_modifiable:
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account_invoice.msg_invoice_line_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):
2021-04-09 10:52:03 +02:00
return super().view_attributes() + [
2015-09-04 12:02:55 +02:00
('/form//field[@name="note"]|/form//field[@name="description"]',
'spell', Eval('party_lang'))]
2015-03-09 19:41:10 +01:00
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):
if invoice.state in ('posted', 'paid', 'cancelled'):
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account_invoice.msg_invoice_line_create',
invoice=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):
2018-12-29 14:20:28 +01:00
raise InvoiceLineValidationError(
gettext('account_invoice.msg_invoice_same_account_line',
account=self.account.rec_name,
invoice=self.invoice.rec_name,
line=self.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.origin = self
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))
2020-09-05 23:37:58 +02:00
line.taxes_date = self.tax_date
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',
2021-02-26 15:41:50 +00:00
select=True, required=True,
states={
'readonly': _states['readonly'] & Bool(Eval('invoice')),
},
depends=_depends)
invoice_state = fields.Function(
fields.Selection('get_invoice_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=[
2019-03-24 18:00:01 +01:00
('type', '!=', None),
('closed', '!=', True),
2011-08-21 11:20:30 +02:00
('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)
2020-10-09 19:09:05 +02:00
currency = fields.Function(fields.Many2One('currency.currency',
'Currency'), 'on_change_with_currency')
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
2021-03-07 11:13:53 +01:00
@classmethod
def __setup__(cls):
super().__setup__()
cls.__access__.add('invoice')
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
2020-03-30 09:58:20 +02:00
@classmethod
def default_invoice_state(cls):
pool = Pool()
Invoice = pool.get('account.invoice')
return Invoice.default_state()
@classmethod
def get_invoice_states(cls):
pool = Pool()
Invoice = pool.get('account.invoice')
return Invoice.fields_get(['state'])['state']['selection']
@fields.depends('invoice', '_parent_invoice.state')
def on_change_with_invoice_state(self, name=None):
if self.invoice:
return self.invoice.state
2020-10-09 19:09:05 +02:00
@fields.depends('invoice', '_parent_invoice.currency')
def on_change_with_currency(self, name=None):
if self.invoice:
return self.invoice.currency.id
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
2019-11-01 22:50:21 +01:00
@property
def _key(self):
# Same as _TaxKey
tax_id = self.tax.id if self.tax else -1
return (self.account.id, tax_id, self.base >= 0)
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:
if not tax.invoice.is_modifiable:
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account_invoice.msg_invoice_tax_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):
if invoice.state in ('posted', 'paid', 'cancelled'):
2018-12-29 14:20:28 +01:00
raise AccessError(
gettext('account_invoice.msg_invoice_tax_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
line.origin = self
2019-06-16 19:14:46 +02:00
if self.tax:
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')],
context={
'company': Eval('company', -1),
},
depends=['company'])
2018-09-01 11:21:06 +02:00
credit_account = fields.Many2One('account.account', "Credit Account",
required=True,
domain=[
2019-03-24 18:00:01 +01:00
('type', '!=', None),
('closed', '!=', True),
2018-09-01 11:21:06 +02:00
('company', '=', Eval('company')),
],
depends=['company'])
debit_account = fields.Many2One('account.account', "Debit Account",
required=True,
domain=[
2019-03-24 18:00:01 +01:00
('type', '!=', None),
('closed', '!=', True),
2018-09-01 11:21:06 +02:00
('company', '=', Eval('company')),
],
depends=['company'])
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('name', 'ASC'))
2018-09-01 11:21:06 +02:00
@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
2021-02-21 19:19:26 +01:00
def _execute(cls, records, header, data, action):
2017-09-03 11:17:09 +02:00
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:
2021-02-21 19:19:26 +01:00
result = super()._execute(records, header, 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 render(cls, *args, **kwargs):
# Reset to default language to always have header and footer rendered
# in the default language
with Transaction().set_context(language=False):
return super().render(*args, **kwargs)
2017-09-03 11:17:09 +02:00
@classmethod
def execute(cls, ids, data):
with Transaction().set_context(address_with_party=True):
2020-09-24 10:29:09 +02:00
return super().execute(ids, data)
2012-09-24 22:17:32 +02:00
@classmethod
2021-02-21 19:19:26 +01:00
def get_context(cls, records, header, data):
pool = Pool()
Date = pool.get('ir.date')
2021-02-21 19:19:26 +01:00
context = super().get_context(records, header, data)
2017-09-03 11:17:09 +02:00
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(
2020-03-01 12:33:51 +01:00
'account.invoice.payment.method', "Payment Method", required=True,
2018-09-01 11:21:06 +02:00
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([
2020-04-10 18:40:26 +02:00
('writeoff', "Write-Off"),
('partial', "Partial Payment"),
('overpayment', "Overpayment"),
], '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__()
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):
2012-01-02 17:57:03 +01:00
default = {}
invoice = self.record
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)
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()
Currency = pool.get('currency.currency')
invoice = self.record
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
Currency = pool.get('currency.currency')
2012-01-02 17:57:03 +01:00
default = {}
invoice = self.record
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
Currency = pool.get('currency.currency')
MoveLine = pool.get('account.move.line')
2018-12-29 14:20:28 +01:00
Lang = pool.get('ir.lang')
invoice = self.record
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)
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
2020-04-10 18:40:26 +02:00
overpayment = 0
if (0 <= invoice.amount_to_pay < amount_invoice
2020-04-10 18:40:26 +02:00
or amount_invoice < invoice.amount_to_pay <= 0):
if self.ask.type == 'partial':
lang = Lang.get()
raise PayInvoiceError(
gettext('account_invoice'
'.msg_invoice_pay_amount_greater_amount_to_pay',
invoice=invoice.rec_name,
amount_to_pay=lang.currency(
invoice.amount_to_pay, invoice.currency)))
else:
overpayment = amount_invoice - invoice.amount_to_pay
2020-04-10 18:40:26 +02:00
lines = []
2012-09-24 22:17:32 +02:00
if not invoice.company.currency.is_zero(amount):
2020-04-10 18:40:26 +02:00
lines = 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,
2020-04-10 18:40:26 +02:00
second_currency, overpayment)
2020-04-10 18:40:26 +02:00
if remainder:
if self.ask.type != 'partial':
to_reconcile = {l for l in self.ask.lines}
to_reconcile.update(
l for l in invoice.payment_lines
if not l.reconciliation)
if self.ask.type == 'writeoff':
to_reconcile.update(lines)
if to_reconcile:
MoveLine.reconcile(
to_reconcile,
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:
2020-04-10 18:40:26 +02:00
reconcile_lines += lines
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'
2020-01-24 00:23:28 +01:00
invoice_date = fields.Date("Invoice Date")
2013-02-16 22:06:26 +01:00
with_refund = fields.Boolean('With Refund',
states={
'readonly': ~Eval('with_refund_allowed'),
'invisible': ~Eval('with_refund_allowed'),
},
depends=['with_refund_allowed'],
help='If true, the current invoice(s) will be cancelled.')
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
def default_start(self, fields):
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 self.records:
if invoice.state != 'posted' or invoice.type == 'in':
2012-01-02 17:57:03 +01:00
default['with_refund'] = False
default['with_refund_allowed'] = False
break
if invoice.payment_lines:
default['with_refund'] = False
2012-01-02 17:57:03 +01:00
return default
2020-06-24 23:00:48 +02:00
@property
def _credit_options(self):
return dict(
refund=self.start.with_refund,
invoice_date=self.start.invoice_date,
)
2012-09-24 22:17:32 +02:00
def do_credit(self, action):
credit_invoices = self.model.credit(
self.records, **self._credit_options)
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