1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
25 from osv import fields, osv
26 from tools.translate import _
29 from mx.DateTime import RelativeDateTime, now, DateTime, localtime
31 class account_move_line(osv.osv):
32 _name = "account.move.line"
33 _description = "Entry lines"
35 def _query_get(self, cr, uid, obj='l', context={}):
36 fiscalyear_obj = self.pool.get('account.fiscalyear')
37 if not context.get('fiscalyear', False):
38 fiscalyear_ids = fiscalyear_obj.search(cr, uid, [('state', '=', 'draft')])
39 fiscalyear_clause = (','.join([str(x) for x in fiscalyear_ids])) or '0'
41 fiscalyear_clause = '%s' % context['fiscalyear']
42 state=context.get('state',False)
44 where_move_lines_by_date = ''
46 if context.get('date_from', False) and context.get('date_to', False):
47 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']+"')"
50 if state.lower() not in ['all']:
51 where_move_state= " AND "+obj+".move_id in (select id from account_move where account_move.state = '"+state+"')"
54 if context.get('periods', False):
55 ids = ','.join([str(x) for x in context['periods']])
56 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)
58 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)
60 def default_get(self, cr, uid, fields, context={}):
61 data = self._default_get(cr, uid, fields, context)
67 def create_analytic_lines(self, cr, uid, ids, context={}):
68 for obj_line in self.browse(cr, uid, ids, context):
69 if obj_line.analytic_account_id:
70 if not obj_line.journal_id.analytic_journal_id:
71 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (obj_line.journal_id.name,))
72 amt = (obj_line.credit or 0.0) - (obj_line.debit or 0.0)
74 'name': obj_line.name,
75 'date': obj_line.date,
76 'account_id': obj_line.analytic_account_id.id,
77 'unit_amount':obj_line.quantity,
78 'product_id': obj_line.product_id and obj_line.product_id.id or False,
79 'product_uom_id': obj_line.product_uom_id and obj_line.product_uom_id.id or False,
81 'general_account_id': obj_line.account_id.id,
82 'journal_id': obj_line.journal_id.analytic_journal_id.id,
86 new_id = self.pool.get('account.analytic.line').create(cr,uid,vals_lines)
89 def _default_get_move_form_hook(self, cursor, user, data):
90 '''Called in the end of default_get method for manual entry in account_move form'''
91 if data.has_key('analytic_account_id'):
92 del(data['analytic_account_id'])
93 if data.has_key('account_tax_id'):
94 del(data['account_tax_id'])
97 def _default_get(self, cr, uid, fields, context={}):
98 # Compute simple values
99 data = super(account_move_line, self).default_get(cr, uid, fields, context)
100 # Starts: Manual entry from account.move form
101 if context.get('lines',[]):
104 for i in context['lines']:
105 total_new +=(i[2]['debit'] or 0.00)- (i[2]['credit'] or 0.00)
107 data[item]=i[2][item]
108 if context['journal']:
109 journal_obj=self.pool.get('account.journal').browse(cr,uid,context['journal'])
110 if journal_obj.type == 'purchase':
112 account = journal_obj.default_credit_account_id
114 account = journal_obj.default_debit_account_id
117 account = journal_obj.default_credit_account_id
119 account = journal_obj.default_debit_account_id
122 if account and ((not fields) or ('debit' in fields) or ('credit' in fields)) and 'partner_id' in data and (data['partner_id']):
123 part = self.pool.get('res.partner').browse(cr, uid, data['partner_id'])
124 account = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, account.id)
125 account = self.pool.get('account.account').browse(cr, uid, account)
126 data['account_id'] = account.id
129 data['debit'] = s>0 and s or 0.0
130 data['credit'] = s<0 and -s or 0.0
131 data = self._default_get_move_form_hook(cr, uid, data)
133 # Ends: Manual entry from account.move form
135 if not 'move_id' in fields: #we are not in manual entry
138 period_obj = self.pool.get('account.period')
140 # Compute the current move
143 if context.get('journal_id',False) and context.get('period_id',False):
144 if 'move_id' in fields:
145 cr.execute('select move_id \
149 journal_id=%s and period_id=%s and create_uid=%s and state=%s \
150 order by id desc limit 1',
151 (context['journal_id'], context['period_id'], uid, 'draft'))
153 move_id = (res and res[0]) or False
158 data['move_id'] = move_id
160 cr.execute('select date \
164 journal_id=%s and period_id=%s and create_uid=%s \
166 (context['journal_id'], context['period_id'], uid))
169 data['date'] = res[0]
171 period = period_obj.browse(cr, uid, context['period_id'],
173 data['date'] = period.date_start
180 move = self.pool.get('account.move').browse(cr, uid, move_id, context)
182 data.setdefault('name', move.line_id[-1].name)
184 for l in move.line_id:
186 partner_id = partner_id or l.partner_id.id
187 ref_id = ref_id or l.ref
188 total += (l.debit or 0.0) - (l.credit or 0.0)
192 if 'partner_id' in fields:
193 data['partner_id'] = partner_id
195 if move.journal_id.type == 'purchase':
197 account = move.journal_id.default_credit_account_id
199 account = move.journal_id.default_debit_account_id
202 account = move.journal_id.default_credit_account_id
204 account = move.journal_id.default_debit_account_id
206 part = partner_id and self.pool.get('res.partner').browse(cr, uid, partner_id) or False
207 # part = False is acceptable for fiscal position.
208 account = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, account.id)
210 account = self.pool.get('account.account').browse(cr, uid, account)
212 if account and ((not fields) or ('debit' in fields) or ('credit' in fields)):
213 data['account_id'] = account.id
214 # Propose the price VAT excluded, the VAT will be added when confirming line
216 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, part and part.property_account_position or False, account.tax_ids)
217 tax = self.pool.get('account.tax').browse(cr, uid, taxes)
218 for t in self.pool.get('account.tax').compute_inv(cr, uid, tax, total, 1):
222 data['debit'] = s>0 and s or 0.0
223 data['credit'] = s<0 and -s or 0.0
225 if account and account.currency_id:
226 data['currency_id'] = account.currency_id.id
230 v = self.pool.get('res.currency').compute(cr, uid,
231 account.company_id.currency_id.id,
233 s, account=acc, account_invert=True)
234 data['amount_currency'] = v
237 def _on_create_write(self, cr, uid, id, context={}):
238 ml = self.browse(cr, uid, id, context)
239 return map(lambda x: x.id, ml.move_id.line_id)
241 def _balance(self, cr, uid, ids, prop, unknow_none, unknow_dict):
243 # TODO group the foreach in sql
245 cr.execute('SELECT date,account_id FROM account_move_line WHERE id=%s', (id,))
246 dt, acc = cr.fetchone()
247 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))
248 res[id] = cr.fetchone()[0]
251 def _invoice(self, cursor, user, ids, name, arg, context=None):
252 invoice_obj = self.pool.get('account.invoice')
256 cursor.execute('SELECT l.id, i.id ' \
257 'FROM account_move_line l, account_invoice i ' \
258 'WHERE l.move_id = i.move_id ' \
259 'AND l.id in (' + ','.join([str(x) for x in ids]) + ')')
261 for line_id, invoice_id in cursor.fetchall():
262 res[line_id] = invoice_id
263 invoice_ids.append(invoice_id)
264 invoice_names = {False: ''}
265 for invoice_id, name in invoice_obj.name_get(cursor, user,
266 invoice_ids, context=context):
267 invoice_names[invoice_id] = name
268 for line_id in res.keys():
269 invoice_id = res[line_id]
270 res[line_id] = (invoice_id, invoice_names[invoice_id])
273 def name_get(self, cr, uid, ids, context={}):
277 for line in self.browse(cr, uid, ids, context):
279 result.append((line.id, (line.name or '')+' ('+line.ref+')'))
281 result.append((line.id, line.name))
284 def _invoice_search(self, cursor, user, obj, name, args):
287 invoice_obj = self.pool.get('account.invoice')
291 fargs = args[i][0].split('.', 1)
293 args[i] = (fargs[0], 'in', invoice_obj.search(cursor, user,
294 [(fargs[1], args[i][1], args[i][2])]))
297 if isinstance(args[i][2], basestring):
298 res_ids = invoice_obj.name_search(cursor, user, args[i][2], [],
300 args[i] = (args[i][0], 'in', [x[0] for x in res_ids])
305 if (x[2] is False) and (x[1] == '='):
306 qu1.append('(i.id IS NULL)')
307 elif (x[2] is False) and (x[1] == '<>' or x[1] == '!='):
308 qu1.append('(i.id IS NOT NULL)')
310 qu1.append('(i.id %s %s)' % (x[1], '%s'))
314 qu1.append('(i.id in (%s))' % (','.join(['%s'] * len(x[2]))))
317 qu1.append(' (False)')
319 qu1 = ' AND' + ' AND'.join(qu1)
322 cursor.execute('SELECT l.id ' \
323 'FROM account_move_line l, account_invoice i ' \
324 'WHERE l.move_id = i.move_id ' + qu1, qu2)
325 res = cursor.fetchall()
327 return [('id', '=', '0')]
328 return [('id', 'in', [x[0] for x in res])]
330 def _get_move_lines(self, cr, uid, ids, context={}):
332 for move in self.pool.get('account.move').browse(cr, uid, ids, context=context):
333 for line in move.line_id:
334 result.append(line.id)
338 'name': fields.char('Name', size=64, required=True),
339 '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."),
340 'product_uom_id': fields.many2one('product.uom', 'UoM'),
341 'product_id': fields.many2one('product.product', 'Product'),
342 'debit': fields.float('Debit', digits=(16,2)),
343 'credit': fields.float('Credit', digits=(16,2)),
344 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade", domain=[('type','<>','view'), ('type', '<>', 'closed')], select=2),
345 'move_id': fields.many2one('account.move', 'Move', ondelete="cascade", states={'valid':[('readonly',True)]}, help="The move of this entry line.", select=2),
347 'ref': fields.char('Ref.', size=32),
348 'statement_id': fields.many2one('account.bank.statement', 'Statement', help="The bank statement used for bank reconciliation", select=1),
349 'reconcile_id': fields.many2one('account.move.reconcile', 'Reconcile', readonly=True, ondelete='set null', select=2),
350 'reconcile_partial_id': fields.many2one('account.move.reconcile', 'Partial Reconcile', readonly=True, ondelete='set null', select=2),
351 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency if it is a multi-currency entry."),
352 'currency_id': fields.many2one('res.currency', 'Currency', help="The optional other currency if it is a multi-currency entry."),
354 'period_id': fields.many2one('account.period', 'Period', required=True, select=2),
355 'journal_id': fields.many2one('account.journal', 'Journal', required=True, select=1),
356 'blocked': fields.boolean('Litigation', help="You can check this box to mark the entry line as a litigation with the associated partner"),
358 'partner_id': fields.many2one('res.partner', 'Partner Ref.'),
359 '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."),
360 'date': fields.related('move_id','date', string='Effective date', type='date', required=True,
362 'account.move': (_get_move_lines, ['date'], 20)
364 'date_created': fields.date('Creation date'),
365 'analytic_lines': fields.one2many('account.analytic.line', 'move_id', 'Analytic lines'),
366 'centralisation': fields.selection([('normal','Normal'),('credit','Credit Centralisation'),('debit','Debit Centralisation')], 'Centralisation', size=6),
367 'balance': fields.function(_balance, method=True, string='Balance'),
368 'state': fields.selection([('draft','Draft'), ('valid','Valid')], 'Status', readonly=True),
369 'tax_code_id': fields.many2one('account.tax.code', 'Tax Account', help="The Account can either be a base tax code or tax code account."),
370 'tax_amount': fields.float('Tax/Base Amount', digits=(16,2), 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,\
371 this field will contain the basic amount(without tax)."),
372 'invoice': fields.function(_invoice, method=True, string='Invoice',
373 type='many2one', relation='account.invoice', fnct_search=_invoice_search),
374 'account_tax_id':fields.many2one('account.tax', 'Tax'),
375 'analytic_account_id' : fields.many2one('account.analytic.account', 'Analytic Account'),
377 'amount_taxed':fields.float("Taxed Amount",digits=(16,2)),
381 def _get_date(self, cr, uid, context):
382 period_obj = self.pool.get('account.period')
383 dt = time.strftime('%Y-%m-%d')
384 if ('journal_id' in context) and ('period_id' in context):
385 cr.execute('select date from account_move_line ' \
386 'where journal_id=%s and period_id=%s ' \
387 'order by id desc limit 1',
388 (context['journal_id'], context['period_id']))
393 period = period_obj.browse(cr, uid, context['period_id'],
395 dt = period.date_start
397 def _get_currency(self, cr, uid, context={}):
398 if not context.get('journal_id', False):
400 cur = self.pool.get('account.journal').browse(cr, uid, context['journal_id']).currency
401 return cur and cur.id or False
404 'blocked': lambda *a: False,
405 'centralisation': lambda *a: 'normal',
407 'date_created': lambda *a: time.strftime('%Y-%m-%d'),
408 'state': lambda *a: 'draft',
409 'currency_id': _get_currency,
410 'journal_id': lambda self, cr, uid, c: c.get('journal_id', False),
411 'period_id': lambda self, cr, uid, c: c.get('period_id', False),
413 _order = "date desc,id desc"
415 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry !'),
416 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'),
419 def _auto_init(self, cr, context={}):
420 super(account_move_line, self)._auto_init(cr, context)
421 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'account_move_line_journal_id_period_id_index\'')
422 if not cr.fetchone():
423 cr.execute('CREATE INDEX account_move_line_journal_id_period_id_index ON account_move_line (journal_id, period_id)')
426 def _check_no_view(self, cr, uid, ids):
427 lines = self.browse(cr, uid, ids)
429 if l.account_id.type == 'view':
433 def _check_no_closed(self, cr, uid, ids):
434 lines = self.browse(cr, uid, ids)
436 if l.account_id.type == 'closed':
441 (_check_no_view, 'You can not create move line on view account.', ['account_id']),
442 (_check_no_closed, 'You can not create move line on closed account.', ['account_id']),
445 #TODO: ONCHANGE_ACCOUNT_ID: set account_tax_id
447 def onchange_currency(self, cr, uid, ids, account_id, amount, currency_id, date=False, journal=False):
448 if (not currency_id) or (not account_id):
451 acc =self.pool.get('account.account').browse(cr, uid, account_id)
452 if (amount>0) and journal:
453 x = self.pool.get('account.journal').browse(cr, uid, journal).default_credit_account_id
455 v = self.pool.get('res.currency').compute(cr, uid, currency_id,acc.company_id.currency_id.id, amount, account=acc)
457 'debit': v>0 and v or 0.0,
458 'credit': v<0 and -v or 0.0
462 def onchange_partner_id(self, cr, uid, ids, move_id, partner_id, account_id=None, debit=0, credit=0, date=False, journal=False):
464 val['date_maturity'] = False
469 date = now().strftime('%Y-%m-%d')
470 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
472 if part.property_payment_term and part.property_payment_term.line_ids:
473 payterm = part.property_payment_term.line_ids[0]
474 res = self.pool.get('account.payment.term').compute(cr, uid, payterm.id, 100, date)
476 val['date_maturity'] = res[0][0]
478 id1 = part.property_account_payable.id
479 id2 = part.property_account_receivable.id
481 jt = self.pool.get('account.journal').browse(cr, uid, journal).type
483 val['account_id'] = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, id2)
486 val['account_id'] = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, id1)
487 if val.get('account_id', False):
488 d = self.onchange_account_id(cr, uid, ids, val['account_id'])
489 val.update(d['value'])
493 def onchange_account_id(self, cr, uid, ids, account_id=False, partner_id=False):
496 res = self.pool.get('account.account').browse(cr, uid, account_id)
497 tax_ids = res.tax_ids
498 if tax_ids and partner_id:
499 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
500 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, part and part.property_account_position or False, tax_ids)[0]
502 tax_id = tax_ids and tax_ids[0].id or False
503 val['account_tax_id'] = tax_id
507 # type: the type if reconciliation (no logic behind this field, for info)
509 # writeoff; entry generated for the difference between the lines
512 def reconcile_partial(self, cr, uid, ids, type='auto', context={}):
517 for line in self.browse(cr, uid, ids, context):
518 if line.reconcile_id:
519 raise osv.except_osv(_('Already Reconciled'), _('Already Reconciled'))
520 if line.reconcile_partial_id:
521 for line2 in line.reconcile_partial_id.line_partial_ids:
522 if not line2.reconcile_id:
523 merges.append(line2.id)
524 total += (line2.debit or 0.0) - (line2.credit or 0.0)
525 merges_rec.append(line.reconcile_partial_id.id)
527 unmerge.append(line.id)
528 total += (line.debit or 0.0) - (line.credit or 0.0)
530 res = self.reconcile(cr, uid, merges+unmerge, context=context)
532 r_id = self.pool.get('account.move.reconcile').create(cr, uid, {
534 'line_partial_ids': map(lambda x: (4,x,False), merges+unmerge)
536 self.pool.get('account.move.reconcile').reconcile_partial_check(cr, uid, [r_id] + merges_rec, context=context)
539 def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context={}):
540 id_set = ','.join(map(str, ids))
542 lines = self.browse(cr, uid, ids, context=context)
543 unrec_lines = filter(lambda x: not x['reconcile_id'], lines)
548 for line in unrec_lines:
549 if line.state <> 'valid':
550 raise osv.except_osv(_('Error'),
551 _('Entry "%s" is not valid !') % line.name)
552 credit += line['credit']
553 debit += line['debit']
554 currency += line['amount_currency'] or 0.0
555 account_id = line['account_id']['id']
556 partner_id = (line['partner_id'] and line['partner_id']['id']) or False
557 writeoff = debit - credit
558 # Ifdate_p in context => take this date
559 if context.has_key('date_p') and context['date_p']:
560 date=context['date_p']
562 date = time.strftime('%Y-%m-%d')
564 cr.execute('SELECT account_id, reconcile_id \
565 FROM account_move_line \
566 WHERE id IN ('+id_set+') \
567 GROUP BY account_id,reconcile_id')
569 #TODO: move this check to a constraint in the account_move_reconcile object
570 if (len(r) != 1) and not context.get('fy_closing', False):
571 raise osv.except_osv(_('Error'), _('Entries are not of the same account or already reconciled ! '))
573 raise osv.except_osv(_('Error'), _('Entry is already reconciled'))
574 account = self.pool.get('account.account').browse(cr, uid, account_id, context=context)
575 if not context.get('fy_closing', False) and not account.reconcile:
576 raise osv.except_osv(_('Error'), _('The account is not defined to be reconciled !'))
578 raise osv.except_osv(_('Error'), _('Some entries are already reconciled !'))
580 if (not self.pool.get('res.currency').is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \
581 (account.currency_id and (not self.pool.get('res.currency').is_zero(cr, uid, account.currency_id, currency))):
582 if not writeoff_acc_id:
583 raise osv.except_osv(_('Warning'), _('You have to provide an account for the write off entry !'))
587 self_credit = writeoff
593 self_debit = -writeoff
595 # If comment exist in context, take it
596 if 'comment' in context and context['comment']:
597 libelle=context['comment']
605 'credit':self_credit,
606 'account_id':account_id,
608 'partner_id':partner_id,
609 'currency_id': account.currency_id.id or False,
610 'amount_currency': account.currency_id.id and -currency or 0.0
616 'account_id':writeoff_acc_id,
618 'partner_id':partner_id
622 writeoff_move_id = self.pool.get('account.move').create(cr, uid, {
623 'period_id': writeoff_period_id,
624 'journal_id': writeoff_journal_id,
627 'line_id': writeoff_lines
630 writeoff_line_ids = self.search(cr, uid, [('move_id', '=', writeoff_move_id), ('account_id', '=', account_id)])
631 ids += writeoff_line_ids
633 r_id = self.pool.get('account.move.reconcile').create(cr, uid, {
636 'line_id': map(lambda x: (4,x,False), ids),
637 'line_partial_ids': map(lambda x: (3,x,False), ids)
639 wf_service = netsvc.LocalService("workflow")
640 # the id of the move.reconcile is written in the move.line (self) by the create method above
641 # because of the way the line_id are defined: (4, x, False)
643 wf_service.trg_trigger(uid, 'account.move.line', id, cr)
646 def view_header_get(self, cr, user, view_id, view_type, context):
647 if context.get('account_id', False):
648 cr.execute('select code from account_account where id=%s', (context['account_id'],))
650 res = _('Entries: ')+ (res[0] or '')
652 if (not context.get('journal_id', False)) or (not context.get('period_id', False)):
654 cr.execute('select code from account_journal where id=%s', (context['journal_id'],))
655 j = cr.fetchone()[0] or ''
656 cr.execute('select code from account_period where id=%s', (context['period_id'],))
657 p = cr.fetchone()[0] or ''
659 return j+(p and (':'+p) or '')
662 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context={}, toolbar=False, submenu=False):
663 result = super(osv.osv, self).fields_view_get(cr, uid, view_id,view_type,context,toolbar=toolbar, submenu=submenu)
664 if view_type=='tree' and 'journal_id' in context:
665 title = self.view_header_get(cr, uid, view_id, view_type, context)
666 journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id'])
668 # if the journal view has a state field, color lines depending on
671 for field in journal.view_id.columns_id:
672 if field.field=='state':
673 state = ' colors="red:state==\'draft\'"'
675 #xml = '''<?xml version="1.0"?>\n<tree string="%s" editable="top" refresh="5"%s>\n\t''' % (title, state)
676 xml = '''<?xml version="1.0"?>\n<tree string="%s" editable="top" refresh="5" on_write="_on_create_write"%s>\n\t''' % (title, state)
686 for field in journal.view_id.columns_id:
687 fields.append(field.field)
689 if field.field=='debit':
690 attrs.append('sum="Total debit"')
691 elif field.field=='credit':
692 attrs.append('sum="Total credit"')
693 elif field.field=='account_tax_id':
694 attrs.append('domain="[(\'parent_id\',\'=\',False)]"')
695 elif field.field=='account_id' and journal.id:
696 attrs.append('domain="[(\'journal_id\', \'=\', '+str(journal.id)+'),(\'type\',\'<>\',\'view\'), (\'type\',\'<>\',\'closed\')]" on_change="onchange_account_id(account_id, partner_id)"')
697 elif field.field == 'partner_id':
698 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 {})"')
700 attrs.append('readonly="1"')
702 attrs.append('required="1"')
704 attrs.append('required="0"')
705 if field.field in ('amount_currency','currency_id'):
706 attrs.append('on_change="onchange_currency(account_id,amount_currency,currency_id,date,((\'journal_id\' in context) and context[\'journal_id\']) or {})"')
708 if field.field in widths:
709 attrs.append('width="'+str(widths[field.field])+'"')
710 xml += '''<field name="%s" %s/>\n''' % (field.field,' '.join(attrs))
714 result['fields'] = self.fields_get(cr, uid, fields, context)
717 def unlink(self, cr, uid, ids, context={}, check=True):
718 self._update_check(cr, uid, ids, context)
720 for line in self.browse(cr, uid, ids, context):
721 context['journal_id']=line.journal_id.id
722 context['period_id']=line.period_id.id
723 result = super(account_move_line, self).unlink(cr, uid, [line.id], context=context)
725 self.pool.get('account.move').validate(cr, uid, [line.move_id.id], context=context)
728 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
731 if vals.get('account_tax_id', False):
732 raise osv.except_osv(_('Unable to change tax !'), _('You can not change the tax, you should remove and recreate lines !'))
734 account_obj = self.pool.get('account.account')
735 if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']:
736 raise osv.except_osv(_('Bad account!'), _('You can not use an inactive account!'))
738 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):
739 self._update_check(cr, uid, ids, context)
742 if vals.get('date', False):
743 todo_date = vals['date']
745 result = super(account_move_line, self).write(cr, uid, ids, vals, context)
749 for line in self.browse(cr, uid, ids):
750 if line.move_id.id not in done:
751 done.append(line.move_id.id)
752 self.pool.get('account.move').validate(cr, uid, [line.move_id.id], context)
754 self.pool.get('account.move').write(cr, uid, [line.move_id.id], {'date': todo_date}, context=context)
757 def _update_journal_check(self, cr, uid, journal_id, period_id, context={}):
758 cr.execute('select state from account_journal_period where journal_id=%s and period_id=%s', (journal_id, period_id))
759 result = cr.fetchall()
760 for (state,) in result:
762 raise osv.except_osv(_('Error !'), _('You can not add/modify entries in a closed journal.'))
764 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context)
765 period = self.pool.get('account.period').browse(cr, uid, period_id, context)
766 self.pool.get('account.journal.period').create(cr, uid, {
767 'name': (journal.code or journal.name)+':'+(period.name or ''),
768 'journal_id': journal.id,
769 'period_id': period.id
773 def _update_check(self, cr, uid, ids, context={}):
775 for line in self.browse(cr, uid, ids, context):
776 if line.move_id.state<>'draft':
777 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 !'))
778 if line.reconcile_id:
779 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 !'))
780 t = (line.journal_id.id, line.period_id.id)
782 self._update_journal_check(cr, uid, line.journal_id.id, line.period_id.id, context)
786 def create(self, cr, uid, vals, context=None, check=True):
789 account_obj = self.pool.get('account.account')
790 tax_obj=self.pool.get('account.tax')
791 if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']:
792 raise osv.except_osv(_('Bad account!'), _('You can not use an inactive account!'))
793 if 'journal_id' in vals and 'journal_id' not in context:
794 context['journal_id'] = vals['journal_id']
795 if 'period_id' in vals and 'period_id' not in context:
796 context['period_id'] = vals['period_id']
797 if ('journal_id' not in context) and ('move_id' in vals) and vals['move_id']:
798 m = self.pool.get('account.move').browse(cr, uid, vals['move_id'])
799 context['journal_id'] = m.journal_id.id
800 context['period_id'] = m.period_id.id
802 self._update_journal_check(cr, uid, context['journal_id'], context['period_id'], context)
803 company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
805 move_id = vals.get('move_id', False)
806 journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id'])
809 if journal.centralisation:
810 # use the first move ever created for this journal and period
811 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']))
814 if res[1] != 'draft':
815 raise osv.except_osv(_('UserError'),
816 _('The account move (%s) for centralisation ' \
817 'has been confirmed!') % res[2])
818 vals['move_id'] = res[0]
820 if not vals.get('move_id', False):
821 if journal.sequence_id:
822 #name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
824 'date': vals.get('date', time.strftime('%Y-%m-%d')),
825 'period_id': context['period_id'],
826 'journal_id': context['journal_id']
828 move_id = self.pool.get('account.move').create(cr, uid, v, context)
829 vals['move_id'] = move_id
831 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.'))
834 ok = not (journal.type_control_ids or journal.account_control_ids)
835 if ('account_id' in vals):
836 account = account_obj.browse(cr, uid, vals['account_id'])
837 if journal.type_control_ids:
838 type = account.user_type
839 for t in journal.type_control_ids:
840 if type.code == t.code:
843 if journal.account_control_ids and not ok:
844 for a in journal.account_control_ids:
845 if a.id==vals['account_id']:
848 if (account.currency_id) and 'amount_currency' not in vals and account.currency_id.id <> company_currency:
849 vals['currency_id'] = account.currency_id.id
850 cur_obj = self.pool.get('res.currency')
853 ctx['date'] = vals['date']
854 vals['amount_currency'] = cur_obj.compute(cr, uid, account.company_id.currency_id.id,
855 account.currency_id.id, vals.get('debit', 0.0)-vals.get('credit', 0.0),
858 raise osv.except_osv(_('Bad account !'), _('You can not use this general account in this journal !'))
860 if 'analytic_account_id' in vals and vals['analytic_account_id']:
861 if journal.analytic_journal_id:
862 vals['analytic_lines'] = [(0,0, {
863 'name': vals['name'],
864 'date': vals.get('date', time.strftime('%Y-%m-%d')),
865 'account_id': vals['analytic_account_id'],
866 'unit_amount':'quantity' in vals and vals['quantity'] or 1.0,
867 'amount': vals['debit'] or vals['credit'],
868 'general_account_id': vals['account_id'],
869 'journal_id': journal.analytic_journal_id.id,
873 # raise osv.except_osv(_('No analytic journal !'), _('Please set an analytic journal on this financial journal !'))
875 #if not 'currency_id' in vals:
876 # vals['currency_id'] = account.company_id.currency_id.id
878 result = super(osv.osv, self).create(cr, uid, vals, context)
880 if 'account_tax_id' in vals and vals['account_tax_id']:
881 tax_id=tax_obj.browse(cr,uid,vals['account_tax_id'])
882 total = vals['debit'] - vals['credit']
883 if journal.refund_journal:
884 base_code = 'ref_base_code_id'
885 tax_code = 'ref_tax_code_id'
886 account_id = 'account_paid_id'
887 base_sign = 'ref_base_sign'
888 tax_sign = 'ref_tax_sign'
890 base_code = 'base_code_id'
891 tax_code = 'tax_code_id'
892 account_id = 'account_collected_id'
893 base_sign = 'base_sign'
894 tax_sign = 'tax_sign'
897 for tax in tax_obj.compute(cr,uid,[tax_id],total,1.00):
898 #create the base movement
902 self.write(cr, uid,[result], {
903 'tax_code_id': tax[base_code],
904 'tax_amount': tax[base_sign] * abs(total)
908 'move_id': vals['move_id'],
909 'journal_id': vals['journal_id'],
910 'period_id': vals['period_id'],
911 'name': vals['name']+' '+tax['name'],
912 'date': vals['date'],
913 'partner_id': vals.get('partner_id',False),
914 'ref': vals.get('ref',False),
915 'account_tax_id': False,
916 'tax_code_id': tax[base_code],
917 'tax_amount': tax[base_sign] * abs(total),
918 'account_id': vals['account_id'],
922 if data['tax_code_id']:
923 self.create(cr, uid, data, context)
925 #create the VAT movement
927 'move_id': vals['move_id'],
928 'journal_id': vals['journal_id'],
929 'period_id': vals['period_id'],
930 'name': vals['name']+' '+tax['name'],
931 'date': vals['date'],
932 'partner_id': vals.get('partner_id',False),
933 'ref': vals.get('ref',False),
934 'account_tax_id': False,
935 'tax_code_id': tax[tax_code],
936 'tax_amount': tax[tax_sign] * abs(tax['amount']),
937 'account_id': tax[account_id] or vals['account_id'],
938 'credit': tax['amount']<0 and -tax['amount'] or 0.0,
939 'debit': tax['amount']>0 and tax['amount'] or 0.0,
941 if data['tax_code_id']:
942 self.create(cr, uid, data, context)
943 del vals['account_tax_id']
945 # No needed, related to the job
946 #if not is_new_move and 'date' in vals:
947 # if context and ('__last_update' in context):
948 # del context['__last_update']
949 # self.pool.get('account.move').write(cr, uid, [move_id], {'date':vals['date']}, context=context)
950 if check and not context.get('no_store_function'):
951 tmp = self.pool.get('account.move').validate(cr, uid, [vals['move_id']], context)
952 if journal.entry_posted and tmp:
953 self.pool.get('account.move').button_validate(cr,uid, [vals['move_id']],context)
958 class account_bank_statement_reconcile(osv.osv):
959 _inherit = "account.bank.statement.reconcile"
961 'line_ids': fields.many2many('account.move.line', 'account_bank_statement_line_rel', 'statement_id', 'line_id', 'Entries'),
963 account_bank_statement_reconcile()
965 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: