[DEL]Remove commented useless fields_view_get
[odoo/odoo.git] / addons / account / account_move_line.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 import sys
23 import time
24 from datetime import datetime
25 from operator import itemgetter
26
27 from lxml import etree
28
29 import netsvc
30 from osv import fields, osv, orm
31 from tools.translate import _
32 import decimal_precision as dp
33 import tools
34
35 class account_move_line(osv.osv):
36     _name = "account.move.line"
37     _description = "Journal Items"
38
39     def _query_get(self, cr, uid, obj='l', context=None):
40         fiscalyear_obj = self.pool.get('account.fiscalyear')
41         fiscalperiod_obj = self.pool.get('account.period')
42         account_obj = self.pool.get('account.account')
43         fiscalyear_ids = []
44         if context is None:
45             context = {}
46         initial_bal = context.get('initial_bal', False)
47         company_clause = " "
48         if context.get('company_id', False):
49             company_clause = " AND " +obj+".company_id = %s" % context.get('company_id', False)
50         if not context.get('fiscalyear', False):
51             if context.get('all_fiscalyear', False):
52                 #this option is needed by the aged balance report because otherwise, if we search only the draft ones, an open invoice of a closed fiscalyear won't be displayed
53                 fiscalyear_ids = fiscalyear_obj.search(cr, uid, [])
54             else:
55                 fiscalyear_ids = fiscalyear_obj.search(cr, uid, [('state', '=', 'draft')])
56         else:
57             #for initial balance as well as for normal query, we check only the selected FY because the best practice is to generate the FY opening entries
58             fiscalyear_ids = [context['fiscalyear']]
59
60         fiscalyear_clause = (','.join([str(x) for x in fiscalyear_ids])) or '0'
61         state = context.get('state', False)
62         where_move_state = ''
63         where_move_lines_by_date = ''
64
65         if context.get('date_from', False) and context.get('date_to', False):
66             if initial_bal:
67                 where_move_lines_by_date = " AND " +obj+".move_id IN (SELECT id FROM account_move WHERE date < '" +context['date_from']+"')"
68             else:
69                 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']+"')"
70
71         if state:
72             if state.lower() not in ['all']:
73                 where_move_state= " AND "+obj+".move_id IN (SELECT id FROM account_move WHERE account_move.state = '"+state+"')"
74         if context.get('period_from', False) and context.get('period_to', False) and not context.get('periods', False):
75             if initial_bal:
76                 period_company_id = fiscalperiod_obj.browse(cr, uid, context['period_from'], context=context).company_id.id
77                 first_period = fiscalperiod_obj.search(cr, uid, [('company_id', '=', period_company_id)], order='date_start', limit=1)[0]
78                 context['periods'] = fiscalperiod_obj.build_ctx_periods(cr, uid, first_period, context['period_from'])
79             else:
80                 context['periods'] = fiscalperiod_obj.build_ctx_periods(cr, uid, context['period_from'], context['period_to'])
81         if context.get('periods', False):
82             if initial_bal:
83                 query = 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)
84                 period_ids = fiscalperiod_obj.search(cr, uid, [('id', 'in', context['periods'])], order='date_start', limit=1)
85                 if period_ids and period_ids[0]:
86                     first_period = fiscalperiod_obj.browse(cr, uid, period_ids[0], context=context)
87                     ids = ','.join([str(x) for x in context['periods']])
88                     query = obj+".state <> 'draft' AND "+obj+".period_id IN (SELECT id FROM account_period WHERE fiscalyear_id IN (%s) AND date_start <= '%s' AND id NOT IN (%s)) %s %s" % (fiscalyear_clause, first_period.date_start, ids, where_move_state, where_move_lines_by_date)
89             else:
90                 ids = ','.join([str(x) for x in context['periods']])
91                 query = 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)
92         else:
93             query = 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)
94
95         if initial_bal and not context.get('periods', False) and not where_move_lines_by_date:
96             #we didn't pass any filter in the context, and the initial balance can't be computed using only the fiscalyear otherwise entries will be summed twice
97             #so we have to invalidate this query
98             raise osv.except_osv(_('Warning!'),_("You have not supplied enough arguments to compute the initial balance, please select a period and a journal in the context."))
99
100
101         if context.get('journal_ids', False):
102             query += ' AND '+obj+'.journal_id IN (%s)' % ','.join(map(str, context['journal_ids']))
103
104         if context.get('chart_account_id', False):
105             child_ids = account_obj._get_children_and_consol(cr, uid, [context['chart_account_id']], context=context)
106             query += ' AND '+obj+'.account_id IN (%s)' % ','.join(map(str, child_ids))
107
108         query += company_clause
109         return query
110
111     def _amount_residual(self, cr, uid, ids, field_names, args, context=None):
112         """
113            This function returns the residual amount on a receivable or payable account.move.line.
114            By default, it returns an amount in the currency of this journal entry (maybe different
115            of the company currency), but if you pass 'residual_in_company_currency' = True in the
116            context then the returned amount will be in company currency.
117         """
118         res = {}
119         if context is None:
120             context = {}
121         cur_obj = self.pool.get('res.currency')
122         for move_line in self.browse(cr, uid, ids, context=context):
123             res[move_line.id] = {
124                 'amount_residual': 0.0,
125                 'amount_residual_currency': 0.0,
126             }
127
128             if move_line.reconcile_id:
129                 continue
130             if not move_line.account_id.type in ('payable', 'receivable'):
131                 #this function does not suport to be used on move lines not related to payable or receivable accounts
132                 continue
133
134             if move_line.currency_id:
135                 move_line_total = move_line.amount_currency
136                 sign = move_line.amount_currency < 0 and -1 or 1
137             else:
138                 move_line_total = move_line.debit - move_line.credit
139                 sign = (move_line.debit - move_line.credit) < 0 and -1 or 1
140             line_total_in_company_currency =  move_line.debit - move_line.credit
141             context_unreconciled = context.copy()
142             if move_line.reconcile_partial_id:
143                 for payment_line in move_line.reconcile_partial_id.line_partial_ids:
144                     if payment_line.id == move_line.id:
145                         continue
146                     if payment_line.currency_id and move_line.currency_id and payment_line.currency_id.id == move_line.currency_id.id:
147                             move_line_total += payment_line.amount_currency
148                     else:
149                         if move_line.currency_id:
150                             context_unreconciled.update({'date': payment_line.date})
151                             amount_in_foreign_currency = cur_obj.compute(cr, uid, move_line.company_id.currency_id.id, move_line.currency_id.id, (payment_line.debit - payment_line.credit), round=False, context=context_unreconciled)
152                             move_line_total += amount_in_foreign_currency
153                         else:
154                             move_line_total += (payment_line.debit - payment_line.credit)
155                     line_total_in_company_currency += (payment_line.debit - payment_line.credit)
156
157             result = move_line_total
158             res[move_line.id]['amount_residual_currency'] =  sign * (move_line.currency_id and self.pool.get('res.currency').round(cr, uid, move_line.currency_id, result) or result)
159             res[move_line.id]['amount_residual'] = sign * line_total_in_company_currency
160         return res
161
162     def default_get(self, cr, uid, fields, context=None):
163         data = self._default_get(cr, uid, fields, context=context)
164         for f in data.keys():
165             if f not in fields:
166                 del data[f]
167         return data
168
169     def create_analytic_lines(self, cr, uid, ids, context=None):
170         acc_ana_line_obj = self.pool.get('account.analytic.line')
171         for obj_line in self.browse(cr, uid, ids, context=context):
172             if obj_line.analytic_account_id:
173                 if not obj_line.journal_id.analytic_journal_id:
174                     raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (obj_line.journal_id.name, ))
175                 amt = (obj_line.credit or  0.0) - (obj_line.debit or 0.0)
176                 vals_lines = {
177                     'name': obj_line.name,
178                     'date': obj_line.date,
179                     'account_id': obj_line.analytic_account_id.id,
180                     'unit_amount': obj_line.quantity,
181                     'product_id': obj_line.product_id and obj_line.product_id.id or False,
182                     'product_uom_id': obj_line.product_uom_id and obj_line.product_uom_id.id or False,
183                     'amount': amt,
184                     'general_account_id': obj_line.account_id.id,
185                     'journal_id': obj_line.journal_id.analytic_journal_id.id,
186                     'ref': obj_line.ref,
187                     'move_id': obj_line.id,
188                     'user_id': uid
189                 }
190                 acc_ana_line_obj.create(cr, uid, vals_lines)
191         return True
192
193     def _default_get_move_form_hook(self, cursor, user, data):
194         '''Called in the end of default_get method for manual entry in account_move form'''
195         if data.has_key('analytic_account_id'):
196             del(data['analytic_account_id'])
197         if data.has_key('account_tax_id'):
198             del(data['account_tax_id'])
199         return data
200
201     def convert_to_period(self, cr, uid, context=None):
202         if context is None:
203             context = {}
204         period_obj = self.pool.get('account.period')
205         #check if the period_id changed in the context from client side
206         if context.get('period_id', False):
207             period_id = context.get('period_id')
208             if type(period_id) == str:
209                 ids = period_obj.search(cr, uid, [('name', 'ilike', period_id)])
210                 context.update({
211                     'period_id': ids and ids[0] or False
212                 })
213         return context
214
215     def _default_get(self, cr, uid, fields, context=None):
216         if context is None:
217             context = {}
218         if not context.get('journal_id', False):
219             context['journal_id'] = context.get('search_default_journal_id')
220         if not context.get('period_id', False):
221             context['period_id'] = context.get('search_default_period_id')
222         account_obj = self.pool.get('account.account')
223         period_obj = self.pool.get('account.period')
224         journal_obj = self.pool.get('account.journal')
225         move_obj = self.pool.get('account.move')
226         tax_obj = self.pool.get('account.tax')
227         fiscal_pos_obj = self.pool.get('account.fiscal.position')
228         partner_obj = self.pool.get('res.partner')
229         currency_obj = self.pool.get('res.currency')
230         context = self.convert_to_period(cr, uid, context)
231         #pass the right context when search_defaul_journal_id
232         if context.get('search_default_journal_id',False):
233             context['journal_id'] = context.get('search_default_journal_id')
234         # Compute simple values
235         data = super(account_move_line, self).default_get(cr, uid, fields, context=context)
236         # Starts: Manual entry from account.move form
237         if context.get('lines'):
238             total_new = context.get('balance', 0.00)
239             if context['journal']:
240                 journal_data = journal_obj.browse(cr, uid, context['journal'], context=context)
241                 if journal_data.type == 'purchase':
242                     if total_new > 0:
243                         account = journal_data.default_credit_account_id
244                     else:
245                         account = journal_data.default_debit_account_id
246                 else:
247                     if total_new > 0:
248                         account = journal_data.default_credit_account_id
249                     else:
250                         account = journal_data.default_debit_account_id
251                 if account and ((not fields) or ('debit' in fields) or ('credit' in fields)) and 'partner_id' in data and (data['partner_id']):
252                     part = partner_obj.browse(cr, uid, data['partner_id'], context=context)
253                     account = fiscal_pos_obj.map_account(cr, uid, part and part.property_account_position or False, account.id)
254                     account = account_obj.browse(cr, uid, account, context=context)
255                     data['account_id'] =  account.id
256
257             s = -total_new
258             data['debit'] = s > 0 and s or 0.0
259             data['credit'] = s < 0 and -s or 0.0
260             data = self._default_get_move_form_hook(cr, uid, data)
261             return data
262         # Ends: Manual entry from account.move form
263         if not 'move_id' in fields: #we are not in manual entry
264             return data
265         # Compute the current move
266         move_id = False
267         partner_id = False
268         if context.get('journal_id', False) and context.get('period_id', False):
269             if 'move_id' in fields:
270                 cr.execute('SELECT move_id \
271                     FROM \
272                         account_move_line \
273                     WHERE \
274                         journal_id = %s and period_id = %s AND create_uid = %s AND state = %s \
275                     ORDER BY id DESC limit 1',
276                     (context['journal_id'], context['period_id'], uid, 'draft'))
277                 res = cr.fetchone()
278                 move_id = (res and res[0]) or False
279                 if not move_id:
280                     return data
281                 else:
282                     data['move_id'] = move_id
283             if 'date' in fields:
284                 cr.execute('SELECT date \
285                     FROM \
286                         account_move_line \
287                     WHERE \
288                         journal_id = %s AND period_id = %s AND create_uid = %s \
289                     ORDER BY id DESC',
290                     (context['journal_id'], context['period_id'], uid))
291                 res = cr.fetchone()
292                 if res:
293                     data['date'] = res[0]
294                 else:
295                     period = period_obj.browse(cr, uid, context['period_id'],
296                             context=context)
297                     data['date'] = period.date_start
298         if not move_id:
299             return data
300         total = 0
301         ref_id = False
302         move = move_obj.browse(cr, uid, move_id, context=context)
303         if 'name' in fields:
304             data.setdefault('name', move.line_id[-1].name)
305         acc1 = False
306         for l in move.line_id:
307             acc1 = l.account_id
308             partner_id = partner_id or l.partner_id.id
309             ref_id = ref_id or l.ref
310             total += (l.debit or 0.0) - (l.credit or 0.0)
311
312         if 'ref' in fields:
313             data['ref'] = ref_id
314         if 'partner_id' in fields:
315             data['partner_id'] = partner_id
316
317         if move.journal_id.type == 'purchase':
318             if total > 0:
319                 account = move.journal_id.default_credit_account_id
320             else:
321                 account = move.journal_id.default_debit_account_id
322         else:
323             if total > 0:
324                 account = move.journal_id.default_credit_account_id
325             else:
326                 account = move.journal_id.default_debit_account_id
327         part = partner_id and partner_obj.browse(cr, uid, partner_id) or False
328         # part = False is acceptable for fiscal position.
329         account = fiscal_pos_obj.map_account(cr, uid, part and part.property_account_position or False, account.id)
330         if account:
331             account = account_obj.browse(cr, uid, account, context=context)
332
333         if account and ((not fields) or ('debit' in fields) or ('credit' in fields)):
334             data['account_id'] = account.id
335             # Propose the price Tax excluded, the Tax will be added when confirming line
336             if account.tax_ids:
337                 taxes = fiscal_pos_obj.map_tax(cr, uid, part and part.property_account_position or False, account.tax_ids)
338                 tax = tax_obj.browse(cr, uid, taxes)
339                 for t in tax_obj.compute_inv(cr, uid, tax, total, 1):
340                     total -= t['amount']
341
342         s = -total
343         data['debit'] = s > 0  and s or 0.0
344         data['credit'] = s < 0  and -s or 0.0
345
346         if account and account.currency_id:
347             data['currency_id'] = account.currency_id.id
348             acc = account
349             if s>0:
350                 acc = acc1
351             compute_ctx = context.copy()
352             compute_ctx.update({
353                     'res.currency.compute.account': acc,
354                     'res.currency.compute.account_invert': True,
355                 })
356             v = currency_obj.compute(cr, uid, account.company_id.currency_id.id, data['currency_id'], s, context=compute_ctx)
357             data['amount_currency'] = v
358         return data
359
360     def on_create_write(self, cr, uid, id, context=None):
361         if not id:
362             return []
363         ml = self.browse(cr, uid, id, context=context)
364         return map(lambda x: x.id, ml.move_id.line_id)
365
366     def _balance(self, cr, uid, ids, name, arg, context=None):
367         if context is None:
368             context = {}
369         c = context.copy()
370         c['initital_bal'] = True
371         sql = """SELECT l2.id, SUM(l1.debit-l1.credit)
372                     FROM account_move_line l1, account_move_line l2
373                     WHERE l2.account_id = l1.account_id
374                       AND l1.id <= l2.id
375                       AND l2.id IN %s AND """ + \
376                 self._query_get(cr, uid, obj='l1', context=c) + \
377                 " GROUP BY l2.id"
378
379         cr.execute(sql, [tuple(ids)])
380         return dict(cr.fetchall())
381
382     def _invoice(self, cursor, user, ids, name, arg, context=None):
383         invoice_obj = self.pool.get('account.invoice')
384         res = {}
385         for line_id in ids:
386             res[line_id] = False
387         cursor.execute('SELECT l.id, i.id ' \
388                         'FROM account_move_line l, account_invoice i ' \
389                         'WHERE l.move_id = i.move_id ' \
390                         'AND l.id IN %s',
391                         (tuple(ids),))
392         invoice_ids = []
393         for line_id, invoice_id in cursor.fetchall():
394             res[line_id] = invoice_id
395             invoice_ids.append(invoice_id)
396         invoice_names = {False: ''}
397         for invoice_id, name in invoice_obj.name_get(cursor, user, invoice_ids, context=context):
398             invoice_names[invoice_id] = name
399         for line_id in res.keys():
400             invoice_id = res[line_id]
401             res[line_id] = (invoice_id, invoice_names[invoice_id])
402         return res
403
404     def name_get(self, cr, uid, ids, context=None):
405         if not ids:
406             return []
407         result = []
408         for line in self.browse(cr, uid, ids, context=context):
409             if line.ref:
410                 result.append((line.id, (line.move_id.name or '')+' ('+line.ref+')'))
411             else:
412                 result.append((line.id, line.move_id.name))
413         return result
414
415     def _balance_search(self, cursor, user, obj, name, args, domain=None, context=None):
416         if context is None:
417             context = {}
418         if not args:
419             return []
420         where = ' AND '.join(map(lambda x: '(abs(sum(debit-credit))'+x[1]+str(x[2])+')',args))
421         cursor.execute('SELECT id, SUM(debit-credit) FROM account_move_line \
422                      GROUP BY id, debit, credit having '+where)
423         res = cursor.fetchall()
424         if not res:
425             return [('id', '=', '0')]
426         return [('id', 'in', [x[0] for x in res])]
427
428     def _invoice_search(self, cursor, user, obj, name, args, context=None):
429         if not args:
430             return []
431         invoice_obj = self.pool.get('account.invoice')
432         i = 0
433         while i < len(args):
434             fargs = args[i][0].split('.', 1)
435             if len(fargs) > 1:
436                 args[i] = (fargs[0], 'in', invoice_obj.search(cursor, user,
437                     [(fargs[1], args[i][1], args[i][2])]))
438                 i += 1
439                 continue
440             if isinstance(args[i][2], basestring):
441                 res_ids = invoice_obj.name_search(cursor, user, args[i][2], [],
442                         args[i][1])
443                 args[i] = (args[i][0], 'in', [x[0] for x in res_ids])
444             i += 1
445         qu1, qu2 = [], []
446         for x in args:
447             if x[1] != 'in':
448                 if (x[2] is False) and (x[1] == '='):
449                     qu1.append('(i.id IS NULL)')
450                 elif (x[2] is False) and (x[1] == '<>' or x[1] == '!='):
451                     qu1.append('(i.id IS NOT NULL)')
452                 else:
453                     qu1.append('(i.id %s %s)' % (x[1], '%s'))
454                     qu2.append(x[2])
455             elif x[1] == 'in':
456                 if len(x[2]) > 0:
457                     qu1.append('(i.id IN (%s))' % (','.join(['%s'] * len(x[2]))))
458                     qu2 += x[2]
459                 else:
460                     qu1.append(' (False)')
461         if qu1:
462             qu1 = ' AND' + ' AND'.join(qu1)
463         else:
464             qu1 = ''
465         cursor.execute('SELECT l.id ' \
466                 'FROM account_move_line l, account_invoice i ' \
467                 'WHERE l.move_id = i.move_id ' + qu1, qu2)
468         res = cursor.fetchall()
469         if not res:
470             return [('id', '=', '0')]
471         return [('id', 'in', [x[0] for x in res])]
472
473     def _get_move_lines(self, cr, uid, ids, context=None):
474         result = []
475         for move in self.pool.get('account.move').browse(cr, uid, ids, context=context):
476             for line in move.line_id:
477                 result.append(line.id)
478         return result
479
480     def _get_reconcile(self, cr, uid, ids,name, unknow_none, context=None):
481         res = dict.fromkeys(ids, False)
482         for line in self.browse(cr, uid, ids, context=context):
483             if line.reconcile_id:
484                 res[line.id] = str(line.reconcile_id.name)
485             elif line.reconcile_partial_id:
486                 res[line.id] = str(line.reconcile_partial_id.name)
487         return res
488
489     _columns = {
490         'name': fields.char('Name', size=64, required=True),
491         '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 useful for some reports."),
492         'product_uom_id': fields.many2one('product.uom', 'Unit of Measure'),
493         'product_id': fields.many2one('product.product', 'Product'),
494         'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
495         'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
496         'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade", domain=[('type','<>','view'), ('type', '<>', 'closed')], select=2),
497         'move_id': fields.many2one('account.move', 'Journal Entry', ondelete="cascade", help="The move of this entry line.", select=2, required=True),
498         'narration': fields.related('move_id','narration', type='text', relation='account.move', string='Internal Note'),
499         'ref': fields.related('move_id', 'ref', string='Reference', type='char', size=64, store=True),
500         'statement_id': fields.many2one('account.bank.statement', 'Statement', help="The bank statement used for bank reconciliation", select=1),
501         'reconcile_id': fields.many2one('account.move.reconcile', 'Reconcile', readonly=True, ondelete='set null', select=2),
502         'reconcile_partial_id': fields.many2one('account.move.reconcile', 'Partial Reconcile', readonly=True, ondelete='set null', select=2),
503         'reconcile': fields.function(_get_reconcile, type='char', string='Reconcile'),
504         'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency if it is a multi-currency entry.", digits_compute=dp.get_precision('Account')),
505         'amount_residual_currency': fields.function(_amount_residual, string='Residual Amount', multi="residual", help="The residual amount on a receivable or payable of a journal entry expressed in its currency (maybe different of the company currency)."),
506         'amount_residual': fields.function(_amount_residual, string='Residual Amount', multi="residual", help="The residual amount on a receivable or payable of a journal entry expressed in the company currency."),
507         'currency_id': fields.many2one('res.currency', 'Currency', help="The optional other currency if it is a multi-currency entry."),
508         'journal_id': fields.related('move_id', 'journal_id', string='Journal', type='many2one', relation='account.journal', required=True, select=True,
509                                 store = {
510                                     'account.move': (_get_move_lines, ['journal_id'], 20)
511                                 }),
512         'period_id': fields.related('move_id', 'period_id', string='Period', type='many2one', relation='account.period', required=True, select=True,
513                                 store = {
514                                     'account.move': (_get_move_lines, ['period_id'], 20)
515                                 }),
516         'blocked': fields.boolean('Litigation', help="You can check this box to mark this journal item as a litigation with the associated partner"),
517         'partner_id': fields.many2one('res.partner', 'Partner', select=1, ondelete='restrict'),
518         'date_maturity': fields.date('Due date', select=True ,help="This field is used for payable and receivable journal entries. You can put the limit date for the payment of this line."),
519         'date': fields.related('move_id','date', string='Effective date', type='date', required=True, select=True,
520                                 store = {
521                                     'account.move': (_get_move_lines, ['date'], 20)
522                                 }),
523         'date_created': fields.date('Creation date', select=True),
524         'analytic_lines': fields.one2many('account.analytic.line', 'move_id', 'Analytic lines'),
525         'centralisation': fields.selection([('normal','Normal'),('credit','Credit Centralisation'),('debit','Debit Centralisation'),('currency','Currency Adjustment')], 'Centralisation', size=8),
526         'balance': fields.function(_balance, fnct_search=_balance_search, string='Balance'),
527         'state': fields.selection([('draft','Unbalanced'), ('valid','Balanced')], 'Status', readonly=True),
528         'tax_code_id': fields.many2one('account.tax.code', 'Tax Account', help="The Account can either be a base tax code or a tax code account."),
529         'tax_amount': fields.float('Tax/Base Amount', digits_compute=dp.get_precision('Account'), select=True, help="If the Tax account is a tax code account, this field will contain the taxed amount.If the tax account is base tax code, "\
530                     "this field will contain the basic amount(without tax)."),
531         'invoice': fields.function(_invoice, string='Invoice',
532             type='many2one', relation='account.invoice', fnct_search=_invoice_search),
533         'account_tax_id':fields.many2one('account.tax', 'Tax'),
534         'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account'),
535         'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
536     }
537
538     def _get_date(self, cr, uid, context=None):
539         if context is None:
540             context or {}
541         period_obj = self.pool.get('account.period')
542         dt = time.strftime('%Y-%m-%d')
543         if ('journal_id' in context) and ('period_id' in context):
544             cr.execute('SELECT date FROM account_move_line ' \
545                     'WHERE journal_id = %s AND period_id = %s ' \
546                     'ORDER BY id DESC limit 1',
547                     (context['journal_id'], context['period_id']))
548             res = cr.fetchone()
549             if res:
550                 dt = res[0]
551             else:
552                 period = period_obj.browse(cr, uid, context['period_id'], context=context)
553                 dt = period.date_start
554         return dt
555
556     def _get_currency(self, cr, uid, context=None):
557         if context is None:
558             context = {}
559         if not context.get('journal_id', False):
560             return False
561         cur = self.pool.get('account.journal').browse(cr, uid, context['journal_id']).currency
562         return cur and cur.id or False
563
564     def _get_period(self, cr, uid, context=None):
565         """
566         Return  default account period value
567         """
568         context = context or {}
569         if context.get('period_id', False):
570             return context['period_id']
571         account_period_obj = self.pool.get('account.period')
572         ids = account_period_obj.find(cr, uid, context=context)
573         period_id = False
574         if ids:
575             period_id = ids[0]
576         return period_id
577
578     def _get_journal(self, cr, uid, context=None):
579         """
580         Return journal based on the journal type
581         """
582         context = context or {}
583         if context.get('journal_id', False):
584             return context['journal_id']
585         journal_id = False
586
587         journal_pool = self.pool.get('account.journal')
588         if context.get('journal_type', False):
589             jids = journal_pool.search(cr, uid, [('type','=', context.get('journal_type'))])
590             if not jids:
591                 raise osv.except_osv(_('Configuration Error!'), _('Cannot find any account journal of %s type for this company.\n\nYou can create one in the menu: \nConfiguration/Journals/Journals.') % context.get('journal_type'))
592             journal_id = jids[0]
593         return journal_id
594
595
596     _defaults = {
597         'blocked': False,
598         'centralisation': 'normal',
599         'date': _get_date,
600         'date_created': fields.date.context_today,
601         'state': 'draft',
602         'currency_id': _get_currency,
603         'journal_id': _get_journal,
604         'credit': 0.0,
605         'debit': 0.0,
606         'amount_currency': 0.0,
607         'account_id': lambda self, cr, uid, c: c.get('account_id', False),
608         'period_id': _get_period,
609         'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.move.line', context=c)
610     }
611     _order = "date desc, id desc"
612     _sql_constraints = [
613         ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in accounting entry !'),
614         ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'),
615     ]
616
617     def _auto_init(self, cr, context=None):
618         super(account_move_line, self)._auto_init(cr, context=context)
619         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'account_move_line_journal_id_period_id_index\'')
620         if not cr.fetchone():
621             cr.execute('CREATE INDEX account_move_line_journal_id_period_id_index ON account_move_line (journal_id, period_id)')
622
623     def _check_no_view(self, cr, uid, ids, context=None):
624         lines = self.browse(cr, uid, ids, context=context)
625         for l in lines:
626             if l.account_id.type == 'view':
627                 return False
628         return True
629
630     def _check_no_closed(self, cr, uid, ids, context=None):
631         lines = self.browse(cr, uid, ids, context=context)
632         for l in lines:
633             if l.account_id.type == 'closed':
634                 raise osv.except_osv(_('Error!'), _('You cannot create journal items on a closed account %s %s.') % (l.account_id.code, l.account_id.name))
635         return True
636
637     def _check_company_id(self, cr, uid, ids, context=None):
638         lines = self.browse(cr, uid, ids, context=context)
639         for l in lines:
640             if l.company_id != l.account_id.company_id or l.company_id != l.period_id.company_id:
641                 return False
642         return True
643
644     def _check_date(self, cr, uid, ids, context=None):
645         for l in self.browse(cr, uid, ids, context=context):
646             if l.journal_id.allow_date:
647                 if not time.strptime(l.date[:10],'%Y-%m-%d') >= time.strptime(l.period_id.date_start, '%Y-%m-%d') or not time.strptime(l.date[:10], '%Y-%m-%d') <= time.strptime(l.period_id.date_stop, '%Y-%m-%d'):
648                     return False
649         return True
650
651     def _check_currency(self, cr, uid, ids, context=None):
652         for l in self.browse(cr, uid, ids, context=context):
653             if l.account_id.currency_id:
654                 if not l.currency_id or not l.currency_id.id == l.account_id.currency_id.id:
655                     return False
656         return True
657
658     _constraints = [
659         (_check_no_view, 'You cannot create journal items on an account of type view.', ['account_id']),
660         (_check_no_closed, 'You cannot create journal items on closed account.', ['account_id']),
661         (_check_company_id, 'Account and Period must belong to the same company.', ['company_id']),
662         (_check_date, 'The date of your Journal Entry is not in the defined period! You should change the date or remove this constraint from the journal.', ['date']),
663         (_check_currency, 'The selected account of your Journal Entry forces to provide a secondary currency. You should remove the secondary currency on the account or select a multi-currency view on the journal.', ['currency_id']),
664     ]
665
666     #TODO: ONCHANGE_ACCOUNT_ID: set account_tax_id
667     def onchange_currency(self, cr, uid, ids, account_id, amount, currency_id, date=False, journal=False, context=None):
668         if context is None:
669             context = {}
670         account_obj = self.pool.get('account.account')
671         journal_obj = self.pool.get('account.journal')
672         currency_obj = self.pool.get('res.currency')
673         if (not currency_id) or (not account_id):
674             return {}
675         result = {}
676         acc = account_obj.browse(cr, uid, account_id, context=context)
677         if (amount>0) and journal:
678             x = journal_obj.browse(cr, uid, journal).default_credit_account_id
679             if x: acc = x
680         context.update({
681                 'date': date,
682                 'res.currency.compute.account': acc,
683             })
684         v = currency_obj.compute(cr, uid, currency_id, acc.company_id.currency_id.id, amount, context=context)
685         result['value'] = {
686             'debit': v > 0 and v or 0.0,
687             'credit': v < 0 and -v or 0.0
688         }
689         return result
690
691     def onchange_partner_id(self, cr, uid, ids, move_id, partner_id, account_id=None, debit=0, credit=0, date=False, journal=False):
692         partner_obj = self.pool.get('res.partner')
693         payment_term_obj = self.pool.get('account.payment.term')
694         journal_obj = self.pool.get('account.journal')
695         fiscal_pos_obj = self.pool.get('account.fiscal.position')
696         val = {}
697         val['date_maturity'] = False
698
699         if not partner_id:
700             return {'value':val}
701         if not date:
702             date = datetime.now().strftime('%Y-%m-%d')
703         part = partner_obj.browse(cr, uid, partner_id)
704
705         if part.property_payment_term:
706             res = payment_term_obj.compute(cr, uid, part.property_payment_term.id, 100, date)
707             if res:
708                 val['date_maturity'] = res[0][0]
709         if not account_id:
710             id1 = part.property_account_payable.id
711             id2 =  part.property_account_receivable.id
712             if journal:
713                 jt = journal_obj.browse(cr, uid, journal).type
714                 if jt in ('sale', 'purchase_refund'):
715                     val['account_id'] = fiscal_pos_obj.map_account(cr, uid, part and part.property_account_position or False, id2)
716                 elif jt in ('purchase', 'sale_refund'):
717                     val['account_id'] = fiscal_pos_obj.map_account(cr, uid, part and part.property_account_position or False, id1)
718                 elif jt in ('general', 'bank', 'cash'):
719                     if part.customer:
720                         val['account_id'] = fiscal_pos_obj.map_account(cr, uid, part and part.property_account_position or False, id2)
721                     elif part.supplier:
722                         val['account_id'] = fiscal_pos_obj.map_account(cr, uid, part and part.property_account_position or False, id1)
723                 if val.get('account_id', False):
724                     d = self.onchange_account_id(cr, uid, ids, val['account_id'])
725                     val.update(d['value'])
726         return {'value':val}
727
728     def onchange_account_id(self, cr, uid, ids, account_id=False, partner_id=False):
729         account_obj = self.pool.get('account.account')
730         partner_obj = self.pool.get('res.partner')
731         fiscal_pos_obj = self.pool.get('account.fiscal.position')
732         val = {}
733         if account_id:
734             res = account_obj.browse(cr, uid, account_id)
735             tax_ids = res.tax_ids
736             if tax_ids and partner_id:
737                 part = partner_obj.browse(cr, uid, partner_id)
738                 tax_id = fiscal_pos_obj.map_tax(cr, uid, part and part.property_account_position or False, tax_ids)[0]
739             else:
740                 tax_id = tax_ids and tax_ids[0].id or False
741             val['account_tax_id'] = tax_id
742         return {'value': val}
743     #
744     # type: the type if reconciliation (no logic behind this field, for info)
745     #
746     # writeoff; entry generated for the difference between the lines
747     #
748     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
749         if context is None:
750             context = {}
751         if context and context.get('next_partner_only', False):
752             if not context.get('partner_id', False):
753                 partner = self.list_partners_to_reconcile(cr, uid, context=context)
754                 if partner:
755                     partner = partner[0]
756             else:
757                 partner = context.get('partner_id', False)
758             if not partner:
759                 return []
760             args.append(('partner_id', '=', partner[0]))
761         return super(account_move_line, self).search(cr, uid, args, offset, limit, order, context, count)
762
763     def list_partners_to_reconcile(self, cr, uid, context=None):
764         cr.execute(
765              """
766              SELECT partner_id
767              FROM (
768                 SELECT l.partner_id, p.last_reconciliation_date, SUM(l.debit) AS debit, SUM(l.credit) AS credit
769                 FROM account_move_line l
770                 RIGHT JOIN account_account a ON (a.id = l.account_id)
771                 RIGHT JOIN res_partner p ON (l.partner_id = p.id)
772                     WHERE a.reconcile IS TRUE
773                     AND l.reconcile_id IS NULL
774                     AND (p.last_reconciliation_date IS NULL OR l.date > p.last_reconciliation_date)
775                     AND l.state <> 'draft'
776                     GROUP BY l.partner_id, p.last_reconciliation_date
777                 ) AS s
778                 WHERE debit > 0 AND credit > 0
779                 ORDER BY last_reconciliation_date""")
780         ids = cr.fetchall()
781         ids = len(ids) and [x[0] for x in ids] or []
782         return self.pool.get('res.partner').name_get(cr, uid, ids, context=context)
783
784     def reconcile_partial(self, cr, uid, ids, type='auto', context=None, writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False):
785         move_rec_obj = self.pool.get('account.move.reconcile')
786         merges = []
787         unmerge = []
788         total = 0.0
789         merges_rec = []
790         company_list = []
791         if context is None:
792             context = {}
793         for line in self.browse(cr, uid, ids, context=context):
794             if company_list and not line.company_id.id in company_list:
795                 raise osv.except_osv(_('Warning!'), _('To reconcile the entries company should be the same for all entries.'))
796             company_list.append(line.company_id.id)
797
798         for line in self.browse(cr, uid, ids, context=context):
799             if line.account_id.currency_id:
800                 currency_id = line.account_id.currency_id
801             else:
802                 currency_id = line.company_id.currency_id
803             if line.reconcile_id:
804                 raise osv.except_osv(_('Warning!'), _('Already reconciled.'))
805             if line.reconcile_partial_id:
806                 for line2 in line.reconcile_partial_id.line_partial_ids:
807                     if not line2.reconcile_id:
808                         if line2.id not in merges:
809                             merges.append(line2.id)
810                         if line2.account_id.currency_id:
811                             total += line2.amount_currency
812                         else:
813                             total += (line2.debit or 0.0) - (line2.credit or 0.0)
814                 merges_rec.append(line.reconcile_partial_id.id)
815             else:
816                 unmerge.append(line.id)
817                 if line.account_id.currency_id:
818                     total += line.amount_currency
819                 else:
820                     total += (line.debit or 0.0) - (line.credit or 0.0)
821         if self.pool.get('res.currency').is_zero(cr, uid, currency_id, total):
822             res = self.reconcile(cr, uid, merges+unmerge, context=context, writeoff_acc_id=writeoff_acc_id, writeoff_period_id=writeoff_period_id, writeoff_journal_id=writeoff_journal_id)
823             return res
824         r_id = move_rec_obj.create(cr, uid, {
825             'type': type,
826             'line_partial_ids': map(lambda x: (4,x,False), merges+unmerge)
827         })
828         move_rec_obj.reconcile_partial_check(cr, uid, [r_id] + merges_rec, context=context)
829         return True
830
831     def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context=None):
832         account_obj = self.pool.get('account.account')
833         move_obj = self.pool.get('account.move')
834         move_rec_obj = self.pool.get('account.move.reconcile')
835         partner_obj = self.pool.get('res.partner')
836         currency_obj = self.pool.get('res.currency')
837         lines = self.browse(cr, uid, ids, context=context)
838         unrec_lines = filter(lambda x: not x['reconcile_id'], lines)
839         credit = debit = 0.0
840         currency = 0.0
841         account_id = False
842         partner_id = False
843         if context is None:
844             context = {}
845         company_list = []
846         for line in self.browse(cr, uid, ids, context=context):
847             if company_list and not line.company_id.id in company_list:
848                 raise osv.except_osv(_('Warning!'), _('To reconcile the entries company should be the same for all entries.'))
849             company_list.append(line.company_id.id)
850         for line in unrec_lines:
851             if line.state <> 'valid':
852                 raise osv.except_osv(_('Error!'),
853                         _('Entry "%s" is not valid !') % line.name)
854             credit += line['credit']
855             debit += line['debit']
856             currency += line['amount_currency'] or 0.0
857             account_id = line['account_id']['id']
858             partner_id = (line['partner_id'] and line['partner_id']['id']) or False
859         writeoff = debit - credit
860
861         # Ifdate_p in context => take this date
862         if context.has_key('date_p') and context['date_p']:
863             date=context['date_p']
864         else:
865             date = time.strftime('%Y-%m-%d')
866
867         cr.execute('SELECT account_id, reconcile_id '\
868                    'FROM account_move_line '\
869                    'WHERE id IN %s '\
870                    'GROUP BY account_id,reconcile_id',
871                    (tuple(ids), ))
872         r = cr.fetchall()
873         #TODO: move this check to a constraint in the account_move_reconcile object
874         if not unrec_lines:
875             raise osv.except_osv(_('Error!'), _('Entry is already reconciled.'))
876         account = account_obj.browse(cr, uid, account_id, context=context)
877         if r[0][1] != None:
878             raise osv.except_osv(_('Error!'), _('Some entries are already reconciled.'))
879
880         if (not currency_obj.is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \
881            (account.currency_id and (not currency_obj.is_zero(cr, uid, account.currency_id, currency))):
882             if not writeoff_acc_id:
883                 raise osv.except_osv(_('Warning!'), _('You have to provide an account for the write off/exchange difference entry.'))
884             if writeoff > 0:
885                 debit = writeoff
886                 credit = 0.0
887                 self_credit = writeoff
888                 self_debit = 0.0
889             else:
890                 debit = 0.0
891                 credit = -writeoff
892                 self_credit = 0.0
893                 self_debit = -writeoff
894             # If comment exist in context, take it
895             if 'comment' in context and context['comment']:
896                 libelle = context['comment']
897             else:
898                 libelle = _('Write-Off')
899
900             cur_obj = self.pool.get('res.currency')
901             cur_id = False
902             amount_currency_writeoff = 0.0
903             if context.get('company_currency_id',False) != context.get('currency_id',False):
904                 cur_id = context.get('currency_id',False)
905                 for line in unrec_lines:
906                     if line.currency_id and line.currency_id.id == context.get('currency_id',False):
907                         amount_currency_writeoff += line.amount_currency
908                     else:
909                         tmp_amount = cur_obj.compute(cr, uid, line.account_id.company_id.currency_id.id, context.get('currency_id',False), abs(line.debit-line.credit), context={'date': line.date})
910                         amount_currency_writeoff += (line.debit > 0) and tmp_amount or -tmp_amount
911
912             writeoff_lines = [
913                 (0, 0, {
914                     'name': libelle,
915                     'debit': self_debit,
916                     'credit': self_credit,
917                     'account_id': account_id,
918                     'date': date,
919                     'partner_id': partner_id,
920                     'currency_id': cur_id or (account.currency_id.id or False),
921                     'amount_currency': amount_currency_writeoff and -1 * amount_currency_writeoff or (account.currency_id.id and -1 * currency or 0.0)
922                 }),
923                 (0, 0, {
924                     'name': libelle,
925                     'debit': debit,
926                     'credit': credit,
927                     'account_id': writeoff_acc_id,
928                     'analytic_account_id': context.get('analytic_id', False),
929                     'date': date,
930                     'partner_id': partner_id,
931                     'currency_id': cur_id or (account.currency_id.id or False),
932                     'amount_currency': amount_currency_writeoff and amount_currency_writeoff or (account.currency_id.id and currency or 0.0)
933                 })
934             ]
935
936             writeoff_move_id = move_obj.create(cr, uid, {
937                 'period_id': writeoff_period_id,
938                 'journal_id': writeoff_journal_id,
939                 'date':date,
940                 'state': 'draft',
941                 'line_id': writeoff_lines
942             })
943
944             writeoff_line_ids = self.search(cr, uid, [('move_id', '=', writeoff_move_id), ('account_id', '=', account_id)])
945             if account_id == writeoff_acc_id:
946                 writeoff_line_ids = [writeoff_line_ids[1]]
947             ids += writeoff_line_ids
948
949         r_id = move_rec_obj.create(cr, uid, {
950             'type': type,
951             'line_id': map(lambda x: (4, x, False), ids),
952             'line_partial_ids': map(lambda x: (3, x, False), ids)
953         })
954         wf_service = netsvc.LocalService("workflow")
955         # the id of the move.reconcile is written in the move.line (self) by the create method above
956         # because of the way the line_id are defined: (4, x, False)
957         for id in ids:
958             wf_service.trg_trigger(uid, 'account.move.line', id, cr)
959
960         if lines and lines[0]:
961             partner_id = lines[0].partner_id and lines[0].partner_id.id or False
962             if partner_id and not partner_obj.has_something_to_reconcile(cr, uid, partner_id, context=context):
963                 partner_obj.mark_as_reconciled(cr, uid, [partner_id], context=context)
964         return r_id
965
966     def view_header_get(self, cr, user, view_id, view_type, context=None):
967         if context is None:
968             context = {}
969         context = self.convert_to_period(cr, user, context=context)
970         if context.get('account_id', False):
971             cr.execute('SELECT code FROM account_account WHERE id = %s', (context['account_id'], ))
972             res = cr.fetchone()
973             if res:
974                 res = _('Entries: ')+ (res[0] or '')
975             return res
976         if (not context.get('journal_id', False)) or (not context.get('period_id', False)):
977             return False
978         if context.get('search_default_journal_id', False):
979             context['journal_id'] = context.get('search_default_journal_id')
980         cr.execute('SELECT code FROM account_journal WHERE id = %s', (context['journal_id'], ))
981         j = cr.fetchone()[0] or ''
982         cr.execute('SELECT code FROM account_period WHERE id = %s', (context['period_id'], ))
983         p = cr.fetchone()[0] or ''
984         if j or p:
985             return j + (p and (':' + p) or '')
986         return False
987
988     def onchange_date(self, cr, user, ids, date, context=None):
989         """
990         Returns a dict that contains new values and context
991         @param cr: A database cursor
992         @param user: ID of the user currently logged in
993         @param date: latest value from user input for field date
994         @param args: other arguments
995         @param context: context arguments, like lang, time zone
996         @return: Returns a dict which contains new values, and context
997         """
998         res = {}
999         if context is None:
1000             context = {}
1001         period_pool = self.pool.get('account.period')
1002         pids = period_pool.search(cr, user, [('date_start','<=',date), ('date_stop','>=',date)])
1003         if pids:
1004             res.update({
1005                 'period_id':pids[0]
1006             })
1007             context.update({
1008                 'period_id':pids[0]
1009             })
1010         return {
1011             'value':res,
1012             'context':context,
1013         }
1014
1015     def _check_moves(self, cr, uid, context=None):
1016         # use the first move ever created for this journal and period
1017         if context is None:
1018             context = {}
1019         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']))
1020         res = cr.fetchone()
1021         if res:
1022             if res[1] != 'draft':
1023                 raise osv.except_osv(_('User Error!'),
1024                        _('The account move (%s) for centralisation ' \
1025                                 'has been confirmed.') % res[2])
1026         return res
1027
1028     def _remove_move_reconcile(self, cr, uid, move_ids=None, context=None):
1029         # Function remove move rencocile ids related with moves
1030         obj_move_line = self.pool.get('account.move.line')
1031         obj_move_rec = self.pool.get('account.move.reconcile')
1032         unlink_ids = []
1033         if not move_ids:
1034             return True
1035         recs = obj_move_line.read(cr, uid, move_ids, ['reconcile_id', 'reconcile_partial_id'])
1036         full_recs = filter(lambda x: x['reconcile_id'], recs)
1037         rec_ids = [rec['reconcile_id'][0] for rec in full_recs]
1038         part_recs = filter(lambda x: x['reconcile_partial_id'], recs)
1039         part_rec_ids = [rec['reconcile_partial_id'][0] for rec in part_recs]
1040         unlink_ids += rec_ids
1041         unlink_ids += part_rec_ids
1042         if unlink_ids:
1043             obj_move_rec.unlink(cr, uid, unlink_ids)
1044         return True
1045
1046     def unlink(self, cr, uid, ids, context=None, check=True):
1047         if context is None:
1048             context = {}
1049         move_obj = self.pool.get('account.move')
1050         self._update_check(cr, uid, ids, context)
1051         result = False
1052         move_ids = set()
1053         for line in self.browse(cr, uid, ids, context=context):
1054             move_ids.add(line.move_id.id)
1055             context['journal_id'] = line.journal_id.id
1056             context['period_id'] = line.period_id.id
1057             result = super(account_move_line, self).unlink(cr, uid, [line.id], context=context)
1058         move_ids = list(move_ids)
1059         if check and move_ids:
1060             move_obj.validate(cr, uid, move_ids, context=context)
1061         return result
1062
1063     def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1064         if context is None:
1065             context={}
1066         move_obj = self.pool.get('account.move')
1067         account_obj = self.pool.get('account.account')
1068         journal_obj = self.pool.get('account.journal')
1069         if isinstance(ids, (int, long)):
1070             ids = [ids]
1071         if vals.get('account_tax_id', False):
1072             raise osv.except_osv(_('Unable to change tax!'), _('You cannot change the tax, you should remove and recreate lines.'))
1073         if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']:
1074             raise osv.except_osv(_('Bad Account!'), _('You cannot use an inactive account.'))
1075         if update_check:
1076             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):
1077                 self._update_check(cr, uid, ids, context)
1078
1079         todo_date = None
1080         if vals.get('date', False):
1081             todo_date = vals['date']
1082             del vals['date']
1083
1084         for line in self.browse(cr, uid, ids, context=context):
1085             ctx = context.copy()
1086             if ('journal_id' not in ctx):
1087                 if line.move_id:
1088                    ctx['journal_id'] = line.move_id.journal_id.id
1089                 else:
1090                     ctx['journal_id'] = line.journal_id.id
1091             if ('period_id' not in ctx):
1092                 if line.move_id:
1093                     ctx['period_id'] = line.move_id.period_id.id
1094                 else:
1095                     ctx['period_id'] = line.period_id.id
1096             #Check for centralisation
1097             journal = journal_obj.browse(cr, uid, ctx['journal_id'], context=ctx)
1098             if journal.centralisation:
1099                 self._check_moves(cr, uid, context=ctx)
1100         result = super(account_move_line, self).write(cr, uid, ids, vals, context)
1101         if check:
1102             done = []
1103             for line in self.browse(cr, uid, ids):
1104                 if line.move_id.id not in done:
1105                     done.append(line.move_id.id)
1106                     move_obj.validate(cr, uid, [line.move_id.id], context)
1107                     if todo_date:
1108                         move_obj.write(cr, uid, [line.move_id.id], {'date': todo_date}, context=context)
1109         return result
1110
1111     def _update_journal_check(self, cr, uid, journal_id, period_id, context=None):
1112         journal_obj = self.pool.get('account.journal')
1113         period_obj = self.pool.get('account.period')
1114         jour_period_obj = self.pool.get('account.journal.period')
1115         cr.execute('SELECT state FROM account_journal_period WHERE journal_id = %s AND period_id = %s', (journal_id, period_id))
1116         result = cr.fetchall()
1117         journal = journal_obj.browse(cr, uid, journal_id, context=context)
1118         period = period_obj.browse(cr, uid, period_id, context=context)
1119         for (state,) in result:
1120             if state == 'done':
1121                 raise osv.except_osv(_('Error !'), _('You can not add/modify entries in a closed period %s of journal %s.' % (period.name,journal.name)))                
1122         if not result:
1123             jour_period_obj.create(cr, uid, {
1124                 'name': (journal.code or journal.name)+':'+(period.name or ''),
1125                 'journal_id': journal.id,
1126                 'period_id': period.id
1127             })
1128         return True
1129
1130     def _update_check(self, cr, uid, ids, context=None):
1131         done = {}
1132         for line in self.browse(cr, uid, ids, context=context):
1133             err_msg = _('Move name (id): %s (%s)') % (line.move_id.name, str(line.move_id.id))
1134             if line.move_id.state <> 'draft' and (not line.journal_id.entry_posted):
1135                 raise osv.except_osv(_('Error!'), _('You cannot do this modification on a confirmed entry. You can just change some non legal fields or you must unconfirm the journal entry first.\n%s.') % err_msg)
1136             if line.reconcile_id:
1137                 raise osv.except_osv(_('Error!'), _('You cannot do this modification on a reconciled entry. You can just change some non legal fields or you must unreconcile first.\n%s.') % err_msg)
1138             t = (line.journal_id.id, line.period_id.id)
1139             if t not in done:
1140                 self._update_journal_check(cr, uid, line.journal_id.id, line.period_id.id, context)
1141                 done[t] = True
1142         return True
1143
1144     def create(self, cr, uid, vals, context=None, check=True):
1145         account_obj = self.pool.get('account.account')
1146         tax_obj = self.pool.get('account.tax')
1147         move_obj = self.pool.get('account.move')
1148         cur_obj = self.pool.get('res.currency')
1149         journal_obj = self.pool.get('account.journal')
1150         if context is None:
1151             context = {}
1152         if vals.get('move_id', False):
1153             company_id = self.pool.get('account.move').read(cr, uid, vals['move_id'], ['company_id']).get('company_id', False)
1154             if company_id:
1155                 vals['company_id'] = company_id[0]
1156         if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']:
1157             raise osv.except_osv(_('Bad Account!'), _('You cannot use an inactive account.'))
1158         if 'journal_id' in vals and vals['journal_id']:
1159             context['journal_id'] = vals['journal_id']
1160         if 'period_id' in vals and vals['period_id']:
1161             context['period_id'] = vals['period_id']
1162         if ('journal_id' not in context) and ('move_id' in vals) and vals['move_id']:
1163             m = move_obj.browse(cr, uid, vals['move_id'])
1164             context['journal_id'] = m.journal_id.id
1165             context['period_id'] = m.period_id.id
1166         #we need to treat the case where a value is given in the context for period_id as a string
1167         if 'period_id' in context and not isinstance(context.get('period_id', ''), (int, long)):
1168             period_candidate_ids = self.pool.get('account.period').name_search(cr, uid, name=context.get('period_id',''))
1169             if len(period_candidate_ids) != 1:
1170                 raise osv.except_osv(_('Error!'), _('No period found or more than one period found for the given date.'))
1171             context['period_id'] = period_candidate_ids[0][0]
1172         if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
1173             context['journal_id'] = context.get('search_default_journal_id')
1174         self._update_journal_check(cr, uid, context['journal_id'], context['period_id'], context)
1175         move_id = vals.get('move_id', False)
1176         journal = journal_obj.browse(cr, uid, context['journal_id'], context=context)
1177         vals['journal_id'] = vals.get('journal_id') or context.get('journal_id')
1178         vals['period_id'] = vals.get('period_id') or context.get('period_id')
1179         vals['date'] = vals.get('date') or context.get('date')
1180         if not move_id:
1181             if journal.centralisation:
1182                 #Check for centralisation
1183                 res = self._check_moves(cr, uid, context)
1184                 if res:
1185                     vals['move_id'] = res[0]
1186             if not vals.get('move_id', False):
1187                 if journal.sequence_id:
1188                     #name = self.pool.get('ir.sequence').next_by_id(cr, uid, journal.sequence_id.id)
1189                     v = {
1190                         'date': vals.get('date', time.strftime('%Y-%m-%d')),
1191                         'period_id': context['period_id'],
1192                         'journal_id': context['journal_id']
1193                     }
1194                     if vals.get('ref', ''):
1195                         v.update({'ref': vals['ref']})
1196                     move_id = move_obj.create(cr, uid, v, context)
1197                     vals['move_id'] = move_id
1198                 else:
1199                     raise osv.except_osv(_('No piece number !'), _('Cannot create an automatic sequence for this piece.\nPut a sequence in the journal definition for automatic numbering or create a sequence manually for this piece.'))
1200         ok = not (journal.type_control_ids or journal.account_control_ids)
1201         if ('account_id' in vals):
1202             account = account_obj.browse(cr, uid, vals['account_id'], context=context)
1203             if journal.type_control_ids:
1204                 type = account.user_type
1205                 for t in journal.type_control_ids:
1206                     if type.code == t.code:
1207                         ok = True
1208                         break
1209             if journal.account_control_ids and not ok:
1210                 for a in journal.account_control_ids:
1211                     if a.id == vals['account_id']:
1212                         ok = True
1213                         break
1214             # Automatically convert in the account's secondary currency if there is one and
1215             # the provided values were not already multi-currency
1216             if account.currency_id and (vals.get('amount_currency', False) is False) and account.currency_id.id != account.company_id.currency_id.id:
1217                 vals['currency_id'] = account.currency_id.id
1218                 ctx = {}
1219                 if 'date' in vals:
1220                     ctx['date'] = vals['date']
1221                 vals['amount_currency'] = cur_obj.compute(cr, uid, account.company_id.currency_id.id,
1222                     account.currency_id.id, vals.get('debit', 0.0)-vals.get('credit', 0.0), context=ctx)
1223         if not ok:
1224             raise osv.except_osv(_('Bad Account!'), _('You cannot use this general account in this journal, check the tab \'Entry Controls\' on the related journal.'))
1225
1226         if vals.get('analytic_account_id',False):
1227             if journal.analytic_journal_id:
1228                 vals['analytic_lines'] = [(0,0, {
1229                         'name': vals['name'],
1230                         'date': vals.get('date', time.strftime('%Y-%m-%d')),
1231                         'account_id': vals.get('analytic_account_id', False),
1232                         'unit_amount': vals.get('quantity', 1.0),
1233                         'amount': vals.get('debit', 0.0) or vals.get('credit', 0.0),
1234                         'general_account_id': vals.get('account_id', False),
1235                         'journal_id': journal.analytic_journal_id.id,
1236                         'ref': vals.get('ref', False),
1237                         'user_id': uid
1238             })]
1239
1240         result = super(account_move_line, self).create(cr, uid, vals, context=context)
1241         # CREATE Taxes
1242         if vals.get('account_tax_id', False):
1243             tax_id = tax_obj.browse(cr, uid, vals['account_tax_id'])
1244             total = vals['debit'] - vals['credit']
1245             if journal.type in ('purchase_refund', 'sale_refund'):
1246                 base_code = 'ref_base_code_id'
1247                 tax_code = 'ref_tax_code_id'
1248                 account_id = 'account_paid_id'
1249                 base_sign = 'ref_base_sign'
1250                 tax_sign = 'ref_tax_sign'
1251             else:
1252                 base_code = 'base_code_id'
1253                 tax_code = 'tax_code_id'
1254                 account_id = 'account_collected_id'
1255                 base_sign = 'base_sign'
1256                 tax_sign = 'tax_sign'
1257             tmp_cnt = 0
1258             for tax in tax_obj.compute_all(cr, uid, [tax_id], total, 1.00, force_excluded=True).get('taxes'):
1259                 #create the base movement
1260                 if tmp_cnt == 0:
1261                     if tax[base_code]:
1262                         tmp_cnt += 1
1263                         self.write(cr, uid,[result], {
1264                             'tax_code_id': tax[base_code],
1265                             'tax_amount': tax[base_sign] * abs(total)
1266                         })
1267                 else:
1268                     data = {
1269                         'move_id': vals['move_id'],
1270                         'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''),
1271                         'date': vals['date'],
1272                         'partner_id': vals.get('partner_id',False),
1273                         'ref': vals.get('ref',False),
1274                         'account_tax_id': False,
1275                         'tax_code_id': tax[base_code],
1276                         'tax_amount': tax[base_sign] * abs(total),
1277                         'account_id': vals['account_id'],
1278                         'credit': 0.0,
1279                         'debit': 0.0,
1280                     }
1281                     if data['tax_code_id']:
1282                         self.create(cr, uid, data, context)
1283                 #create the Tax movement
1284                 data = {
1285                     'move_id': vals['move_id'],
1286                     'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''),
1287                     'date': vals['date'],
1288                     'partner_id': vals.get('partner_id',False),
1289                     'ref': vals.get('ref',False),
1290                     'account_tax_id': False,
1291                     'tax_code_id': tax[tax_code],
1292                     'tax_amount': tax[tax_sign] * abs(tax['amount']),
1293                     'account_id': tax[account_id] or vals['account_id'],
1294                     'credit': tax['amount']<0 and -tax['amount'] or 0.0,
1295                     'debit': tax['amount']>0 and tax['amount'] or 0.0,
1296                 }
1297                 if data['tax_code_id']:
1298                     self.create(cr, uid, data, context)
1299             del vals['account_tax_id']
1300
1301         if check and ((not context.get('no_store_function')) or journal.entry_posted):
1302             tmp = move_obj.validate(cr, uid, [vals['move_id']], context)
1303             if journal.entry_posted and tmp:
1304                 move_obj.button_validate(cr,uid, [vals['move_id']], context)
1305         return result
1306
1307     def list_periods(self, cr, uid, context=None):
1308         ids = self.pool.get('account.period').search(cr,uid,[])
1309         return self.pool.get('account.period').name_get(cr, uid, ids, context=context)
1310
1311     def list_journals(self, cr, uid, context=None):
1312         ng = dict(self.pool.get('account.journal').name_search(cr,uid,'',[]))
1313         ids = ng.keys()
1314         result = []
1315         for journal in self.pool.get('account.journal').browse(cr, uid, ids, context=context):
1316             result.append((journal.id,ng[journal.id],journal.type,
1317                 bool(journal.currency),bool(journal.analytic_journal_id)))
1318         return result
1319
1320 account_move_line()
1321
1322 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: