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