The account domain on invoice field is applied when the party is set, but it should only be applied when the account is set. This gives an invalid domain when the party is set and the account is not set. issue5766 review32391002
827 lines
29 KiB
Python
827 lines
29 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
from decimal import Decimal
|
|
from collections import namedtuple
|
|
from itertools import groupby
|
|
|
|
from sql import Null
|
|
from sql.aggregate import Max, Sum
|
|
from sql.conditionals import Case
|
|
|
|
from trytond.model import Workflow, ModelView, ModelSQL, fields, Check
|
|
from trytond.pyson import Eval, If, Bool
|
|
from trytond.transaction import Transaction
|
|
from trytond import backend
|
|
from trytond.pool import Pool
|
|
from trytond.modules.company import CompanyReport
|
|
|
|
__all__ = ['Statement', 'Line', 'LineGroup', 'StatementReport']
|
|
|
|
_STATES = {'readonly': Eval('state') != 'draft'}
|
|
_DEPENDS = ['state']
|
|
|
|
_BALANCE_STATES = _STATES.copy()
|
|
_BALANCE_STATES.update({
|
|
'invisible': ~Eval('validation', '').in_(['balance']),
|
|
'required': Eval('validation', '').in_(['balance']),
|
|
})
|
|
_BALANCE_DEPENDS = _DEPENDS + ['validation']
|
|
|
|
_AMOUNT_STATES = _STATES.copy()
|
|
_AMOUNT_STATES.update({
|
|
'invisible': ~Eval('validation', '').in_(['amount']),
|
|
'required': Eval('validation', '').in_(['amount']),
|
|
})
|
|
_AMOUNT_DEPENDS = _DEPENDS + ['validation']
|
|
|
|
_NUMBER_STATES = _STATES.copy()
|
|
_NUMBER_STATES.update({
|
|
'invisible': ~Eval('validation', '').in_(['number_of_lines']),
|
|
'required': Eval('validation', '').in_(['number_of_lines']),
|
|
})
|
|
_NUMBER_DEPENDS = _DEPENDS + ['validation']
|
|
|
|
STATES = [
|
|
('draft', 'Draft'),
|
|
('validated', 'Validated'),
|
|
('cancel', 'Canceled'),
|
|
('posted', 'Posted'),
|
|
]
|
|
|
|
|
|
class Unequal(object):
|
|
"Always different"
|
|
|
|
def __eq__(self, other):
|
|
return False
|
|
|
|
def __ne__(self, other):
|
|
return True
|
|
|
|
def __str__(self):
|
|
return ''
|
|
|
|
|
|
class Statement(Workflow, ModelSQL, ModelView):
|
|
'Account Statement'
|
|
__name__ = 'account.statement'
|
|
name = fields.Char('Name', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True,
|
|
select=True, states=_STATES, domain=[
|
|
('id', If(Eval('context', {}).contains('company'), '=', '!='),
|
|
Eval('context', {}).get('company', -1)),
|
|
],
|
|
depends=_DEPENDS)
|
|
journal = fields.Many2One('account.statement.journal', 'Journal',
|
|
required=True, select=True,
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
],
|
|
states={
|
|
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
|
|
},
|
|
depends=['state', 'company'])
|
|
currency_digits = fields.Function(fields.Integer('Currency Digits'),
|
|
'on_change_with_currency_digits')
|
|
date = fields.Date('Date', required=True, select=True)
|
|
start_balance = fields.Numeric('Start Balance',
|
|
digits=(16, Eval('currency_digits', 2)),
|
|
states=_BALANCE_STATES, depends=_BALANCE_DEPENDS + ['currency_digits'])
|
|
end_balance = fields.Numeric('End Balance',
|
|
digits=(16, Eval('currency_digits', 2)),
|
|
states=_BALANCE_STATES, depends=_BALANCE_DEPENDS + ['currency_digits'])
|
|
balance = fields.Function(
|
|
fields.Numeric('Balance',
|
|
digits=(16, Eval('currency_digits', 2)),
|
|
states=_BALANCE_STATES,
|
|
depends=_BALANCE_DEPENDS + ['currency_digits']),
|
|
'on_change_with_balance')
|
|
total_amount = fields.Numeric('Total Amount',
|
|
digits=(16, Eval('currency_digits', 2)),
|
|
states=_AMOUNT_STATES, depends=_AMOUNT_DEPENDS + ['currency_digits'])
|
|
number_of_lines = fields.Integer('Number of Lines',
|
|
states=_NUMBER_STATES, depends=_NUMBER_DEPENDS)
|
|
lines = fields.One2Many('account.statement.line', 'statement',
|
|
'Lines', states={
|
|
'readonly': (Eval('state') != 'draft') | ~Eval('journal'),
|
|
},
|
|
depends=['state', 'journal'])
|
|
state = fields.Selection(STATES, 'State', readonly=True, select=True)
|
|
validation = fields.Function(fields.Char('Validation'),
|
|
'on_change_with_validation')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Statement, cls).__setup__()
|
|
cls._order[0] = ('id', 'DESC')
|
|
cls._error_messages.update({
|
|
'wrong_end_balance': 'End Balance must be "%s".',
|
|
'wrong_total_amount': 'Total Amount must be "%s".',
|
|
'wrong_number_of_lines': 'Number of Lines must be "%s".',
|
|
'delete_cancel': ('Statement "%s" must be cancelled before '
|
|
'deletion.'),
|
|
'paid_invoice_draft_statement': ('There are paid invoices on '
|
|
'draft statements.'),
|
|
'debit_credit_account_statement_journal': ('Please provide '
|
|
'debit and credit account on statement journal "%s".'),
|
|
})
|
|
cls._transitions |= set((
|
|
('draft', 'validated'),
|
|
('draft', 'cancel'),
|
|
('validated', 'posted'),
|
|
('validated', 'cancel'),
|
|
('cancel', 'draft'),
|
|
))
|
|
cls._buttons.update({
|
|
'draft': {
|
|
'invisible': Eval('state') != 'cancel',
|
|
},
|
|
'validate_statement': {
|
|
'invisible': Eval('state') != 'draft',
|
|
},
|
|
'post': {
|
|
'invisible': Eval('state') != 'validated',
|
|
},
|
|
'cancel': {
|
|
'invisible': ~Eval('state').in_(['draft', 'validated']),
|
|
},
|
|
})
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
TableHandler = backend.get('TableHandler')
|
|
transaction = Transaction()
|
|
cursor = transaction.connection.cursor()
|
|
sql_table = cls.__table__()
|
|
|
|
# Migration from 1.8: new field company
|
|
table = TableHandler(cls, module_name)
|
|
company_exist = table.column_exist('company')
|
|
|
|
super(Statement, cls).__register__(module_name)
|
|
|
|
# Migration from 1.8: fill new field company
|
|
if not company_exist:
|
|
offset = 0
|
|
limit = transaction.database.IN_MAX
|
|
statements = True
|
|
while statements:
|
|
statements = cls.search([], offset=offset, limit=limit)
|
|
offset += limit
|
|
for statement in statements:
|
|
cls.write([statement], {
|
|
'company': statement.journal.company.id,
|
|
})
|
|
table = TableHandler(cls, module_name)
|
|
table.not_null_action('company', action='add')
|
|
|
|
# Migration from 3.2: remove required on start/end balance
|
|
table.not_null_action('start_balance', action='remove')
|
|
table.not_null_action('end_balance', action='remove')
|
|
|
|
# Migration from 3.2: add required name
|
|
cursor.execute(*sql_table.update([sql_table.name],
|
|
[sql_table.id.cast(cls.name.sql_type().base)],
|
|
where=sql_table.name == Null))
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
Date = Pool().get('ir.date')
|
|
return Date.today()
|
|
|
|
@staticmethod
|
|
def default_currency_digits():
|
|
Company = Pool().get('company.company')
|
|
if Transaction().context.get('company'):
|
|
company = Company(Transaction().context['company'])
|
|
return company.currency.digits
|
|
return 2
|
|
|
|
@fields.depends('journal', 'state', 'lines')
|
|
def on_change_journal(self):
|
|
if not self.journal:
|
|
return
|
|
|
|
statements = self.search([
|
|
('journal', '=', self.journal.id),
|
|
], order=[
|
|
('date', 'DESC'),
|
|
], limit=1)
|
|
if not statements:
|
|
return
|
|
|
|
statement, = statements
|
|
self.start_balance = statement.end_balance
|
|
|
|
@fields.depends('journal')
|
|
def on_change_with_currency_digits(self, name=None):
|
|
if self.journal:
|
|
return self.journal.currency.digits
|
|
return 2
|
|
|
|
def get_end_balance(self, name):
|
|
end_balance = self.start_balance
|
|
for line in self.lines:
|
|
end_balance += line.amount
|
|
return end_balance
|
|
|
|
@fields.depends('start_balance', 'end_balance')
|
|
def on_change_with_balance(self, name=None):
|
|
return ((getattr(self, 'end_balance', 0) or 0)
|
|
- (getattr(self, 'start_balance', 0) or 0))
|
|
|
|
@fields.depends('lines', 'journal')
|
|
def on_change_lines(self):
|
|
pool = Pool()
|
|
Currency = pool.get('currency.currency')
|
|
Line = pool.get('account.statement.line')
|
|
if self.journal and self.lines:
|
|
invoices = set()
|
|
for line in self.lines:
|
|
if getattr(line, 'invoice', None):
|
|
invoices.add(line.invoice)
|
|
invoice_id2amount_to_pay = {}
|
|
for invoice in invoices:
|
|
with Transaction().set_context(date=invoice.currency_date):
|
|
if invoice.type == 'out':
|
|
sign = -1
|
|
else:
|
|
sign = 1
|
|
invoice_id2amount_to_pay[invoice.id] = sign * (
|
|
Currency.compute(invoice.currency,
|
|
invoice.amount_to_pay, self.journal.currency))
|
|
|
|
lines = list(self.lines)
|
|
line_offset = 0
|
|
for index, line in enumerate(self.lines or []):
|
|
if getattr(line, 'invoice', None) and line.id:
|
|
amount_to_pay = invoice_id2amount_to_pay[line.invoice.id]
|
|
if (not self.journal.currency.is_zero(amount_to_pay)
|
|
and getattr(line, 'amount', None)
|
|
and (line.amount >= 0) == (amount_to_pay <= 0)):
|
|
if abs(line.amount) > abs(amount_to_pay):
|
|
new_line = Line()
|
|
for field_name, field in Line._fields.iteritems():
|
|
if field_name == 'id':
|
|
continue
|
|
try:
|
|
setattr(new_line, field_name,
|
|
getattr(line, field_name))
|
|
except AttributeError:
|
|
pass
|
|
new_line.amount = line.amount + amount_to_pay
|
|
new_line.invoice = None
|
|
line_offset += 1
|
|
lines.insert(index + line_offset, new_line)
|
|
invoice_id2amount_to_pay[line.invoice.id] = 0
|
|
line.amount = amount_to_pay.copy_sign(line.amount)
|
|
else:
|
|
invoice_id2amount_to_pay[line.invoice.id] = (
|
|
line.amount + amount_to_pay)
|
|
else:
|
|
line.invoice = None
|
|
self.lines = lines
|
|
|
|
@fields.depends('journal')
|
|
def on_change_with_validation(self, name=None):
|
|
if self.journal:
|
|
return self.journal.validation
|
|
|
|
def _group_key(self, line):
|
|
key = (
|
|
('number', line.number or Unequal()),
|
|
('date', line.date),
|
|
('party', line.party),
|
|
)
|
|
return key
|
|
|
|
def _get_grouped_line(self):
|
|
"Return Line class for grouped lines"
|
|
assert self.lines
|
|
|
|
keys = [k[0] for k in self._group_key(self.lines[0])]
|
|
|
|
class Line(namedtuple('Line', keys + ['lines'])):
|
|
|
|
@property
|
|
def amount(self):
|
|
return sum((l.amount for l in self.lines))
|
|
|
|
@property
|
|
def descriptions(self):
|
|
done = set()
|
|
for line in self.lines:
|
|
if line.description and line.description not in done:
|
|
done.add(line.description)
|
|
yield line.description
|
|
return Line
|
|
|
|
@property
|
|
def grouped_lines(self):
|
|
if self.lines:
|
|
Line = self._get_grouped_line()
|
|
for key, lines in groupby(self.lines, key=self._group_key):
|
|
yield Line(**dict(key + (('lines', list(lines)),)))
|
|
|
|
@classmethod
|
|
def delete(cls, statements):
|
|
# Cancel before delete
|
|
cls.cancel(statements)
|
|
for statement in statements:
|
|
if statement.state != 'cancel':
|
|
cls.raise_user_error('delete_cancel', (statement.rec_name,))
|
|
super(Statement, cls).delete(statements)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('draft')
|
|
def draft(cls, statements):
|
|
pass
|
|
|
|
def validate_balance(self):
|
|
pool = Pool()
|
|
Lang = pool.get('ir.lang')
|
|
|
|
end_balance = (self.start_balance
|
|
+ sum(l.amount for l in self.lines))
|
|
if end_balance != self.end_balance:
|
|
lang, = Lang.search([
|
|
('code', '=', Transaction().language),
|
|
])
|
|
amount = Lang.format(lang,
|
|
'%.' + str(self.journal.currency.digits) + 'f',
|
|
end_balance, True)
|
|
self.raise_user_error('wrong_end_balance', amount)
|
|
|
|
def validate_amount(self):
|
|
pool = Pool()
|
|
Lang = pool.get('ir.lang')
|
|
|
|
amount = sum(l.amount for l in self.lines)
|
|
if amount != self.total_amount:
|
|
lang, = Lang.search([
|
|
('code', '=', Transaction().language),
|
|
])
|
|
amount = Lang.format(lang,
|
|
'%.' + str(self.journal.currency.digits) + 'f',
|
|
amount, True)
|
|
self.raise_user_error('wrong_total_amount', amount)
|
|
|
|
def validate_number_of_lines(self):
|
|
number = len(list(self.grouped_lines))
|
|
if number != self.number_of_lines:
|
|
self.raise_user_error('wrong_number_of_lines', number)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('validated')
|
|
def validate_statement(cls, statements):
|
|
pool = Pool()
|
|
Line = pool.get('account.statement.line')
|
|
|
|
for statement in statements:
|
|
getattr(statement, 'validate_%s' % statement.validation)()
|
|
|
|
cls.create_move(statements)
|
|
|
|
cls.write(statements, {
|
|
'state': 'validated',
|
|
})
|
|
common_lines = Line.search([
|
|
('statement.state', '=', 'draft'),
|
|
('invoice.state', '=', 'paid'),
|
|
])
|
|
if common_lines:
|
|
warning_key = '_'.join(str(l.id) for l in common_lines)
|
|
cls.raise_user_warning(warning_key, 'paid_invoice_draft_statement')
|
|
Line.write(common_lines, {
|
|
'invoice': None,
|
|
})
|
|
|
|
@classmethod
|
|
def create_move(cls, statements):
|
|
'''Create move for the statements and try to reconcile the lines.
|
|
Returns the list of move, statement and lines
|
|
'''
|
|
pool = Pool()
|
|
Line = pool.get('account.statement.line')
|
|
Move = pool.get('account.move')
|
|
MoveLine = pool.get('account.move.line')
|
|
|
|
moves = []
|
|
for statement in statements:
|
|
for key, lines in groupby(
|
|
statement.lines, key=statement._group_key):
|
|
lines = list(lines)
|
|
key = dict(key)
|
|
move = statement._get_move(key)
|
|
moves.append((move, statement, lines))
|
|
|
|
Move.save([m for m, _, _ in moves])
|
|
|
|
to_write = []
|
|
for move, _, lines in moves:
|
|
to_write.append(lines)
|
|
to_write.append({
|
|
'move': move.id,
|
|
})
|
|
if to_write:
|
|
Line.write(*to_write)
|
|
|
|
move_lines = []
|
|
for move, statement, lines in moves:
|
|
amount = 0
|
|
amount_second_currency = 0
|
|
for line in lines:
|
|
move_line = line.get_move_line()
|
|
move_line.move = move
|
|
amount += move_line.debit - move_line.credit
|
|
if move_line.amount_second_currency:
|
|
amount_second_currency += move_line.amount_second_currency
|
|
move_lines.append((move_line, line))
|
|
|
|
move_line = statement._get_move_line(
|
|
amount, amount_second_currency)
|
|
move_line.move = move
|
|
move_lines.append((move_line, None))
|
|
|
|
MoveLine.save([l for l, _ in move_lines])
|
|
|
|
for move_line, line in move_lines:
|
|
if line:
|
|
line.reconcile(move_line)
|
|
return moves
|
|
|
|
def _get_move(self, key):
|
|
'Return Move for the grouping key'
|
|
pool = Pool()
|
|
Move = pool.get('account.move')
|
|
Period = pool.get('account.period')
|
|
|
|
period_id = Period.find(self.company.id, date=key['date'])
|
|
return Move(
|
|
period=period_id,
|
|
journal=self.journal.journal,
|
|
date=key['date'],
|
|
origin=self,
|
|
company=self.company,
|
|
)
|
|
|
|
def _get_move_line(self, amount, amount_second_currency):
|
|
'Return counterpart Move Line for the amount'
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
|
|
if amount < 0:
|
|
account = self.journal.journal.debit_account
|
|
else:
|
|
account = self.journal.journal.credit_account
|
|
|
|
if not account:
|
|
self.raise_user_error('debit_credit_account_statement_journal',
|
|
(self.journal.rec_name,))
|
|
|
|
if self.journal.currency != self.company.currency:
|
|
second_currency = self.journal.currency
|
|
amount_second_currency *= -1
|
|
else:
|
|
second_currency = None
|
|
amount_second_currency = None
|
|
|
|
return MoveLine(
|
|
debit=abs(amount) if amount < 0 else 0,
|
|
credit=abs(amount) if amount > 0 else 0,
|
|
account=account,
|
|
second_currency=second_currency,
|
|
amount_second_currency=amount_second_currency,
|
|
)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('posted')
|
|
def post(cls, statements):
|
|
StatementLine = Pool().get('account.statement.line')
|
|
|
|
lines = [l for s in statements for l in s.lines]
|
|
StatementLine.post_move(lines)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('cancel')
|
|
def cancel(cls, statements):
|
|
StatementLine = Pool().get('account.statement.line')
|
|
|
|
lines = [l for s in statements for l in s.lines]
|
|
StatementLine.delete_move(lines)
|
|
|
|
|
|
class Line(ModelSQL, ModelView):
|
|
'Account Statement Line'
|
|
__name__ = 'account.statement.line'
|
|
_states = {
|
|
'readonly': Eval('statement_state') != 'draft',
|
|
}
|
|
_depends = ['statement_state']
|
|
|
|
statement = fields.Many2One('account.statement', 'Statement',
|
|
required=True, ondelete='CASCADE', states=_states, depends=_depends)
|
|
statement_state = fields.Function(
|
|
fields.Selection(STATES, 'Statement State'),
|
|
'on_change_with_statement_state')
|
|
sequence = fields.Integer('Sequence')
|
|
number = fields.Char('Number')
|
|
date = fields.Date('Date', required=True, states=_states, depends=_depends)
|
|
amount = fields.Numeric('Amount', required=True,
|
|
digits=(16, Eval('_parent_statement', {}).get('currency_digits', 2)),
|
|
states=_states, depends=_depends)
|
|
party = fields.Many2One('party.party', 'Party',
|
|
states=_states, depends=_depends)
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
domain=[
|
|
('company', '=', Eval('_parent_statement', {}).get('company', 0)),
|
|
('kind', '!=', 'view'),
|
|
],
|
|
states=_states, depends=_depends)
|
|
description = fields.Char('Description', states=_states, depends=_depends)
|
|
move = fields.Many2One('account.move', 'Account Move', readonly=True,
|
|
domain=[
|
|
('company', '=', Eval('_parent_statement', {}).get('company', -1)),
|
|
])
|
|
invoice = fields.Many2One('account.invoice', 'Invoice',
|
|
domain=[
|
|
If(Bool(Eval('party')), [('party', '=', Eval('party'))], []),
|
|
If(Bool(Eval('account')), [('account', '=', Eval('account'))], []),
|
|
If(Eval('_parent_statement', {}).get('state') == 'draft',
|
|
('state', '=', 'posted'),
|
|
('state', '!=', '')),
|
|
],
|
|
states=_states,
|
|
depends=['party', 'account'] + _depends)
|
|
|
|
del _states, _depends
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Line, cls).__setup__()
|
|
cls._order.insert(0, ('sequence', 'ASC'))
|
|
cls._error_messages.update({
|
|
'amount_greater_invoice_amount_to_pay': ('Amount "%s" is '
|
|
'greater than the amount to pay of invoice.'),
|
|
})
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('check_statement_line_amount', Check(t, t.amount != 0),
|
|
'Amount should be a positive or negative value.'),
|
|
]
|
|
|
|
@fields.depends('statement', '_parent_statement.state')
|
|
def on_change_with_statement_state(self, name=None):
|
|
if self.statement:
|
|
return self.statement.state
|
|
|
|
@staticmethod
|
|
def order_sequence(tables):
|
|
table, _ = tables[None]
|
|
return [Case((table.sequence == Null, 0), else_=1), table.sequence]
|
|
|
|
@staticmethod
|
|
def default_amount():
|
|
return Decimal(0)
|
|
|
|
@fields.depends('amount', 'party', 'invoice')
|
|
def on_change_party(self):
|
|
if self.party:
|
|
if self.amount:
|
|
if self.amount > Decimal("0.0"):
|
|
self.account = self.party.account_receivable
|
|
else:
|
|
self.account = self.party.account_payable
|
|
|
|
if self.invoice:
|
|
if self.party:
|
|
if self.invoice.party != self.party:
|
|
self.invoice = None
|
|
else:
|
|
self.invoice = None
|
|
|
|
@fields.depends('amount', 'party', 'account', 'invoice',
|
|
'_parent_statement.journal')
|
|
def on_change_amount(self):
|
|
Currency = Pool().get('currency.currency')
|
|
if self.party:
|
|
if self.account and self.account not in (
|
|
self.party.account_receivable, self.party.account_payable):
|
|
# The user has entered a non-default value, we keep it.
|
|
pass
|
|
elif self.amount:
|
|
if self.amount > Decimal("0.0"):
|
|
self.account = self.party.account_receivable
|
|
else:
|
|
self.account = self.party.account_payable
|
|
if self.invoice:
|
|
if self.amount and self.statement and self.statement.journal:
|
|
invoice = self.invoice
|
|
journal = self.statement.journal
|
|
with Transaction().set_context(date=invoice.currency_date):
|
|
amount_to_pay = Currency.compute(invoice.currency,
|
|
invoice.amount_to_pay, journal.currency)
|
|
if abs(self.amount) > amount_to_pay:
|
|
self.invoice = None
|
|
else:
|
|
self.invoice = None
|
|
|
|
@fields.depends('account', 'invoice')
|
|
def on_change_account(self):
|
|
if self.invoice:
|
|
if self.account:
|
|
if self.invoice.account != self.account:
|
|
self.invoice = None
|
|
else:
|
|
self.invoice = None
|
|
|
|
@fields.depends('party', 'account', 'invoice')
|
|
def on_change_invoice(self):
|
|
if self.invoice:
|
|
if not self.party:
|
|
self.party = self.invoice.party
|
|
if not self.account:
|
|
self.account = self.invoice.account
|
|
|
|
def get_rec_name(self, name):
|
|
return self.statement.rec_name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return [('statement.rec_name',) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def copy(cls, lines, default=None):
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
default.setdefault('move', None)
|
|
default.setdefault('invoice', None)
|
|
return super(Line, cls).copy(lines, default=default)
|
|
|
|
def reconcile(self, move_line):
|
|
pool = Pool()
|
|
Currency = pool.get('currency.currency')
|
|
Lang = pool.get('ir.lang')
|
|
Invoice = pool.get('account.invoice')
|
|
MoveLine = pool.get('account.move.line')
|
|
|
|
if self.invoice:
|
|
with Transaction().set_context(date=self.invoice.currency_date):
|
|
amount_to_pay = Currency.compute(self.invoice.currency,
|
|
self.invoice.amount_to_pay,
|
|
self.statement.journal.currency)
|
|
if abs(amount_to_pay) < abs(self.amount):
|
|
lang, = Lang.search([
|
|
('code', '=', Transaction().language),
|
|
])
|
|
|
|
amount = Lang.format(lang,
|
|
'%.' + str(self.statement.journal.currency.digits) + 'f',
|
|
self.amount, True)
|
|
self.raise_user_error('amount_greater_invoice_amount_to_pay',
|
|
error_args=(amount,))
|
|
|
|
with Transaction().set_context(date=self.invoice.currency_date):
|
|
amount = Currency.compute(self.statement.journal.currency,
|
|
self.amount, self.statement.company.currency)
|
|
|
|
reconcile_lines = self.invoice.get_reconcile_lines_for_amount(
|
|
amount)
|
|
|
|
assert move_line.account == self.invoice.account
|
|
|
|
Invoice.write([self.invoice], {
|
|
'payment_lines': [('add', [move_line.id])],
|
|
})
|
|
if reconcile_lines[1] == Decimal('0.0'):
|
|
lines = reconcile_lines[0] + [move_line]
|
|
MoveLine.reconcile(lines)
|
|
|
|
@classmethod
|
|
def post_move(cls, lines):
|
|
Move = Pool().get('account.move')
|
|
Move.post(list({l.move for l in lines if l.move}))
|
|
|
|
@classmethod
|
|
def delete_move(cls, lines):
|
|
Move = Pool().get('account.move')
|
|
Move.delete(list({l.move for l in lines if l.move}))
|
|
|
|
def get_move_line(self):
|
|
'''
|
|
Return the move line for the statement line
|
|
'''
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
Currency = Pool().get('currency.currency')
|
|
zero = Decimal("0.0")
|
|
with Transaction().set_context(date=self.date):
|
|
amount = Currency.compute(self.statement.journal.currency,
|
|
self.amount, self.statement.company.currency)
|
|
if self.statement.journal.currency != self.statement.company.currency:
|
|
second_currency = self.statement.journal.currency.id
|
|
amount_second_currency = -self.amount
|
|
else:
|
|
amount_second_currency = None
|
|
second_currency = None
|
|
|
|
return MoveLine(
|
|
description=self.description,
|
|
debit=amount < zero and -amount or zero,
|
|
credit=amount >= zero and amount or zero,
|
|
account=self.account,
|
|
party=self.party if self.account.party_required else None,
|
|
second_currency=second_currency,
|
|
amount_second_currency=amount_second_currency,
|
|
)
|
|
|
|
|
|
class LineGroup(ModelSQL, ModelView):
|
|
'Account Statement Line Group'
|
|
__name__ = 'account.statement.line.group'
|
|
_rec_name = 'number'
|
|
statement = fields.Many2One('account.statement', 'Statement')
|
|
journal = fields.Function(fields.Many2One('account.statement.journal',
|
|
'Journal'), 'get_journal', searcher='search_journal')
|
|
number = fields.Char('Number')
|
|
date = fields.Date('Date')
|
|
amount = fields.Numeric('Amount',
|
|
digits=(16, Eval('currency_digits', 2)),
|
|
depends=['currency_digits'])
|
|
currency = fields.Function(fields.Many2One('currency.currency',
|
|
'Currency'), 'get_currency')
|
|
currency_digits = fields.Function(fields.Integer('Currency Digits'),
|
|
'get_currency_digits')
|
|
party = fields.Many2One('party.party', 'Party')
|
|
move = fields.Many2One('account.move', 'Move')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(LineGroup, cls).__setup__()
|
|
cls._order.insert(0, ('date', 'DESC'))
|
|
|
|
@classmethod
|
|
def _grouped_columns(cls, line):
|
|
return [
|
|
Max(line.statement).as_('statement'),
|
|
Max(line.number).as_('number'),
|
|
Max(line.date).as_('date'),
|
|
Sum(line.amount).as_('amount'),
|
|
Max(line.party).as_('party'),
|
|
]
|
|
|
|
@classmethod
|
|
def table_query(cls):
|
|
pool = Pool()
|
|
Move = pool.get('account.move')
|
|
Line = pool.get('account.statement.line')
|
|
move = Move.__table__()
|
|
line = Line.__table__()
|
|
|
|
std_columns = [
|
|
move.id,
|
|
move.create_uid,
|
|
move.create_date,
|
|
move.write_uid,
|
|
move.write_date,
|
|
]
|
|
|
|
columns = (std_columns + [move.id.as_('move')]
|
|
+ cls._grouped_columns(line))
|
|
return move.join(line,
|
|
condition=move.id == line.move
|
|
).select(*columns,
|
|
where=move.origin.ilike(Statement.__name__ + ',%'),
|
|
group_by=std_columns + [move.id]
|
|
)
|
|
|
|
def get_journal(self, name):
|
|
return self.statement.journal.id
|
|
|
|
@classmethod
|
|
def search_journal(cls, name, clause):
|
|
return [('statement.journal',) + tuple(clause[1:])]
|
|
|
|
def get_currency(self, name):
|
|
return self.statement.journal.currency.id
|
|
|
|
def get_currency_digits(self, name):
|
|
return self.statement.journal.currency.digits
|
|
|
|
|
|
class StatementReport(CompanyReport):
|
|
__name__ = 'account.statement'
|