1652 lines
64 KiB
Python
1652 lines
64 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
|
|
import datetime
|
|
import time
|
|
|
|
from trytond.model import ModelView, ModelSQL, fields
|
|
from trytond.wizard import Wizard, StateTransition, StateView, StateAction, \
|
|
Button
|
|
from trytond.report import Report
|
|
from trytond.backend import TableHandler, FIELDS
|
|
from trytond.pyson import Eval, PYSONEncoder
|
|
from trytond.transaction import Transaction
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.rpc import RPC
|
|
|
|
__all__ = ['Move', 'Reconciliation', 'Line', 'Move2', 'OpenJournalAsk',
|
|
'OpenJournal', 'OpenAccount', 'ReconcileLinesWriteOff', 'ReconcileLines',
|
|
'UnreconcileLinesStart', 'UnreconcileLines', 'OpenReconcileLinesStart',
|
|
'OpenReconcileLines', 'FiscalYearLine', 'FiscalYear2',
|
|
'PrintGeneralJournalStart', 'PrintGeneralJournal', 'GeneralJournal']
|
|
__metaclass__ = PoolMeta
|
|
|
|
_MOVE_STATES = {
|
|
'readonly': Eval('state') == 'posted',
|
|
}
|
|
_MOVE_DEPENDS = ['state']
|
|
_LINE_STATES = {
|
|
'readonly': Eval('state') == 'valid',
|
|
}
|
|
_LINE_DEPENDS = ['state']
|
|
|
|
|
|
class Move(ModelSQL, ModelView):
|
|
'Account Move'
|
|
__name__ = 'account.move'
|
|
_rec_name = 'number'
|
|
number = fields.Char('Number', required=True, readonly=True)
|
|
post_number = fields.Char('Post Number', readonly=True,
|
|
help='Also known as Folio Number')
|
|
period = fields.Many2One('account.period', 'Period', required=True,
|
|
states=_MOVE_STATES, depends=_MOVE_DEPENDS, select=True)
|
|
journal = fields.Many2One('account.journal', 'Journal', required=True,
|
|
states=_MOVE_STATES, depends=_MOVE_DEPENDS)
|
|
date = fields.Date('Effective Date', required=True, states=_MOVE_STATES,
|
|
depends=_MOVE_DEPENDS, on_change_with=['period', 'journal', 'date'])
|
|
post_date = fields.Date('Post Date', readonly=True)
|
|
description = fields.Char('Description', states=_MOVE_STATES,
|
|
depends=_MOVE_DEPENDS)
|
|
origin = fields.Reference('Origin', selection='get_origin',
|
|
states=_MOVE_STATES, depends=_MOVE_DEPENDS)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('posted', 'Posted'),
|
|
], 'State', required=True, readonly=True, select=True)
|
|
lines = fields.One2Many('account.move.line', 'move', 'Lines',
|
|
states=_MOVE_STATES, depends=_MOVE_DEPENDS,
|
|
context={
|
|
'journal': Eval('journal'),
|
|
'period': Eval('period'),
|
|
'date': Eval('date'),
|
|
})
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Move, cls).__setup__()
|
|
cls._check_modify_exclude = ['state']
|
|
cls._constraints += [
|
|
('check_centralisation', 'period_centralized_journal'),
|
|
('check_company', 'company_in_move'),
|
|
('check_date', 'date_outside_period'),
|
|
]
|
|
cls._order.insert(0, ('date', 'DESC'))
|
|
cls._order.insert(1, ('number', 'DESC'))
|
|
cls._error_messages.update({
|
|
'del_posted_move': 'You can not delete posted moves!',
|
|
'post_empty_move': 'You can not post an empty move!',
|
|
'post_unbalanced_move': 'You can not post an unbalanced move!',
|
|
'draft_posted_move_journal': 'You can not set a posted move ' \
|
|
'to draft in this journal!',
|
|
'modify_posted_move': 'Move "%s" is already posted!\n'
|
|
'You can not modify a posted move.',
|
|
'period_centralized_journal': 'You can not create more than ' \
|
|
'one move per period\n' \
|
|
'in a centralized journal!',
|
|
'company_in_move': 'You can not create lines on accounts\n' \
|
|
'of different companies in the same move!',
|
|
'date_outside_period': 'You can not create move ' \
|
|
'with a date outside the period!',
|
|
'draft_closed_period': 'You can not set to draft a move ' \
|
|
'from a closed period',
|
|
})
|
|
cls._buttons.update({
|
|
'post': {
|
|
'invisible': Eval('state') == 'posted',
|
|
},
|
|
'draft': {
|
|
'invisible': Eval('state') == 'draft',
|
|
},
|
|
})
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
cursor = Transaction().cursor
|
|
table = TableHandler(cursor, cls, module_name)
|
|
|
|
# Migration from 2.4:
|
|
# - name renamed into number
|
|
# - reference renamed into post_number
|
|
if table.column_exist('name'):
|
|
table.column_rename('name', 'number')
|
|
if table.column_exist('reference'):
|
|
table.column_rename('reference', 'post_number')
|
|
|
|
super(Move, cls).__register__(module_name)
|
|
|
|
table = TableHandler(cursor, cls, module_name)
|
|
table.index_action(['journal', 'period'], 'add')
|
|
|
|
# Add index on create_date
|
|
table.index_action('create_date', action='add')
|
|
|
|
@staticmethod
|
|
def default_period():
|
|
Period = Pool().get('account.period')
|
|
return Period.find(Transaction().context.get('company'),
|
|
exception=False)
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@classmethod
|
|
def default_date(cls):
|
|
pool = Pool()
|
|
Period = pool.get('account.period')
|
|
Date = pool.get('ir.date')
|
|
period_id = cls.default_period()
|
|
if period_id:
|
|
period = Period(period_id)
|
|
return period.start_date
|
|
return Date.today()
|
|
|
|
def on_change_with_date(self):
|
|
Line = Pool().get('account.move.line')
|
|
date = self.date
|
|
lines = Line.search([
|
|
('journal', '=', self.journal),
|
|
('period', '=', self.period),
|
|
], order=[('id', 'DESC')], limit=1)
|
|
if lines:
|
|
date = lines[0].date
|
|
elif self.period:
|
|
date = self.period.start_date
|
|
return date
|
|
|
|
@classmethod
|
|
def _get_origin(cls):
|
|
'Return list of Model names for origin Reference'
|
|
return []
|
|
|
|
@classmethod
|
|
def get_origin(cls):
|
|
Model = Pool().get('ir.model')
|
|
models = cls._get_origin()
|
|
models = Model.search([
|
|
('model', 'in', models),
|
|
])
|
|
return [('', '')] + [(m.model, m.name) for m in models]
|
|
|
|
def check_centralisation(self):
|
|
if self.journal.centralised:
|
|
moves = self.search([
|
|
('period', '=', self.period.id),
|
|
('journal', '=', self.journal.id),
|
|
('state', '!=', 'posted'),
|
|
], limit=2)
|
|
if len(moves) > 1:
|
|
return False
|
|
return True
|
|
|
|
def check_company(self):
|
|
company_id = -1
|
|
for line in self.lines:
|
|
if company_id < 0:
|
|
company_id = line.account.company.id
|
|
if line.account.company.id != company_id:
|
|
return False
|
|
return True
|
|
|
|
def check_date(self):
|
|
return self.period.start_date <= self.date <= self.period.end_date
|
|
|
|
@classmethod
|
|
def check_modify(cls, moves):
|
|
'Check posted moves for modifications.'
|
|
for move in moves:
|
|
if move.state == 'posted':
|
|
cls.raise_user_error('modify_posted_move',
|
|
error_args=(move.rec_name,))
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
moves = cls.search(['OR',
|
|
('post_number',) + tuple(clause[1:]),
|
|
(cls._rec_name,) + tuple(clause[1:]),
|
|
])
|
|
return [('id', 'in', [m.id for m in moves])]
|
|
|
|
@classmethod
|
|
def write(cls, moves, vals):
|
|
keys = vals.keys()
|
|
for key in cls._check_modify_exclude:
|
|
if key in keys:
|
|
keys.remove(key)
|
|
if len(keys):
|
|
cls.check_modify(moves)
|
|
super(Move, cls).write(moves, vals)
|
|
cls.validate(moves)
|
|
|
|
@classmethod
|
|
def create(cls, vals):
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
Sequence = pool.get('ir.sequence')
|
|
Journal = pool.get('account.journal')
|
|
|
|
if not vals.get('number'):
|
|
journal_id = (vals.get('journal')
|
|
or Transaction().context.get('journal'))
|
|
if journal_id:
|
|
vals = vals.copy()
|
|
journal = Journal(journal_id)
|
|
vals['number'] = Sequence.get_id(journal.sequence.id)
|
|
|
|
move = super(Move, cls).create(vals)
|
|
if move.journal.centralised:
|
|
line = MoveLine.create({
|
|
'account': move.journal.credit_account.id,
|
|
'move': move.id,
|
|
})
|
|
cls.write([move], {
|
|
'centralised_line': line.id,
|
|
})
|
|
if 'lines' in vals:
|
|
cls.validate([move])
|
|
return move
|
|
|
|
@classmethod
|
|
def delete(cls, moves):
|
|
MoveLine = Pool().get('account.move.line')
|
|
cls.check_modify(moves)
|
|
MoveLine.delete([l for m in moves for l in m.lines])
|
|
super(Move, cls).delete(moves)
|
|
|
|
@classmethod
|
|
def copy(cls, moves, default=None):
|
|
Line = Pool().get('account.move.line')
|
|
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
default['number'] = None
|
|
default['post_number'] = None
|
|
default['state'] = cls.default_state()
|
|
default['post_date'] = None
|
|
default['lines'] = None
|
|
|
|
new_moves = []
|
|
for move in moves:
|
|
new_move, = super(Move, cls).copy([move], default=default)
|
|
Line.copy(move.lines, default={
|
|
'move': new_move.id,
|
|
})
|
|
new_moves.append(new_move)
|
|
return new_moves
|
|
|
|
@classmethod
|
|
def validate(cls, moves):
|
|
'''
|
|
Validate balanced move and centralise it if in centralised journal
|
|
'''
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
for move in moves:
|
|
if not move.lines:
|
|
continue
|
|
amount = Decimal('0.0')
|
|
company = None
|
|
draft_lines = []
|
|
for line in move.lines:
|
|
amount += line.debit - line.credit
|
|
if not company:
|
|
company = line.account.company
|
|
if line.state == 'draft':
|
|
draft_lines.append(line)
|
|
if not company.currency.is_zero(amount):
|
|
if not move.journal.centralised:
|
|
MoveLine.write(
|
|
[x for x in move.lines if x.state != 'draft'], {
|
|
'state': 'draft',
|
|
})
|
|
else:
|
|
if not move.centralised_line:
|
|
centralised_amount = - amount
|
|
else:
|
|
centralised_amount = move.centralised_line.debit \
|
|
- move.centralised_line.credit \
|
|
- amount
|
|
if centralised_amount >= Decimal('0.0'):
|
|
debit = centralised_amount
|
|
credit = Decimal('0.0')
|
|
account_id = move.journal.debit_account.id
|
|
else:
|
|
debit = Decimal('0.0')
|
|
credit = - centralised_amount
|
|
account_id = move.journal.credit_account.id
|
|
if not move.centralised_line:
|
|
centralised_line = MoveLine.create({
|
|
'debit': debit,
|
|
'credit': credit,
|
|
'account': account_id,
|
|
'move': move.id,
|
|
})
|
|
cls.write([move], {
|
|
'centralised_line': centralised_line.id,
|
|
})
|
|
else:
|
|
MoveLine.write([move.centralised_line], {
|
|
'debit': debit,
|
|
'credit': credit,
|
|
'account': account_id,
|
|
})
|
|
continue
|
|
if not draft_lines:
|
|
continue
|
|
MoveLine.write(draft_lines, {
|
|
'state': 'valid',
|
|
})
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def post(cls, moves):
|
|
pool = Pool()
|
|
Sequence = pool.get('ir.sequence')
|
|
Date = pool.get('ir.date')
|
|
|
|
for move in moves:
|
|
amount = Decimal('0.0')
|
|
if not move.lines:
|
|
cls.raise_user_error('post_empty_move')
|
|
company = None
|
|
for line in move.lines:
|
|
amount += line.debit - line.credit
|
|
if not company:
|
|
company = line.account.company
|
|
if not company.currency.is_zero(amount):
|
|
cls.raise_user_error('post_unbalanced_move')
|
|
for move in moves:
|
|
values = {
|
|
'state': 'posted',
|
|
}
|
|
if not move.post_number:
|
|
values['post_date'] = Date.today()
|
|
values['post_number'] = Sequence.get_id(
|
|
move.period.post_move_sequence.id)
|
|
cls.write([move], values)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def draft(cls, moves):
|
|
for move in moves:
|
|
if not move.journal.update_posted:
|
|
cls.raise_user_error('draft_posted_move_journal')
|
|
if move.period.state == 'close':
|
|
cls.raise_user_error('draft_closed_period')
|
|
return cls.write(moves, {
|
|
'state': 'draft',
|
|
})
|
|
|
|
|
|
class Reconciliation(ModelSQL, ModelView):
|
|
'Account Move Reconciliation Lines'
|
|
__name__ = 'account.move.reconciliation'
|
|
name = fields.Char('Name', size=None, required=True)
|
|
lines = fields.One2Many('account.move.line', 'reconciliation',
|
|
'Lines')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Reconciliation, cls).__setup__()
|
|
cls._constraints += [
|
|
('check_lines', 'invalid_reconciliation'),
|
|
]
|
|
cls._error_messages.update({
|
|
'modify': 'You can not modify a reconciliation!',
|
|
'invalid_reconciliation': 'You can not create reconciliation ' \
|
|
'where lines are not balanced, nor valid, ' \
|
|
'nor in the same account, nor in account to reconcile, ' \
|
|
'nor from the same party!',
|
|
})
|
|
|
|
@classmethod
|
|
def create(cls, vals):
|
|
Sequence = Pool().get('ir.sequence')
|
|
|
|
if 'name' not in vals:
|
|
vals = vals.copy()
|
|
vals['name'] = Sequence.get('account.move.reconciliation')
|
|
|
|
return super(Reconciliation, cls).create(vals)
|
|
|
|
@classmethod
|
|
def write(cls, moves, vals):
|
|
cls.raise_user_error('modify')
|
|
|
|
@classmethod
|
|
def check_lines(cls, reconciliations):
|
|
for reconciliation in reconciliations:
|
|
amount = Decimal('0.0')
|
|
account = None
|
|
party = None
|
|
for line in reconciliation.lines:
|
|
if line.state != 'valid':
|
|
return False
|
|
amount += line.debit - line.credit
|
|
if not account:
|
|
account = line.account
|
|
elif account.id != line.account.id:
|
|
return False
|
|
if not account.reconcile:
|
|
return False
|
|
if not party:
|
|
party = line.party
|
|
elif line.party and party.id != line.party.id:
|
|
return False
|
|
if not account.company.currency.is_zero(amount):
|
|
return False
|
|
return True
|
|
|
|
|
|
class Line(ModelSQL, ModelView):
|
|
'Account Move Line'
|
|
__name__ = 'account.move.line'
|
|
_rec_name = 'id'
|
|
debit = fields.Numeric('Debit', digits=(16, Eval('currency_digits', 2)),
|
|
required=True,
|
|
on_change=['account', 'debit', 'credit', 'tax_lines', 'journal',
|
|
'move'],
|
|
depends=['currency_digits', 'credit', 'tax_lines', 'journal'])
|
|
credit = fields.Numeric('Credit', digits=(16, Eval('currency_digits', 2)),
|
|
required=True,
|
|
on_change=['account', 'debit', 'credit', 'tax_lines', 'journal',
|
|
'move'],
|
|
depends=['currency_digits', 'debit', 'tax_lines', 'journal'])
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
domain=[('kind', '!=', 'view')],
|
|
select=True,
|
|
on_change=['account', 'debit', 'credit', 'tax_lines',
|
|
'journal', 'move'])
|
|
move = fields.Many2One('account.move', 'Move', select=True, required=True,
|
|
states={
|
|
'required': False,
|
|
'readonly': Eval('state') == 'valid',
|
|
},
|
|
depends=['state'])
|
|
journal = fields.Function(fields.Many2One('account.journal', 'Journal'),
|
|
'get_move_field', setter='set_move_field',
|
|
searcher='search_move_field')
|
|
period = fields.Function(fields.Many2One('account.period', 'Period'),
|
|
'get_move_field', setter='set_move_field',
|
|
searcher='search_move_field')
|
|
date = fields.Function(fields.Date('Effective Date', required=True),
|
|
'get_move_field', setter='set_move_field',
|
|
searcher='search_move_field')
|
|
origin = fields.Function(fields.Reference('Origin',
|
|
selection='get_origin'),
|
|
'get_move_field', searcher='search_move_field')
|
|
description = fields.Char('Description')
|
|
amount_second_currency = fields.Numeric('Amount Second Currency',
|
|
digits=(16, Eval('second_currency_digits', 2)),
|
|
help='The amount expressed in a second currency',
|
|
depends=['second_currency_digits'])
|
|
second_currency = fields.Many2One('currency.currency', 'Second Currency',
|
|
help='The second currency')
|
|
party = fields.Many2One('party.party', 'Party',
|
|
on_change=['move', 'party', 'account', 'debit', 'credit',
|
|
'journal'], select=True, depends=['debit', 'credit', 'account',
|
|
'journal'])
|
|
maturity_date = fields.Date('Maturity Date',
|
|
help='This field is used for payable and receivable lines. \n' \
|
|
'You can put the limit date for the payment.')
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('valid', 'Valid'),
|
|
], 'State', readonly=True, required=True, select=True)
|
|
reconciliation = fields.Many2One('account.move.reconciliation',
|
|
'Reconciliation', readonly=True, ondelete='SET NULL', select=True)
|
|
tax_lines = fields.One2Many('account.tax.line', 'move_line', 'Tax Lines')
|
|
move_state = fields.Function(fields.Selection([
|
|
('draft', 'Draft'),
|
|
('posted', 'Posted'),
|
|
], 'Move State'), 'get_move_field', searcher='search_move_field')
|
|
currency_digits = fields.Function(fields.Integer('Currency Digits'),
|
|
'get_currency_digits')
|
|
second_currency_digits = fields.Function(fields.Integer(
|
|
'Second Currency Digits'), 'get_currency_digits')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Line, cls).__setup__()
|
|
cls._sql_constraints += [
|
|
('credit_debit',
|
|
'CHECK(credit * debit = 0.0)',
|
|
'Wrong credit/debit values!'),
|
|
]
|
|
cls._constraints += [
|
|
('check_account', 'move_view_inactive_account'),
|
|
]
|
|
cls.__rpc__.update({
|
|
'on_write': RPC(instantiate=0),
|
|
'get_origin': RPC(),
|
|
})
|
|
cls._order[0] = ('id', 'DESC')
|
|
cls._error_messages.update({
|
|
'add_modify_closed_journal_period': 'You can not ' \
|
|
'add/modify lines in a closed journal period!',
|
|
'modify_posted_move': 'You can not modify lines of a posted move!',
|
|
'modify_reconciled': 'You can not modify reconciled lines!',
|
|
'no_journal': 'No journal defined!',
|
|
'move_view_inactive_account': 'You can not create move lines\n' \
|
|
'on view/inactive accounts!',
|
|
'already_reconciled': 'Line "%s" (%d) already reconciled!',
|
|
})
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
cursor = Transaction().cursor
|
|
table = TableHandler(cursor, cls, module_name)
|
|
|
|
# Migration from 2.4: reference renamed into description
|
|
if table.column_exist('reference'):
|
|
table.column_rename('reference', 'description')
|
|
|
|
super(Line, cls).__register__(module_name)
|
|
|
|
table = TableHandler(cursor, cls, module_name)
|
|
# Index for General Ledger
|
|
table.index_action(['move', 'account'], 'add')
|
|
|
|
# Migration from 1.2
|
|
table.not_null_action('blocked', action='remove')
|
|
|
|
# Migration from 2.4: remove name, active
|
|
table.not_null_action('name', action='remove')
|
|
table.not_null_action('active', action='remove')
|
|
table.index_action('active', action='remove')
|
|
|
|
@classmethod
|
|
def default_date(cls):
|
|
'''
|
|
Return the date of the last line for journal, period
|
|
or the starting date of the period
|
|
or today
|
|
'''
|
|
pool = Pool()
|
|
Period = pool.get('account.period')
|
|
Date = pool.get('ir.date')
|
|
|
|
date = Date.today()
|
|
lines = cls.search([
|
|
('journal', '=', Transaction().context.get('journal')),
|
|
('period', '=', Transaction().context.get('period')),
|
|
], order=[('id', 'DESC')], limit=1)
|
|
if lines:
|
|
date = lines[0].date
|
|
elif Transaction().context.get('period'):
|
|
period = Period(Transaction().context['period'])
|
|
date = period.start_date
|
|
if Transaction().context.get('date'):
|
|
date = Transaction().context['date']
|
|
return date
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@staticmethod
|
|
def default_currency_digits():
|
|
return 2
|
|
|
|
@staticmethod
|
|
def default_debit():
|
|
return Decimal(0)
|
|
|
|
@staticmethod
|
|
def default_credit():
|
|
return Decimal(0)
|
|
|
|
@classmethod
|
|
def default_get(cls, fields, with_rec_name=True):
|
|
pool = Pool()
|
|
Move = pool.get('account.move')
|
|
Tax = pool.get('account.tax')
|
|
Account = pool.get('account.account')
|
|
TaxCode = pool.get('account.tax.code')
|
|
values = super(Line, cls).default_get(fields,
|
|
with_rec_name=with_rec_name)
|
|
|
|
if 'move' not in fields:
|
|
#Not manual entry
|
|
if 'date' in values:
|
|
values = values.copy()
|
|
del values['date']
|
|
return values
|
|
|
|
if (Transaction().context.get('journal')
|
|
and Transaction().context.get('period')):
|
|
lines = cls.search([
|
|
('move.journal', '=', Transaction().context['journal']),
|
|
('move.period', '=', Transaction().context['period']),
|
|
('create_uid', '=', Transaction().user),
|
|
('state', '=', 'draft'),
|
|
], order=[('id', 'DESC')], limit=1)
|
|
if not lines:
|
|
return values
|
|
values['move'] = lines[0].move.id
|
|
|
|
if 'move' not in values:
|
|
return values
|
|
|
|
move = Move(values['move'])
|
|
total = Decimal('0.0')
|
|
taxes = {}
|
|
no_code_taxes = []
|
|
for line in move.lines:
|
|
total += line.debit - line.credit
|
|
if line.party and 'party' in fields:
|
|
values.setdefault('party', line.party.id)
|
|
if move.journal.type in ('expense', 'revenue'):
|
|
line_code_taxes = [x.code.id for x in line.tax_lines]
|
|
for tax in line.account.taxes:
|
|
if move.journal.type == 'revenue':
|
|
if line.debit:
|
|
base_id = (tax.credit_note_base_code.id
|
|
if tax.credit_note_base_code else None)
|
|
code_id = (tax.credit_note_tax_code.id
|
|
if tax.credit_note_tax_code else None)
|
|
account_id = (tax.credit_note_account.id
|
|
if tax.credit_note_account else None)
|
|
else:
|
|
base_id = (tax.invoice_base_code.id
|
|
if tax.invoice_base_code else None)
|
|
code_id = (tax.invoice_tax_code.id
|
|
if tax.invoice_tax_code else None)
|
|
account_id = (tax.invoice_account.id
|
|
if tax.invoice_account else None)
|
|
else:
|
|
if line.debit:
|
|
base_id = (tax.invoice_base_code.id
|
|
if tax.invoice_base_code else None)
|
|
code_id = (tax.invoice_tax_code.id
|
|
if tax.invoice_tax_code else None)
|
|
account_id = (tax.invoice_account.id
|
|
if tax.invoice_account else None)
|
|
else:
|
|
base_id = (tax.credit_note_base_code.id
|
|
if tax.credit_note_base_code else None)
|
|
code_id = (tax.credit_note_tax_code.id
|
|
if tax.credit_note_tax_code else None)
|
|
account_id = (tax.credit_note_account.id
|
|
if tax.credit_note_account else None)
|
|
if base_id in line_code_taxes or not base_id:
|
|
taxes.setdefault((account_id, code_id, tax.id), None)
|
|
for tax_line in line.tax_lines:
|
|
taxes[(line.account.id, tax_line.code.id,
|
|
tax_line.tax.id)] = True
|
|
if not line.tax_lines:
|
|
no_code_taxes.append(line.account.id)
|
|
for no_code_account_id in no_code_taxes:
|
|
for (account_id, code_id, tax_id), test in \
|
|
taxes.iteritems():
|
|
if (not test
|
|
and not code_id
|
|
and no_code_account_id == account_id):
|
|
taxes[(account_id, code_id, tax_id)] = True
|
|
|
|
if 'account' in fields:
|
|
if total >= Decimal('0.0'):
|
|
values['account'] = (move.journal.credit_account \
|
|
and move.journal.credit_account.id or None)
|
|
else:
|
|
values['account'] = (move.journal.debit_account \
|
|
and move.journal.debit_account.id or None)
|
|
|
|
if ('debit' in fields) or ('credit' in fields):
|
|
values['debit'] = total < 0 and - total or Decimal(0)
|
|
values['credit'] = total > 0 and total or Decimal(0)
|
|
|
|
if move.journal.type in ('expense', 'revenue'):
|
|
for account_id, code_id, tax_id in taxes:
|
|
if taxes[(account_id, code_id, tax_id)]:
|
|
continue
|
|
for line in move.lines:
|
|
if move.journal.type == 'revenue':
|
|
if line.debit:
|
|
key = 'credit_note'
|
|
else:
|
|
key = 'invoice'
|
|
else:
|
|
if line.debit:
|
|
key = 'invoice'
|
|
else:
|
|
key = 'credit_note'
|
|
line_amount = Decimal('0.0')
|
|
tax_amount = Decimal('0.0')
|
|
for tax_line in Tax.compute(line.account.taxes,
|
|
line.debit or line.credit, 1):
|
|
tax_account = getattr(tax_line['tax'],
|
|
key + '_account')
|
|
tax_code = getattr(tax_line['tax'], key + '_tax_code')
|
|
if ((tax_account.id if tax_account
|
|
else line.account.id) == account_id
|
|
and (tax_code.id if tax_code else None
|
|
== code_id)
|
|
and tax_line['tax'].id == tax_id):
|
|
if line.debit:
|
|
line_amount += tax_line['amount']
|
|
else:
|
|
line_amount -= tax_line['amount']
|
|
tax_amount += tax_line['amount'] * \
|
|
tax_line['tax'][key + '_tax_sign']
|
|
line_amount = line.account.company.currency.round(
|
|
line_amount)
|
|
tax_amount = line.account.company.currency.round(
|
|
tax_amount)
|
|
if ('debit' in fields):
|
|
values['debit'] = line_amount > Decimal('0.0') \
|
|
and line_amount or Decimal('0.0')
|
|
if ('credit' in fields):
|
|
values['credit'] = line_amount < Decimal('0.0') \
|
|
and - line_amount or Decimal('0.0')
|
|
if 'account' in fields and account_id:
|
|
values['account'] = account_id
|
|
values['account.rec_name'] = Account(
|
|
account_id).rec_name
|
|
if 'tax_lines' in fields and code_id:
|
|
values['tax_lines'] = [
|
|
{
|
|
'amount': tax_amount,
|
|
'currency_digits': line.currency_digits,
|
|
'code': code_id,
|
|
'code.rec_name': TaxCode(code_id).rec_name,
|
|
'tax': tax_id,
|
|
'tax.rec_name': Tax(tax_id).rec_name,
|
|
},
|
|
]
|
|
return values
|
|
|
|
@classmethod
|
|
def get_currency_digits(cls, lines, names):
|
|
digits = {}
|
|
for line in lines:
|
|
for name in names:
|
|
digits.setdefault(name, {})
|
|
digits[name].setdefault(line.id, 2)
|
|
if name == 'currency_digits':
|
|
digits[name][line.id] = line.account.currency_digits
|
|
elif name == 'second_currency_digits':
|
|
second_currency = line.account.second_currency
|
|
if second_currency:
|
|
digits[name][line.id] = second_currency.digits
|
|
return digits
|
|
|
|
@classmethod
|
|
def get_origin(cls):
|
|
Move = Pool().get('account.move')
|
|
return Move.get_origin()
|
|
|
|
def on_change_debit(self):
|
|
changes = {}
|
|
Journal = Pool().get('account.journal')
|
|
if self.journal or Transaction().context.get('journal'):
|
|
journal = self.journal or Journal(Transaction().context['journal'])
|
|
if journal.type in ('expense', 'revenue'):
|
|
changes['tax_lines'] = self._compute_tax_lines(journal.type)
|
|
if not changes['tax_lines']:
|
|
del changes['tax_lines']
|
|
if self.debit:
|
|
changes['credit'] = Decimal('0.0')
|
|
return changes
|
|
|
|
def on_change_credit(self):
|
|
changes = {}
|
|
Journal = Pool().get('account.journal')
|
|
if self.journal or Transaction().context.get('journal'):
|
|
journal = self.journal or Journal(Transaction().context['journal'])
|
|
if journal.type in ('expense', 'revenue'):
|
|
changes['tax_lines'] = self._compute_tax_lines(journal.type)
|
|
if not changes['tax_lines']:
|
|
del changes['tax_lines']
|
|
if self.credit:
|
|
changes['debit'] = Decimal('0.0')
|
|
return changes
|
|
|
|
def on_change_account(self):
|
|
Journal = Pool().get('account.journal')
|
|
|
|
changes = {}
|
|
if Transaction().context.get('journal'):
|
|
journal = Journal(Transaction().context['journal'])
|
|
if journal.type in ('expense', 'revenue'):
|
|
changes['tax_lines'] = self._compute_tax_lines(journal.type)
|
|
if not changes['tax_lines']:
|
|
del changes['tax_lines']
|
|
|
|
if self.account:
|
|
changes['currency_digits'] = self.account.currency_digits
|
|
if self.account.second_currency:
|
|
changes['second_currency_digits'] = \
|
|
self.account.second_currency.digits
|
|
return changes
|
|
|
|
def _compute_tax_lines(self, journal_type):
|
|
res = {}
|
|
pool = Pool()
|
|
TaxCode = pool.get('account.tax.code')
|
|
Tax = pool.get('account.tax')
|
|
TaxLine = pool.get('account.tax.line')
|
|
|
|
if self.move:
|
|
#Only for first line
|
|
return res
|
|
if self.tax_lines:
|
|
res['remove'] = [x['id'] for x in self.tax_lines]
|
|
if self.account:
|
|
debit = self.debit or Decimal('0.0')
|
|
credit = self.credit or Decimal('0.0')
|
|
for tax in self.account.taxes:
|
|
if journal_type == 'revenue':
|
|
if debit:
|
|
key = 'credit_note'
|
|
else:
|
|
key = 'invoice'
|
|
else:
|
|
if debit:
|
|
key = 'invoice'
|
|
else:
|
|
key = 'credit_note'
|
|
base_amounts = {}
|
|
for tax_line in Tax.compute(self.account.taxes,
|
|
debit or credit, 1):
|
|
code = getattr(tax_line['tax'], key + '_base_code')
|
|
code_id = code.id if code else None
|
|
if not code_id:
|
|
continue
|
|
tax_id = tax_line['tax'].id
|
|
base_amounts.setdefault((code_id, tax_id), Decimal('0.0'))
|
|
base_amounts[code_id, tax_id] += tax_line['base'] * \
|
|
getattr(tax_line['tax'], key + '_tax_sign')
|
|
for code_id, tax_id in base_amounts:
|
|
if not base_amounts[code_id, tax_id]:
|
|
continue
|
|
value = TaxLine.default_get(TaxLine._fields.keys())
|
|
value.update({
|
|
'amount': base_amounts[code_id, tax_id],
|
|
'currency_digits': self.account.currency_digits,
|
|
'code': code_id,
|
|
'code.rec_name': TaxCode(code_id).rec_name,
|
|
'tax': tax_id,
|
|
'tax.rec_name': Tax(tax_id).rec_name,
|
|
})
|
|
res.setdefault('add', []).append(value)
|
|
return res
|
|
|
|
def on_change_party(self):
|
|
Journal = Pool().get('account.journal')
|
|
cursor = Transaction().cursor
|
|
res = {}
|
|
if (not self.party) or self.account:
|
|
return res
|
|
|
|
if not self.party.account_receivable \
|
|
or not self.party.account_payable:
|
|
return res
|
|
|
|
if self.party and (not self.debit) and (not self.credit):
|
|
type_name = FIELDS[self.__class__.debit._type].sql_type(
|
|
self.__class__.debit)[0]
|
|
query = 'SELECT ' \
|
|
'CAST(COALESCE(SUM(' \
|
|
'(COALESCE(debit, 0) - COALESCE(credit, 0))' \
|
|
'), 0) AS ' + type_name + ') ' \
|
|
'FROM account_move_line ' \
|
|
'WHERE reconciliation IS NULL ' \
|
|
'AND party = %s ' \
|
|
'AND account = %s'
|
|
cursor.execute(query,
|
|
(self.party.id, self.party.account_receivable.id))
|
|
amount = cursor.fetchone()[0]
|
|
# SQLite uses float for SUM
|
|
if not isinstance(amount, Decimal):
|
|
amount = Decimal(str(amount))
|
|
if not self.party.account_receivable.currency.is_zero(amount):
|
|
if amount > Decimal('0.0'):
|
|
res['credit'] = \
|
|
self.party.account_receivable.currency.round(amount)
|
|
res['debit'] = Decimal('0.0')
|
|
else:
|
|
res['credit'] = Decimal('0.0')
|
|
res['debit'] = \
|
|
- self.party.account_receivable.currency.round(amount)
|
|
res['account'] = self.party.account_receivable.id
|
|
res['account.rec_name'] = \
|
|
self.party.account_receivable.rec_name
|
|
else:
|
|
cursor.execute(query,
|
|
(self.party.id, self.party.account_payable.id))
|
|
amount = cursor.fetchone()[0]
|
|
if not self.party.account_payable.currency.is_zero(amount):
|
|
if amount > Decimal('0.0'):
|
|
res['credit'] = \
|
|
self.party.account_payable.currency.round(amount)
|
|
res['debit'] = Decimal('0.0')
|
|
else:
|
|
res['credit'] = Decimal('0.0')
|
|
res['debit'] = \
|
|
- self.party.account_payable.currency.round(amount)
|
|
res['account'] = self.party.account_payable.id
|
|
res['account.rec_name'] = \
|
|
self.party.account_payable.rec_name
|
|
|
|
if self.party and self.debit:
|
|
if self.debit > Decimal('0.0'):
|
|
if 'account' not in res:
|
|
res['account'] = self.party.account_receivable.id
|
|
res['account.rec_name'] = \
|
|
self.party.account_receivable.rec_name
|
|
else:
|
|
if 'account' not in res:
|
|
res['account'] = self.party.account_payable.id
|
|
res['account.rec_name'] = \
|
|
self.party.account_payable.rec_name
|
|
|
|
if self.party and self.credit:
|
|
if self.credit > Decimal('0.0'):
|
|
if 'account' not in res:
|
|
res['account'] = self.party.account_payable.id
|
|
res['account.rec_name'] = \
|
|
self.party.account_payable.rec_name
|
|
else:
|
|
if 'account' not in res:
|
|
res['account'] = self.party.account_receivable.id
|
|
res['account.rec_name'] = \
|
|
self.party.account_receivable.rec_name
|
|
|
|
journal = None
|
|
if self.journal:
|
|
journal = self.journal
|
|
elif Transaction().context.get('journal'):
|
|
journal = Journal(Transaction().context.get('journal'))
|
|
if journal and self.party:
|
|
if journal.type == 'revenue':
|
|
if 'account' not in res:
|
|
res['account'] = self.party.account_receivable.id
|
|
res['account.rec_name'] = \
|
|
self.party.account_receivable.rec_name
|
|
elif journal.type == 'expense':
|
|
if 'account' not in res:
|
|
res['account'] = self.party.account_payable.id
|
|
res['account.rec_name'] = \
|
|
self.party.account_payable.rec_name
|
|
return res
|
|
|
|
def get_move_field(self, name):
|
|
if name == 'move_state':
|
|
name = 'state'
|
|
if name in ('date', 'state'):
|
|
return getattr(self.move, name)
|
|
elif name == 'origin':
|
|
origin = getattr(self.move, name)
|
|
if origin:
|
|
return str(origin)
|
|
return None
|
|
else:
|
|
return getattr(self.move, name).id
|
|
|
|
@classmethod
|
|
def set_move_field(cls, lines, name, value):
|
|
if name == 'move_state':
|
|
name = 'state'
|
|
if not value:
|
|
return
|
|
Move = Pool().get('account.move')
|
|
Move.write([line.move for line in lines], {
|
|
name: value,
|
|
})
|
|
|
|
@classmethod
|
|
def search_move_field(cls, name, clause):
|
|
if name == 'move_state':
|
|
name = 'state'
|
|
return [('move.' + name,) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def query_get(cls, obj='l'):
|
|
'''
|
|
Return SQL clause and fiscal years for account move line
|
|
depending of the context.
|
|
obj is the SQL alias of account_move_line in the query
|
|
'''
|
|
FiscalYear = Pool().get('account.fiscalyear')
|
|
|
|
if Transaction().context.get('date'):
|
|
time.strptime(str(Transaction().context['date']), '%Y-%m-%d')
|
|
fiscalyears = FiscalYear.search([
|
|
('start_date', '<=', Transaction().context['date']),
|
|
('end_date', '>=', Transaction().context['date']),
|
|
], limit=1)
|
|
|
|
fiscalyear_id = fiscalyears and fiscalyears[0].id or 0
|
|
|
|
if Transaction().context.get('posted'):
|
|
return (obj + '.state != \'draft\' ' \
|
|
'AND ' + obj + '.move IN (' \
|
|
'SELECT m.id FROM account_move AS m, ' \
|
|
'account_period AS p ' \
|
|
'WHERE m.period = p.id ' \
|
|
'AND p.fiscalyear = ' + \
|
|
str(fiscalyear_id) + ' ' \
|
|
'AND m.date <= date(\'' + \
|
|
str(Transaction().context['date']) + \
|
|
'\') ' \
|
|
'AND m.state = \'posted\' ' \
|
|
')', [f.id for f in fiscalyears])
|
|
else:
|
|
return (obj + '.state != \'draft\' ' \
|
|
'AND ' + obj + '.move IN (' \
|
|
'SELECT m.id FROM account_move AS m, ' \
|
|
'account_period AS p ' \
|
|
'WHERE m.period = p.id ' \
|
|
'AND p.fiscalyear = ' + \
|
|
str(fiscalyear_id) + ' ' \
|
|
'AND m.date <= date(\'' + \
|
|
str(Transaction().context['date']) + \
|
|
'\')' \
|
|
')', [f.id for f in fiscalyears])
|
|
|
|
if Transaction().context.get('periods'):
|
|
if Transaction().context.get('fiscalyear'):
|
|
fiscalyear_ids = [int(Transaction().context['fiscalyear'])]
|
|
else:
|
|
fiscalyear_ids = []
|
|
ids = ','.join(
|
|
str(int(x)) for x in Transaction().context['periods'])
|
|
if Transaction().context.get('posted'):
|
|
return (obj + '.state != \'draft\' '
|
|
'AND ' + obj + '.move IN ('
|
|
'SELECT id FROM account_move '
|
|
'WHERE period IN (' + ids + ') '
|
|
'AND state = \'posted\' '
|
|
')', fiscalyear_ids)
|
|
else:
|
|
return (obj + '.state != \'draft\' '
|
|
'AND ' + obj + '.move IN ('
|
|
'SELECT id FROM account_move '
|
|
'WHERE period IN (' + ids + ')'
|
|
')', fiscalyear_ids)
|
|
else:
|
|
if not Transaction().context.get('fiscalyear'):
|
|
fiscalyears = FiscalYear.search([
|
|
('state', '=', 'open'),
|
|
])
|
|
fiscalyear_ids = [f.id for f in fiscalyears]
|
|
fiscalyear_clause = (','.join(
|
|
str(f.id) for f in fiscalyears)) or '0'
|
|
else:
|
|
fiscalyear_ids = [int(Transaction().context.get('fiscalyear'))]
|
|
fiscalyear_clause = '%s' % int(
|
|
Transaction().context.get('fiscalyear'))
|
|
|
|
if Transaction().context.get('posted'):
|
|
return (obj + '.state != \'draft\' '
|
|
'AND ' + obj + '.move IN ('
|
|
'SELECT id FROM account_move '
|
|
'WHERE period IN ('
|
|
'SELECT id FROM account_period '
|
|
'WHERE fiscalyear IN (' + fiscalyear_clause + ')'
|
|
') '
|
|
'AND state = \'posted\' '
|
|
')', fiscalyear_ids)
|
|
else:
|
|
return (obj + '.state != \'draft\' '
|
|
'AND ' + obj + '.move IN ('
|
|
'SELECT id FROM account_move '
|
|
'WHERE period IN ('
|
|
'SELECT id FROM account_period '
|
|
'WHERE fiscalyear IN (' + fiscalyear_clause + ')'
|
|
')'
|
|
')', fiscalyear_ids)
|
|
|
|
@classmethod
|
|
def on_write(cls, lines):
|
|
return list(set(l.id for line in lines for l in line.move.lines))
|
|
|
|
def check_account(self):
|
|
if self.account.kind in ('view',):
|
|
return False
|
|
if not self.account.active:
|
|
return False
|
|
return True
|
|
|
|
@classmethod
|
|
def check_journal_period_modify(cls, period, journal):
|
|
'''
|
|
Check if the lines can be modified or created for the journal - period
|
|
and if there is no journal - period, create it
|
|
'''
|
|
JournalPeriod = Pool().get('account.journal.period')
|
|
journal_periods = JournalPeriod.search([
|
|
('journal', '=', journal.id),
|
|
('period', '=', period.id),
|
|
], limit=1)
|
|
if journal_periods:
|
|
journal_period, = journal_periods
|
|
if journal_period.state == 'close':
|
|
cls.raise_user_error('add_modify_closed_journal_period')
|
|
else:
|
|
JournalPeriod.create({
|
|
'name': journal.name + ' - ' + period.name,
|
|
'journal': journal.id,
|
|
'period': period.id,
|
|
})
|
|
|
|
@classmethod
|
|
def check_modify(cls, lines):
|
|
'''
|
|
Check if the lines can be modified
|
|
'''
|
|
journal_period_done = []
|
|
for line in lines:
|
|
if line.move.state == 'posted':
|
|
cls.raise_user_error('modify_posted_move')
|
|
if line.reconciliation:
|
|
cls.raise_user_error('modify_reconciled')
|
|
journal_period = (line.journal.id, line.period.id)
|
|
if journal_period not in journal_period_done:
|
|
cls.check_journal_period_modify(line.period,
|
|
line.journal)
|
|
journal_period_done.append(journal_period)
|
|
|
|
@classmethod
|
|
def delete(cls, lines):
|
|
Move = Pool().get('account.move')
|
|
cls.check_modify(lines)
|
|
moves = [x.move for x in lines]
|
|
super(Line, cls).delete(lines)
|
|
Move.validate(moves)
|
|
|
|
@classmethod
|
|
def write(cls, lines, vals):
|
|
Move = Pool().get('account.move')
|
|
|
|
if len(vals) > 1 or 'reconciliation' not in vals:
|
|
cls.check_modify(lines)
|
|
moves = [x.move for x in lines]
|
|
super(Line, cls).write(lines, vals)
|
|
|
|
Transaction().timestamp = {}
|
|
|
|
for line in lines:
|
|
if line.move not in moves:
|
|
moves.append(line.move)
|
|
Move.validate(moves)
|
|
|
|
@classmethod
|
|
def create(cls, vals):
|
|
pool = Pool()
|
|
Journal = pool.get('account.journal')
|
|
Move = pool.get('account.move')
|
|
vals = vals.copy()
|
|
if not vals.get('move'):
|
|
journal_id = (vals.get('journal')
|
|
or Transaction().context.get('journal'))
|
|
if not journal_id:
|
|
cls.raise_user_error('no_journal')
|
|
journal = Journal(journal_id)
|
|
if journal.centralised:
|
|
moves = Move.search([
|
|
('period', '=', vals.get('period')
|
|
or Transaction().context.get('period')),
|
|
('journal', '=', journal_id),
|
|
('state', '!=', 'posted'),
|
|
], limit=1)
|
|
if moves:
|
|
vals['move'] = moves[0].id
|
|
if not vals.get('move'):
|
|
vals['move'] = Move.create({
|
|
'period': (vals.get('period')
|
|
or Transaction().context.get('period')),
|
|
'journal': journal_id,
|
|
'date': vals.get('date'),
|
|
}).id
|
|
line = super(Line, cls).create(vals)
|
|
cls.check_journal_period_modify(line.period, line.journal)
|
|
Move.validate([line.move])
|
|
return line
|
|
|
|
@classmethod
|
|
def copy(cls, lines, default=None):
|
|
if default is None:
|
|
default = {}
|
|
if 'move' not in default:
|
|
default['move'] = None
|
|
if 'reconciliation' not in default:
|
|
default['reconciliation'] = None
|
|
return super(Line, cls).copy(lines, default=default)
|
|
|
|
@classmethod
|
|
def view_header_get(cls, value, view_type='form'):
|
|
JournalPeriod = Pool().get('account.journal.period')
|
|
if (not Transaction().context.get('journal')
|
|
or not Transaction().context.get('period')):
|
|
return value
|
|
journal_periods = JournalPeriod.search([
|
|
('journal', '=', Transaction().context['journal']),
|
|
('period', '=', Transaction().context['period']),
|
|
], limit=1)
|
|
if not journal_periods:
|
|
return value
|
|
journal_period, = journal_periods
|
|
return value + ': ' + journal_period.rec_name
|
|
|
|
@classmethod
|
|
def fields_view_get(cls, view_id=None, view_type='form'):
|
|
Journal = Pool().get('account.journal')
|
|
result = super(Line, cls).fields_view_get(view_id=view_id,
|
|
view_type=view_type)
|
|
if view_type == 'tree' and 'journal' in Transaction().context:
|
|
title = cls.view_header_get('', view_type=view_type)
|
|
journal = Journal(Transaction().context['journal'])
|
|
|
|
if not journal.view:
|
|
return result
|
|
|
|
xml = '<?xml version="1.0"?>\n' \
|
|
'<tree string="%s" editable="top" on_write="on_write" ' \
|
|
'colors="red:state==\'draft\'">\n' % title
|
|
fields = set()
|
|
for column in journal.view.columns:
|
|
fields.add(column.field.name)
|
|
attrs = []
|
|
if column.field.name == 'debit':
|
|
attrs.append('sum="Debit"')
|
|
elif column.field.name == 'credit':
|
|
attrs.append('sum="Credit"')
|
|
if column.readonly:
|
|
attrs.append('readonly="1"')
|
|
if column.required:
|
|
attrs.append('required="1"')
|
|
else:
|
|
attrs.append('required="0"')
|
|
xml += ('<field name="%s" %s/>\n'
|
|
% (column.field.name, ' '.join(attrs)))
|
|
for depend in getattr(cls, column.field.name).depends:
|
|
fields.add(depend)
|
|
fields.add('state')
|
|
xml += '</tree>'
|
|
result['arch'] = xml
|
|
result['fields'] = cls.fields_get(fields_names=list(fields))
|
|
return result
|
|
|
|
@classmethod
|
|
def reconcile(cls, lines, journal=None, date=None, account=None):
|
|
pool = Pool()
|
|
Move = pool.get('account.move')
|
|
Reconciliation = pool.get('account.move.reconciliation')
|
|
Period = pool.get('account.period')
|
|
Date = pool.get('ir.date')
|
|
|
|
for line in lines:
|
|
if line.reconciliation:
|
|
cls.raise_user_error('already_reconciled',
|
|
error_args=(line.move.number, line.id,))
|
|
|
|
lines = lines[:]
|
|
if journal and account:
|
|
if not date:
|
|
date = Date.today()
|
|
reconcile_account = None
|
|
amount = Decimal('0.0')
|
|
for line in lines:
|
|
amount += line.debit - line.credit
|
|
if not reconcile_account:
|
|
reconcile_account = line.account
|
|
amount = reconcile_account.currency.round(amount)
|
|
period_id = Period.find(reconcile_account.company.id, date=date)
|
|
move = Move.create({
|
|
'journal': journal.id,
|
|
'period': period_id,
|
|
'date': date,
|
|
'lines': [
|
|
('create', {
|
|
'account': reconcile_account.id,
|
|
'debit': amount < Decimal('0.0') and - amount \
|
|
or Decimal('0.0'),
|
|
'credit': amount > Decimal('0.0') and amount \
|
|
or Decimal('0.0'),
|
|
}),
|
|
('create', {
|
|
'account': account.id,
|
|
'debit': amount > Decimal('0.0') and amount \
|
|
or Decimal('0.0'),
|
|
'credit': amount < Decimal('0.0') and - amount \
|
|
or Decimal('0.0'),
|
|
}),
|
|
],
|
|
})
|
|
lines += cls.search([
|
|
('move', '=', move.id),
|
|
('account', '=', reconcile_account.id),
|
|
('debit', '=', amount < Decimal('0.0') and - amount \
|
|
or Decimal('0.0')),
|
|
('credit', '=', amount > Decimal('0.0') and amount \
|
|
or Decimal('0.0')),
|
|
], limit=1)
|
|
return Reconciliation.create({
|
|
'lines': [('add', [x.id for x in lines])],
|
|
})
|
|
|
|
|
|
class Move2:
|
|
__name__ = 'account.move'
|
|
centralised_line = fields.Many2One('account.move.line', 'Centralised Line',
|
|
readonly=True)
|
|
|
|
|
|
class OpenJournalAsk(ModelView):
|
|
'Open Journal Ask'
|
|
__name__ = 'account.move.open_journal.ask'
|
|
journal = fields.Many2One('account.journal', 'Journal', required=True)
|
|
period = fields.Many2One('account.period', 'Period', required=True,
|
|
domain=[
|
|
('state', '!=', 'close'),
|
|
('fiscalyear.company.id', '=',
|
|
Eval('context', {}).get('company', 0)),
|
|
])
|
|
|
|
@staticmethod
|
|
def default_period():
|
|
Period = Pool().get('account.period')
|
|
return Period.find(Transaction().context.get('company'),
|
|
exception=False)
|
|
|
|
|
|
class OpenJournal(Wizard):
|
|
'Open Journal'
|
|
__name__ = 'account.move.open_journal'
|
|
start = StateTransition()
|
|
ask = StateView('account.move.open_journal.ask',
|
|
'account.open_journal_ask_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'open_', 'tryton-ok', default=True),
|
|
])
|
|
open_ = StateAction('account.act_move_line_form')
|
|
|
|
def transition_start(self):
|
|
if (Transaction().context.get('active_model', '')
|
|
== 'account.journal.period'
|
|
and Transaction().context.get('active_id')):
|
|
return 'open_'
|
|
return 'ask'
|
|
|
|
def default_ask(self, fields):
|
|
JournalPeriod = Pool().get('account.journal.period')
|
|
if (Transaction().context.get('active_model', '') ==
|
|
'account.journal.period'
|
|
and Transaction().context.get('active_id')):
|
|
journal_period = JournalPeriod(Transaction().context['active_id'])
|
|
return {
|
|
'journal': journal_period.journal.id,
|
|
'period': journal_period.period.id,
|
|
}
|
|
return {}
|
|
|
|
def do_open_(self, action):
|
|
JournalPeriod = Pool().get('account.journal.period')
|
|
|
|
if (Transaction().context.get('active_model', '') ==
|
|
'account.journal.period'
|
|
and Transaction().context.get('active_id')):
|
|
journal_period = JournalPeriod(Transaction().context['active_id'])
|
|
journal = journal_period.journal
|
|
period = journal_period.period
|
|
else:
|
|
journal = self.ask.journal
|
|
period = self.ask.period
|
|
journal_periods = JournalPeriod.search([
|
|
('journal', '=', journal.id),
|
|
('period', '=', period.id),
|
|
], limit=1)
|
|
if not journal_periods:
|
|
journal_period = JournalPeriod.create({
|
|
'name': journal.name + ' - ' + period.name,
|
|
'journal': journal.id,
|
|
'period': period.id,
|
|
})
|
|
else:
|
|
journal_period, = journal_periods
|
|
|
|
action['name'] += ' - %s' % journal_period.rec_name
|
|
action['pyson_domain'] = PYSONEncoder().encode([
|
|
('journal', '=', journal.id),
|
|
('period', '=', period.id),
|
|
])
|
|
action['pyson_context'] = PYSONEncoder().encode({
|
|
'journal': journal.id,
|
|
'period': period.id,
|
|
})
|
|
return action, {}
|
|
|
|
def transition_open_(self):
|
|
return 'end'
|
|
|
|
|
|
class OpenAccount(Wizard):
|
|
'Open Account'
|
|
__name__ = 'account.move.open_account'
|
|
start_state = 'open_'
|
|
open_ = StateAction('account.act_move_line_form')
|
|
|
|
def do_open_(self, action):
|
|
FiscalYear = Pool().get('account.fiscalyear')
|
|
|
|
if not Transaction().context.get('fiscalyear'):
|
|
fiscalyears = FiscalYear.search([
|
|
('state', '=', 'open'),
|
|
])
|
|
else:
|
|
fiscalyears = [FiscalYear(Transaction().context['fiscalyear'])]
|
|
|
|
periods = [p for f in fiscalyears for p in f.periods]
|
|
|
|
action['pyson_domain'] = [
|
|
('period', 'in', [p.id for p in periods]),
|
|
('account', '=', Transaction().context['active_id']),
|
|
]
|
|
if Transaction().context.get('posted'):
|
|
action['pyson_domain'].append(('move.state', '=', 'posted'))
|
|
if Transaction().context.get('date'):
|
|
action['pyson_domain'].append(('move.date', '<=',
|
|
Transaction().context['date']))
|
|
action['pyson_domain'] = PYSONEncoder().encode(action['pyson_domain'])
|
|
action['pyson_context'] = PYSONEncoder().encode({
|
|
'fiscalyear': Transaction().context.get('fiscalyear'),
|
|
})
|
|
return action, {}
|
|
|
|
|
|
class ReconcileLinesWriteOff(ModelView):
|
|
'Reconcile Lines Write-Off'
|
|
__name__ = 'account.move.reconcile_lines.writeoff'
|
|
journal = fields.Many2One('account.journal', 'Journal', required=True)
|
|
date = fields.Date('Date', required=True)
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
domain=[('kind', '!=', 'view')])
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
Date = Pool().get('ir.date')
|
|
return Date.today()
|
|
|
|
|
|
class ReconcileLines(Wizard):
|
|
'Reconcile Lines'
|
|
__name__ = 'account.move.reconcile_lines'
|
|
start = StateTransition()
|
|
writeoff = StateView('account.move.reconcile_lines.writeoff',
|
|
'account.reconcile_lines_writeoff_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Reconcile', 'reconcile', 'tryton-ok', default=True),
|
|
])
|
|
reconcile = StateTransition()
|
|
|
|
def transition_start(self):
|
|
Line = Pool().get('account.move.line')
|
|
|
|
company = None
|
|
amount = Decimal('0.0')
|
|
for line in Line.browse(Transaction().context['active_ids']):
|
|
amount += line.debit - line.credit
|
|
if not company:
|
|
company = line.account.company
|
|
if not company:
|
|
return 'end'
|
|
if company.currency.is_zero(amount):
|
|
return 'reconcile'
|
|
return 'writeoff'
|
|
|
|
def transition_reconcile(self):
|
|
Line = Pool().get('account.move.line')
|
|
|
|
journal = getattr(self.writeoff, 'journal', None)
|
|
date = getattr(self.writeoff, 'date', None)
|
|
account = getattr(self.writeoff, 'account', None)
|
|
Line.reconcile(Line.browse(Transaction().context['active_ids']),
|
|
journal, date, account)
|
|
return 'end'
|
|
|
|
|
|
class UnreconcileLinesStart(ModelView):
|
|
'Unreconcile Lines'
|
|
__name__ = 'account.move.unreconcile_lines.start'
|
|
|
|
|
|
class UnreconcileLines(Wizard):
|
|
'Unreconcile Lines'
|
|
__name__ = 'account.move.unreconcile_lines'
|
|
start_state = 'unreconcile'
|
|
unreconcile = StateTransition()
|
|
|
|
def transition_unreconcile(self):
|
|
pool = Pool()
|
|
Line = pool.get('account.move.line')
|
|
Reconciliation = pool.get('account.move.reconciliation')
|
|
|
|
lines = Line.browse(Transaction().context['active_ids'])
|
|
reconciliations = [x.reconciliation for x in lines if x.reconciliation]
|
|
if reconciliations:
|
|
Reconciliation.delete(reconciliations)
|
|
return 'end'
|
|
|
|
|
|
class OpenReconcileLinesStart(ModelView):
|
|
'Open Reconcile Lines'
|
|
__name__ = 'account.move.open_reconcile_lines.start'
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
domain=[('kind', '!=', 'view'), ('reconcile', '=', True)])
|
|
|
|
|
|
class OpenReconcileLines(Wizard):
|
|
'Open Reconcile Lines'
|
|
__name__ = 'account.move.open_reconcile_lines'
|
|
start = StateView('account.move.open_reconcile_lines.start',
|
|
'account.open_reconcile_lines_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'open_', 'tryton-ok', default=True),
|
|
])
|
|
open_ = StateAction('account.act_move_line_form')
|
|
|
|
def do_open_(self, action):
|
|
action['pyson_domain'] = PYSONEncoder().encode([
|
|
('account', '=', self.start.account.id),
|
|
('reconciliation', '=', None),
|
|
])
|
|
return action, {}
|
|
|
|
|
|
class FiscalYearLine(ModelSQL):
|
|
'Fiscal Year - Move Line'
|
|
__name__ = 'account.fiscalyear-account.move.line'
|
|
_table = 'account_fiscalyear_line_rel'
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
ondelete='CASCADE', select=True)
|
|
line = fields.Many2One('account.move.line', 'Line', ondelete='RESTRICT',
|
|
select=True, required=True)
|
|
|
|
|
|
class FiscalYear2:
|
|
__name__ = 'account.fiscalyear'
|
|
close_lines = fields.Many2Many('account.fiscalyear-account.move.line',
|
|
'fiscalyear', 'line', 'Close Lines')
|
|
|
|
|
|
class PrintGeneralJournalStart(ModelView):
|
|
'Print General Journal'
|
|
__name__ = 'account.move.print_general_journal.start'
|
|
from_date = fields.Date('From Date', required=True)
|
|
to_date = fields.Date('To Date', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
posted = fields.Boolean('Posted Move', help='Show only posted move')
|
|
|
|
@staticmethod
|
|
def default_from_date():
|
|
Date = Pool().get('ir.date')
|
|
return datetime.date(Date.today().year, 1, 1)
|
|
|
|
@staticmethod
|
|
def default_to_date():
|
|
Date = Pool().get('ir.date')
|
|
return Date.today()
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_posted():
|
|
return False
|
|
|
|
|
|
class PrintGeneralJournal(Wizard):
|
|
'Print General Journal'
|
|
__name__ = 'account.move.print_general_journal'
|
|
start = StateView('account.move.print_general_journal.start',
|
|
'account.print_general_journal_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateAction('account.report_general_journal')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'from_date': self.start.from_date,
|
|
'to_date': self.start.to_date,
|
|
'posted': self.start.posted,
|
|
}
|
|
return action, data
|
|
|
|
|
|
class GeneralJournal(Report):
|
|
__name__ = 'account.move.general_journal'
|
|
|
|
@classmethod
|
|
def _get_records(cls, ids, model, data):
|
|
Move = Pool().get('account.move')
|
|
|
|
clause = [
|
|
('date', '>=', data['from_date']),
|
|
('date', '<=', data['to_date']),
|
|
('period.fiscalyear.company', '=', data['company']),
|
|
]
|
|
if data['posted']:
|
|
clause.append(('state', '=', 'posted'))
|
|
return Move.search(clause,
|
|
order=[('date', 'ASC'), ('id', 'ASC')])
|
|
|
|
@classmethod
|
|
def parse(cls, report, moves, data, localcontext):
|
|
Company = Pool().get('company.company')
|
|
|
|
company = Company(data['company'])
|
|
|
|
localcontext['company'] = company
|
|
localcontext['digits'] = company.currency.digits
|
|
localcontext['from_date'] = data['from_date']
|
|
localcontext['to_date'] = data['to_date']
|
|
|
|
return super(GeneralJournal, cls).parse(report, moves, data,
|
|
localcontext)
|