1166 lines
46 KiB
Python
1166 lines
46 KiB
Python
'Move'
|
|
|
|
from trytond.osv import fields, OSV, ExceptORM
|
|
from trytond.wizard import Wizard, WizardOSV
|
|
from decimal import Decimal
|
|
import datetime
|
|
from trytond.netsvc import LocalService
|
|
|
|
_MOVE_STATES = {
|
|
'readonly': "state == 'posted'",
|
|
}
|
|
_LINE_STATES = {
|
|
'readonly': "state == 'valid'",
|
|
}
|
|
|
|
|
|
class Move(OSV):
|
|
'Account Move'
|
|
_name = 'account.move'
|
|
_description = __doc__
|
|
_order = 'id DESC'
|
|
|
|
name = fields.Char('Name', size=None, required=True)
|
|
reference = fields.Char('Reference', size=None, readonly=True)
|
|
period = fields.Many2One('account.period', 'Period', required=True,
|
|
states=_MOVE_STATES)
|
|
journal = fields.Many2One('account.journal', 'Journal', required=True,
|
|
states=_MOVE_STATES)
|
|
date = fields.Date('Effective Date', required=True, states=_MOVE_STATES)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('posted', 'Posted'),
|
|
], 'State', required=True, readonly=True)
|
|
lines = fields.One2Many('account.move.line', 'move', 'Lines',
|
|
states=_MOVE_STATES)
|
|
|
|
def __init__(self):
|
|
super(Move, self).__init__()
|
|
self._constraints += [
|
|
('check_centralisation',
|
|
'Error! You can not create more than one move per period \n' \
|
|
'in centralized journal', ['journal']),
|
|
('check_company',
|
|
'Error! You can not create lines on account \n' \
|
|
'from different company in the same move!', ['lines']),
|
|
]
|
|
self._rpc_allowed += [
|
|
'button_post',
|
|
'button_draft',
|
|
]
|
|
|
|
def _auto_init(self, cursor, module_name):
|
|
super(Move, self)._auto_init(cursor, module_name)
|
|
cursor.execute('SELECT indexname FROM pg_indexes ' \
|
|
'WHERE indexname = \'account_move_journal_period_index\'')
|
|
if not cursor.rowcount:
|
|
cursor.execute('CREATE INDEX account_move_journal_period_index ' \
|
|
'ON account_move (period, journal)')
|
|
|
|
def default_name(self, cursor, user, context=None):
|
|
sequence_obj = self.pool.get('ir.sequence')
|
|
return sequence_obj.get(cursor, user, 'account.move')
|
|
|
|
def default_period(self, cursor, user, context=None):
|
|
period_obj = self.pool.get('account.period')
|
|
return period_obj.find(cursor, user, exception=False, context=context)
|
|
|
|
def default_state(self, cursor, user, context=None):
|
|
return 'draft'
|
|
|
|
def default_date(self, cursor, user, context=None):
|
|
return datetime.date.today()
|
|
|
|
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),
|
|
], 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 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):
|
|
res = super(Move, self).create(cursor, user, vals, context=context)
|
|
if 'lines' in vals:
|
|
self.validate(cursor, user, [res], context=context)
|
|
return res
|
|
|
|
def unlink(self, cursor, user, ids, context=None):
|
|
move_line_obj = self.pool.get('account.move.line')
|
|
for move in self.browse(cursor, user, ids, context=context):
|
|
if move.state == 'posted':
|
|
raise ExceptORM('UserError',
|
|
'You can not delete posted move!')
|
|
if move.lines:
|
|
move_line_ids = [x.id for x in move.lines]
|
|
move_line_obj.unlink(cursor, user, move_line_ids,
|
|
context=context)
|
|
return super(Move, self).unlink(cursor, user, ids, context=context)
|
|
|
|
def validate(self, cursor, user, ids, context=None):
|
|
'''
|
|
Validate balanced move and centralise it if in centralised journal
|
|
'''
|
|
currency_obj = self.pool.get('account.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 move.journal.centralised:
|
|
#TODO centralised move
|
|
raise Exception('Not implemented')
|
|
else:
|
|
move_line_obj.write(cursor, user,
|
|
[x.id for x in move.lines if x.state != 'draft'], {
|
|
'state': 'draft',
|
|
}, 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)
|
|
if move.journal.type not in ('expense', 'revenue'):
|
|
continue
|
|
#TODO compute tax
|
|
return
|
|
|
|
def post(self, cursor, user, ids, context=None):
|
|
currency_obj = self.pool.get('account.currency')
|
|
sequence_obj = self.pool.get('ir.sequence')
|
|
moves = self.browse(cursor, user, ids, context=context)
|
|
for move in moves:
|
|
amount = Decimal('0.0')
|
|
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):
|
|
raise ExceptORM('UserError',
|
|
'You can not post a unbalanced move!')
|
|
for move in moves:
|
|
reference = sequence_obj.get_id(cursor, user, move.journal.sequence.id)
|
|
self.write(cursor, user, move.id, {
|
|
'reference': reference,
|
|
'state': 'posted',
|
|
}, 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:
|
|
raise ExceptORM('UserError',
|
|
'You can not modify a posted move in this journal!')
|
|
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(OSV):
|
|
'Account Move Reconciliation Lines'
|
|
_name = 'account.move.reconciliation'
|
|
|
|
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', 'You can not create reconciliation \n' \
|
|
'where lines are not balanced, not valid, \n' \
|
|
'not in the same account!',
|
|
['lines']),
|
|
]
|
|
|
|
def default_name(self, cursor, user, context=None):
|
|
sequence_obj = self.pool.get('ir.sequence')
|
|
return sequence_obj.get(cursor, user, 'account.move.reconciliation')
|
|
|
|
def create(self, cursor, user, vals, context=None):
|
|
workflow_service = LocalService('workflow')
|
|
res = super(Reconciliation, self).create(cursor, user, vals, context=context)
|
|
reconciliation = self.browse(cursor, user, res, context=context)
|
|
for line in reconciliation.lines:
|
|
workflow_service.trg_trigger(user, 'account.move.line', line.id,
|
|
cursor)
|
|
return res
|
|
|
|
def check_lines(self, cursor, user, ids):
|
|
currency_obj = self.pool.get('account.currency')
|
|
for reconciliation in self.browse(cursor, user, ids):
|
|
amount = Decimal('0.0')
|
|
account = 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 currency_obj.is_zero(cursor, user, account.company.currency,
|
|
amount):
|
|
return False
|
|
return True
|
|
|
|
Reconciliation()
|
|
|
|
|
|
class Line(OSV):
|
|
'Account Move Line'
|
|
_name = 'account.move.line'
|
|
_description = __doc__
|
|
_order = 'id DESC'
|
|
|
|
name = fields.Char('Name', size=None, required=True)
|
|
debit = fields.Numeric('Debit', digits=(16, 2))
|
|
credit = fields.Numeric('Credit', digits=(16, 2))
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
domain=[('type', '!=', 'view'), ('type', '!=', 'closed')],
|
|
select=1)
|
|
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, 2), help='The amount expressed in a second currency')
|
|
second_currency = fields.Many2One('account.currency', 'Second Currency',
|
|
help='The second currency')
|
|
partner = fields.Many2One('partner.partner', 'Partner',
|
|
on_change=['move', 'partner', 'account', 'debit', 'credit',
|
|
'journal'])
|
|
blocked = fields.Boolean('Litigation',
|
|
help='Mark the line as litigation with the partner.')
|
|
maturity_date = fields.Date('Muturity Date',
|
|
help='This field is used for payable and receivable linees. \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)
|
|
|
|
def __init__(self):
|
|
super(Line, self).__init__()
|
|
self._sql_constraints += [
|
|
('credit_debit',
|
|
'CHECK((credit * debit = 0) AND (credit + debit >= 0))',
|
|
'Wrong credit/debit values!'),
|
|
]
|
|
self._constraints += [
|
|
('check_account', 'You can not create move line \n' \
|
|
'on view/closed/inactive account!', ['account']),
|
|
]
|
|
self._rpc_allowed += [
|
|
'on_write',
|
|
]
|
|
|
|
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
|
|
'''
|
|
if context is None:
|
|
context = {}
|
|
period_obj = self.pool.get('account.period')
|
|
res = datetime.date.today()
|
|
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_get(self, cursor, user, fields, context=None):
|
|
if context is None:
|
|
context = {}
|
|
move_obj = self.pool.get('account.move')
|
|
values = super(Line, self).default_get(cursor, user, fields,
|
|
context=context)
|
|
|
|
if 'move' not in fields:
|
|
#Not manual entry
|
|
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')
|
|
for line in move.lines:
|
|
total += line.debit - line.credit
|
|
if line.partner and 'partner' in fields:
|
|
values.setdefault('partner', line.partner.id)
|
|
if 'reference' in fields:
|
|
values.setdefault('reference', line.reference)
|
|
if 'name' in fields:
|
|
values.setdefault('name', line.name)
|
|
#TODO taxes
|
|
|
|
if 'account' in fields:
|
|
if total >= 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 values['account']:
|
|
#TODO add taxes code
|
|
pass
|
|
|
|
if move.journal.type in ('expense', 'revenue'):
|
|
#TODO taxes
|
|
pass
|
|
|
|
#Compute last line
|
|
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)
|
|
return values
|
|
|
|
def on_change_partner(self, cursor, user, ids, vals, context=None):
|
|
partner_obj = self.pool.get('partner.partner')
|
|
journal_obj = self.pool.get('account.journal')
|
|
account_obj = self.pool.get('account.account')
|
|
currency_obj = self.pool.get('account.currency')
|
|
res = {}
|
|
if (not vals.get('partner')) or vals.get('account'):
|
|
return res
|
|
partner = partner_obj.browse(cursor, user, vals.get('partner'),
|
|
context=context)
|
|
|
|
if partner 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 partner = %s ' \
|
|
'AND account = %s'
|
|
cursor.execute(query, (partner.id, partner.account_receivable.id))
|
|
amount = cursor.fetchone()[0]
|
|
if not currency_obj.is_zero(cursor, user,
|
|
partner.account_receivable.currency, amount):
|
|
if amount > 0:
|
|
res['credit'] = currency_obj.round(cursor, user,
|
|
partner.account_receivable.currency, amount)
|
|
res['debit'] = Decimal('0.0')
|
|
else:
|
|
res['credit'] = Decimal('0.0')
|
|
res['debit'] = - currency_obj.round(cursor, user,
|
|
partner.account_receivable.currency, amount)
|
|
res['account'] = account_obj.name_get(cursor, user,
|
|
partner.account_receivable.id, context=context)[0]
|
|
else:
|
|
cursor.execute(query, (partner.id, partner.account_payable.id))
|
|
amount = cursor.fetchone()[0]
|
|
if not currency_obj.is_zero(cursor, user,
|
|
partner.account_payable.currency, amount):
|
|
if amount > 0:
|
|
res['credit'] = currency_obj.round(cursor, user,
|
|
partner.account_payable.currency, amount)
|
|
res['debit'] = Decimal('0.0')
|
|
else:
|
|
res['credit'] = Decimal('0.0')
|
|
res['debit'] = - currency_obj.round(cursor, user,
|
|
partner.account_payable.currency, amount)
|
|
res['account'] = account_obj.name_get(cursor, user,
|
|
partner.account_payable.id, context=context)[0]
|
|
|
|
if partner and vals.get('debit'):
|
|
if vals['debit'] > 0:
|
|
res.setdefault('account', account_obj.name_get(cursor, user,
|
|
partner.account_receivable.id, context=context)[0])
|
|
else:
|
|
res.setdefault('account', account_obj.name_get(cursor, user,
|
|
partner.account_payable.id, context=context)[0])
|
|
|
|
if partner and vals.get('credit'):
|
|
if vals['credit'] > 0:
|
|
res.setdefault('account', account_obj.name_get(cursor, user,
|
|
partner.account_payable.id, context=context)[0])
|
|
else:
|
|
res.setdefault('account', account_obj.name_get(cursor, user,
|
|
partner.account_receivable.id, context=context)[0])
|
|
|
|
journal_id = vals.get('journal') or context.get('journal')
|
|
if journal_id and partner:
|
|
journal = journal_obj.browse(cursor, user, journal_id,
|
|
context=context)
|
|
if journal.type == 'revenue':
|
|
res.setdefault('account', account_obj.name_get(cursor, user,
|
|
partner.account_receivable.id, context=context)[0])
|
|
elif journal.type == 'expense':
|
|
res.setdefault('account', account_obj.name_get(cursor, user,
|
|
partner.account_payable.id, context=context)[0])
|
|
return res
|
|
|
|
def get_move_field(self, cursor, user, ids, name, arg, context=None):
|
|
if name not in ('period', 'journal', 'date'):
|
|
raise Exception('Invalid name')
|
|
obj = self.pool.get('account.' + name)
|
|
res = {}
|
|
for line in self.browse(cursor, user, ids, context=context):
|
|
if name in ('date',):
|
|
res[line.id] = line.move[name]
|
|
else:
|
|
res[line.id] = line.move[name].id
|
|
if name in ('date',):
|
|
return res
|
|
obj_names = {}
|
|
for obj_id, obj_name in obj.name_get(cursor, user,
|
|
[x for x in res.values() if x], context=context):
|
|
obj_names[obj_id] = obj_name
|
|
|
|
for i in res.keys():
|
|
if res[i] and res[i] in obj_names:
|
|
res[i] = (res[i], obj_names[res[i]])
|
|
else:
|
|
res[i] = False
|
|
return res
|
|
|
|
def set_move_field(self, cursor, user, id, name, value, arg, context=None):
|
|
if name not in ('period', 'journal', 'date'):
|
|
raise Exception('Invalid name')
|
|
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):
|
|
args2.append(('move.' + args[i][0], args[i][1], args[i][2]))
|
|
i += 1
|
|
return args2
|
|
|
|
def query_get(self, cursor, user, obj='l', context=None):
|
|
'''
|
|
Return SQL clause for account move line.
|
|
obj is the SQL alias of the account_move_line in the query.
|
|
'''
|
|
fiscalyear_obj = self.pool.get('account.fiscalyear')
|
|
if context is None:
|
|
context = {}
|
|
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 'False'
|
|
else:
|
|
fiscalyear_clause = '%s' % int(context.get('fiscalyear'))
|
|
if context.get('periods', False):
|
|
ids = ','.join([str(int(x)) for x in context['periods']])
|
|
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 id IN (' + 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 + ')' \
|
|
')' \
|
|
')'
|
|
|
|
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.type in ('view', 'closed'):
|
|
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':
|
|
raise ExceptORM('UserError',
|
|
'You can not add/modify lines \n' \
|
|
'in a closed journal period!')
|
|
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':
|
|
raise ExceptORM('UserError',
|
|
'You can not modify line from a posted move!')
|
|
if line.reconciliation:
|
|
raise ExceptORM('UserError',
|
|
'You can not modify reconciled line!')
|
|
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 unlink(self, cursor, user, ids, context=None):
|
|
move_obj = self.pool.get('account.move')
|
|
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).unlink(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')
|
|
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)
|
|
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', context.get('journal'))
|
|
if not journal_id:
|
|
raise ExceptORM('Error', 'No journal defined!')
|
|
journal = journal_obj.browse(cursor, user, journal_id,
|
|
context=context)
|
|
if journal.centralised:
|
|
#TODO journalised
|
|
raise Exception('Not implemented')
|
|
else:
|
|
vals['move'] = move_obj.create(cursor, user, {
|
|
'period': vals.get('period', context.get('period')),
|
|
'journal': vals.get('journal', context.get('journal')),
|
|
}, 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 view_header_get(self, cursor, user, view_id=None, view_type='form',
|
|
context=None):
|
|
if context is None:
|
|
context = {}
|
|
journal_obj = self.pool.get('account.journal')
|
|
period_obj = self.pool.get('account.period')
|
|
if not context.get('journal') or not context.get('period'):
|
|
return False
|
|
journal = journal_obj.browse(cursor, user, context['journal'],
|
|
context=context)
|
|
period = period_obj.browse(cursor, user, context['period'],
|
|
context=context)
|
|
if journal and period:
|
|
return journal.name + ': ' + period.name
|
|
return False
|
|
|
|
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_id=view_id,
|
|
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'
|
|
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))
|
|
xml += '</tree>'
|
|
result['arch'] = xml
|
|
result['fields'] = self.fields_get(cursor, user, fields_names=fields,
|
|
context=context)
|
|
#TODO add hexmd5
|
|
return result
|
|
|
|
def reconcile(self, cursor, user, ids, journal_id=False, period_id=False,
|
|
account_id=False, context=None):
|
|
move_obj = self.pool.get('account.move')
|
|
currency_obj = self.pool.get('account.currency')
|
|
reconciliation_obj = self.pool.get('account.move.reconciliation')
|
|
ids = ids[:]
|
|
if journal_id and period_id and account_id:
|
|
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)
|
|
move_id = move_obj.create(cursor, user, {
|
|
'journal': journal_id,
|
|
'period': period_id,
|
|
'date': datetime.date.today(),
|
|
'lines': [
|
|
(0, 0, {
|
|
'name': 'Write-Off',
|
|
'account': account.id,
|
|
'debit': amount < 0 and -amount or Decimal('0.0'),
|
|
'credit': amount > 0 and amount or Decimal('0.0'),
|
|
}),
|
|
(0, 0, {
|
|
'name': 'Write-Off',
|
|
'account': account_id,
|
|
'debit': amount > 0 and amount or Decimal('0.0'),
|
|
'credit': amount < 0 and - amount or Decimal('0.0'),
|
|
}),
|
|
],
|
|
}, context=context)
|
|
ids += self.search(cursor, user, [
|
|
('move', '=', move_id),
|
|
('account', '=', account.id),
|
|
('debit', '=', amount < 0 and -amount or Decimal('0.0')),
|
|
('credit', '=', amount > 0 and amount or Decimal('0.0')),
|
|
], limit=1, context=context)
|
|
return reconciliation_obj.create(cursor, user, {
|
|
'lines': [(4, x) for x in ids],
|
|
}, context=context)
|
|
|
|
Line()
|
|
|
|
|
|
class OpenJournalAsk(WizardOSV):
|
|
_name = 'account.move.open_journal.ask'
|
|
journal = fields.Many2One('account.journal', 'Journal', required=True)
|
|
period = fields.Many2One('account.period', 'Period', required=True)
|
|
|
|
def default_period(self, cursor, user, context=None):
|
|
period_obj = self.pool.get('account.period')
|
|
return period_obj.find(cursor, user, 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', 'gtk-cancel'),
|
|
('open', 'Open', 'gtk-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')
|
|
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)
|
|
return {
|
|
'domain': str([
|
|
('journal', '=', journal_id),
|
|
('period', '=', period_id),
|
|
]),
|
|
'view_type': 'form',
|
|
'view_mode': 'tree,form',
|
|
'res_model': 'account.move.line',
|
|
'type': 'ir.action.act_window',
|
|
'context': str({
|
|
'journal': journal_id,
|
|
'period': period_id,
|
|
}),
|
|
}
|
|
|
|
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'),
|
|
], 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([
|
|
('period', 'in', period_ids),
|
|
('account', '=', data['id']),
|
|
])
|
|
res['context'] = str({'fiscalyear': context.get('fiscalyear')})
|
|
return res
|
|
|
|
OpenAccount()
|
|
|
|
|
|
class ReconcileLinesWriteOff(WizardOSV):
|
|
'Reconcile Lines Write-Off'
|
|
_name = 'account.move.reconcile_lines.writeoff'
|
|
journal = fields.Many2One('account.journal', 'Journal', required=True)
|
|
period = fields.Many2One('account.period', 'Period', required=True)
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
domain=[('type', '!=', 'view'), ('type', '!=', 'closed')])
|
|
|
|
def default_period(self, cursor, user, context=None):
|
|
period_obj = self.pool.get('account.period')
|
|
return period_obj.find(cursor, user, exception=False, 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', 'gtk-cancel'),
|
|
('reconcile', 'Reconcile', 'gtk-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('account.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 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')
|
|
period_id = data['form'].get('period')
|
|
account_id = data['form'].get('account')
|
|
else:
|
|
journal_id = False
|
|
period_id = False
|
|
account_id = False
|
|
line_obj.reconcile(cursor, user, data['ids'], journal_id, period_id,
|
|
account_id, context=context)
|
|
return {}
|
|
|
|
ReconcileLines()
|
|
|
|
|
|
class OpenReconcileLinesInit(WizardOSV):
|
|
_name = 'account.move.open_reconcile_lines.init'
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
domain=[('type', '!=', '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', 'gtk-cancel'),
|
|
('open', 'Open', 'gtk-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'),
|
|
], 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 Partner(OSV):
|
|
_name = 'partner.partner'
|
|
receivable = fields.Function('get_receivable_payable',
|
|
fnct_search='search_receivable_payable', string='Receivable')
|
|
payable = fields.Function('get_receivable_payable',
|
|
fnct_search='search_receivable_payable', string='Payable')
|
|
|
|
def get_receivable_payable(self, cursor, user_id, ids, name, arg,
|
|
context=None):
|
|
res = {}
|
|
move_line_obj = self.pool.get('account.move.line')
|
|
company_obj = self.pool.get('company.company')
|
|
user_obj = self.pool.get('res.user')
|
|
|
|
if context is None:
|
|
context = {}
|
|
|
|
if name not in ('receivable', 'payable'):
|
|
raise Exception('Bad argument')
|
|
|
|
for i in ids:
|
|
res[i] = Decimal('0.0')
|
|
|
|
company_id = None
|
|
user = user_obj.browse(cursor, user_id, user_id, context=context)
|
|
if context.get('company'):
|
|
child_company_ids = company_obj.search(cursor, user_id, [
|
|
('parent', 'child_of', [user.main_company.id]),
|
|
], context=context)
|
|
if context['company'] in child_company_ids:
|
|
company_id = context['company']
|
|
|
|
if not company_id:
|
|
company_id = user.company.id or user.main_company.id
|
|
|
|
if not company_id:
|
|
return res
|
|
|
|
line_query = move_line_obj.query_get(cursor, user_id, context=context)
|
|
|
|
cursor.execute('SELECT l.partner, ' \
|
|
'SUM((COALESCE(l.debit, 0) - COALESCE(l.credit, 0))) ' \
|
|
'FROM account_move_line AS l, ' \
|
|
'account_account AS a ' \
|
|
'WHERE a.id = l.account ' \
|
|
'AND a.active ' \
|
|
'AND a.type = %s ' \
|
|
'AND l.partner IN ' \
|
|
'(' + ','.join(['%s' for x in ids]) + ') ' \
|
|
'AND l.reconciliation IS NULL ' \
|
|
'AND ' + line_query + ' ' \
|
|
'AND a.company = %s ' \
|
|
'GROUP BY l.partner', (name,) + tuple(ids) + (company_id,))
|
|
for partner_id, sum in cursor.fetchall():
|
|
res[partner_id] = sum
|
|
return res
|
|
|
|
def search_receivable_payable(self, cursor, user_id, name, args,
|
|
context=None):
|
|
if not len(args):
|
|
return []
|
|
move_line_obj = self.pool.get('account.move.line')
|
|
company_obj = self.pool.get('company.company')
|
|
user_obj = self.pool.get('res.user')
|
|
|
|
if context is None:
|
|
context = {}
|
|
|
|
if name not in ('receivable', 'payable'):
|
|
raise Exception('Bad argument')
|
|
|
|
company_id = None
|
|
user = user_obj.browse(cursor, user_id, user_id, context=context)
|
|
if context.get('company'):
|
|
child_company_ids = company_obj.search(cursor, user_id, [
|
|
('parent', 'child_of', [user.main_company.id]),
|
|
], context=context)
|
|
if context['company'] in child_company_ids:
|
|
company_id = context['company']
|
|
|
|
if not company_id:
|
|
company_id = user.company.id or user.main_company.id
|
|
|
|
if not company_id:
|
|
return []
|
|
|
|
line_query = move_line_obj.query_get(cursor, user_id, context=context)
|
|
|
|
cursor.execute('SELECT l.partner ' \
|
|
'FROM account_move_line AS l, ' \
|
|
'account_account AS a ' \
|
|
'WHERE a.id = l.account ' \
|
|
'AND a.active ' \
|
|
'AND a.type = %s ' \
|
|
'AND l.partner IS NOT NULL ' \
|
|
'AND l.reconciliation IS NOT NULL ' \
|
|
'AND ' + line_query + ' ' \
|
|
'AND a.company = %s ' \
|
|
'GROUP BY l.partner ' \
|
|
'HAVING ' + \
|
|
'AND'.join(['(SUM((COALESCE(l.debit, 0) - COALESCE(l.credit, 0))) ' \
|
|
+ ' ' + x[1] + ' ' + str(x[2]) + ') ' for x in args]),
|
|
(name, company_id))
|
|
if not cursor.rowcount:
|
|
return [('id', '=', 0)]
|
|
return [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
|
|
|
Partner()
|