1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 from osv import fields, osv
25 from tools.translate import _
28 from mx.DateTime import RelativeDateTime, now, DateTime, localtime
32 class account_move_line(osv.osv):
33 _name = "account.move.line"
34 _description = "Entry lines"
36 def _query_get(self, cr, uid, obj='l', context={}):
37 fiscalyear_obj = self.pool.get('account.fiscalyear')
38 if not context.get('fiscalyear', False):
39 fiscalyear_ids = fiscalyear_obj.search(cr, uid, [('state', '=', 'draft')])
40 fiscalyear_clause = (','.join([str(x) for x in fiscalyear_ids])) or '0'
42 fiscalyear_clause = '%s' % context['fiscalyear']
43 state=context.get('state',False)
45 where_move_lines_by_date = ''
47 if context.get('date_from', False) and context.get('date_to', False):
48 where_move_lines_by_date = " AND " +obj+".move_id in ( select id from account_move where date >= '" +context['date_from']+"' AND date <= '"+context['date_to']+"')"
51 if state.lower() not in ['all']:
52 where_move_state= " AND "+obj+".move_id in (select id from account_move where account_move.state = '"+state+"')"
55 if context.get('periods', False):
56 ids = ','.join([str(x) for x in context['periods']])
57 return obj+".state<>'draft' AND "+obj+".period_id in (SELECT id from account_period WHERE fiscalyear_id in (%s) AND id in (%s)) %s %s" % (fiscalyear_clause, ids,where_move_state,where_move_lines_by_date)
59 return obj+".state<>'draft' AND "+obj+".period_id in (SELECT id from account_period WHERE fiscalyear_id in (%s) %s %s)" % (fiscalyear_clause,where_move_state,where_move_lines_by_date)
61 def default_get(self, cr, uid, fields, context={}):
62 data = self._default_get(cr, uid, fields, context)
68 def create_analytic_lines(self, cr, uid, ids, context={}):
69 for obj_line in self.browse(cr, uid, ids, context):
70 if obj_line.analytic_account_id:
71 if not obj_line.journal_id.analytic_journal_id:
72 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (obj_line.journal_id.name,))
73 amt = (obj_line.credit or 0.0) - (obj_line.debit or 0.0)
75 'name': obj_line.name,
76 'date': obj_line.date,
77 'account_id': obj_line.analytic_account_id.id,
78 'unit_amount':obj_line.quantity,
79 'product_id': obj_line.product_id and obj_line.product_id.id or False,
80 'product_uom_id': obj_line.product_uom_id and obj_line.product_uom_id.id or False,
82 'general_account_id': obj_line.account_id.id,
83 'journal_id': obj_line.journal_id.analytic_journal_id.id,
87 new_id = self.pool.get('account.analytic.line').create(cr,uid,vals_lines)
90 def _default_get_move_form_hook(self, cursor, user, data):
91 '''Called in the end of default_get method for manual entry in account_move form'''
92 if data.has_key('analytic_account_id'):
93 del(data['analytic_account_id'])
94 if data.has_key('account_tax_id'):
95 del(data['account_tax_id'])
98 def _default_get(self, cr, uid, fields, context={}):
99 # Compute simple values
100 data = super(account_move_line, self).default_get(cr, uid, fields, context)
101 # Starts: Manual entry from account.move form
102 if context.get('lines',[]):
105 for i in context['lines']:
106 total_new +=(i[2]['debit'] or 0.00)- (i[2]['credit'] or 0.00)
108 data[item]=i[2][item]
109 if context['journal']:
110 journal_obj=self.pool.get('account.journal').browse(cr,uid,context['journal'])
111 if journal_obj.type == 'purchase':
113 account = journal_obj.default_credit_account_id
115 account = journal_obj.default_debit_account_id
118 account = journal_obj.default_credit_account_id
120 account = journal_obj.default_debit_account_id
123 if account and ((not fields) or ('debit' in fields) or ('credit' in fields)) and 'partner_id' in data and (data['partner_id']):
124 part = self.pool.get('res.partner').browse(cr, uid, data['partner_id'])
125 account = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, account.id)
126 account = self.pool.get('account.account').browse(cr, uid, account)
127 data['account_id'] = account.id
130 data['debit'] = s>0 and s or 0.0
131 data['credit'] = s<0 and -s or 0.0
132 data = self._default_get_move_form_hook(cr, uid, data)
134 # Ends: Manual entry from account.move form
136 if not 'move_id' in fields: #we are not in manual entry
139 period_obj = self.pool.get('account.period')
141 # Compute the current move
144 if context.get('journal_id',False) and context.get('period_id',False):
145 if 'move_id' in fields:
146 cr.execute('select move_id \
150 journal_id=%s and period_id=%s and create_uid=%s and state=%s \
151 order by id desc limit 1',
152 (context['journal_id'], context['period_id'], uid, 'draft'))
154 move_id = (res and res[0]) or False
159 data['move_id'] = move_id
161 cr.execute('select date \
165 journal_id=%s and period_id=%s and create_uid=%s \
167 (context['journal_id'], context['period_id'], uid))
170 data['date'] = res[0]
172 period = period_obj.browse(cr, uid, context['period_id'],
174 data['date'] = period.date_start
181 move = self.pool.get('account.move').browse(cr, uid, move_id, context)
183 data.setdefault('name', move.line_id[-1].name)
185 for l in move.line_id:
187 partner_id = partner_id or l.partner_id.id
188 ref_id = ref_id or l.ref
189 total += (l.debit or 0.0) - (l.credit or 0.0)
193 if 'partner_id' in fields:
194 data['partner_id'] = partner_id
196 if move.journal_id.type == 'purchase':
198 account = move.journal_id.default_credit_account_id
200 account = move.journal_id.default_debit_account_id
203 account = move.journal_id.default_credit_account_id
205 account = move.journal_id.default_debit_account_id
207 part = partner_id and self.pool.get('res.partner').browse(cr, uid, partner_id) or False
208 # part = False is acceptable for fiscal position.
209 account = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, account.id)
211 account = self.pool.get('account.account').browse(cr, uid, account)
213 if account and ((not fields) or ('debit' in fields) or ('credit' in fields)):
214 data['account_id'] = account.id
215 # Propose the price VAT excluded, the VAT will be added when confirming line
217 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, part and part.property_account_position or False, account.tax_ids)
218 tax = self.pool.get('account.tax').browse(cr, uid, taxes)
219 for t in self.pool.get('account.tax').compute_inv(cr, uid, tax, total, 1):
223 data['debit'] = s>0 and s or 0.0
224 data['credit'] = s<0 and -s or 0.0
226 if account and account.currency_id:
227 data['currency_id'] = account.currency_id.id
231 v = self.pool.get('res.currency').compute(cr, uid,
232 account.company_id.currency_id.id,
234 s, account=acc, account_invert=True)
235 data['amount_currency'] = v
238 def _on_create_write(self, cr, uid, id, context={}):
239 ml = self.browse(cr, uid, id, context)
240 return map(lambda x: x.id, ml.move_id.line_id)
242 def _balance(self, cr, uid, ids, prop, unknow_none, unknow_dict):
244 # TODO group the foreach in sql
246 cr.execute('SELECT date,account_id FROM account_move_line WHERE id=%s', (id,))
247 dt, acc = cr.fetchone()
248 cr.execute('SELECT SUM(debit-credit) FROM account_move_line WHERE account_id=%s AND (date<%s OR (date=%s AND id<=%s))', (acc,dt,dt,id))
249 res[id] = cr.fetchone()[0]
252 def _invoice(self, cursor, user, ids, name, arg, context=None):
253 invoice_obj = self.pool.get('account.invoice')
257 cursor.execute('SELECT l.id, i.id ' \
258 'FROM account_move_line l, account_invoice i ' \
259 'WHERE l.move_id = i.move_id ' \
260 'AND l.id in (' + ','.join([str(x) for x in ids]) + ')')
262 for line_id, invoice_id in cursor.fetchall():
263 res[line_id] = invoice_id
264 invoice_ids.append(invoice_id)
265 invoice_names = {False: ''}
266 for invoice_id, name in invoice_obj.name_get(cursor, user,
267 invoice_ids, context=context):
268 invoice_names[invoice_id] = name
269 for line_id in res.keys():
270 invoice_id = res[line_id]
271 res[line_id] = (invoice_id, invoice_names[invoice_id])
274 def name_get(self, cr, uid, ids, context={}):
278 for line in self.browse(cr, uid, ids, context):
280 result.append((line.id, (line.name or '')+' ('+line.ref+')'))
282 result.append((line.id, line.name))
285 def _balance_search(self, cursor, user, obj, name, args):
288 where = ' and '.join(map(lambda x: '(abs(sum(debit-credit))'+x[1]+str(x[2])+')',args))
289 cursor.execute('select id, sum(debit-credit) from account_move_line \
290 group by id,debit,credit having '+where)
291 res = cursor.fetchall()
293 return [('id', '=', '0')]
294 return [('id', 'in', [x[0] for x in res])]
296 def _invoice_search(self, cursor, user, obj, name, args):
299 invoice_obj = self.pool.get('account.invoice')
303 fargs = args[i][0].split('.', 1)
305 args[i] = (fargs[0], 'in', invoice_obj.search(cursor, user,
306 [(fargs[1], args[i][1], args[i][2])]))
309 if isinstance(args[i][2], basestring):
310 res_ids = invoice_obj.name_search(cursor, user, args[i][2], [],
312 args[i] = (args[i][0], 'in', [x[0] for x in res_ids])
317 if (x[2] is False) and (x[1] == '='):
318 qu1.append('(i.id IS NULL)')
319 elif (x[2] is False) and (x[1] == '<>' or x[1] == '!='):
320 qu1.append('(i.id IS NOT NULL)')
322 qu1.append('(i.id %s %s)' % (x[1], '%s'))
326 qu1.append('(i.id in (%s))' % (','.join(['%s'] * len(x[2]))))
329 qu1.append(' (False)')
331 qu1 = ' AND' + ' AND'.join(qu1)
334 cursor.execute('SELECT l.id ' \
335 'FROM account_move_line l, account_invoice i ' \
336 'WHERE l.move_id = i.move_id ' + qu1, qu2)
337 res = cursor.fetchall()
339 return [('id', '=', '0')]
340 return [('id', 'in', [x[0] for x in res])]
342 def _get_move_lines(self, cr, uid, ids, context={}):
344 for move in self.pool.get('account.move').browse(cr, uid, ids, context=context):
345 for line in move.line_id:
346 result.append(line.id)
350 'name': fields.char('Name', size=64, required=True),
351 'quantity': fields.float('Quantity', digits=(16,2), help="The optional quantity expressed by this line, eg: number of product sold. The quantity is not a legal requirement but is very usefull for some reports."),
352 'product_uom_id': fields.many2one('product.uom', 'UoM'),
353 'product_id': fields.many2one('product.product', 'Product'),
354 'debit': fields.float('Debit', digits=(16,int(tools.config['price_accuracy']))),
355 'credit': fields.float('Credit', digits=(16,int(tools.config['price_accuracy']))),
356 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade", domain=[('type','<>','view'), ('type', '<>', 'closed')], select=2),
357 'move_id': fields.many2one('account.move', 'Move', ondelete="cascade", states={'valid':[('readonly',True)]}, help="The move of this entry line.", select=2),
359 'ref': fields.char('Ref.', size=32),
360 'statement_id': fields.many2one('account.bank.statement', 'Statement', help="The bank statement used for bank reconciliation", select=1),
361 'reconcile_id': fields.many2one('account.move.reconcile', 'Reconcile', readonly=True, ondelete='set null', select=2),
362 'reconcile_partial_id': fields.many2one('account.move.reconcile', 'Partial Reconcile', readonly=True, ondelete='set null', select=2),
363 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency if it is a multi-currency entry.", digits=(16,int(tools.config['price_accuracy']))),
364 'currency_id': fields.many2one('res.currency', 'Currency', help="The optional other currency if it is a multi-currency entry."),
366 'period_id': fields.many2one('account.period', 'Period', required=True, select=2),
367 'journal_id': fields.many2one('account.journal', 'Journal', required=True, select=1),
368 'blocked': fields.boolean('Litigation', help="You can check this box to mark the entry line as a litigation with the associated partner"),
370 'partner_id': fields.many2one('res.partner', 'Partner Ref.'),
371 'date_maturity': fields.date('Maturity date', help="This field is used for payable and receivable entries. You can put the limit date for the payment of this entry line."),
372 'date': fields.related('move_id','date', string='Effective date', type='date', required=True,
374 'account.move': (_get_move_lines, ['date'], 20)
376 'date_created': fields.date('Creation date'),
377 'analytic_lines': fields.one2many('account.analytic.line', 'move_id', 'Analytic lines'),
378 'centralisation': fields.selection([('normal','Normal'),('credit','Credit Centralisation'),('debit','Debit Centralisation')], 'Centralisation', size=6),
379 'balance': fields.function(_balance, fnct_search=_balance_search, method=True, string='Balance'),
380 'state': fields.selection([('draft','Draft'), ('valid','Valid')], 'State', readonly=True,
381 help='When new move line is created the state will be \'Draft\'.\n* When all the payments are done it will be in \'Valid\' state.'),
382 'tax_code_id': fields.many2one('account.tax.code', 'Tax Account', help="The Account can either be a base tax code or tax code account."),
383 'tax_amount': fields.float('Tax/Base Amount', digits=(16,int(tools.config['price_accuracy'])), select=True, help="If the Tax account is tax code account, this field will contain the taxed amount.If the tax account is base tax code,\
384 this field will contain the basic amount(without tax)."),
385 'invoice': fields.function(_invoice, method=True, string='Invoice',
386 type='many2one', relation='account.invoice', fnct_search=_invoice_search),
387 'account_tax_id':fields.many2one('account.tax', 'Tax'),
388 'analytic_account_id' : fields.many2one('account.analytic.account', 'Analytic Account'),
390 'amount_taxed':fields.float("Taxed Amount",digits=(16,int(tools.config['price_accuracy']))),
391 'company_id': fields.related('account_id','company_id',type='many2one',object='res.company',string='Company')
395 def _get_date(self, cr, uid, context):
396 period_obj = self.pool.get('account.period')
397 dt = time.strftime('%Y-%m-%d')
398 if ('journal_id' in context) and ('period_id' in context):
399 cr.execute('select date from account_move_line ' \
400 'where journal_id=%s and period_id=%s ' \
401 'order by id desc limit 1',
402 (context['journal_id'], context['period_id']))
407 period = period_obj.browse(cr, uid, context['period_id'],
409 dt = period.date_start
411 def _get_currency(self, cr, uid, context={}):
412 if not context.get('journal_id', False):
414 cur = self.pool.get('account.journal').browse(cr, uid, context['journal_id']).currency
415 return cur and cur.id or False
418 'blocked': lambda *a: False,
419 'centralisation': lambda *a: 'normal',
421 'date_created': lambda *a: time.strftime('%Y-%m-%d'),
422 'state': lambda *a: 'draft',
423 'currency_id': _get_currency,
424 'journal_id': lambda self, cr, uid, c: c.get('journal_id', False),
425 'period_id': lambda self, cr, uid, c: c.get('period_id', False),
426 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.move.line', c)
428 _order = "date desc,id desc"
430 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry !'),
431 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'),
434 def _auto_init(self, cr, context={}):
435 super(account_move_line, self)._auto_init(cr, context)
436 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'account_move_line_journal_id_period_id_index\'')
437 if not cr.fetchone():
438 cr.execute('CREATE INDEX account_move_line_journal_id_period_id_index ON account_move_line (journal_id, period_id)')
441 def _check_no_view(self, cr, uid, ids):
442 lines = self.browse(cr, uid, ids)
444 if l.account_id.type == 'view':
448 def _check_no_closed(self, cr, uid, ids):
449 lines = self.browse(cr, uid, ids)
451 if l.account_id.type == 'closed':
456 (_check_no_view, 'You can not create move line on view account.', ['account_id']),
457 (_check_no_closed, 'You can not create move line on closed account.', ['account_id']),
460 #TODO: ONCHANGE_ACCOUNT_ID: set account_tax_id
462 def onchange_currency(self, cr, uid, ids, account_id, amount, currency_id, date=False, journal=False):
463 if (not currency_id) or (not account_id):
466 acc =self.pool.get('account.account').browse(cr, uid, account_id)
467 if (amount>0) and journal:
468 x = self.pool.get('account.journal').browse(cr, uid, journal).default_credit_account_id
470 v = self.pool.get('res.currency').compute(cr, uid, currency_id,acc.company_id.currency_id.id, amount, account=acc)
472 'debit': v>0 and v or 0.0,
473 'credit': v<0 and -v or 0.0
477 def onchange_partner_id(self, cr, uid, ids, move_id, partner_id, account_id=None, debit=0, credit=0, date=False, journal=False):
479 val['date_maturity'] = False
484 date = now().strftime('%Y-%m-%d')
485 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
487 if part.property_payment_term and part.property_payment_term.line_ids:
488 payterm = part.property_payment_term.line_ids[0]
489 res = self.pool.get('account.payment.term').compute(cr, uid, payterm.id, 100, date)
491 val['date_maturity'] = res[0][0]
493 id1 = part.property_account_payable.id
494 id2 = part.property_account_receivable.id
496 jt = self.pool.get('account.journal').browse(cr, uid, journal).type
498 val['account_id'] = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, id2)
501 val['account_id'] = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, id1)
502 if val.get('account_id', False):
503 d = self.onchange_account_id(cr, uid, ids, val['account_id'])
504 val.update(d['value'])
508 def onchange_account_id(self, cr, uid, ids, account_id=False, partner_id=False):
511 res = self.pool.get('account.account').browse(cr, uid, account_id)
512 tax_ids = res.tax_ids
513 if tax_ids and partner_id:
514 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
515 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, part and part.property_account_position or False, tax_ids)[0]
517 tax_id = tax_ids and tax_ids[0].id or False
518 val['account_tax_id'] = tax_id
522 # type: the type if reconciliation (no logic behind this field, for info)
524 # writeoff; entry generated for the difference between the lines
527 def reconcile_partial(self, cr, uid, ids, type='auto', context={}):
532 for line in self.browse(cr, uid, ids, context):
533 if line.reconcile_id:
534 raise osv.except_osv(_('Already Reconciled'), _('Already Reconciled'))
535 if line.reconcile_partial_id:
536 for line2 in line.reconcile_partial_id.line_partial_ids:
537 if not line2.reconcile_id:
538 merges.append(line2.id)
539 total += (line2.debit or 0.0) - (line2.credit or 0.0)
540 merges_rec.append(line.reconcile_partial_id.id)
542 unmerge.append(line.id)
543 total += (line.debit or 0.0) - (line.credit or 0.0)
545 res = self.reconcile(cr, uid, merges+unmerge, context=context)
547 r_id = self.pool.get('account.move.reconcile').create(cr, uid, {
549 'line_partial_ids': map(lambda x: (4,x,False), merges+unmerge)
551 self.pool.get('account.move.reconcile').reconcile_partial_check(cr, uid, [r_id] + merges_rec, context=context)
554 def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context={}):
555 id_set = ','.join(map(str, ids))
557 lines = self.browse(cr, uid, ids, context=context)
558 unrec_lines = filter(lambda x: not x['reconcile_id'], lines)
563 for line in unrec_lines:
564 if line.state <> 'valid':
565 raise osv.except_osv(_('Error'),
566 _('Entry "%s" is not valid !') % line.name)
567 credit += line['credit']
568 debit += line['debit']
569 currency += line['amount_currency'] or 0.0
570 account_id = line['account_id']['id']
571 partner_id = (line['partner_id'] and line['partner_id']['id']) or False
572 writeoff = debit - credit
573 # Ifdate_p in context => take this date
574 if context.has_key('date_p') and context['date_p']:
575 date=context['date_p']
577 date = time.strftime('%Y-%m-%d')
579 cr.execute('SELECT account_id, reconcile_id \
580 FROM account_move_line \
581 WHERE id IN ('+id_set+') \
582 GROUP BY account_id,reconcile_id')
584 #TODO: move this check to a constraint in the account_move_reconcile object
585 if (len(r) != 1) and not context.get('fy_closing', False):
586 raise osv.except_osv(_('Error'), _('Entries are not of the same account or already reconciled ! '))
588 raise osv.except_osv(_('Error'), _('Entry is already reconciled'))
589 account = self.pool.get('account.account').browse(cr, uid, account_id, context=context)
590 if not context.get('fy_closing', False) and not account.reconcile:
591 raise osv.except_osv(_('Error'), _('The account is not defined to be reconciled !'))
593 raise osv.except_osv(_('Error'), _('Some entries are already reconciled !'))
595 if (not self.pool.get('res.currency').is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \
596 (account.currency_id and (not self.pool.get('res.currency').is_zero(cr, uid, account.currency_id, currency))):
597 if not writeoff_acc_id:
598 raise osv.except_osv(_('Warning'), _('You have to provide an account for the write off entry !'))
602 self_credit = writeoff
608 self_debit = -writeoff
610 # If comment exist in context, take it
611 if 'comment' in context and context['comment']:
612 libelle=context['comment']
620 'credit':self_credit,
621 'account_id':account_id,
623 'partner_id':partner_id,
624 'currency_id': account.currency_id.id or False,
625 'amount_currency': account.currency_id.id and -currency or 0.0
631 'account_id':writeoff_acc_id,
632 'analytic_account_id': context.get('analytic_id', False),
634 'partner_id':partner_id
638 writeoff_move_id = self.pool.get('account.move').create(cr, uid, {
639 'period_id': writeoff_period_id,
640 'journal_id': writeoff_journal_id,
643 'line_id': writeoff_lines
646 writeoff_line_ids = self.search(cr, uid, [('move_id', '=', writeoff_move_id), ('account_id', '=', account_id)])
647 ids += writeoff_line_ids
649 r_id = self.pool.get('account.move.reconcile').create(cr, uid, {
652 'line_id': map(lambda x: (4,x,False), ids),
653 'line_partial_ids': map(lambda x: (3,x,False), ids)
655 wf_service = netsvc.LocalService("workflow")
656 # the id of the move.reconcile is written in the move.line (self) by the create method above
657 # because of the way the line_id are defined: (4, x, False)
659 wf_service.trg_trigger(uid, 'account.move.line', id, cr)
662 def view_header_get(self, cr, user, view_id, view_type, context):
663 if context.get('account_id', False):
664 cr.execute('select code from account_account where id=%s', (context['account_id'],))
666 res = _('Entries: ')+ (res[0] or '')
668 if (not context.get('journal_id', False)) or (not context.get('period_id', False)):
670 cr.execute('select code from account_journal where id=%s', (context['journal_id'],))
671 j = cr.fetchone()[0] or ''
672 cr.execute('select code from account_period where id=%s', (context['period_id'],))
673 p = cr.fetchone()[0] or ''
675 return j+(p and (':'+p) or '')
678 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context={}, toolbar=False, submenu=False):
679 result = super(osv.osv, self).fields_view_get(cr, uid, view_id,view_type,context,toolbar=toolbar, submenu=submenu)
680 if view_type=='tree' and 'journal_id' in context:
681 title = self.view_header_get(cr, uid, view_id, view_type, context)
682 journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id'])
684 # if the journal view has a state field, color lines depending on
687 for field in journal.view_id.columns_id:
688 if field.field=='state':
689 state = ' colors="red:state==\'draft\'"'
691 #xml = '''<?xml version="1.0"?>\n<tree string="%s" editable="top" refresh="5"%s>\n\t''' % (title, state)
692 xml = '''<?xml version="1.0"?>\n<tree string="%s" editable="top" refresh="5" on_write="_on_create_write"%s>\n\t''' % (title, state)
702 for field in journal.view_id.columns_id:
703 fields.append(field.field)
705 if field.field=='debit':
706 attrs.append('sum="Total debit"')
707 elif field.field=='credit':
708 attrs.append('sum="Total credit"')
709 elif field.field=='account_tax_id':
710 attrs.append('domain="[(\'parent_id\',\'=\',False)]"')
711 elif field.field=='account_id' and journal.id:
712 attrs.append('domain="[(\'journal_id\', \'=\', '+str(journal.id)+'),(\'type\',\'<>\',\'view\'), (\'type\',\'<>\',\'closed\')]" on_change="onchange_account_id(account_id, partner_id)"')
713 elif field.field == 'partner_id':
714 attrs.append('on_change="onchange_partner_id(move_id,partner_id,account_id,debit,credit,date,((\'journal_id\' in context) and context[\'journal_id\']) or {})"')
716 attrs.append('readonly="1"')
718 attrs.append('required="1"')
720 attrs.append('required="0"')
721 if field.field in ('amount_currency','currency_id'):
722 attrs.append('on_change="onchange_currency(account_id,amount_currency,currency_id,date,((\'journal_id\' in context) and context[\'journal_id\']) or {})"')
724 if field.field in widths:
725 attrs.append('width="'+str(widths[field.field])+'"')
726 xml += '''<field name="%s" %s/>\n''' % (field.field,' '.join(attrs))
730 result['fields'] = self.fields_get(cr, uid, fields, context)
733 def unlink(self, cr, uid, ids, context={}, check=True):
734 self._update_check(cr, uid, ids, context)
736 for line in self.browse(cr, uid, ids, context):
737 context['journal_id']=line.journal_id.id
738 context['period_id']=line.period_id.id
739 result = super(account_move_line, self).unlink(cr, uid, [line.id], context=context)
741 self.pool.get('account.move').validate(cr, uid, [line.move_id.id], context=context)
744 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
747 if vals.get('account_tax_id', False):
748 raise osv.except_osv(_('Unable to change tax !'), _('You can not change the tax, you should remove and recreate lines !'))
750 account_obj = self.pool.get('account.account')
751 if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']:
752 raise osv.except_osv(_('Bad account!'), _('You can not use an inactive account!'))
754 if ('account_id' in vals) or ('journal_id' in vals) or ('period_id' in vals) or ('move_id' in vals) or ('debit' in vals) or ('credit' in vals) or ('date' in vals):
755 self._update_check(cr, uid, ids, context)
758 if vals.get('date', False):
759 todo_date = vals['date']
761 result = super(account_move_line, self).write(cr, uid, ids, vals, context)
765 for line in self.browse(cr, uid, ids):
766 if line.move_id.id not in done:
767 done.append(line.move_id.id)
768 self.pool.get('account.move').validate(cr, uid, [line.move_id.id], context)
770 self.pool.get('account.move').write(cr, uid, [line.move_id.id], {'date': todo_date}, context=context)
773 def _update_journal_check(self, cr, uid, journal_id, period_id, context={}):
774 cr.execute('select state from account_journal_period where journal_id=%s and period_id=%s', (journal_id, period_id))
775 result = cr.fetchall()
776 for (state,) in result:
778 raise osv.except_osv(_('Error !'), _('You can not add/modify entries in a closed journal.'))
780 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context)
781 period = self.pool.get('account.period').browse(cr, uid, period_id, context)
782 self.pool.get('account.journal.period').create(cr, uid, {
783 'name': (journal.code or journal.name)+':'+(period.name or ''),
784 'journal_id': journal.id,
785 'period_id': period.id
789 def _update_check(self, cr, uid, ids, context={}):
791 for line in self.browse(cr, uid, ids, context):
792 if line.move_id.state<>'draft':
793 raise osv.except_osv(_('Error !'), _('You can not do this modification on a confirmed entry ! Please note that you can just change some non important fields !'))
794 if line.reconcile_id:
795 raise osv.except_osv(_('Error !'), _('You can not do this modification on a reconciled entry ! Please note that you can just change some non important fields !'))
796 t = (line.journal_id.id, line.period_id.id)
798 self._update_journal_check(cr, uid, line.journal_id.id, line.period_id.id, context)
802 def create(self, cr, uid, vals, context=None, check=True):
805 account_obj = self.pool.get('account.account')
806 tax_obj=self.pool.get('account.tax')
807 if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']:
808 raise osv.except_osv(_('Bad account!'), _('You can not use an inactive account!'))
809 if 'journal_id' in vals and 'journal_id' not in context:
810 context['journal_id'] = vals['journal_id']
811 if 'period_id' in vals and 'period_id' not in context:
812 context['period_id'] = vals['period_id']
813 if ('journal_id' not in context) and ('move_id' in vals) and vals['move_id']:
814 m = self.pool.get('account.move').browse(cr, uid, vals['move_id'])
815 context['journal_id'] = m.journal_id.id
816 context['period_id'] = m.period_id.id
818 self._update_journal_check(cr, uid, context['journal_id'], context['period_id'], context)
819 company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
821 move_id = vals.get('move_id', False)
822 journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id'])
825 if journal.centralisation:
826 # use the first move ever created for this journal and period
827 cr.execute('select id, state, name from account_move where journal_id=%s and period_id=%s order by id limit 1', (context['journal_id'],context['period_id']))
830 if res[1] != 'draft':
831 raise osv.except_osv(_('UserError'),
832 _('The Ledger Posting (%s) for centralisation ' \
833 'has been confirmed!') % res[2])
834 vals['move_id'] = res[0]
836 if not vals.get('move_id', False):
837 if journal.sequence_id:
838 #name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
840 'date': vals.get('date', time.strftime('%Y-%m-%d')),
841 'period_id': context['period_id'],
842 'journal_id': context['journal_id']
844 move_id = self.pool.get('account.move').create(cr, uid, v, context)
845 vals['move_id'] = move_id
847 raise osv.except_osv(_('No piece number !'), _('Can not create an automatic sequence for this piece !\n\nPut a sequence in the journal definition for automatic numbering or create a sequence manually for this piece.'))
850 ok = not (journal.type_control_ids or journal.account_control_ids)
851 if ('account_id' in vals):
852 account = account_obj.browse(cr, uid, vals['account_id'])
853 if journal.type_control_ids:
854 type = account.user_type
855 for t in journal.type_control_ids:
856 if type.code == t.code:
859 if journal.account_control_ids and not ok:
860 for a in journal.account_control_ids:
861 if a.id==vals['account_id']:
864 if (account.currency_id) and 'amount_currency' not in vals and account.currency_id.id <> company_currency:
865 vals['currency_id'] = account.currency_id.id
866 cur_obj = self.pool.get('res.currency')
869 ctx['date'] = vals['date']
870 vals['amount_currency'] = cur_obj.compute(cr, uid, account.company_id.currency_id.id,
871 account.currency_id.id, vals.get('debit', 0.0)-vals.get('credit', 0.0),
874 raise osv.except_osv(_('Bad account !'), _('You can not use this general account in this journal !'))
876 if 'analytic_account_id' in vals and vals['analytic_account_id']:
877 if journal.analytic_journal_id:
878 vals['analytic_lines'] = [(0,0, {
879 'name': vals['name'],
880 'date': vals.get('date', time.strftime('%Y-%m-%d')),
881 'account_id': vals['analytic_account_id'],
882 'unit_amount':'quantity' in vals and vals['quantity'] or 1.0,
883 'amount': vals['debit'] or vals['credit'],
884 'general_account_id': vals['account_id'],
885 'journal_id': journal.analytic_journal_id.id,
886 'ref': vals.get('ref', False),
889 # raise osv.except_osv(_('No analytic journal !'), _('Please set an analytic journal on this financial journal !'))
891 #if not 'currency_id' in vals:
892 # vals['currency_id'] = account.company_id.currency_id.id
894 result = super(osv.osv, self).create(cr, uid, vals, context)
896 if 'account_tax_id' in vals and vals['account_tax_id']:
897 tax_id=tax_obj.browse(cr,uid,vals['account_tax_id'])
898 total = vals['debit'] - vals['credit']
899 if journal.refund_journal:
900 base_code = 'ref_base_code_id'
901 tax_code = 'ref_tax_code_id'
902 account_id = 'account_paid_id'
903 base_sign = 'ref_base_sign'
904 tax_sign = 'ref_tax_sign'
906 base_code = 'base_code_id'
907 tax_code = 'tax_code_id'
908 account_id = 'account_collected_id'
909 base_sign = 'base_sign'
910 tax_sign = 'tax_sign'
913 for tax in tax_obj.compute(cr,uid,[tax_id],total,1.00):
914 #create the base movement
918 self.write(cr, uid,[result], {
919 'tax_code_id': tax[base_code],
920 'tax_amount': tax[base_sign] * abs(total)
924 'move_id': vals['move_id'],
925 'journal_id': vals['journal_id'],
926 'period_id': vals['period_id'],
927 'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''),
928 'date': vals['date'],
929 'partner_id': vals.get('partner_id',False),
930 'ref': vals.get('ref',False),
931 'account_tax_id': False,
932 'tax_code_id': tax[base_code],
933 'tax_amount': tax[base_sign] * abs(total),
934 'account_id': vals['account_id'],
938 if data['tax_code_id']:
939 self.create(cr, uid, data, context)
941 #create the VAT movement
943 'move_id': vals['move_id'],
944 'journal_id': vals['journal_id'],
945 'period_id': vals['period_id'],
946 'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''),
947 'date': vals['date'],
948 'partner_id': vals.get('partner_id',False),
949 'ref': vals.get('ref',False),
950 'account_tax_id': False,
951 'tax_code_id': tax[tax_code],
952 'tax_amount': tax[tax_sign] * abs(tax['amount']),
953 'account_id': tax[account_id] or vals['account_id'],
954 'credit': tax['amount']<0 and -tax['amount'] or 0.0,
955 'debit': tax['amount']>0 and tax['amount'] or 0.0,
957 if data['tax_code_id']:
958 self.create(cr, uid, data, context)
959 del vals['account_tax_id']
961 # No needed, related to the job
962 #if not is_new_move and 'date' in vals:
963 # if context and ('__last_update' in context):
964 # del context['__last_update']
965 # self.pool.get('account.move').write(cr, uid, [move_id], {'date':vals['date']}, context=context)
966 if check and not context.get('no_store_function'):
967 tmp = self.pool.get('account.move').validate(cr, uid, [vals['move_id']], context)
968 if journal.entry_posted and tmp:
969 self.pool.get('account.move').button_validate(cr,uid, [vals['move_id']],context)
974 class account_bank_statement_reconcile(osv.osv):
975 _inherit = "account.bank.statement.reconcile"
977 'line_ids': fields.many2many('account.move.line', 'account_bank_statement_line_rel', 'statement_id', 'line_id', 'Entries'),
979 account_bank_statement_reconcile()
981 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: