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
33 class account_move_line(osv.osv):
34 _name = "account.move.line"
35 _description = "Entry lines"
37 def _query_get(self, cr, uid, obj='l', context={}):
38 fiscalyear_obj = self.pool.get('account.fiscalyear')
39 if not context.get('fiscalyear', False):
40 fiscalyear_ids = fiscalyear_obj.search(cr, uid, [('state', '=', 'draft')])
41 fiscalyear_clause = (','.join([str(x) for x in fiscalyear_ids])) or '0'
43 fiscalyear_clause = '%s' % context['fiscalyear']
44 state=context.get('state',False)
46 where_move_lines_by_date = ''
48 if context.get('date_from', False) and context.get('date_to', False):
49 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']+"')"
52 if state.lower() not in ['all']:
53 where_move_state= " AND "+obj+".move_id in (select id from account_move where account_move.state = '"+state+"')"
56 if context.get('periods', False):
57 ids = ','.join([str(x) for x in context['periods']])
58 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)
60 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)
62 def default_get(self, cr, uid, fields, context={}):
63 data = self._default_get(cr, uid, fields, context)
69 def create_analytic_lines(self, cr, uid, ids, context={}):
70 for obj_line in self.browse(cr, uid, ids, context):
71 if obj_line.analytic_account_id:
72 if not obj_line.journal_id.analytic_journal_id:
73 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (obj_line.journal_id.name,))
74 amt = (obj_line.credit or 0.0) - (obj_line.debit or 0.0)
76 'name': obj_line.name,
77 'date': obj_line.date,
78 'account_id': obj_line.analytic_account_id.id,
79 'unit_amount':obj_line.quantity,
80 'product_id': obj_line.product_id and obj_line.product_id.id or False,
81 'product_uom_id': obj_line.product_uom_id and obj_line.product_uom_id.id or False,
83 'general_account_id': obj_line.account_id.id,
84 'journal_id': obj_line.journal_id.analytic_journal_id.id,
88 new_id = self.pool.get('account.analytic.line').create(cr,uid,vals_lines)
91 def _default_get_move_form_hook(self, cursor, user, data):
92 '''Called in the end of default_get method for manual entry in account_move form'''
93 if data.has_key('analytic_account_id'):
94 del(data['analytic_account_id'])
95 if data.has_key('account_tax_id'):
96 del(data['account_tax_id'])
99 def _default_get(self, cr, uid, fields, context={}):
100 # Compute simple values
101 data = super(account_move_line, self).default_get(cr, uid, fields, context)
102 # Starts: Manual entry from account.move form
103 if context.get('lines',[]):
106 for i in context['lines']:
107 total_new +=(i[2]['debit'] or 0.00)- (i[2]['credit'] or 0.00)
109 data[item]=i[2][item]
110 if context['journal']:
111 journal_obj=self.pool.get('account.journal').browse(cr,uid,context['journal'])
112 if journal_obj.type == 'purchase':
114 account = journal_obj.default_credit_account_id
116 account = journal_obj.default_debit_account_id
119 account = journal_obj.default_credit_account_id
121 account = journal_obj.default_debit_account_id
124 if account and ((not fields) or ('debit' in fields) or ('credit' in fields)) and 'partner_id' in data and (data['partner_id']):
125 part = self.pool.get('res.partner').browse(cr, uid, data['partner_id'])
126 account = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, account.id)
127 account = self.pool.get('account.account').browse(cr, uid, account)
128 data['account_id'] = account.id
131 data['debit'] = s>0 and s or 0.0
132 data['credit'] = s<0 and -s or 0.0
133 data = self._default_get_move_form_hook(cr, uid, data)
135 # Ends: Manual entry from account.move form
137 if not 'move_id' in fields: #we are not in manual entry
140 period_obj = self.pool.get('account.period')
142 # Compute the current move
145 if context.get('journal_id',False) and context.get('period_id',False):
146 if 'move_id' in fields:
147 cr.execute('select move_id \
151 journal_id=%s and period_id=%s and create_uid=%s and state=%s \
152 order by id desc limit 1',
153 (context['journal_id'], context['period_id'], uid, 'draft'))
155 move_id = (res and res[0]) or False
160 data['move_id'] = move_id
162 cr.execute('select date \
166 journal_id=%s and period_id=%s and create_uid=%s \
168 (context['journal_id'], context['period_id'], uid))
171 data['date'] = res[0]
173 period = period_obj.browse(cr, uid, context['period_id'],
175 data['date'] = period.date_start
182 move = self.pool.get('account.move').browse(cr, uid, move_id, context)
184 data.setdefault('name', move.line_id[-1].name)
186 for l in move.line_id:
188 partner_id = partner_id or l.partner_id.id
189 ref_id = ref_id or l.ref
190 total += (l.debit or 0.0) - (l.credit or 0.0)
194 if 'partner_id' in fields:
195 data['partner_id'] = partner_id
197 if move.journal_id.type == 'purchase':
199 account = move.journal_id.default_credit_account_id
201 account = move.journal_id.default_debit_account_id
204 account = move.journal_id.default_credit_account_id
206 account = move.journal_id.default_debit_account_id
208 part = partner_id and self.pool.get('res.partner').browse(cr, uid, partner_id) or False
209 # part = False is acceptable for fiscal position.
210 account = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, account.id)
212 account = self.pool.get('account.account').browse(cr, uid, account)
214 if account and ((not fields) or ('debit' in fields) or ('credit' in fields)):
215 data['account_id'] = account.id
216 # Propose the price VAT excluded, the VAT will be added when confirming line
218 taxes = self.pool.get('account.fiscal.position').map_tax(cr, uid, part and part.property_account_position or False, account.tax_ids)
219 tax = self.pool.get('account.tax').browse(cr, uid, taxes)
220 for t in self.pool.get('account.tax').compute_inv(cr, uid, tax, total, 1):
224 data['debit'] = s>0 and s or 0.0
225 data['credit'] = s<0 and -s or 0.0
227 if account and account.currency_id:
228 data['currency_id'] = account.currency_id.id
232 v = self.pool.get('res.currency').compute(cr, uid,
233 account.company_id.currency_id.id,
235 s, account=acc, account_invert=True)
236 data['amount_currency'] = v
239 def _on_create_write(self, cr, uid, id, context={}):
240 ml = self.browse(cr, uid, id, context)
241 return map(lambda x: x.id, ml.move_id.line_id)
243 def _balance(self, cr, uid, ids, prop, unknow_none, unknow_dict):
245 # TODO group the foreach in sql
247 cr.execute('SELECT date,account_id FROM account_move_line WHERE id=%s', (id,))
248 dt, acc = cr.fetchone()
249 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))
250 res[id] = cr.fetchone()[0]
253 def _invoice(self, cursor, user, ids, name, arg, context=None):
254 invoice_obj = self.pool.get('account.invoice')
258 cursor.execute('SELECT l.id, i.id ' \
259 'FROM account_move_line l, account_invoice i ' \
260 'WHERE l.move_id = i.move_id ' \
261 'AND l.id in (' + ','.join([str(x) for x in ids]) + ')')
263 for line_id, invoice_id in cursor.fetchall():
264 res[line_id] = invoice_id
265 invoice_ids.append(invoice_id)
266 invoice_names = {False: ''}
267 for invoice_id, name in invoice_obj.name_get(cursor, user,
268 invoice_ids, context=context):
269 invoice_names[invoice_id] = name
270 for line_id in res.keys():
271 invoice_id = res[line_id]
272 res[line_id] = (invoice_id, invoice_names[invoice_id])
275 def name_get(self, cr, uid, ids, context={}):
279 for line in self.browse(cr, uid, ids, context):
281 result.append((line.id, (line.name or '')+' ('+line.ref+')'))
283 result.append((line.id, line.name))
286 def _invoice_search(self, cursor, user, obj, name, args):
289 invoice_obj = self.pool.get('account.invoice')
293 fargs = args[i][0].split('.', 1)
295 args[i] = (fargs[0], 'in', invoice_obj.search(cursor, user,
296 [(fargs[1], args[i][1], args[i][2])]))
299 if isinstance(args[i][2], basestring):
300 res_ids = invoice_obj.name_search(cursor, user, args[i][2], [],
302 args[i] = (args[i][0], 'in', [x[0] for x in res_ids])
307 if (x[2] is False) and (x[1] == '='):
308 qu1.append('(i.id IS NULL)')
309 elif (x[2] is False) and (x[1] == '<>' or x[1] == '!='):
310 qu1.append('(i.id IS NOT NULL)')
312 qu1.append('(i.id %s %s)' % (x[1], '%s'))
316 qu1.append('(i.id in (%s))' % (','.join(['%s'] * len(x[2]))))
319 qu1.append(' (False)')
321 qu1 = ' AND' + ' AND'.join(qu1)
324 cursor.execute('SELECT l.id ' \
325 'FROM account_move_line l, account_invoice i ' \
326 'WHERE l.move_id = i.move_id ' + qu1, qu2)
327 res = cursor.fetchall()
329 return [('id', '=', '0')]
330 return [('id', 'in', [x[0] for x in res])]
332 def _get_move_lines(self, cr, uid, ids, context={}):
334 for move in self.pool.get('account.move').browse(cr, uid, ids, context=context):
335 for line in move.line_id:
336 result.append(line.id)
340 'name': fields.char('Name', size=64, required=True),
341 '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."),
342 'product_uom_id': fields.many2one('product.uom', 'UoM'),
343 'product_id': fields.many2one('product.product', 'Product'),
344 'debit': fields.float('Debit', digits=(16,2)),
345 'credit': fields.float('Credit', digits=(16,2)),
346 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade", domain=[('type','<>','view'), ('type', '<>', 'closed')], select=2),
347 'move_id': fields.many2one('account.move', 'Move', ondelete="cascade", states={'valid':[('readonly',True)]}, help="The move of this entry line.", select=2),
349 'ref': fields.char('Ref.', size=32),
350 'statement_id': fields.many2one('account.bank.statement', 'Statement', help="The bank statement used for bank reconciliation", select=1),
351 'reconcile_id': fields.many2one('account.move.reconcile', 'Reconcile', readonly=True, ondelete='set null', select=2),
352 'reconcile_partial_id': fields.many2one('account.move.reconcile', 'Partial Reconcile', readonly=True, ondelete='set null', select=2),
353 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency if it is a multi-currency entry."),
354 'currency_id': fields.many2one('res.currency', 'Currency', help="The optional other currency if it is a multi-currency entry."),
356 'period_id': fields.many2one('account.period', 'Period', required=True, select=2),
357 'journal_id': fields.many2one('account.journal', 'Journal', required=True, select=1),
358 'blocked': fields.boolean('Litigation', help="You can check this box to mark the entry line as a litigation with the associated partner"),
360 'partner_id': fields.many2one('res.partner', 'Partner Ref.'),
361 '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."),
362 'date': fields.related('move_id','date', string='Effective date', type='date', required=True,
364 'account.move': (_get_move_lines, ['date'], 20)
366 'date_created': fields.date('Creation date'),
367 'analytic_lines': fields.one2many('account.analytic.line', 'move_id', 'Analytic lines'),
368 'centralisation': fields.selection([('normal','Normal'),('credit','Credit Centralisation'),('debit','Debit Centralisation')], 'Centralisation', size=6),
369 'balance': fields.function(_balance, method=True, string='Balance'),
370 'state': fields.selection([('draft','Draft'), ('valid','Valid')], 'Status', readonly=True),
371 'tax_code_id': fields.many2one('account.tax.code', 'Tax Account', help="The Account can either be a base tax code or tax code account."),
372 '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,\
373 this field will contain the basic amount(without tax)."),
374 'invoice': fields.function(_invoice, method=True, string='Invoice',
375 type='many2one', relation='account.invoice', fnct_search=_invoice_search),
376 'account_tax_id':fields.many2one('account.tax', 'Tax'),
377 'analytic_account_id' : fields.many2one('account.analytic.account', 'Analytic Account'),
379 'amount_taxed':fields.float("Taxed Amount",digits=(16,2)),
383 def _get_date(self, cr, uid, context):
384 period_obj = self.pool.get('account.period')
385 dt = time.strftime('%Y-%m-%d')
386 if ('journal_id' in context) and ('period_id' in context):
387 cr.execute('select date from account_move_line ' \
388 'where journal_id=%s and period_id=%s ' \
389 'order by id desc limit 1',
390 (context['journal_id'], context['period_id']))
395 period = period_obj.browse(cr, uid, context['period_id'],
397 dt = period.date_start
399 def _get_currency(self, cr, uid, context={}):
400 if not context.get('journal_id', False):
402 cur = self.pool.get('account.journal').browse(cr, uid, context['journal_id']).currency
403 return cur and cur.id or False
406 'blocked': lambda *a: False,
407 'centralisation': lambda *a: 'normal',
409 'date_created': lambda *a: time.strftime('%Y-%m-%d'),
410 'state': lambda *a: 'draft',
411 'currency_id': _get_currency,
412 'journal_id': lambda self, cr, uid, c: c.get('journal_id', False),
413 'period_id': lambda self, cr, uid, c: c.get('period_id', False),
415 _order = "date desc,id desc"
417 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry !'),
418 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'),
421 def _auto_init(self, cr, context={}):
422 super(account_move_line, self)._auto_init(cr, context)
423 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'account_move_line_journal_id_period_id_index\'')
424 if not cr.fetchone():
425 cr.execute('CREATE INDEX account_move_line_journal_id_period_id_index ON account_move_line (journal_id, period_id)')
428 def _check_no_view(self, cr, uid, ids):
429 lines = self.browse(cr, uid, ids)
431 if l.account_id.type == 'view':
435 def _check_no_closed(self, cr, uid, ids):
436 lines = self.browse(cr, uid, ids)
438 if l.account_id.type == 'closed':
443 (_check_no_view, 'You can not create move line on view account.', ['account_id']),
444 (_check_no_closed, 'You can not create move line on closed account.', ['account_id']),
447 #TODO: ONCHANGE_ACCOUNT_ID: set account_tax_id
449 def onchange_currency(self, cr, uid, ids, account_id, amount, currency_id, date=False, journal=False):
450 if (not currency_id) or (not account_id):
453 acc =self.pool.get('account.account').browse(cr, uid, account_id)
454 if (amount>0) and journal:
455 x = self.pool.get('account.journal').browse(cr, uid, journal).default_credit_account_id
457 v = self.pool.get('res.currency').compute(cr, uid, currency_id,acc.company_id.currency_id.id, amount, account=acc)
459 'debit': v>0 and v or 0.0,
460 'credit': v<0 and -v or 0.0
464 def onchange_partner_id(self, cr, uid, ids, move_id, partner_id, account_id=None, debit=0, credit=0, date=False, journal=False):
466 val['date_maturity'] = False
471 date = now().strftime('%Y-%m-%d')
472 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
474 if part.property_payment_term and part.property_payment_term.line_ids:
475 payterm = part.property_payment_term.line_ids[0]
476 res = self.pool.get('account.payment.term').compute(cr, uid, payterm.id, 100, date)
478 val['date_maturity'] = res[0][0]
480 id1 = part.property_account_payable.id
481 id2 = part.property_account_receivable.id
483 jt = self.pool.get('account.journal').browse(cr, uid, journal).type
485 val['account_id'] = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, id2)
488 val['account_id'] = self.pool.get('account.fiscal.position').map_account(cr, uid, part and part.property_account_position or False, id1)
489 if val.get('account_id', False):
490 d = self.onchange_account_id(cr, uid, ids, val['account_id'])
491 val.update(d['value'])
495 def onchange_account_id(self, cr, uid, ids, account_id=False, partner_id=False):
498 res = self.pool.get('account.account').browse(cr, uid, account_id)
499 tax_ids = res.tax_ids
500 if tax_ids and partner_id:
501 part = self.pool.get('res.partner').browse(cr, uid, partner_id)
502 tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, part and part.property_account_position or False, tax_ids)[0]
504 tax_id = tax_ids and tax_ids[0].id or False
505 val['account_tax_id'] = tax_id
509 # type: the type if reconciliation (no logic behind this field, for info)
511 # writeoff; entry generated for the difference between the lines
514 def reconcile_partial(self, cr, uid, ids, type='auto', context={}):
519 for line in self.browse(cr, uid, ids, context):
520 if line.reconcile_id:
521 raise osv.except_osv(_('Already Reconciled'), _('Already Reconciled'))
522 if line.reconcile_partial_id:
523 for line2 in line.reconcile_partial_id.line_partial_ids:
524 if not line2.reconcile_id:
525 merges.append(line2.id)
526 total += (line2.debit or 0.0) - (line2.credit or 0.0)
527 merges_rec.append(line.reconcile_partial_id.id)
529 unmerge.append(line.id)
530 total += (line.debit or 0.0) - (line.credit or 0.0)
532 res = self.reconcile(cr, uid, merges+unmerge, context=context)
534 r_id = self.pool.get('account.move.reconcile').create(cr, uid, {
536 'line_partial_ids': map(lambda x: (4,x,False), merges+unmerge)
538 self.pool.get('account.move.reconcile').reconcile_partial_check(cr, uid, [r_id] + merges_rec, context=context)
541 def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context={}):
542 id_set = ','.join(map(str, ids))
544 lines = self.browse(cr, uid, ids, context=context)
545 unrec_lines = filter(lambda x: not x['reconcile_id'], lines)
550 for line in unrec_lines:
551 if line.state <> 'valid':
552 raise osv.except_osv(_('Error'),
553 _('Entry "%s" is not valid !') % line.name)
554 credit += line['credit']
555 debit += line['debit']
556 currency += line['amount_currency'] or 0.0
557 account_id = line['account_id']['id']
558 partner_id = (line['partner_id'] and line['partner_id']['id']) or False
559 writeoff = debit - credit
560 # Ifdate_p in context => take this date
561 if context.has_key('date_p') and context['date_p']:
562 date=context['date_p']
564 date = time.strftime('%Y-%m-%d')
566 cr.execute('SELECT account_id, reconcile_id \
567 FROM account_move_line \
568 WHERE id IN ('+id_set+') \
569 GROUP BY account_id,reconcile_id')
571 #TODO: move this check to a constraint in the account_move_reconcile object
572 if (len(r) != 1) and not context.get('fy_closing', False):
573 raise osv.except_osv(_('Error'), _('Entries are not of the same account or already reconciled ! '))
575 raise osv.except_osv(_('Error'), _('Entry is already reconciled'))
576 account = self.pool.get('account.account').browse(cr, uid, account_id, context=context)
577 if not context.get('fy_closing', False) and not account.reconcile:
578 raise osv.except_osv(_('Error'), _('The account is not defined to be reconciled !'))
580 raise osv.except_osv(_('Error'), _('Some entries are already reconciled !'))
582 if (not self.pool.get('res.currency').is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \
583 (account.currency_id and (not self.pool.get('res.currency').is_zero(cr, uid, account.currency_id, currency))):
584 if not writeoff_acc_id:
585 raise osv.except_osv(_('Warning'), _('You have to provide an account for the write off entry !'))
589 self_credit = writeoff
595 self_debit = -writeoff
597 # If comment exist in context, take it
598 if 'comment' in context and context['comment']:
599 libelle=context['comment']
607 'credit':self_credit,
608 'account_id':account_id,
610 'partner_id':partner_id,
611 'currency_id': account.currency_id.id or False,
612 'amount_currency': account.currency_id.id and -currency or 0.0
618 'account_id':writeoff_acc_id,
619 'analytic_account_id': context.get('analytic_id', False),
621 'partner_id':partner_id
625 writeoff_move_id = self.pool.get('account.move').create(cr, uid, {
626 'period_id': writeoff_period_id,
627 'journal_id': writeoff_journal_id,
630 'line_id': writeoff_lines
633 writeoff_line_ids = self.search(cr, uid, [('move_id', '=', writeoff_move_id), ('account_id', '=', account_id)])
634 ids += writeoff_line_ids
636 r_id = self.pool.get('account.move.reconcile').create(cr, uid, {
639 'line_id': map(lambda x: (4,x,False), ids),
640 'line_partial_ids': map(lambda x: (3,x,False), ids)
642 wf_service = netsvc.LocalService("workflow")
643 # the id of the move.reconcile is written in the move.line (self) by the create method above
644 # because of the way the line_id are defined: (4, x, False)
646 wf_service.trg_trigger(uid, 'account.move.line', id, cr)
649 def view_header_get(self, cr, user, view_id, view_type, context):
650 if context.get('account_id', False):
651 cr.execute('select code from account_account where id=%s', (context['account_id'],))
653 res = _('Entries: ')+ (res[0] or '')
655 if (not context.get('journal_id', False)) or (not context.get('period_id', False)):
657 cr.execute('select code from account_journal where id=%s', (context['journal_id'],))
658 j = cr.fetchone()[0] or ''
659 cr.execute('select code from account_period where id=%s', (context['period_id'],))
660 p = cr.fetchone()[0] or ''
662 return j+(p and (':'+p) or '')
665 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context={}, toolbar=False):
666 result = super(osv.osv, self).fields_view_get(cr, uid, view_id,view_type,context,toolbar=toolbar)
667 if view_type=='tree' and 'journal_id' in context:
668 title = self.view_header_get(cr, uid, view_id, view_type, context)
669 journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id'])
671 # if the journal view has a state field, color lines depending on
674 for field in journal.view_id.columns_id:
675 if field.field=='state':
676 state = ' colors="red:state==\'draft\'"'
678 #xml = '''<?xml version="1.0"?>\n<tree string="%s" editable="top" refresh="5"%s>\n\t''' % (title, state)
679 xml = '''<?xml version="1.0"?>\n<tree string="%s" editable="top" refresh="5" on_write="_on_create_write"%s>\n\t''' % (title, state)
689 for field in journal.view_id.columns_id:
690 fields.append(field.field)
692 if field.field=='debit':
693 attrs.append('sum="Total debit"')
694 elif field.field=='credit':
695 attrs.append('sum="Total credit"')
696 elif field.field=='account_tax_id':
697 attrs.append('domain="[(\'parent_id\',\'=\',False)]"')
698 elif field.field=='account_id' and journal.id:
699 attrs.append('domain="[(\'journal_id\', \'=\', '+str(journal.id)+'),(\'type\',\'<>\',\'view\'), (\'type\',\'<>\',\'closed\')]" on_change="onchange_account_id(account_id, partner_id)"')
700 elif field.field == 'partner_id':
701 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 {})"')
703 attrs.append('readonly="1"')
705 attrs.append('required="1"')
707 attrs.append('required="0"')
708 if field.field in ('amount_currency','currency_id'):
709 attrs.append('on_change="onchange_currency(account_id,amount_currency,currency_id,date,((\'journal_id\' in context) and context[\'journal_id\']) or {})"')
711 if field.field in widths:
712 attrs.append('width="'+str(widths[field.field])+'"')
713 xml += '''<field name="%s" %s/>\n''' % (field.field,' '.join(attrs))
717 result['fields'] = self.fields_get(cr, uid, fields, context)
720 def unlink(self, cr, uid, ids, context={}, check=True):
721 self._update_check(cr, uid, ids, context)
723 for line in self.browse(cr, uid, ids, context):
724 context['journal_id']=line.journal_id.id
725 context['period_id']=line.period_id.id
726 result = super(account_move_line, self).unlink(cr, uid, [line.id], context=context)
728 self.pool.get('account.move').validate(cr, uid, [line.move_id.id], context=context)
731 def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
734 if vals.get('account_tax_id', False):
735 raise osv.except_osv(_('Unable to change tax !'), _('You can not change the tax, you should remove and recreate lines !'))
737 account_obj = self.pool.get('account.account')
738 if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']:
739 raise osv.except_osv(_('Bad account!'), _('You can not use an inactive account!'))
741 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):
742 self._update_check(cr, uid, ids, context)
745 if vals.get('date', False):
746 todo_date = vals['date']
748 result = super(account_move_line, self).write(cr, uid, ids, vals, context)
752 for line in self.browse(cr, uid, ids):
753 if line.move_id.id not in done:
754 done.append(line.move_id.id)
755 self.pool.get('account.move').validate(cr, uid, [line.move_id.id], context)
757 self.pool.get('account.move').write(cr, uid, [line.move_id.id], {'date': todo_date}, context=context)
760 def _update_journal_check(self, cr, uid, journal_id, period_id, context={}):
761 cr.execute('select state from account_journal_period where journal_id=%s and period_id=%s', (journal_id, period_id))
762 result = cr.fetchall()
763 for (state,) in result:
765 raise osv.except_osv(_('Error !'), _('You can not add/modify entries in a closed journal.'))
767 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context)
768 period = self.pool.get('account.period').browse(cr, uid, period_id, context)
769 self.pool.get('account.journal.period').create(cr, uid, {
770 'name': (journal.code or journal.name)+':'+(period.name or ''),
771 'journal_id': journal.id,
772 'period_id': period.id
776 def _update_check(self, cr, uid, ids, context={}):
778 for line in self.browse(cr, uid, ids, context):
779 if line.move_id.state<>'draft':
780 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 !'))
781 if line.reconcile_id:
782 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 !'))
783 t = (line.journal_id.id, line.period_id.id)
785 self._update_journal_check(cr, uid, line.journal_id.id, line.period_id.id, context)
789 def create(self, cr, uid, vals, context=None, check=True):
792 account_obj = self.pool.get('account.account')
793 tax_obj=self.pool.get('account.tax')
794 if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']:
795 raise osv.except_osv(_('Bad account!'), _('You can not use an inactive account!'))
796 if 'journal_id' in vals and 'journal_id' not in context:
797 context['journal_id'] = vals['journal_id']
798 if 'period_id' in vals and 'period_id' not in context:
799 context['period_id'] = vals['period_id']
800 if ('journal_id' not in context) and ('move_id' in vals) and vals['move_id']:
801 m = self.pool.get('account.move').browse(cr, uid, vals['move_id'])
802 context['journal_id'] = m.journal_id.id
803 context['period_id'] = m.period_id.id
805 self._update_journal_check(cr, uid, context['journal_id'], context['period_id'], context)
806 company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
808 move_id = vals.get('move_id', False)
809 journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id'])
812 if journal.centralisation:
813 # use the first move ever created for this journal and period
814 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']))
817 if res[1] != 'draft':
818 raise osv.except_osv(_('UserError'),
819 _('The account move (%s) for centralisation ' \
820 'has been confirmed!') % res[2])
821 vals['move_id'] = res[0]
823 if not vals.get('move_id', False):
824 if journal.sequence_id:
825 #name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
827 'date': vals.get('date', time.strftime('%Y-%m-%d')),
828 'period_id': context['period_id'],
829 'journal_id': context['journal_id']
831 move_id = self.pool.get('account.move').create(cr, uid, v, context)
832 vals['move_id'] = move_id
834 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.'))
837 ok = not (journal.type_control_ids or journal.account_control_ids)
838 if ('account_id' in vals):
839 account = account_obj.browse(cr, uid, vals['account_id'])
840 if journal.type_control_ids:
841 type = account.user_type
842 for t in journal.type_control_ids:
843 if type.code == t.code:
846 if journal.account_control_ids and not ok:
847 for a in journal.account_control_ids:
848 if a.id==vals['account_id']:
851 if (account.currency_id) and 'amount_currency' not in vals and account.currency_id.id <> company_currency:
852 vals['currency_id'] = account.currency_id.id
853 cur_obj = self.pool.get('res.currency')
856 ctx['date'] = vals['date']
857 vals['amount_currency'] = cur_obj.compute(cr, uid, account.company_id.currency_id.id,
858 account.currency_id.id, vals.get('debit', 0.0)-vals.get('credit', 0.0),
861 raise osv.except_osv(_('Bad account !'), _('You can not use this general account in this journal !'))
863 if 'analytic_account_id' in vals and vals['analytic_account_id']:
864 if journal.analytic_journal_id:
865 vals['analytic_lines'] = [(0,0, {
866 'name': vals['name'],
867 'date': vals.get('date', time.strftime('%Y-%m-%d')),
868 'account_id': vals['analytic_account_id'],
869 'unit_amount':'quantity' in vals and vals['quantity'] or 1.0,
870 'amount': vals['debit'] or vals['credit'],
871 'general_account_id': vals['account_id'],
872 'journal_id': journal.analytic_journal_id.id,
873 'ref': vals.get('ref', False),
876 # raise osv.except_osv(_('No analytic journal !'), _('Please set an analytic journal on this financial journal !'))
878 #if not 'currency_id' in vals:
879 # vals['currency_id'] = account.company_id.currency_id.id
881 result = super(osv.osv, self).create(cr, uid, vals, context)
883 if 'account_tax_id' in vals and vals['account_tax_id']:
884 tax_id=tax_obj.browse(cr,uid,vals['account_tax_id'])
885 total = vals['debit'] - vals['credit']
886 if journal.refund_journal:
887 base_code = 'ref_base_code_id'
888 tax_code = 'ref_tax_code_id'
889 account_id = 'account_paid_id'
890 base_sign = 'ref_base_sign'
891 tax_sign = 'ref_tax_sign'
893 base_code = 'base_code_id'
894 tax_code = 'tax_code_id'
895 account_id = 'account_collected_id'
896 base_sign = 'base_sign'
897 tax_sign = 'tax_sign'
900 for tax in tax_obj.compute(cr,uid,[tax_id],total,1.00):
901 #create the base movement
905 self.write(cr, uid,[result], {
906 'tax_code_id': tax[base_code],
907 'tax_amount': tax[base_sign] * abs(total)
911 'move_id': vals['move_id'],
912 'journal_id': vals['journal_id'],
913 'period_id': vals['period_id'],
914 'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''),
915 'date': vals['date'],
916 'partner_id': vals.get('partner_id',False),
917 'ref': vals.get('ref',False),
918 'account_tax_id': False,
919 'tax_code_id': tax[base_code],
920 'tax_amount': tax[base_sign] * abs(total),
921 'account_id': vals['account_id'],
925 if data['tax_code_id']:
926 self.create(cr, uid, data, context)
928 #create the VAT movement
930 'move_id': vals['move_id'],
931 'journal_id': vals['journal_id'],
932 'period_id': vals['period_id'],
933 'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''),
934 'date': vals['date'],
935 'partner_id': vals.get('partner_id',False),
936 'ref': vals.get('ref',False),
937 'account_tax_id': False,
938 'tax_code_id': tax[tax_code],
939 'tax_amount': tax[tax_sign] * abs(tax['amount']),
940 'account_id': tax[account_id] or vals['account_id'],
941 'credit': tax['amount']<0 and -tax['amount'] or 0.0,
942 'debit': tax['amount']>0 and tax['amount'] or 0.0,
944 if data['tax_code_id']:
945 self.create(cr, uid, data, context)
946 del vals['account_tax_id']
948 # No needed, related to the job
949 #if not is_new_move and 'date' in vals:
950 # if context and ('__last_update' in context):
951 # del context['__last_update']
952 # self.pool.get('account.move').write(cr, uid, [move_id], {'date':vals['date']}, context=context)
953 if check and not context.get('no_store_function'):
954 tmp = self.pool.get('account.move').validate(cr, uid, [vals['move_id']], context)
955 if journal.entry_posted and tmp:
956 self.pool.get('account.move').button_validate(cr,uid, [vals['move_id']],context)
961 class account_bank_statement_reconcile(osv.osv):
962 _inherit = "account.bank.statement.reconcile"
964 'line_ids': fields.many2many('account.move.line', 'account_bank_statement_line_rel', 'statement_id', 'line_id', 'Entries'),
966 account_bank_statement_reconcile()
968 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: