2013-06-27 17:38:40 +02:00
|
|
|
# This file is part of account_bank module for Tryton.
|
|
|
|
# The COPYRIGHT file at the top level of this repository contains
|
|
|
|
# the full copyright notices and license terms.
|
2016-11-11 17:36:52 +01:00
|
|
|
from sql import Null
|
|
|
|
from sql.aggregate import BoolOr
|
|
|
|
from sql.operators import In
|
2014-03-05 18:44:58 +01:00
|
|
|
from decimal import Decimal
|
2014-11-04 11:22:05 +01:00
|
|
|
|
2014-04-03 20:00:30 +02:00
|
|
|
from trytond.model import ModelView, fields
|
2013-06-27 17:38:40 +02:00
|
|
|
from trytond.pool import Pool, PoolMeta
|
2014-05-06 11:44:21 +02:00
|
|
|
from trytond.pyson import Eval, Bool, If
|
2013-06-27 17:38:40 +02:00
|
|
|
from trytond.transaction import Transaction
|
2014-03-05 18:44:58 +01:00
|
|
|
from trytond.wizard import Wizard, StateTransition, StateView, Button
|
|
|
|
|
2016-12-29 15:41:33 +01:00
|
|
|
__all__ = ['PaymentType', 'BankAccount', 'Party', 'Invoice', 'Reconciliation',
|
2015-09-09 16:42:39 +02:00
|
|
|
'Line', 'CompensationMoveStart', 'CompensationMove']
|
2013-06-27 17:38:40 +02:00
|
|
|
|
2014-05-06 11:44:21 +02:00
|
|
|
ACCOUNT_BANK_KIND = [
|
|
|
|
('none', 'None'),
|
|
|
|
('party', 'Party'),
|
|
|
|
('company', 'Company'),
|
|
|
|
('other', 'Other'),
|
|
|
|
]
|
|
|
|
|
2013-06-27 17:38:40 +02:00
|
|
|
|
2018-08-17 22:59:55 +02:00
|
|
|
class PaymentType(metaclass=PoolMeta):
|
2013-06-27 17:38:40 +02:00
|
|
|
__name__ = 'account.payment.type'
|
2016-02-10 16:22:13 +01:00
|
|
|
account_bank = fields.Selection(ACCOUNT_BANK_KIND, 'Account Bank Kind',
|
2014-05-06 11:44:21 +02:00
|
|
|
select=True, required=True)
|
|
|
|
party = fields.Many2One('party.party', 'Party',
|
|
|
|
states={
|
|
|
|
'required': Eval('account_bank') == 'other',
|
|
|
|
'invisible': Eval('account_bank') != 'other',
|
|
|
|
},
|
|
|
|
depends=['account_bank'])
|
2016-12-29 15:41:33 +01:00
|
|
|
bank_account = fields.Many2One('bank.account', 'Bank Account',
|
|
|
|
domain=[
|
|
|
|
If(Eval('party', None) == None,
|
|
|
|
('id', '=', -1),
|
|
|
|
('owners.id', '=', Eval('party')),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
states={
|
|
|
|
'required': Eval('account_bank') == 'other',
|
|
|
|
'invisible': Eval('account_bank') != 'other',
|
|
|
|
},
|
|
|
|
depends=['party', 'account_bank'])
|
2013-06-27 17:38:40 +02:00
|
|
|
|
2014-10-30 13:42:24 +01:00
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
|
|
|
super(PaymentType, cls).__setup__()
|
|
|
|
cls._check_modify_fields |= set(['account_bank', 'party',
|
|
|
|
'bank_account'])
|
|
|
|
|
2013-06-27 17:38:40 +02:00
|
|
|
@staticmethod
|
|
|
|
def default_account_bank():
|
|
|
|
return 'none'
|
|
|
|
|
|
|
|
|
2018-08-17 22:59:55 +02:00
|
|
|
class BankAccount(metaclass=PoolMeta):
|
2015-09-09 16:42:39 +02:00
|
|
|
__name__ = 'bank.account'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
|
|
|
super(BankAccount, cls).__setup__()
|
|
|
|
cls._check_owners_fields = set(['owners'])
|
|
|
|
cls._check_owners_related_models = set([
|
2018-09-10 15:11:13 +02:00
|
|
|
('account.move.line', 'bank_account'),
|
2016-02-18 14:54:10 +01:00
|
|
|
('account.invoice', 'bank_account'),
|
2015-09-09 16:42:39 +02:00
|
|
|
])
|
|
|
|
cls._error_messages.update({
|
2016-12-29 16:09:10 +01:00
|
|
|
'modify_with_related_model': ('It is not possible to modify '
|
2015-09-09 16:42:39 +02:00
|
|
|
'the owner of bank account "%(account)s" as it is used on '
|
2016-12-29 16:09:10 +01:00
|
|
|
'the %(field)s of %(model)s "%(name)s"'),
|
2015-09-09 16:42:39 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def write(cls, *args):
|
|
|
|
actions = iter(args)
|
|
|
|
all_accounts = []
|
|
|
|
for accounts, values in zip(actions, actions):
|
|
|
|
if set(values.keys()) & cls._check_owners_fields:
|
|
|
|
all_accounts += accounts
|
|
|
|
super(BankAccount, cls).write(*args)
|
|
|
|
cls.check_owners(all_accounts)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def check_owners(cls, accounts):
|
|
|
|
pool = Pool()
|
|
|
|
IrModel = pool.get('ir.model')
|
|
|
|
Field = pool.get('ir.model.field')
|
|
|
|
account_ids = [a.id for a in accounts]
|
|
|
|
for value in cls._check_owners_related_models:
|
2016-02-18 14:54:10 +01:00
|
|
|
model_name, field_name = value
|
2015-09-09 16:42:39 +02:00
|
|
|
Model = pool.get(model_name)
|
|
|
|
records = Model.search([(field_name, 'in', account_ids)])
|
|
|
|
model, = IrModel.search([('model', '=', model_name)])
|
|
|
|
field, = Field.search([
|
|
|
|
('model.model', '=', model_name),
|
|
|
|
('name', '=', field_name),
|
|
|
|
], limit=1)
|
|
|
|
for record in records:
|
2016-02-18 14:54:10 +01:00
|
|
|
target = record.account_bank_from
|
2015-09-09 16:42:39 +02:00
|
|
|
account = getattr(record, field_name)
|
|
|
|
if target not in account.owners:
|
|
|
|
error_args = {
|
|
|
|
'account': account.rec_name,
|
|
|
|
'model': model.name,
|
|
|
|
'field': field.field_description,
|
|
|
|
'name': record.rec_name,
|
|
|
|
}
|
2016-12-29 16:09:10 +01:00
|
|
|
cls.raise_user_error('modify_with_related_model',
|
2015-09-09 16:42:39 +02:00
|
|
|
error_args)
|
|
|
|
|
|
|
|
|
2018-08-17 22:59:55 +02:00
|
|
|
class Party(metaclass=PoolMeta):
|
2016-12-29 15:41:33 +01:00
|
|
|
__name__ = 'party.party'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def write(cls, *args):
|
|
|
|
pool = Pool()
|
|
|
|
BankAccount = pool.get('bank.account')
|
|
|
|
actions = iter(args)
|
|
|
|
all_accounts = []
|
|
|
|
for parties, values in zip(actions, actions):
|
|
|
|
if set(values.keys()) & set(['bank_accounts']):
|
|
|
|
all_accounts += list(set(
|
|
|
|
[a for p in parties for a in p.bank_accounts]))
|
|
|
|
super(Party, cls).write(*args)
|
|
|
|
BankAccount.check_owners(all_accounts)
|
|
|
|
|
|
|
|
|
2016-03-29 11:43:10 +02:00
|
|
|
class BankMixin(object):
|
2014-05-06 11:44:21 +02:00
|
|
|
account_bank = fields.Function(fields.Selection(ACCOUNT_BANK_KIND,
|
2014-05-26 17:14:06 +02:00
|
|
|
'Account Bank'),
|
2014-05-06 11:44:21 +02:00
|
|
|
'on_change_with_account_bank')
|
2013-06-27 17:38:40 +02:00
|
|
|
account_bank_from = fields.Function(fields.Many2One('party.party',
|
2014-05-26 17:14:06 +02:00
|
|
|
'Account Bank From'),
|
2013-06-27 17:38:40 +02:00
|
|
|
'on_change_with_account_bank_from')
|
|
|
|
bank_account = fields.Many2One('bank.account', 'Bank Account',
|
|
|
|
domain=[
|
2016-12-29 15:41:33 +01:00
|
|
|
If(Eval('account_bank_from', None) == None,
|
|
|
|
('id', '=', -1),
|
|
|
|
('owners.id', '=', Eval('account_bank_from')),
|
|
|
|
),
|
2013-06-27 17:38:40 +02:00
|
|
|
],
|
|
|
|
states={
|
2016-12-29 15:41:33 +01:00
|
|
|
'readonly': Eval('account_bank') == 'other',
|
2014-05-06 11:44:21 +02:00
|
|
|
'invisible': ~Bool(Eval('account_bank_from')),
|
2016-11-11 17:38:49 +01:00
|
|
|
},
|
2016-02-05 10:52:23 +01:00
|
|
|
depends=['party', 'payment_type', 'account_bank_from', 'account_bank'],
|
|
|
|
ondelete='RESTRICT')
|
2013-06-27 17:38:40 +02:00
|
|
|
|
2014-05-26 17:14:06 +02:00
|
|
|
@fields.depends('payment_type')
|
2014-05-06 11:44:21 +02:00
|
|
|
def on_change_with_account_bank(self, name=None):
|
|
|
|
if self.payment_type:
|
|
|
|
return self.payment_type.account_bank
|
2013-06-27 17:38:40 +02:00
|
|
|
|
2015-08-28 23:19:47 +02:00
|
|
|
def _get_bank_account(self):
|
2016-12-29 15:41:33 +01:00
|
|
|
pool = Pool()
|
|
|
|
Party = pool.get('party.party')
|
|
|
|
|
|
|
|
self.bank_account = None
|
|
|
|
if self.party and self.payment_type:
|
|
|
|
if self.payment_type.account_bank == 'other':
|
|
|
|
self.bank_account = self.payment_type.bank_account
|
|
|
|
else:
|
|
|
|
party_fname = '%s_bank_account' % self.payment_type.kind
|
|
|
|
if hasattr(Party, party_fname):
|
|
|
|
account_bank = self.payment_type.account_bank
|
|
|
|
if account_bank == 'company':
|
|
|
|
party_company_fname = ('%s_company_bank_account' %
|
|
|
|
self.payment_type.kind)
|
|
|
|
company_bank = getattr(self.party, party_company_fname,
|
|
|
|
None)
|
|
|
|
if company_bank:
|
|
|
|
self.bank_account = company_bank
|
|
|
|
elif hasattr(self, 'company') and self.company:
|
|
|
|
default_bank = getattr(
|
|
|
|
self.company.party, party_fname)
|
|
|
|
self.bank_account = default_bank
|
|
|
|
return
|
|
|
|
elif account_bank == 'party' and self.party:
|
|
|
|
default_bank = getattr(self.party, party_fname)
|
|
|
|
self.bank_account = default_bank
|
|
|
|
return
|
|
|
|
|
2018-08-17 22:59:48 +02:00
|
|
|
@fields.depends('party', 'payment_type',
|
|
|
|
methods=['on_change_with_payment_type'])
|
2014-10-16 20:00:12 +02:00
|
|
|
def on_change_with_bank_account(self):
|
2013-06-27 17:38:40 +02:00
|
|
|
'''
|
2014-10-16 20:00:12 +02:00
|
|
|
Add account bank when changes payment_type or party.
|
2013-06-27 17:38:40 +02:00
|
|
|
'''
|
2016-12-29 15:41:33 +01:00
|
|
|
self._get_bank_account()
|
|
|
|
return self.bank_account.id if self.bank_account else None
|
2013-06-27 17:38:40 +02:00
|
|
|
|
2018-08-17 22:59:48 +02:00
|
|
|
@fields.depends('payment_type', 'party',
|
|
|
|
methods=['on_change_with_payment_type'])
|
2014-05-06 11:44:21 +02:00
|
|
|
def on_change_with_account_bank_from(self, name=None):
|
2013-06-27 17:38:40 +02:00
|
|
|
'''
|
2014-05-06 11:44:21 +02:00
|
|
|
Sets the party where get bank account for this move line.
|
2013-06-27 17:38:40 +02:00
|
|
|
'''
|
2018-02-15 12:09:20 +01:00
|
|
|
Company = Pool().get('company.company')
|
2015-08-28 23:19:47 +02:00
|
|
|
|
2016-12-29 15:41:33 +01:00
|
|
|
if self.payment_type and self.party:
|
2014-05-06 11:44:21 +02:00
|
|
|
payment_type = self.payment_type
|
2016-12-29 15:41:33 +01:00
|
|
|
party = self.party
|
2014-05-06 11:44:21 +02:00
|
|
|
if payment_type.account_bank == 'party':
|
2016-12-29 15:41:33 +01:00
|
|
|
return party.id
|
2014-05-06 11:44:21 +02:00
|
|
|
elif payment_type.account_bank == 'company':
|
2016-12-29 16:12:03 +01:00
|
|
|
company = Transaction().context.get('company')
|
|
|
|
if company:
|
|
|
|
return Company(company).party.id
|
2014-05-06 11:44:21 +02:00
|
|
|
elif payment_type.account_bank == 'other':
|
|
|
|
return payment_type.party.id
|
2014-01-13 15:56:01 +01:00
|
|
|
|
2014-05-06 11:44:21 +02:00
|
|
|
|
2018-08-17 22:59:55 +02:00
|
|
|
class Invoice(BankMixin, metaclass=PoolMeta):
|
2014-05-06 11:44:21 +02:00
|
|
|
__name__ = 'account.invoice'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
|
|
|
super(Invoice, cls).__setup__()
|
|
|
|
readonly = ~Eval('state').in_(['draft', 'validated'])
|
|
|
|
previous_readonly = cls.bank_account.states.get('readonly')
|
|
|
|
if previous_readonly:
|
|
|
|
readonly = readonly | previous_readonly
|
|
|
|
cls.bank_account.states.update({
|
|
|
|
'readonly': readonly,
|
|
|
|
})
|
|
|
|
cls._error_messages.update({
|
2016-04-11 15:10:07 +02:00
|
|
|
'invoice_without_bank_account': ('Invoice "%(invoice)s" has '
|
|
|
|
'no bank account associated but payment type '
|
2014-07-15 16:37:31 +02:00
|
|
|
'"%(payment_type)s" requires it.'),
|
2014-05-06 11:44:21 +02:00
|
|
|
})
|
2013-06-27 17:38:40 +02:00
|
|
|
|
2016-12-29 15:41:33 +01:00
|
|
|
@fields.depends('payment_type', 'party', 'company')
|
2015-11-05 12:54:53 +01:00
|
|
|
def on_change_party(self):
|
|
|
|
'''
|
|
|
|
Add account bank to invoice line when changes party.
|
|
|
|
'''
|
|
|
|
super(Invoice, self).on_change_party()
|
|
|
|
self.bank_account = None
|
|
|
|
if self.payment_type:
|
2016-12-29 15:41:33 +01:00
|
|
|
self._get_bank_account()
|
2015-11-05 12:54:53 +01:00
|
|
|
|
2013-06-27 17:38:40 +02:00
|
|
|
@classmethod
|
|
|
|
def post(cls, invoices):
|
|
|
|
'''
|
|
|
|
Check up invoices that requires bank account because its payment type,
|
|
|
|
has one
|
|
|
|
'''
|
2017-09-29 16:09:54 +02:00
|
|
|
to_save = []
|
2013-06-27 17:38:40 +02:00
|
|
|
for invoice in invoices:
|
2014-01-13 15:56:01 +01:00
|
|
|
account_bank = (invoice.payment_type and
|
|
|
|
invoice.payment_type.account_bank or 'none')
|
|
|
|
if (invoice.payment_type and account_bank != 'none'
|
2015-02-05 13:16:37 +01:00
|
|
|
and not invoice.bank_account):
|
2017-09-29 16:09:54 +02:00
|
|
|
invoice._get_bank_account()
|
|
|
|
if not invoice.bank_account:
|
|
|
|
cls.raise_user_error('invoice_without_bank_account', {
|
|
|
|
'invoice': invoice.rec_name,
|
|
|
|
'payment_type': invoice.payment_type.rec_name,
|
|
|
|
})
|
|
|
|
to_save.append(invoice)
|
|
|
|
if to_save:
|
|
|
|
cls.save(to_save)
|
2014-02-10 12:02:06 +01:00
|
|
|
super(Invoice, cls).post(invoices)
|
2013-06-27 17:38:40 +02:00
|
|
|
|
2018-09-10 15:11:13 +02:00
|
|
|
def _get_move_line(self, date, amount):
|
|
|
|
'''Add account bank to move line when post invoice.'''
|
|
|
|
line = super(Invoice, self)._get_move_line(date, amount)
|
|
|
|
if self.bank_account:
|
|
|
|
line.bank_account = self.bank_account
|
|
|
|
return line
|
|
|
|
|
2013-06-27 17:38:40 +02:00
|
|
|
|
2018-08-17 22:59:55 +02:00
|
|
|
class Reconciliation(metaclass=PoolMeta):
|
2014-03-05 18:44:58 +01:00
|
|
|
__name__ = 'account.move.reconciliation'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def create(cls, vlist):
|
|
|
|
Invoice = Pool().get('account.invoice')
|
|
|
|
reconciliations = super(Reconciliation, cls).create(vlist)
|
|
|
|
moves = set()
|
|
|
|
for reconciliation in reconciliations:
|
|
|
|
moves |= set(l.move for l in reconciliation.lines)
|
2018-09-06 13:51:22 +02:00
|
|
|
invoices = set()
|
2014-03-05 18:44:58 +01:00
|
|
|
for move in moves:
|
2014-04-03 19:57:41 +02:00
|
|
|
if (move.origin and isinstance(move.origin, Invoice)
|
|
|
|
and move.origin.state == 'posted'):
|
2018-09-06 13:51:22 +02:00
|
|
|
invoices.add(move.origin)
|
2014-03-05 18:44:58 +01:00
|
|
|
if invoices:
|
|
|
|
Invoice.process(invoices)
|
|
|
|
return reconciliations
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def delete(cls, reconciliations):
|
|
|
|
Invoice = Pool().get('account.invoice')
|
|
|
|
|
|
|
|
moves = set()
|
|
|
|
for reconciliation in reconciliations:
|
|
|
|
moves |= set(l.move for l in reconciliation.lines)
|
2018-09-18 18:37:48 +02:00
|
|
|
invoices = set()
|
2014-03-05 18:44:58 +01:00
|
|
|
for move in moves:
|
|
|
|
if move.origin and isinstance(move.origin, Invoice):
|
2018-09-18 18:37:48 +02:00
|
|
|
invoices.add(move.origin)
|
2014-03-05 18:44:58 +01:00
|
|
|
super(Reconciliation, cls).delete(reconciliations)
|
|
|
|
if invoices:
|
|
|
|
Invoice.process(invoices)
|
|
|
|
|
|
|
|
|
2018-09-10 15:11:13 +02:00
|
|
|
class Line(BankMixin, metaclass=PoolMeta):
|
2014-03-05 18:44:58 +01:00
|
|
|
__name__ = 'account.move.line'
|
|
|
|
|
|
|
|
reverse_moves = fields.Function(fields.Boolean('With Reverse Moves'),
|
|
|
|
'get_reverse_moves', searcher='search_reverse_moves')
|
2016-08-10 17:17:52 +02:00
|
|
|
netting_moves = fields.Function(fields.Boolean('With Netting Moves'),
|
|
|
|
'get_netting_moves', searcher='search_netting_moves')
|
2014-03-05 18:44:58 +01:00
|
|
|
|
2018-09-10 15:11:13 +02:00
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
|
|
|
super(Line, cls).__setup__()
|
|
|
|
if hasattr(cls, '_check_modify_exclude'):
|
|
|
|
cls._check_modify_exclude.add('bank_account')
|
|
|
|
readonly = Bool(Eval('reconciliation'))
|
|
|
|
previous_readonly = cls.bank_account.states.get('readonly')
|
|
|
|
if previous_readonly:
|
|
|
|
readonly = readonly | previous_readonly
|
|
|
|
cls.bank_account.states.update({
|
|
|
|
'readonly': readonly,
|
|
|
|
})
|
|
|
|
|
|
|
|
@fields.depends('party', 'payment_type')
|
|
|
|
def on_change_party(self):
|
|
|
|
'''Add account bank to account move line when changes party.'''
|
|
|
|
try:
|
|
|
|
super(Line, self).on_change_party()
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
if self.payment_type and self.party:
|
|
|
|
self._get_bank_account()
|
|
|
|
|
2018-09-10 16:44:22 +02:00
|
|
|
@fields.depends('party', 'debit', 'credit', 'move')
|
|
|
|
def on_change_with_payment_type(self, name=None):
|
|
|
|
if self.party:
|
|
|
|
if self.credit > 0 or self.debit < 0:
|
|
|
|
name = 'supplier_payment_type'
|
|
|
|
elif self.debit > 0 or self.credit < 0:
|
|
|
|
name = 'customer_payment_type'
|
|
|
|
payment_type = getattr(self.party, name)
|
|
|
|
if payment_type:
|
|
|
|
return payment_type.id
|
|
|
|
return None
|
|
|
|
|
2018-09-10 15:11:13 +02:00
|
|
|
@classmethod
|
|
|
|
def copy(cls, lines, default=None):
|
|
|
|
if default is None:
|
|
|
|
default = {}
|
|
|
|
if (Transaction().context.get('cancel_move')
|
|
|
|
and 'bank_account' not in default):
|
|
|
|
default['bank_account'] = None
|
|
|
|
return super(Line, cls).copy(lines, default)
|
|
|
|
|
2014-03-05 18:44:58 +01:00
|
|
|
def get_reverse_moves(self, name):
|
2016-04-11 15:10:07 +02:00
|
|
|
if (not self.account
|
|
|
|
or self.account.kind not in ['receivable', 'payable']):
|
2014-03-05 18:44:58 +01:00
|
|
|
return False
|
|
|
|
domain = [
|
|
|
|
('account', '=', self.account.id),
|
2014-03-07 07:49:15 +01:00
|
|
|
('reconciliation', '=', None),
|
2014-03-05 18:44:58 +01:00
|
|
|
]
|
|
|
|
if self.party:
|
|
|
|
domain.append(('party', '=', self.party.id))
|
|
|
|
if self.credit > Decimal('0.0'):
|
|
|
|
domain.append(('debit', '>', 0))
|
|
|
|
if self.debit > Decimal('0.0'):
|
|
|
|
domain.append(('credit', '>', 0))
|
|
|
|
moves = self.search(domain, limit=1)
|
|
|
|
return len(moves) > 0
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def search_reverse_moves(cls, name, clause):
|
2016-11-11 17:36:52 +01:00
|
|
|
pool = Pool()
|
|
|
|
Account = pool.get('account.account')
|
|
|
|
MoveLine = pool.get('account.move.line')
|
2014-03-05 18:44:58 +01:00
|
|
|
operator = 'in' if clause[2] else 'not in'
|
2016-11-11 17:36:52 +01:00
|
|
|
lines = MoveLine.__table__()
|
|
|
|
move_line = MoveLine.__table__()
|
|
|
|
account = Account.__table__()
|
2016-03-10 13:34:38 +01:00
|
|
|
cursor = Transaction().connection.cursor()
|
2016-11-11 17:36:52 +01:00
|
|
|
|
|
|
|
reverse = move_line.join(account, condition=(
|
|
|
|
account.id == move_line.account)).select(
|
|
|
|
move_line.account, move_line.party,
|
|
|
|
where=(account.reconcile
|
|
|
|
& (move_line.reconciliation == Null)),
|
|
|
|
group_by=(move_line.account, move_line.party),
|
|
|
|
having=((BoolOr((move_line.debit) != Decimal(0)))
|
|
|
|
& (BoolOr((move_line.credit) != Decimal(0))))
|
|
|
|
)
|
|
|
|
query = lines.select(lines.id, where=(
|
|
|
|
In((lines.account, lines.party), reverse)))
|
|
|
|
# Fetch the data otherwise its too slow
|
|
|
|
cursor.execute(*query)
|
|
|
|
|
2014-03-05 18:44:58 +01:00
|
|
|
return [('id', operator, [x[0] for x in cursor.fetchall()])]
|
|
|
|
|
2016-08-10 17:17:52 +02:00
|
|
|
def get_netting_moves(self, name):
|
2016-11-11 17:38:49 +01:00
|
|
|
if (not self.account
|
|
|
|
or self.account.kind not in ['receivable', 'payable']):
|
2016-08-10 17:17:52 +02:00
|
|
|
return False
|
|
|
|
if not self.account.party_required:
|
|
|
|
return False
|
|
|
|
domain = [
|
|
|
|
('party', '=', self.party.id),
|
|
|
|
('reconciliation', '=', None),
|
|
|
|
]
|
|
|
|
if self.credit > Decimal('0.0'):
|
|
|
|
domain.append(('debit', '>', 0))
|
|
|
|
if self.debit > Decimal('0.0'):
|
|
|
|
domain.append(('credit', '>', 0))
|
|
|
|
moves = self.search(domain, limit=1)
|
|
|
|
return len(moves) > 0
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def search_netting_moves(cls, name, clause):
|
2016-11-11 17:36:52 +01:00
|
|
|
pool = Pool()
|
|
|
|
Account = pool.get('account.account')
|
|
|
|
MoveLine = pool.get('account.move.line')
|
2016-08-10 17:17:52 +02:00
|
|
|
operator = 'in' if clause[2] else 'not in'
|
2016-11-11 17:36:52 +01:00
|
|
|
|
|
|
|
move_line = MoveLine.__table__()
|
|
|
|
account = Account.__table__()
|
|
|
|
query = move_line.join(account, condition=(
|
|
|
|
account.id == move_line.account)).select(
|
|
|
|
move_line.party,
|
|
|
|
where=(account.reconcile
|
|
|
|
& (move_line.reconciliation == Null)),
|
|
|
|
group_by=(move_line.party,),
|
|
|
|
having=((BoolOr((move_line.debit) != Decimal(0)))
|
|
|
|
& (BoolOr((move_line.credit) != Decimal(0))))
|
2016-08-10 17:17:52 +02:00
|
|
|
)
|
2016-11-11 17:36:52 +01:00
|
|
|
return [('party', operator, query)]
|
2016-08-10 17:17:52 +02:00
|
|
|
|
2014-03-05 18:44:58 +01:00
|
|
|
|
2018-09-10 15:11:13 +02:00
|
|
|
class CompensationMoveStart(ModelView, BankMixin):
|
2014-05-06 11:44:21 +02:00
|
|
|
'Create Compensation Move Start'
|
|
|
|
__name__ = 'account.move.compensation_move.start'
|
2014-03-05 18:44:58 +01:00
|
|
|
party = fields.Many2One('party.party', 'Party', readonly=True)
|
2018-09-10 15:11:13 +02:00
|
|
|
account = fields.Many2One('account.account', 'Account',
|
|
|
|
domain=[('kind', 'in', ['payable', 'receivable'])],
|
|
|
|
required=True)
|
2016-08-10 02:22:41 +02:00
|
|
|
date = fields.Date('Date')
|
|
|
|
maturity_date = fields.Date('Maturity Date')
|
2018-09-06 14:50:17 +02:00
|
|
|
description = fields.Char('Description')
|
2018-09-10 15:11:13 +02:00
|
|
|
payment_kind = fields.Selection([
|
|
|
|
('both', 'Both'),
|
|
|
|
('payable', 'Payable'),
|
|
|
|
('receivable', 'Receivable'),
|
|
|
|
], 'Payment Kind')
|
|
|
|
payment_type = fields.Many2One('account.payment.type', 'Payment Type',
|
|
|
|
domain=[
|
|
|
|
('kind', '=', Eval('payment_kind'))
|
|
|
|
],
|
|
|
|
depends=['payment_kind'])
|
2014-03-05 18:44:58 +01:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
2014-05-06 11:44:21 +02:00
|
|
|
super(CompensationMoveStart, cls).__setup__()
|
2014-03-05 18:44:58 +01:00
|
|
|
cls._error_messages.update({
|
2016-04-11 15:10:07 +02:00
|
|
|
'normal_reconcile': ('Selected moves are balanced. Use '
|
|
|
|
'concile wizard instead of creating a compensation move.'),
|
2014-05-22 19:57:45 +02:00
|
|
|
'different_parties': ('Parties can not be mixed to create a '
|
|
|
|
'compensation move. Party "%s" in line "%s" is different '
|
|
|
|
'from previous party "%s"'),
|
2014-03-05 18:44:58 +01:00
|
|
|
})
|
|
|
|
|
2016-08-10 02:22:41 +02:00
|
|
|
@staticmethod
|
|
|
|
def default_date():
|
|
|
|
pool = Pool()
|
|
|
|
return pool.get('ir.date').today()
|
|
|
|
|
2014-03-05 18:44:58 +01:00
|
|
|
@staticmethod
|
|
|
|
def default_maturity_date():
|
|
|
|
pool = Pool()
|
|
|
|
return pool.get('ir.date').today()
|
|
|
|
|
|
|
|
@classmethod
|
2014-11-04 11:22:05 +01:00
|
|
|
def default_get(cls, fields, with_rec_name=True):
|
2014-03-05 18:44:58 +01:00
|
|
|
pool = Pool()
|
|
|
|
Line = pool.get('account.move.line')
|
2018-09-10 15:11:13 +02:00
|
|
|
PaymentType = pool.get('account.payment.type')
|
2014-03-05 18:44:58 +01:00
|
|
|
|
2016-07-28 16:58:22 +02:00
|
|
|
defaults = super(CompensationMoveStart, cls).default_get(fields,
|
2014-11-04 11:22:05 +01:00
|
|
|
with_rec_name)
|
2014-03-05 18:44:58 +01:00
|
|
|
|
|
|
|
party = None
|
|
|
|
company = None
|
|
|
|
amount = Decimal('0.0')
|
|
|
|
|
2016-08-10 17:17:52 +02:00
|
|
|
lines = Line.browse(Transaction().context.get('active_ids', []))
|
|
|
|
for line in lines:
|
2014-03-05 18:44:58 +01:00
|
|
|
amount += line.debit - line.credit
|
|
|
|
if not party:
|
|
|
|
party = line.party
|
|
|
|
elif party != line.party:
|
|
|
|
cls.raise_user_error('different_parties', (line.party.rec_name,
|
|
|
|
line.rec_name, party.rec_name))
|
|
|
|
if not company:
|
|
|
|
company = line.account.company
|
2016-08-10 17:17:52 +02:00
|
|
|
if (company and company.currency.is_zero(amount)
|
|
|
|
and len(set([x.account for x in lines])) == 1):
|
2014-03-05 18:44:58 +01:00
|
|
|
cls.raise_user_error('normal_reconcile')
|
2018-09-10 15:11:13 +02:00
|
|
|
if amount > 0:
|
|
|
|
defaults['payment_kind'] = 'receivable'
|
|
|
|
else:
|
|
|
|
defaults['payment_kind'] = 'payable'
|
|
|
|
defaults['bank_account'] = None
|
2016-08-11 13:09:52 +02:00
|
|
|
if party:
|
|
|
|
defaults['party'] = party.id
|
2018-09-10 15:11:13 +02:00
|
|
|
if (defaults['payment_kind'] in ['receivable', 'both']
|
|
|
|
and party.customer_payment_type):
|
|
|
|
defaults['payment_type'] = party.customer_payment_type.id
|
|
|
|
elif (defaults['payment_kind'] in ['payable', 'both']
|
|
|
|
and party.supplier_payment_type):
|
|
|
|
defaults['payment_type'] = party.supplier_payment_type.id
|
|
|
|
if defaults.get('payment_type'):
|
|
|
|
payment_type = PaymentType(defaults['payment_type'])
|
|
|
|
defaults['account_bank'] = payment_type.account_bank
|
|
|
|
|
|
|
|
self = cls()
|
|
|
|
self.payment_type = payment_type
|
|
|
|
self.party = party
|
|
|
|
self._get_bank_account()
|
|
|
|
defaults['account_bank_from'] = (
|
|
|
|
self.on_change_with_account_bank_from())
|
|
|
|
defaults['bank_account'] = (self.bank_account.id
|
|
|
|
if self.bank_account else None)
|
2016-08-11 13:09:52 +02:00
|
|
|
if amount > 0:
|
|
|
|
defaults['account'] = (party.account_receivable.id
|
|
|
|
if party.account_receivable else None)
|
|
|
|
else:
|
|
|
|
defaults['account'] = (party.account_payable.id
|
|
|
|
if party.account_payable else None)
|
2016-07-28 16:58:22 +02:00
|
|
|
return defaults
|
2014-03-05 18:44:58 +01:00
|
|
|
|
2018-09-10 16:44:22 +02:00
|
|
|
def on_change_with_payment_type(self, name=None):
|
|
|
|
pass
|
|
|
|
|
2014-03-05 18:44:58 +01:00
|
|
|
|
2014-05-06 11:44:21 +02:00
|
|
|
class CompensationMove(Wizard):
|
|
|
|
'Create Compensation Move'
|
|
|
|
__name__ = 'account.move.compensation_move'
|
|
|
|
start = StateView('account.move.compensation_move.start',
|
|
|
|
'account_bank.compensation_move_lines_start_view_form', [
|
2014-03-05 18:44:58 +01:00
|
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
2014-05-22 19:57:45 +02:00
|
|
|
Button('Create', 'create_move', 'tryton-ok', default=True),
|
2014-03-05 18:44:58 +01:00
|
|
|
])
|
2014-05-22 19:57:45 +02:00
|
|
|
create_move = StateTransition()
|
2014-03-05 18:44:58 +01:00
|
|
|
|
2014-05-22 19:57:45 +02:00
|
|
|
def transition_create_move(self):
|
2014-03-05 18:44:58 +01:00
|
|
|
pool = Pool()
|
|
|
|
Move = pool.get('account.move')
|
|
|
|
Line = pool.get('account.move.line')
|
|
|
|
|
|
|
|
move_lines = []
|
|
|
|
lines = Line.browse(Transaction().context.get('active_ids'))
|
|
|
|
|
|
|
|
for line in lines:
|
2016-04-11 15:10:07 +02:00
|
|
|
if (line.account.kind not in ('payable', 'receivable')
|
|
|
|
or line.reconciliation):
|
2014-03-05 18:44:58 +01:00
|
|
|
continue
|
|
|
|
move_lines.append(self.get_counterpart_line(line))
|
|
|
|
|
|
|
|
if not lines or not move_lines:
|
2015-08-28 23:19:47 +02:00
|
|
|
return 'end'
|
2014-03-05 18:44:58 +01:00
|
|
|
|
|
|
|
move = self.get_move(lines)
|
2016-08-10 02:22:41 +02:00
|
|
|
extra_lines, origin = self.get_extra_lines(lines, self.start.account,
|
|
|
|
self.start.party)
|
2014-03-05 18:44:58 +01:00
|
|
|
|
|
|
|
if origin:
|
|
|
|
move.origin = origin
|
|
|
|
move.lines = move_lines + extra_lines
|
|
|
|
move.save()
|
|
|
|
Move.post([move])
|
2016-08-10 02:22:41 +02:00
|
|
|
to_reconcile = {}
|
|
|
|
for line in lines:
|
|
|
|
to_reconcile.setdefault(line.account.id, []).append(line)
|
2014-03-05 18:44:58 +01:00
|
|
|
for line in move.lines:
|
|
|
|
append = True
|
|
|
|
for extra_line in extra_lines:
|
|
|
|
if self.is_extra_line(line, extra_line):
|
|
|
|
append = False
|
|
|
|
break
|
|
|
|
if append:
|
2016-08-10 02:22:41 +02:00
|
|
|
to_reconcile.setdefault(line.account.id, []).append(line)
|
|
|
|
for lines_to_reconcile in to_reconcile.values():
|
|
|
|
Line.reconcile(lines_to_reconcile)
|
2014-03-05 18:44:58 +01:00
|
|
|
return 'end'
|
|
|
|
|
|
|
|
def is_extra_line(self, line, extra_line):
|
|
|
|
" Returns true if both lines are equal"
|
|
|
|
return (line.debit == extra_line.debit and
|
|
|
|
line.credit == extra_line.credit and
|
2018-09-10 15:11:13 +02:00
|
|
|
line.maturity_date == extra_line.maturity_date and
|
|
|
|
line.payment_type == extra_line.payment_type and
|
|
|
|
line.bank_account == extra_line.bank_account)
|
2014-03-05 18:44:58 +01:00
|
|
|
|
|
|
|
def get_counterpart_line(self, line):
|
2014-05-22 19:57:45 +02:00
|
|
|
'Returns the counterpart line to create from line'
|
2014-03-05 18:44:58 +01:00
|
|
|
pool = Pool()
|
|
|
|
Line = pool.get('account.move.line')
|
|
|
|
|
|
|
|
new_line = Line()
|
|
|
|
new_line.account = line.account
|
|
|
|
new_line.debit = line.credit
|
|
|
|
new_line.credit = line.debit
|
|
|
|
new_line.description = line.description
|
|
|
|
new_line.second_curency = line.second_currency
|
|
|
|
new_line.amount_second_currency = line.amount_second_currency
|
|
|
|
new_line.party = line.party
|
|
|
|
|
|
|
|
return new_line
|
|
|
|
|
|
|
|
def get_move(self, lines):
|
2014-05-22 19:57:45 +02:00
|
|
|
'Returns the new move to create from lines'
|
2014-03-05 18:44:58 +01:00
|
|
|
pool = Pool()
|
|
|
|
Move = pool.get('account.move')
|
|
|
|
Period = pool.get('account.period')
|
|
|
|
Date = pool.get('ir.date')
|
|
|
|
|
|
|
|
period_id = Period.find(lines[0].account.company.id)
|
|
|
|
move = Move()
|
|
|
|
move.period = Period(period_id)
|
|
|
|
move.journal = lines[0].move.journal
|
|
|
|
move.date = Date.today()
|
2018-09-06 14:50:17 +02:00
|
|
|
move.description = self.start.description
|
2014-03-05 18:44:58 +01:00
|
|
|
|
|
|
|
return move
|
|
|
|
|
2016-08-10 02:22:41 +02:00
|
|
|
def get_extra_lines(self, lines, account, party):
|
2014-03-05 18:44:58 +01:00
|
|
|
'Returns extra lines to balance move and move origin'
|
|
|
|
pool = Pool()
|
|
|
|
Line = pool.get('account.move.line')
|
|
|
|
|
|
|
|
amount = Decimal('0.0')
|
|
|
|
origins = {}
|
|
|
|
for line in lines:
|
|
|
|
line_amount = line.debit - line.credit
|
|
|
|
amount += line_amount
|
|
|
|
if line.origin:
|
|
|
|
if line.origin not in origins:
|
|
|
|
origins[line.origin] = Decimal('0.0')
|
|
|
|
origins[line.origin] += abs(line_amount)
|
|
|
|
|
|
|
|
if not account or not party:
|
2017-12-22 16:24:12 +01:00
|
|
|
return ([], None)
|
2014-03-05 18:44:58 +01:00
|
|
|
|
|
|
|
extra_line = Line()
|
|
|
|
extra_line.account = account
|
|
|
|
extra_line.party = party
|
|
|
|
extra_line.maturity_date = self.start.maturity_date
|
2018-09-10 15:11:13 +02:00
|
|
|
extra_line.payment_type = self.start.payment_type
|
|
|
|
extra_line.bank_account = self.start.bank_account
|
2018-09-06 14:50:17 +02:00
|
|
|
extra_line.description = self.start.description
|
2014-03-05 18:44:58 +01:00
|
|
|
extra_line.credit = extra_line.debit = Decimal('0.0')
|
|
|
|
if amount > 0:
|
|
|
|
extra_line.debit = amount
|
|
|
|
else:
|
|
|
|
extra_line.credit = abs(amount)
|
|
|
|
|
|
|
|
origin = None
|
2018-08-17 22:59:55 +02:00
|
|
|
for line_origin, line_amount in sorted(origins.items(),
|
2014-03-05 18:44:58 +01:00
|
|
|
key=lambda x: x[1]):
|
|
|
|
if abs(amount) < line_amount:
|
|
|
|
origin = line_origin
|
|
|
|
break
|
|
|
|
return [extra_line], origin
|