1790 lines
72 KiB
Python
1790 lines
72 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.
|
|
'Move'
|
|
from trytond.model import ModelView, ModelSQL, fields
|
|
from trytond.wizard import Wizard
|
|
from trytond.report import Report
|
|
from trytond.backend import TableHandler
|
|
from decimal import Decimal
|
|
import datetime
|
|
import md5
|
|
import mx.DateTime
|
|
|
|
_MOVE_STATES = {
|
|
'readonly': "state == 'posted'",
|
|
}
|
|
_MOVE_DEPENDS = ['state']
|
|
_LINE_STATES = {
|
|
'readonly': "state == 'valid'",
|
|
}
|
|
_LINE_DEPENDS = ['state']
|
|
|
|
|
|
class Move(ModelSQL, ModelView):
|
|
'Account Move'
|
|
_name = 'account.move'
|
|
_description = __doc__
|
|
|
|
name = fields.Char('Name', size=None, required=True)
|
|
reference = fields.Char('Reference', size=None, readonly=True,
|
|
help='Also known as Folio Number')
|
|
period = fields.Many2One('account.period', 'Period', required=True,
|
|
states=_MOVE_STATES, depends=_MOVE_DEPENDS)
|
|
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)
|
|
post_date = fields.Date('Post Date', readonly=True)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('posted', 'Posted'),
|
|
], 'State', required=True, readonly=True)
|
|
lines = fields.One2Many('account.move.line', 'move', 'Lines',
|
|
states=_MOVE_STATES, depends=_MOVE_DEPENDS)
|
|
|
|
def __init__(self):
|
|
super(Move, self).__init__()
|
|
self._constraints += [
|
|
('check_centralisation', 'period_centralized_journal'),
|
|
('check_company', 'company_in_move'),
|
|
('check_date', 'date_outside_period'),
|
|
]
|
|
self._rpc.update({
|
|
'button_post': True,
|
|
'button_draft': True,
|
|
})
|
|
self._order.insert(0, ('date', 'DESC'))
|
|
self._order.insert(1, ('reference', 'DESC'))
|
|
self._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!',
|
|
'modify_posted_move': 'You can not modify a posted move ' \
|
|
'in this journal!',
|
|
'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!',
|
|
})
|
|
|
|
def init(self, cursor, module_name):
|
|
super(Move, self).init(cursor, module_name)
|
|
|
|
table = TableHandler(cursor, self, module_name)
|
|
table.index_action(['journal', 'period'], 'add')
|
|
|
|
def default_period(self, cursor, user, context=None):
|
|
period_obj = self.pool.get('account.period')
|
|
if context is None:
|
|
context = {}
|
|
return period_obj.find(cursor, user, context.get('company', False),
|
|
exception=False, context=context)
|
|
|
|
def default_state(self, cursor, user, context=None):
|
|
return 'draft'
|
|
|
|
def default_date(self, cursor, user, context=None):
|
|
date_obj = self.pool.get('ir.date')
|
|
return date_obj.today(cursor, user, context=context)
|
|
|
|
def check_centralisation(self, cursor, user, ids):
|
|
for move in self.browse(cursor, user, ids):
|
|
if move.journal.centralised:
|
|
move_ids = self.search(cursor, user, [
|
|
('period', '=', move.period.id),
|
|
('journal', '=', move.journal.id),
|
|
('state', '!=', 'posted'),
|
|
], limit=2)
|
|
if len(move_ids) > 1:
|
|
return False
|
|
return True
|
|
|
|
def check_company(self, cursor, user, ids):
|
|
for move in self.browse(cursor, user, ids):
|
|
company_id = -1
|
|
for line in move.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, cursor, user, ids):
|
|
for move in self.browse(cursor, user, ids):
|
|
if move.date < move.period.start_date:
|
|
return False
|
|
if move.date > move.period.end_date:
|
|
return False
|
|
return True
|
|
|
|
def search_rec_name(self, cursor, user, name, args, context=None):
|
|
args2 = []
|
|
i = 0
|
|
while i < len(args):
|
|
ids = self.search(cursor, user, ['OR',
|
|
('reference', args[i][1], args[i][2]),
|
|
(self._rec_name, args[i][1], args[i][2]),
|
|
], context=context)
|
|
args2.append(('id', 'in', ids))
|
|
i += 1
|
|
return args2
|
|
|
|
def write(self, cursor, user, ids, vals, context=None):
|
|
res = super(Move, self).write(cursor, user, ids, vals, context=context)
|
|
self.validate(cursor, user, ids, context=context)
|
|
return res
|
|
|
|
def create(self, cursor, user, vals, context=None):
|
|
move_line_obj = self.pool.get('account.move.line')
|
|
sequence_obj = self.pool.get('ir.sequence')
|
|
journal_obj = self.pool.get('account.journal')
|
|
|
|
if context is None:
|
|
context = {}
|
|
|
|
if not vals.get('name'):
|
|
journal_id = vals.get('journal') or context.get('journal')
|
|
if journal_id:
|
|
vals = vals.copy()
|
|
journal = journal_obj.browse(cursor, user, journal_id,
|
|
context=context)
|
|
vals['name'] = sequence_obj.get_id(cursor, user,
|
|
journal.sequence.id, context=context)
|
|
|
|
res = super(Move, self).create(cursor, user, vals, context=context)
|
|
move = self.browse(cursor, user, res, context=context)
|
|
if move.journal.centralised:
|
|
line_id = move_line_obj.create(cursor, user, {
|
|
'account': move.journal.credit_account.id,
|
|
'move': move.id,
|
|
'name': 'Centralised Counterpart',
|
|
}, context=context)
|
|
self.write(cursor, user, move.id, {
|
|
'centralised_line': line_id,
|
|
}, context=context)
|
|
if 'lines' in vals:
|
|
self.validate(cursor, user, [res], context=context)
|
|
return res
|
|
|
|
def delete(self, cursor, user, ids, context=None):
|
|
if isinstance(ids, (int, long)):
|
|
ids = [ids]
|
|
move_line_obj = self.pool.get('account.move.line')
|
|
for move in self.browse(cursor, user, ids, context=context):
|
|
if move.state == 'posted':
|
|
self.raise_user_error(cursor, 'del_posted_move',
|
|
context=context)
|
|
if move.lines:
|
|
move_line_ids = [x.id for x in move.lines]
|
|
move_line_obj.delete(cursor, user, move_line_ids,
|
|
context=context)
|
|
return super(Move, self).delete(cursor, user, ids, context=context)
|
|
|
|
def copy(self, cursor, user, ids, default=None, context=None):
|
|
line_obj = self.pool.get('account.move.line')
|
|
|
|
int_id = False
|
|
if isinstance(ids, (int, long)):
|
|
int_id = True
|
|
ids = [ids]
|
|
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
default['reference'] = False
|
|
default['state'] = self.default_state(cursor, user, context=context)
|
|
default['post_date'] = False
|
|
default['lines'] = False
|
|
|
|
new_ids = []
|
|
for move in self.browse(cursor, user, ids, context=context):
|
|
new_id = super(Move, self).copy(cursor, user, move.id,
|
|
default=default, context=context)
|
|
line_obj.copy(cursor, user, [x.id for x in move.lines], default={
|
|
'move': new_id,
|
|
}, context=context)
|
|
new_ids.append(new_id)
|
|
|
|
if int_id:
|
|
return new_ids[0]
|
|
return new_ids
|
|
|
|
def validate(self, cursor, user, ids, context=None):
|
|
'''
|
|
Validate balanced move and centralise it if in centralised journal
|
|
'''
|
|
currency_obj = self.pool.get('currency.currency')
|
|
move_line_obj = self.pool.get('account.move.line')
|
|
if isinstance(ids, (int, long)):
|
|
ids = [ids]
|
|
for move in self.browse(cursor, user, ids, context=context):
|
|
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 currency_obj.is_zero(cursor, user, company.currency, amount):
|
|
if not move.journal.centralised:
|
|
move_line_obj.write(cursor, user,
|
|
[x.id for x in move.lines if x.state != 'draft'], {
|
|
'state': 'draft',
|
|
}, context=context)
|
|
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_id = move_line_obj.create(cursor,
|
|
user, {
|
|
'debit': debit,
|
|
'credit': credit,
|
|
'account': account_id,
|
|
'move': move.id,
|
|
'name': 'Centralised Counterpart',
|
|
}, context=context)
|
|
self.write(cursor, user, move.id, {
|
|
'centralised_line': centralised_line_id,
|
|
}, context=context)
|
|
else:
|
|
move_line_obj.write(cursor, user,
|
|
move.centralised_line.id, {
|
|
'debit': debit,
|
|
'credit': credit,
|
|
'account': account_id,
|
|
}, context=context)
|
|
continue
|
|
if not draft_lines:
|
|
continue
|
|
move_line_obj.write(cursor, user,
|
|
[x.id for x in draft_lines], {
|
|
'state': 'valid',
|
|
}, context=context)
|
|
return
|
|
|
|
def post(self, cursor, user, ids, context=None):
|
|
currency_obj = self.pool.get('currency.currency')
|
|
sequence_obj = self.pool.get('ir.sequence')
|
|
date_obj = self.pool.get('ir.date')
|
|
|
|
if isinstance(ids, (int, long)):
|
|
ids = [ids]
|
|
|
|
moves = self.browse(cursor, user, ids, context=context)
|
|
for move in moves:
|
|
amount = Decimal('0.0')
|
|
if not move.lines:
|
|
self.raise_user_error(cursor, 'post_empty_move',
|
|
context=context)
|
|
company = None
|
|
for line in move.lines:
|
|
amount += line.debit - line.credit
|
|
if not company:
|
|
company = line.account.company
|
|
if not currency_obj.is_zero(cursor, user, company.currency, amount):
|
|
self.raise_user_error(cursor, 'post_unbalanced_move',
|
|
context=context)
|
|
for move in moves:
|
|
reference = sequence_obj.get_id(cursor, user,
|
|
move.period.post_move_sequence.id, context=context)
|
|
self.write(cursor, user, move.id, {
|
|
'reference': reference,
|
|
'state': 'posted',
|
|
'post_date': date_obj.today(cursor, user, context=context),
|
|
}, context=context)
|
|
return
|
|
|
|
def draft(self, cursor, user, ids, context=None):
|
|
for move in self.browse(cursor, user, ids, context=context):
|
|
if not move.journal.update_posted:
|
|
self.raise_user_error(cursor, 'modify_posted_move',
|
|
context=context)
|
|
return self.write(cursor, user, ids, {
|
|
'state': 'draft',
|
|
}, context=context)
|
|
|
|
def button_post(self, cursor, user, ids, context=None):
|
|
return self.post(cursor, user, ids, context=None)
|
|
|
|
def button_draft(self, cursor, user, ids, context=None):
|
|
return self.draft(cursor, user, ids, context=context)
|
|
|
|
Move()
|
|
|
|
|
|
class Reconciliation(ModelSQL, ModelView):
|
|
'Account Move Reconciliation Lines'
|
|
_name = 'account.move.reconciliation'
|
|
_description = __doc__
|
|
|
|
name = fields.Char('Name', size=None, required=True)
|
|
lines = fields.One2Many('account.move.line', 'reconciliation',
|
|
'Lines')
|
|
|
|
def __init__(self):
|
|
super(Reconciliation, self).__init__()
|
|
self._constraints += [
|
|
('check_lines', 'invalid_reconciliation'),
|
|
]
|
|
self._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!',
|
|
})
|
|
|
|
def default_name(self, cursor, user, context=None):
|
|
sequence_obj = self.pool.get('ir.sequence')
|
|
return sequence_obj.get(cursor, user, 'account.move.reconciliation',
|
|
context=context)
|
|
|
|
def create(self, cursor, user, vals, context=None):
|
|
move_line_obj = self.pool.get('account.move.line')
|
|
res = super(Reconciliation, self).create(cursor, user, vals, context=context)
|
|
reconciliation = self.browse(cursor, user, res, context=context)
|
|
|
|
move_line_obj.workflow_trigger_trigger(cursor, user,
|
|
[x.id for x in reconciliation.lines], context=context)
|
|
return res
|
|
|
|
def write(self, cursor, user, ids, vals, context=None):
|
|
self.raise_user_error(cursor, 'modify', context=context)
|
|
|
|
def check_lines(self, cursor, user, ids):
|
|
currency_obj = self.pool.get('currency.currency')
|
|
for reconciliation in self.browse(cursor, user, ids):
|
|
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 currency_obj.is_zero(cursor, user, account.company.currency,
|
|
amount):
|
|
return False
|
|
return True
|
|
|
|
Reconciliation()
|
|
|
|
|
|
class Line(ModelSQL, ModelView):
|
|
'Account Move Line'
|
|
_name = 'account.move.line'
|
|
_description = __doc__
|
|
|
|
name = fields.Char('Name', size=None, required=True)
|
|
debit = fields.Numeric('Debit', digits="(16, currency_digits)",
|
|
on_change=['account', 'debit', 'credit', 'tax_lines',
|
|
'journal', 'move'], depends=['currency_digits', 'credit',
|
|
'tax_lines', 'journal'])
|
|
credit = fields.Numeric('Credit', digits="(16, currency_digits)",
|
|
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=1,
|
|
on_change=['account', 'debit', 'credit', 'tax_lines',
|
|
'journal', 'move', 'account_kind'])
|
|
move = fields.Many2One('account.move', 'Move', states=_LINE_STATES,
|
|
select=1, required=True)
|
|
journal = fields.Function('get_move_field', fnct_inv='set_move_field',
|
|
type='many2one', relation='account.journal', string='Journal',
|
|
fnct_search='search_move_field')
|
|
period = fields.Function('get_move_field', fnct_inv='set_move_field',
|
|
type='many2one', relation='account.period', string='Period',
|
|
fnct_search='search_move_field')
|
|
date = fields.Function('get_move_field', fnct_inv='set_move_field',
|
|
type='date', string='Effective Date', required=True,
|
|
fnct_search='search_move_field')
|
|
reference = fields.Char('Reference', size=None)
|
|
amount_second_currency = fields.Numeric('Amount Second Currency',
|
|
digits="(16, second_currency_digits)",
|
|
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=1, depends=['debit', 'credit', 'account',
|
|
'journal'])
|
|
blocked = fields.Boolean('Litigation',
|
|
help='Mark the line as litigation with the party.')
|
|
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)
|
|
active = fields.Boolean('Active', select=2)
|
|
reconciliation = fields.Many2One('account.move.reconciliation',
|
|
'Reconciliation', readonly=True, ondelete='SET NULL', select=2)
|
|
tax_lines = fields.One2Many('account.tax.line', 'move_line', 'Tax Lines')
|
|
move_state = fields.Function('get_move_field', type='selection',
|
|
selection=[
|
|
('draft', 'Draft'),
|
|
('posted', 'Posted'),
|
|
], string='Move State', fnct_search='search_move_field')
|
|
currency_digits = fields.Function('get_currency_digits', type='integer',
|
|
string='Currency Digits')
|
|
second_currency_digits = fields.Function('get_currency_digits',
|
|
type='integer', string='Second Currency Digits')
|
|
account_kind = fields.Function('get_account_kind', type='selection',
|
|
string='Account Kind', selection=[
|
|
('other', 'Other'),
|
|
('payable', 'Payable'),
|
|
('revenue', 'Revenue'),
|
|
('receivable', 'Receivable'),
|
|
('expense', 'Expense'),
|
|
('view', 'View'),
|
|
])
|
|
|
|
def __init__(self):
|
|
super(Line, self).__init__()
|
|
self._sql_constraints += [
|
|
('credit_debit',
|
|
'CHECK(credit * debit = 0.0)',
|
|
'Wrong credit/debit values!'),
|
|
]
|
|
self._constraints += [
|
|
('check_account', 'move_view_inactive_account'),
|
|
]
|
|
self._rpc.update({
|
|
'on_write': False,
|
|
})
|
|
self._order[0] = ('id', 'DESC')
|
|
self._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!',
|
|
})
|
|
|
|
def default_date(self, cursor, user, context=None):
|
|
'''
|
|
Return the date of the last line for journal, period
|
|
or the starting date of the period
|
|
or today
|
|
'''
|
|
period_obj = self.pool.get('account.period')
|
|
date_obj = self.pool.get('ir.date')
|
|
|
|
if context is None:
|
|
context = {}
|
|
res = date_obj.today(cursor, user, context=context)
|
|
if context.get('journal') and context.get('period'):
|
|
line_ids = self.search(cursor, user, [
|
|
('journal', '=', context['journal']),
|
|
('period', '=', context['period']),
|
|
], order=[('id', 'DESC')], limit=1, context=context)
|
|
if line_ids:
|
|
line = self.browse(cursor, user, line_ids[0], context=context)
|
|
res = line.date
|
|
else:
|
|
period = period_obj.browse(cursor, user, context['period'],
|
|
context=context)
|
|
res = period.start_date
|
|
return res
|
|
|
|
def default_state(self, cursor, user, context=None):
|
|
return 'draft'
|
|
|
|
def default_blocked(self, cursor, user, context=None):
|
|
return False
|
|
|
|
def default_active(self, cursor, user, context=None):
|
|
return True
|
|
|
|
def default_currency_digits(self, cursor, user, context=None):
|
|
return 2
|
|
|
|
def default_get(self, cursor, user, fields, context=None,
|
|
with_rec_name=True):
|
|
if context is None:
|
|
context = {}
|
|
move_obj = self.pool.get('account.move')
|
|
tax_obj = self.pool.get('account.tax')
|
|
account_obj = self.pool.get('account.account')
|
|
tax_code_obj = self.pool.get('account.tax.code')
|
|
values = super(Line, self).default_get(cursor, user, fields,
|
|
context=context, 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 context.get('journal') and context.get('period'):
|
|
line_ids = self.search(cursor, user, [
|
|
('move.journal', '=', context['journal']),
|
|
('move.period', '=', context['period']),
|
|
('create_uid', '=', user),
|
|
('state', '=', 'draft'),
|
|
], order=[('id', 'DESC')], limit=1, context=context)
|
|
if not line_ids:
|
|
return values
|
|
line = self.browse(cursor, user, line_ids[0], context=context)
|
|
values['move'] = line.move.id
|
|
|
|
if 'move' not in values:
|
|
return values
|
|
|
|
move = move_obj.browse(cursor, user, values['move'], context=context)
|
|
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 'reference' in fields:
|
|
values.setdefault('reference', line.reference)
|
|
if 'name' in fields:
|
|
values.setdefault('name', line.name)
|
|
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
|
|
code_id = tax.credit_note_tax_code.id
|
|
account_id = tax.credit_note_account.id
|
|
else:
|
|
base_id = tax.invoice_base_code.id
|
|
code_id = tax.invoice_tax_code.id
|
|
account_id = tax.invoice_account.id
|
|
else:
|
|
if line.debit:
|
|
base_id = tax.invoice_base_code.id
|
|
code_id = tax.invoice_tax_code.id
|
|
account_id = tax.invoice_account.id
|
|
else:
|
|
base_id = tax.credit_note_base_code.id
|
|
code_id = tax.credit_note_tax_code.id
|
|
account_id = tax.credit_note_account.id
|
|
if base_id in line_code_taxes or not base_id:
|
|
taxes.setdefault((account_id, code_id), False)
|
|
for tax_line in line.tax_lines:
|
|
taxes[(line.account.id, tax_line.code.id)] = True
|
|
if not line.tax_lines and line.account.taxes:
|
|
if line.account.id in no_code_taxes:
|
|
taxes[(line.account.id, False)] = True
|
|
else:
|
|
no_code_taxes.append(line.account.id)
|
|
elif not line.tax_lines:
|
|
taxes[(line.account.id, False)] = True
|
|
|
|
if 'account' in fields:
|
|
if total >= Decimal('0.0'):
|
|
values.setdefault('account', move.journal.credit_account \
|
|
and move.journal.credit_account.id or False)
|
|
else:
|
|
values.setdefault('account', move.journal.debit_account \
|
|
and move.journal.debit_account.id or False)
|
|
|
|
if ('debit' in fields) or ('credit' in fields):
|
|
values.setdefault('debit', total < 0 and - total or False)
|
|
values.setdefault('credit', total > 0 and total or False)
|
|
|
|
if move.journal.type in ('expense', 'revenue'):
|
|
for account_id, code_id in taxes:
|
|
if taxes[(account_id, code_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_obj.compute(cursor, user,
|
|
[x.id for x in line.account.taxes],
|
|
line.debit or line.credit, 1, context=context):
|
|
if (tax_line['tax'][key + '_account'].id \
|
|
or line.account.id) == account_id \
|
|
and tax_line['tax'][key + '_tax_code'].id \
|
|
== code_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']
|
|
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:
|
|
values['account'] = account_id
|
|
values['account.rec_name'] = account_obj.browse(cursor,
|
|
user, account_id, context=context).rec_name
|
|
if 'tax_lines' in fields and code_id:
|
|
values['tax_lines'] = [
|
|
{
|
|
'amount': tax_amount,
|
|
'code': code_id,
|
|
'code.rec_name': tax_code_obj.browse(cursor,
|
|
user, code_id, context=context).rec_name,
|
|
},
|
|
]
|
|
return values
|
|
|
|
def get_currency_digits(self, cursor, user, ids, names, arg, context=None):
|
|
res = {}
|
|
for line in self.browse(cursor, user, ids, context=context):
|
|
for name in names:
|
|
res.setdefault(name, {})
|
|
res[name].setdefault(line.id, 2)
|
|
if name == 'currency_digits':
|
|
res[name][line.id] = line.account.currency_digits
|
|
elif name == 'second_currency_digits':
|
|
if line.account.second_currency:
|
|
res[name][line.id] = line.account.second_currency.digits
|
|
return res
|
|
|
|
def get_account_kind(self, cursor, user, ids, name, arg, context=None):
|
|
res = {}
|
|
for line in self.browse(cursor, user, ids, context=context):
|
|
res[line.id] = line.account.kind
|
|
return res
|
|
|
|
def on_change_debit(self, cursor, user, ids, vals, context=None):
|
|
res = {}
|
|
if context is None:
|
|
context = {}
|
|
journal_obj = self.pool.get('account.journal')
|
|
if vals.get('journal') or context.get('journal'):
|
|
journal = journal_obj.browse(cursor, user,
|
|
vals.get('journal') or context.get('journal'),
|
|
context=context)
|
|
if journal.type in ('expense', 'revenue'):
|
|
res['tax_lines'] = self._compute_tax_lines(cursor, user,
|
|
ids, vals, journal.type, context=context)
|
|
if not res['tax_lines']:
|
|
del res['tax_lines']
|
|
if vals.get('debit'):
|
|
res['credit'] = Decimal('0.0')
|
|
return res
|
|
|
|
def on_change_credit(self, cursor, user, ids, vals, context=None):
|
|
res = {}
|
|
if context is None:
|
|
context = {}
|
|
journal_obj = self.pool.get('account.journal')
|
|
if vals.get('journal') or context.get('journal'):
|
|
journal = journal_obj.browse(cursor, user,
|
|
vals.get('journal') or context.get('journal'),
|
|
context=context)
|
|
if journal.type in ('expense', 'revenue'):
|
|
res['tax_lines'] = self._compute_tax_lines(cursor, user,
|
|
ids, vals, journal.type, context=context)
|
|
if not res['tax_lines']:
|
|
del res['tax_lines']
|
|
if vals.get('credit'):
|
|
res['debit'] = Decimal('0.0')
|
|
return res
|
|
|
|
def on_change_account(self, cursor, user, ids, vals, context=None):
|
|
account_obj = self.pool.get('account.account')
|
|
journal_obj = self.pool.get('account.journal')
|
|
|
|
res = {}
|
|
if context is None:
|
|
context = {}
|
|
|
|
if context.get('journal'):
|
|
journal = journal_obj.browse(cursor, user,
|
|
context['journal'], context=context)
|
|
if journal.type in ('expense', 'revenue'):
|
|
res['tax_lines'] = self._compute_tax_lines(cursor, user,
|
|
ids, vals, journal.type, context=context)
|
|
if not res['tax_lines']:
|
|
del res['tax_lines']
|
|
|
|
res['account_kind'] = 'other'
|
|
if vals.get('account'):
|
|
account = account_obj.browse(cursor, user, vals['account'],
|
|
context=context)
|
|
res['currency_digits'] = account.currency_digits
|
|
if account.second_currency:
|
|
res['second_currency_digits'] = account.second_currency.digits
|
|
res['account_kind'] = account.kind
|
|
return res
|
|
|
|
def _compute_tax_lines(self, cursor, user, ids, vals, journal_type,
|
|
context=None):
|
|
res = {}
|
|
account_obj = self.pool.get('account.account')
|
|
tax_code_obj = self.pool.get('account.tax.code')
|
|
tax_obj = self.pool.get('account.tax')
|
|
move_obj = self.pool.get('account.move')
|
|
if vals.get('move'):
|
|
#Only for first line
|
|
return res
|
|
if vals.get('tax_lines'):
|
|
res['remove'] = [x['id'] for x in vals['tax_lines']]
|
|
if vals.get('account'):
|
|
account = account_obj.browse(cursor, user, vals['account'],
|
|
context=context)
|
|
debit = vals.get('debit', Decimal('0.0'))
|
|
credit = vals.get('credit', Decimal('0.0'))
|
|
for tax in 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_obj.compute(cursor, user,
|
|
[x.id for x in account.taxes],
|
|
debit or credit, 1, context=context):
|
|
code_id = tax_line['tax'][key + '_base_code'].id
|
|
base_amounts.setdefault(code_id, Decimal('0.0'))
|
|
base_amounts[code_id] += tax_line['base'] * \
|
|
tax_line['tax'][key + '_tax_sign']
|
|
for code_id in base_amounts:
|
|
if not code_id:
|
|
continue
|
|
res.setdefault('add', []).append({
|
|
'amount': base_amounts[code_id],
|
|
'code': code_id,
|
|
'code.rec_name': tax_code_obj.browse(cursor, user,
|
|
code_id, context=context).rec_name,
|
|
})
|
|
return res
|
|
|
|
def on_change_party(self, cursor, user, ids, vals, context=None):
|
|
party_obj = self.pool.get('party.party')
|
|
journal_obj = self.pool.get('account.journal')
|
|
account_obj = self.pool.get('account.account')
|
|
currency_obj = self.pool.get('currency.currency')
|
|
res = {}
|
|
if (not vals.get('party')) or vals.get('account'):
|
|
return res
|
|
party = party_obj.browse(cursor, user, vals.get('party'),
|
|
context=context)
|
|
|
|
if not party.account_receivable \
|
|
or not party.account_payable:
|
|
return res
|
|
|
|
if party and (not vals.get('debit')) and (not vals.get('credit')):
|
|
query = 'SELECT ' \
|
|
'COALESCE(SUM(' \
|
|
'(COALESCE(debit, 0) - COALESCE(credit, 0))' \
|
|
'), 0)::NUMERIC ' \
|
|
'FROM account_move_line ' \
|
|
'WHERE reconciliation IS NULL ' \
|
|
'AND party = %s ' \
|
|
'AND account = %s'
|
|
cursor.execute(query, (party.id, party.account_receivable.id))
|
|
amount = cursor.fetchone()[0]
|
|
if not currency_obj.is_zero(cursor, user,
|
|
party.account_receivable.currency, amount):
|
|
if amount > Decimal('0.0'):
|
|
res['credit'] = currency_obj.round(cursor, user,
|
|
party.account_receivable.currency, amount)
|
|
res['debit'] = Decimal('0.0')
|
|
else:
|
|
res['credit'] = Decimal('0.0')
|
|
res['debit'] = - currency_obj.round(cursor, user,
|
|
party.account_receivable.currency, amount)
|
|
res['account'] = party.account_receivable.id
|
|
res['account.rec_name'] = party.account_receivable.rec_name
|
|
else:
|
|
cursor.execute(query, (party.id, party.account_payable.id))
|
|
amount = cursor.fetchone()[0]
|
|
if not currency_obj.is_zero(cursor, user,
|
|
party.account_payable.currency, amount):
|
|
if amount > Decimal('0.0'):
|
|
res['credit'] = currency_obj.round(cursor, user,
|
|
party.account_payable.currency, amount)
|
|
res['debit'] = Decimal('0.0')
|
|
else:
|
|
res['credit'] = Decimal('0.0')
|
|
res['debit'] = - currency_obj.round(cursor, user,
|
|
party.account_payable.currency, amount)
|
|
res['account'] = party.account_payable.id
|
|
res['account.rec_name'] = party.account_payable.rec_name
|
|
|
|
if party and vals.get('debit'):
|
|
if vals['debit'] > Decimal('0.0'):
|
|
if 'account' not in res:
|
|
res['account'] = party.account_receivable.id
|
|
res['account.rec_name'] = party.account_receivable.rec_name
|
|
else:
|
|
if 'account' not in res:
|
|
res['account'] = party.account_payable.id
|
|
res['account.rec_name'] = party.account_payable.id
|
|
|
|
if party and vals.get('credit'):
|
|
if vals['credit'] > Decimal('0.0'):
|
|
if 'account' not in res:
|
|
res['account'] = party.account_payable.id
|
|
res['account.rec_name'] = party.account_payable.rec_name
|
|
else:
|
|
if 'account' not in res:
|
|
res['account'] = party.account_receivable.id
|
|
res['account.rec_name'] = party.account_receivable.rec_name
|
|
|
|
journal_id = vals.get('journal') or context.get('journal')
|
|
if journal_id and party:
|
|
journal = journal_obj.browse(cursor, user, journal_id,
|
|
context=context)
|
|
if journal.type == 'revenue':
|
|
if 'account' not in res:
|
|
res['account'] = party.account_receivable.id
|
|
res['account.rec_name'] = party.account_receivable.rec_name
|
|
elif journal.type == 'expense':
|
|
if 'account' not in res:
|
|
res['account'] = party.account_payable.id
|
|
res['account.rec_name'] = party.account_payable.rec_name
|
|
return res
|
|
|
|
def get_move_field(self, cursor, user, ids, name, arg, context=None):
|
|
if name == 'move_state':
|
|
name = 'state'
|
|
if name not in ('period', 'journal', 'date', 'state'):
|
|
raise Exception('Invalid name')
|
|
res = {}
|
|
for line in self.browse(cursor, user, ids, context=context):
|
|
if name in ('date', 'state'):
|
|
res[line.id] = line.move[name]
|
|
else:
|
|
res[line.id] = line.move[name].id
|
|
return res
|
|
|
|
def set_move_field(self, cursor, user, id, name, value, arg, context=None):
|
|
if name == 'move_state':
|
|
name = 'state'
|
|
if name not in ('period', 'journal', 'date', 'state'):
|
|
raise Exception('Invalid name')
|
|
if not value:
|
|
return
|
|
move_obj = self.pool.get('account.move')
|
|
line = self.browse(cursor, user, id, context=context)
|
|
move_obj.write(cursor, user, line.move.id, {
|
|
name: value,
|
|
}, context=context)
|
|
|
|
def search_move_field(self, cursor, user, name, args, context=None):
|
|
args2 = []
|
|
i = 0
|
|
while i < len(args):
|
|
field = args[i][0]
|
|
if args[i][0] == 'move_state':
|
|
field = 'state'
|
|
args2.append(('move.' + field, args[i][1], args[i][2]))
|
|
i += 1
|
|
return args2
|
|
|
|
def query_get(self, cursor, user, obj='l', context=None):
|
|
'''
|
|
Return SQL clause and fiscal years for account move line
|
|
depending of the context.
|
|
|
|
:param cursor: the database cursor
|
|
:param user: the user id
|
|
:param obj: the SQL alias of account_move_line in the query
|
|
:param context: the context
|
|
:return: a tuple with the SQL clause and the fiscalyear ids
|
|
'''
|
|
fiscalyear_obj = self.pool.get('account.fiscalyear')
|
|
if context is None:
|
|
context = {}
|
|
|
|
if context.get('date'):
|
|
mx.DateTime.strptime(str(context['date']), '%Y-%m-%d')
|
|
fiscalyear_ids = fiscalyear_obj.search(cursor, user, [
|
|
('start_date', '<=', context['date']),
|
|
('end_date', '>=', context['date']),
|
|
], limit=1, context=context)
|
|
if not fiscalyear_ids:
|
|
fiscalyear_ids = [0]
|
|
if context.get('posted'):
|
|
return (obj + '.active ' \
|
|
'AND ' + 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_ids[0]) + ' ' \
|
|
'AND m.date <= date(\'' + \
|
|
str(context['date']) + '\') ' \
|
|
'AND m.state = \'posted\' ' \
|
|
')', fiscalyear_ids)
|
|
else:
|
|
return (obj + '.active ' \
|
|
'AND ' + 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_ids[0]) + ' ' \
|
|
'AND m.date <= date(\'' + \
|
|
str(context['date']) + '\')' \
|
|
')', fiscalyear_ids)
|
|
|
|
if not context.get('fiscalyear', False):
|
|
fiscalyear_ids = fiscalyear_obj.search(cursor, user, [
|
|
('state', '=', 'open'),
|
|
], context=context)
|
|
fiscalyear_clause = (','.join([str(x) for x in fiscalyear_ids])) or '0'
|
|
else:
|
|
fiscalyear_ids = [int(context.get('fiscalyear'))]
|
|
fiscalyear_clause = '%s' % int(context.get('fiscalyear'))
|
|
|
|
if context.get('periods', False):
|
|
ids = ','.join([str(int(x)) for x in context['periods']])
|
|
if context.get('posted'):
|
|
return (obj + '.active ' \
|
|
'AND ' + obj + '.state != \'draft\' ' \
|
|
'AND ' + obj + '.move IN (' \
|
|
'SELECT id FROM account_move ' \
|
|
'WHERE period IN (' + ids + ') ' \
|
|
'AND state = \'posted\' ' \
|
|
')', [])
|
|
else:
|
|
return (obj + '.active ' \
|
|
'AND ' + obj + '.state != \'draft\' ' \
|
|
'AND ' + obj + '.move IN (' \
|
|
'SELECT id FROM account_move ' \
|
|
'WHERE period IN (' + ids + ')' \
|
|
')', [])
|
|
else:
|
|
if context.get('posted'):
|
|
return (obj + '.active ' \
|
|
'AND ' + 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 + '.active ' \
|
|
'AND ' + 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)
|
|
|
|
def on_write(self, cursor, user, ids, context=None):
|
|
lines = self.browse(cursor, user, ids, context)
|
|
res = []
|
|
for line in lines:
|
|
res.extend([x.id for x in line.move.lines])
|
|
return list({}.fromkeys(res))
|
|
|
|
def check_account(self, cursor, user, ids):
|
|
for line in self.browse(cursor, user, ids):
|
|
if line.account.kind in ('view',):
|
|
return False
|
|
if not line.account.active:
|
|
return False
|
|
return True
|
|
|
|
def check_journal_period_modify(self, cursor, user, period_id,
|
|
journal_id, context=None):
|
|
'''
|
|
Check if the lines can be modified or created for the journal - period
|
|
and if there is no journal - period, create it
|
|
'''
|
|
journal_period_obj = self.pool.get('account.journal.period')
|
|
journal_obj = self.pool.get('account.journal')
|
|
period_obj = self.pool.get('account.period')
|
|
journal_period_ids = journal_period_obj.search(cursor, user, [
|
|
('journal', '=', journal_id),
|
|
('period', '=', period_id),
|
|
], limit=1, context=context)
|
|
if journal_period_ids:
|
|
journal_period = journal_period_obj.browse(cursor, user,
|
|
journal_period_ids[0], context=context)
|
|
if journal_period.state == 'close':
|
|
self.raise_user_error(cursor,
|
|
'add_modify_closed_journal_period', context=context)
|
|
else:
|
|
journal = journal_obj.browse(cursor, user, journal_id,
|
|
context=context)
|
|
period = period_obj.browse(cursor, user, period_id,
|
|
context=context)
|
|
journal_period_obj.create(cursor, user, {
|
|
'name': journal.name + ' - ' + period.name,
|
|
'journal': journal.id,
|
|
'period': period.id,
|
|
}, context=context)
|
|
return
|
|
|
|
def check_modify(self, cursor, user, ids, context=None):
|
|
'''
|
|
Check if the lines can be modified
|
|
'''
|
|
journal_period_done = []
|
|
for line in self.browse(cursor, user, ids, context=context):
|
|
if line.move.state == 'posted':
|
|
self.raise_user_error(cursor, 'modify_posted_move',
|
|
context=context)
|
|
if line.reconciliation:
|
|
self.raise_user_error(cursor, 'modify_reconciled',
|
|
context=context)
|
|
journal_period = (line.journal.id, line.period.id)
|
|
if journal_period not in journal_period_done:
|
|
self.check_journal_period_modify(cursor, user, line.period.id,
|
|
line.journal.id, context=context)
|
|
journal_period_done.append(journal_period)
|
|
return
|
|
|
|
def delete(self, cursor, user, ids, context=None):
|
|
move_obj = self.pool.get('account.move')
|
|
if isinstance(ids, (int, long)):
|
|
ids = [ids]
|
|
self.check_modify(cursor, user, ids, context=context)
|
|
lines = self.browse(cursor, user, ids, context=context)
|
|
move_ids = [x.move.id for x in lines]
|
|
res = super(Line, self).delete(cursor, user, ids, context=context)
|
|
move_obj.validate(cursor, user, move_ids, context=context)
|
|
return res
|
|
|
|
def write(self, cursor, user, ids, vals, context=None):
|
|
move_obj = self.pool.get('account.move')
|
|
|
|
if context is None:
|
|
context = {}
|
|
|
|
if isinstance(ids, (int, long)):
|
|
ids = [ids]
|
|
|
|
if len(vals) > 1 or 'reconciliation' not in vals:
|
|
self.check_modify(cursor, user, ids, context=context)
|
|
lines = self.browse(cursor, user, ids, context=context)
|
|
move_ids = [x.move.id for x in lines]
|
|
res = super(Line, self).write(cursor, user, ids, vals, context=context)
|
|
|
|
if context.get('_timestamp'):
|
|
context = context.copy()
|
|
del context['_timestamp']
|
|
|
|
lines = self.browse(cursor, user, ids, context=context)
|
|
for line in lines:
|
|
if line.move.id not in move_ids:
|
|
move_ids.append(line.move.id)
|
|
move_obj.validate(cursor, user, move_ids, context=context)
|
|
return res
|
|
|
|
def create(self, cursor, user, vals, context=None):
|
|
if context is None:
|
|
context = {}
|
|
journal_obj = self.pool.get('account.journal')
|
|
move_obj = self.pool.get('account.move')
|
|
vals = vals.copy()
|
|
if not vals.get('move'):
|
|
journal_id = vals.get('journal') or context.get('journal')
|
|
if not journal_id:
|
|
self.raise_user_error(cursor, 'no_journal',
|
|
context=context)
|
|
journal = journal_obj.browse(cursor, user, journal_id,
|
|
context=context)
|
|
if journal.centralised:
|
|
move_ids = move_obj.search(cursor, user, [
|
|
('period', '=',
|
|
vals.get('period') or context.get('period')),
|
|
('journal', '=', journal_id),
|
|
('state', '!=', 'posted'),
|
|
], limit=1, context=context)
|
|
if move_ids:
|
|
vals['move'] = move_ids[0]
|
|
if not vals.get('move'):
|
|
vals['move'] = move_obj.create(cursor, user, {
|
|
'period': vals.get('period') or context.get('period'),
|
|
'journal': journal_id,
|
|
'date': vals.get('date', False),
|
|
}, context=context)
|
|
res = super(Line, self).create(cursor, user, vals, context=context)
|
|
line = self.browse(cursor, user, res, context=context)
|
|
self.check_journal_period_modify(cursor, user, line.period.id,
|
|
line.journal.id, context=context)
|
|
move_obj.validate(cursor, user, [vals['move']], context=context)
|
|
return res
|
|
|
|
def copy(self, cursor, user, ids, default=None, context=None):
|
|
if default is None:
|
|
default = {}
|
|
if 'move' not in default:
|
|
default['move'] = False
|
|
if 'reconciliation' not in default:
|
|
default['reconciliation'] = False
|
|
return super(Line, self).copy(cursor, user, ids, default=default,
|
|
context=context)
|
|
|
|
def view_header_get(self, cursor, user, value, view_type='form',
|
|
context=None):
|
|
journal_period_obj = self.pool.get('account.journal.period')
|
|
if context is None:
|
|
context = {}
|
|
if not context.get('journal') or not context.get('period'):
|
|
return value
|
|
journal_period_ids = journal_period_obj.search(cursor, user, [
|
|
('journal', '=', context['journal']),
|
|
('period', '=', context['period']),
|
|
], context=context, limit=1)
|
|
if not journal_period_ids:
|
|
return value
|
|
journal_period = journal_period_obj.browse(cursor, user,
|
|
journal_period_ids[0], context=context)
|
|
return value + ': ' + journal_period.rec_name
|
|
|
|
def fields_view_get(self, cursor, user, view_id=None, view_type='form',
|
|
context=None, toolbar=False, hexmd5=None):
|
|
if context is None:
|
|
context = {}
|
|
journal_obj = self.pool.get('account.journal')
|
|
result = super(Line, self).fields_view_get(cursor, user,
|
|
view_id=view_id, view_type=view_type, context=context,
|
|
toolbar=toolbar, hexmd5=hexmd5)
|
|
if view_type == 'tree' and 'journal' in context:
|
|
title = self.view_header_get(cursor, user, '',
|
|
view_type=view_type, context=context)
|
|
journal = journal_obj.browse(cursor, user, context['journal'],
|
|
context=context)
|
|
|
|
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 = []
|
|
for column in journal.view.columns:
|
|
fields.append(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))
|
|
if 'currency_digits' not in journal.view.columns:
|
|
xml += '<field name="currency_digits" tree_invisible="1"/>'
|
|
if 'second_currency' in journal.view.columns \
|
|
and 'second_currency_digits' not in journal.view.columns:
|
|
xml += '<field name="second_currency_digits" tree_invisible="1"/>'
|
|
xml += '</tree>'
|
|
result['arch'] = xml
|
|
result['fields'] = self.fields_get(cursor, user, fields_names=fields,
|
|
context=context)
|
|
del result['md5']
|
|
result['md5'] = md5.new(str(result)).hexdigest()
|
|
if hexmd5 == result['md5']:
|
|
return True
|
|
return result
|
|
|
|
def reconcile(self, cursor, user, ids, journal_id=False, date=False,
|
|
account_id=False, context=None):
|
|
move_obj = self.pool.get('account.move')
|
|
currency_obj = self.pool.get('currency.currency')
|
|
reconciliation_obj = self.pool.get('account.move.reconciliation')
|
|
period_obj = self.pool.get('account.period')
|
|
date_obj = self.pool.get('ir.date')
|
|
translation_obj = self.pool.get('ir.translation')
|
|
|
|
for line in self.browse(cursor, user, ids, context=context):
|
|
if line.reconciliation:
|
|
self.raise_user_error(cursor, 'already_reconciled',
|
|
error_args=(line.name, line.id,), context=context)
|
|
|
|
ids = ids[:]
|
|
if journal_id and account_id:
|
|
if not date:
|
|
date = date_obj.today(cursor, user, context=context)
|
|
account = None
|
|
amount = Decimal('0.0')
|
|
for line in self.browse(cursor, user, ids, context=context):
|
|
amount += line.debit - line.credit
|
|
if not account:
|
|
account = line.account
|
|
amount = currency_obj.round(cursor, user, account.currency, amount)
|
|
period_id = period_obj.find(cursor, user, account.company.id,
|
|
date=date, context=context)
|
|
lang_code = 'en_US'
|
|
if account.company.lang:
|
|
lang_code = account.company.lang.code
|
|
writeoff = translation_obj._get_source(cursor,
|
|
'account.move.reconcile_lines.writeoff', 'view',
|
|
lang_code, 'Write-Off') or 'Write-Off'
|
|
move_id = move_obj.create(cursor, user, {
|
|
'journal': journal_id,
|
|
'period': period_id,
|
|
'date': date,
|
|
'lines': [
|
|
('create', {
|
|
'name': writeoff,
|
|
'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'),
|
|
}),
|
|
('create', {
|
|
'name': writeoff,
|
|
'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'),
|
|
}),
|
|
],
|
|
}, context=context)
|
|
ids += self.search(cursor, user, [
|
|
('move', '=', move_id),
|
|
('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')),
|
|
], limit=1, context=context)
|
|
return reconciliation_obj.create(cursor, user, {
|
|
'lines': [('add', x) for x in ids],
|
|
}, context=context)
|
|
|
|
Line()
|
|
|
|
|
|
class Move2(ModelSQL, ModelView):
|
|
_name = 'account.move'
|
|
centralised_line = fields.Many2One('account.move.line', 'Centralised Line',
|
|
readonly=True)
|
|
|
|
Move2()
|
|
|
|
|
|
class OpenJournalAsk(ModelView):
|
|
'Open Journal Ask'
|
|
_name = 'account.move.open_journal.ask'
|
|
_description = __doc__
|
|
journal = fields.Many2One('account.journal', 'Journal', required=True)
|
|
period = fields.Many2One('account.period', 'Period', required=True,
|
|
domain="[('state', '!=', 'close'), " \
|
|
"('fiscalyear.company.id', '=', " \
|
|
"context.get('company', False))]")
|
|
|
|
def default_period(self, cursor, user, context=None):
|
|
period_obj = self.pool.get('account.period')
|
|
if context is None:
|
|
context = {}
|
|
return period_obj.find(cursor, user, context.get('company', False),
|
|
exception=False, context=context)
|
|
|
|
OpenJournalAsk()
|
|
|
|
|
|
class OpenJournal(Wizard):
|
|
'Open Journal'
|
|
_name = 'account.move.open_journal'
|
|
states = {
|
|
'init': {
|
|
'result': {
|
|
'type': 'choice',
|
|
'next_state': '_next',
|
|
},
|
|
},
|
|
'ask': {
|
|
'result': {
|
|
'type': 'form',
|
|
'object': 'account.move.open_journal.ask',
|
|
'state': [
|
|
('end', 'Cancel', 'tryton-cancel'),
|
|
('open', 'Open', 'tryton-ok', True),
|
|
],
|
|
},
|
|
},
|
|
'open': {
|
|
'result': {
|
|
'type': 'action',
|
|
'action': '_action_open_journal',
|
|
'state': 'end',
|
|
},
|
|
},
|
|
}
|
|
|
|
def _next(self, cursor, user, data, context=None):
|
|
if data.get('model', '') == 'account.journal.period' \
|
|
and data.get('id'):
|
|
return 'open'
|
|
return 'ask'
|
|
|
|
def _get_journal_period(self, cursor, user, data, context=None):
|
|
journal_period_obj = self.pool.get('account.journal.period')
|
|
if data.get('model', '') == 'account.journal.period' \
|
|
and data.get('id'):
|
|
journal_period = journal_period_obj.browse(cursor, user,
|
|
data['id'], context=context)
|
|
return {
|
|
'journal': journal_period.journal.id,
|
|
'period': journal_period.period.id,
|
|
}
|
|
return {}
|
|
|
|
def _action_open_journal(self, cursor, user, data, context=None):
|
|
journal_period_obj = self.pool.get('account.journal.period')
|
|
journal_obj = self.pool.get('account.journal')
|
|
period_obj = self.pool.get('account.period')
|
|
model_data_obj = self.pool.get('ir.model.data')
|
|
act_window_obj = self.pool.get('ir.action.act_window')
|
|
if data.get('model', '') == 'account.journal.period' \
|
|
and data.get('id'):
|
|
journal_period = journal_period_obj.browse(cursor, user,
|
|
data['id'], context=context)
|
|
journal_id = journal_period.journal.id
|
|
period_id = journal_period.period.id
|
|
else:
|
|
journal_id = data['form']['journal']
|
|
period_id = data['form']['period']
|
|
if not journal_period_obj.search(cursor, user, [
|
|
('journal', '=', journal_id),
|
|
('period', '=', period_id),
|
|
], context=context):
|
|
journal = journal_obj.browse(cursor, user, journal_id,
|
|
context=context)
|
|
period = period_obj.browse(cursor, user, period_id,
|
|
context=context)
|
|
journal_period_obj.create(cursor, user, {
|
|
'name': journal.name + ' - ' + period.name,
|
|
'journal': journal.id,
|
|
'period': period.id,
|
|
}, context=context)
|
|
|
|
model_data_ids = model_data_obj.search(cursor, user, [
|
|
('fs_id', '=', 'act_move_line_form'),
|
|
('module', '=', 'account'),
|
|
('inherit', '=', False),
|
|
], limit=1, context=context)
|
|
model_data = model_data_obj.browse(cursor, user, model_data_ids[0],
|
|
context=context)
|
|
res = act_window_obj.read(cursor, user, model_data.db_id, context=context)
|
|
# Remove name to use the one from view_header_get
|
|
del res['name']
|
|
res['domain'] = str([
|
|
('journal', '=', journal_id),
|
|
('period', '=', period_id),
|
|
])
|
|
res['context'] = str({
|
|
'journal': journal_id,
|
|
'period': period_id,
|
|
})
|
|
return res
|
|
|
|
OpenJournal()
|
|
|
|
|
|
class OpenAccount(Wizard):
|
|
'Open Account'
|
|
_name = 'account.move.open_account'
|
|
states = {
|
|
'init': {
|
|
'result': {
|
|
'type': 'action',
|
|
'action': '_action_open_account',
|
|
'state': 'end',
|
|
},
|
|
},
|
|
}
|
|
|
|
def _action_open_account(self, cursor, user, data, context=None):
|
|
if context is None:
|
|
context = {}
|
|
model_data_obj = self.pool.get('ir.model.data')
|
|
act_window_obj = self.pool.get('ir.action.act_window')
|
|
fiscalyear_obj = self.pool.get('account.fiscalyear')
|
|
|
|
if not context.get('fiscalyear'):
|
|
fiscalyear_ids = fiscalyear_obj.search(cursor, user, [
|
|
('state', '=', 'open'),
|
|
], context=context)
|
|
else:
|
|
fiscalyear_ids = [context['fiscalyear']]
|
|
|
|
period_ids = []
|
|
for fiscalyear in fiscalyear_obj.browse(cursor, user, fiscalyear_ids,
|
|
context=context):
|
|
for period in fiscalyear.periods:
|
|
period_ids.append(period.id)
|
|
|
|
model_data_ids = model_data_obj.search(cursor, user, [
|
|
('fs_id', '=', 'act_move_line_form'),
|
|
('module', '=', 'account'),
|
|
('inherit', '=', False),
|
|
], limit=1, context=context)
|
|
model_data = model_data_obj.browse(cursor, user, model_data_ids[0],
|
|
context=context)
|
|
res = act_window_obj.read(cursor, user, model_data.db_id, context=context)
|
|
res['domain'] = [
|
|
('period', 'in', period_ids),
|
|
('account', '=', data['id']),
|
|
]
|
|
if context.get('posted'):
|
|
res['domain'].append(('move.state', '=', 'posted'))
|
|
res['domain'] = str(res['domain'])
|
|
res['context'] = str({'fiscalyear': context.get('fiscalyear')})
|
|
return res
|
|
|
|
OpenAccount()
|
|
|
|
|
|
class ReconcileLinesWriteOff(ModelView):
|
|
'Reconcile Lines Write-Off'
|
|
_name = 'account.move.reconcile_lines.writeoff'
|
|
_description = __doc__
|
|
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')])
|
|
|
|
def default_date(self, cursor, user, context=None):
|
|
date_obj = self.pool.get('ir.date')
|
|
return date_obj.today(cursor, user, context=context)
|
|
|
|
ReconcileLinesWriteOff()
|
|
|
|
|
|
class ReconcileLines(Wizard):
|
|
'Reconcile Lines'
|
|
_name = 'account.move.reconcile_lines'
|
|
states = {
|
|
'init': {
|
|
'result': {
|
|
'type': 'choice',
|
|
'next_state': '_check_writeoff',
|
|
},
|
|
},
|
|
'writeoff': {
|
|
'result': {
|
|
'type': 'form',
|
|
'object': 'account.move.reconcile_lines.writeoff',
|
|
'state': [
|
|
('end', 'Cancel', 'tryton-cancel'),
|
|
('reconcile', 'Reconcile', 'tryton-ok', True),
|
|
],
|
|
},
|
|
},
|
|
'reconcile': {
|
|
'actions': ['_reconcile'],
|
|
'result': {
|
|
'type': 'state',
|
|
'state': 'end',
|
|
},
|
|
},
|
|
}
|
|
|
|
def _check_writeoff(self, cursor, user, data, context=None):
|
|
line_obj = self.pool.get('account.move.line')
|
|
currency_obj = self.pool.get('currency.currency')
|
|
|
|
company = None
|
|
amount = Decimal('0.0')
|
|
for line in line_obj.browse(cursor, user, data['ids'],
|
|
context=context):
|
|
amount += line.debit - line.credit
|
|
if not company:
|
|
company = line.account.company
|
|
if not company:
|
|
return 'end'
|
|
if currency_obj.is_zero(cursor, user, company.currency, amount):
|
|
return 'reconcile'
|
|
return 'writeoff'
|
|
|
|
def _reconcile(self, cursor, user, data, context=None):
|
|
line_obj = self.pool.get('account.move.line')
|
|
|
|
if data['form']:
|
|
journal_id = data['form'].get('journal')
|
|
date = data['form'].get('date')
|
|
account_id = data['form'].get('account')
|
|
else:
|
|
journal_id = False
|
|
date = False
|
|
account_id = False
|
|
line_obj.reconcile(cursor, user, data['ids'], journal_id, date,
|
|
account_id, context=context)
|
|
return {}
|
|
|
|
ReconcileLines()
|
|
|
|
|
|
class UnreconcileLinesInit(ModelView):
|
|
'Unreconcile Lines Init'
|
|
_name = 'account.move.unreconcile_lines.init'
|
|
_description = __doc__
|
|
|
|
UnreconcileLinesInit()
|
|
|
|
|
|
class UnreconcileLines(Wizard):
|
|
'Unreconcile Lines'
|
|
_name = 'account.move.unreconcile_lines'
|
|
states = {
|
|
'init': {
|
|
'result': {
|
|
'type': 'form',
|
|
'object': 'account.move.unreconcile_lines.init',
|
|
'state': [
|
|
('end', 'Cancel', 'tryton-cancel'),
|
|
('unreconcile', 'Unreconcile', 'tryton-ok', True),
|
|
],
|
|
},
|
|
},
|
|
'unreconcile': {
|
|
'actions': ['_unreconcile'],
|
|
'result': {
|
|
'type': 'state',
|
|
'state': 'end',
|
|
},
|
|
},
|
|
}
|
|
|
|
def _unreconcile(self, cursor, user, data, context=None):
|
|
line_obj = self.pool.get('account.move.line')
|
|
reconciliation_obj = self.pool.get('account.move.reconciliation')
|
|
|
|
lines = line_obj.browse(cursor, user, data['ids'], context=context)
|
|
reconciliation_ids = [x.reconciliation.id for x in lines \
|
|
if x.reconciliation]
|
|
if reconciliation_ids:
|
|
reconciliation_obj.delete(cursor, user, reconciliation_ids,
|
|
context=context)
|
|
return {}
|
|
|
|
UnreconcileLines()
|
|
|
|
|
|
class OpenReconcileLinesInit(ModelView):
|
|
'Open Reconcile Lines Init'
|
|
_name = 'account.move.open_reconcile_lines.init'
|
|
_description = __doc__
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
domain=[('kind', '!=', 'view'), ('reconcile', '=', True)])
|
|
|
|
OpenReconcileLinesInit()
|
|
|
|
|
|
class OpenReconcileLines(Wizard):
|
|
'Open Reconcile Lines'
|
|
_name = 'account.move.open_reconcile_lines'
|
|
states = {
|
|
'init': {
|
|
'result': {
|
|
'type': 'form',
|
|
'object': 'account.move.open_reconcile_lines.init',
|
|
'state': [
|
|
('end', 'Cancel', 'tryton-cancel'),
|
|
('open', 'Open', 'tryton-ok', True),
|
|
],
|
|
},
|
|
},
|
|
'open': {
|
|
'result': {
|
|
'type': 'action',
|
|
'action': '_action_open_reconcile_lines',
|
|
'state': 'end',
|
|
},
|
|
},
|
|
}
|
|
|
|
def _action_open_reconcile_lines(self, cursor, user, data, context=None):
|
|
model_data_obj = self.pool.get('ir.model.data')
|
|
act_window_obj = self.pool.get('ir.action.act_window')
|
|
|
|
model_data_ids = model_data_obj.search(cursor, user, [
|
|
('fs_id', '=', 'act_move_line_form'),
|
|
('module', '=', 'account'),
|
|
('inherit', '=', False),
|
|
], limit=1, context=context)
|
|
model_data = model_data_obj.browse(cursor, user, model_data_ids[0],
|
|
context=context)
|
|
res = act_window_obj.read(cursor, user, model_data.db_id, context=context)
|
|
res['domain'] = str([
|
|
('account', '=', data['form']['account']),
|
|
('reconciliation', '=', False),
|
|
])
|
|
return res
|
|
|
|
OpenReconcileLines()
|
|
|
|
|
|
class FiscalYearLine(ModelSQL):
|
|
'Fiscal Year - Move Line'
|
|
_name = 'account.fiscalyear-account.move.line'
|
|
_table = 'account_fiscalyear_line_rel'
|
|
_description = __doc__
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
ondelete='CASCADE', select=1)
|
|
line = fields.Many2One('account.move.line', 'Line', ondelete='RESTRICT',
|
|
select=1, required=True)
|
|
|
|
FiscalYearLine()
|
|
|
|
|
|
class FiscalYear(ModelSQL, ModelView):
|
|
_name = 'account.fiscalyear'
|
|
close_lines = fields.Many2Many('account.fiscalyear-account.move.line',
|
|
'fiscalyear', 'line', 'Close Lines')
|
|
|
|
FiscalYear()
|
|
|
|
|
|
class PrintGeneralJournalInit(ModelView):
|
|
'Print General Journal Init'
|
|
_name = 'account.move.print_general_journal.init'
|
|
_description = __doc__
|
|
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')
|
|
|
|
def default_from_date(self, cursor, user, context=None):
|
|
date_obj = self.pool.get('ir.date')
|
|
return datetime.date(
|
|
date_obj.today(cursor, user, context=context).year, 1, 1)
|
|
|
|
def default_to_date(self, cursor, user, context=None):
|
|
date_obj = self.pool.get('ir.date')
|
|
return date_obj.today(cursor, user, context=context)
|
|
|
|
def default_company(self, cursor, user, context=None):
|
|
if context is None:
|
|
context = {}
|
|
company_obj = self.pool.get('company.company')
|
|
if context.get('company'):
|
|
return context['company']
|
|
return False
|
|
|
|
def default_posted(self, cursor, user, context=None):
|
|
return False
|
|
|
|
PrintGeneralJournalInit()
|
|
|
|
|
|
class PrintGeneralJournal(Wizard):
|
|
'Print General Journal'
|
|
_name = 'account.move.print_general_journal'
|
|
states = {
|
|
'init': {
|
|
'result': {
|
|
'type': 'form',
|
|
'object': 'account.move.print_general_journal.init',
|
|
'state': [
|
|
('end', 'Cancel', 'tryton-cancel'),
|
|
('print', 'Print', 'tryton-print', True),
|
|
],
|
|
},
|
|
},
|
|
'print': {
|
|
'result': {
|
|
'type': 'print',
|
|
'report': 'account.move.general_journal',
|
|
'state': 'end',
|
|
},
|
|
},
|
|
}
|
|
|
|
PrintGeneralJournal()
|
|
|
|
|
|
class GeneralJournal(Report):
|
|
_name = 'account.move.general_journal'
|
|
|
|
def _get_objects(self, cursor, user, ids, model, datas, context):
|
|
move_obj = self.pool.get('account.move')
|
|
|
|
clause = [
|
|
('date', '>=', datas['form']['from_date']),
|
|
('date', '<=', datas['form']['to_date']),
|
|
]
|
|
if datas['form']['posted']:
|
|
clause.append(('state', '=', 'posted'))
|
|
move_ids = move_obj.search(cursor, user, clause,
|
|
order=[('date', 'ASC'), ('reference', 'ASC'), ('id', 'ASC')],
|
|
context=context)
|
|
return move_obj.browse(cursor, user, move_ids, context=context)
|
|
|
|
def parse(self, cursor, user, report, objects, datas, context):
|
|
if context is None:
|
|
context = {}
|
|
company_obj = self.pool.get('company.company')
|
|
context = context.copy()
|
|
|
|
company = company_obj.browse(cursor, user,
|
|
datas['form']['company'], context=context)
|
|
|
|
context['company'] = company
|
|
context['digits'] = company.currency.digits
|
|
context['from_date'] = datas['form']['from_date']
|
|
context['to_date'] = datas['form']['to_date']
|
|
|
|
return super(GeneralJournal, self).parse(cursor, user, report,
|
|
objects, datas, context)
|
|
|
|
GeneralJournal()
|