[IMP] Improve typos in warning (Grammar mistake, Capitalize title, Typos)
[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         ctx = dict(context, account_period_prefer_normal=True)
517         ids = account_period_obj.find(cr, uid, context=ctx)
518         period_id = False
519         if ids:
520             period_id = ids[0]
521         return period_id
522
523     def _get_journal(self, cr, uid, context=None):
524         """
525         Return journal based on the journal type
526         """
527         context = context or {}
528         if context.get('journal_id', False):
529             return context['journal_id']
530         journal_id = False
531
532         journal_pool = self.pool.get('account.journal')
533         if context.get('journal_type', False):
534             jids = journal_pool.search(cr, uid, [('type','=', context.get('journal_type'))])
535             if not jids:
536                 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'))
537             journal_id = jids[0]
538         return journal_id
539
540
541     _defaults = {
542         'blocked': False,
543         'centralisation': 'normal',
544         'date': _get_date,
545         'date_created': fields.date.context_today,
546         'state': 'draft',
547         'currency_id': _get_currency,
548         'journal_id': _get_journal,
549         'credit': 0.0,
550         'debit': 0.0,
551         'amount_currency': 0.0,
552         'account_id': lambda self, cr, uid, c: c.get('account_id', False),
553         'period_id': _get_period,
554         'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.move.line', context=c)
555     }
556     _order = "date desc, id desc"
557     _sql_constraints = [
558         ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in accounting entry !'),
559         ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'),
560     ]
561
562     def _auto_init(self, cr, context=None):
563         super(account_move_line, self)._auto_init(cr, context=context)
564         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'account_move_line_journal_id_period_id_index\'')
565         if not cr.fetchone():
566             cr.execute('CREATE INDEX account_move_line_journal_id_period_id_index ON account_move_line (journal_id, period_id)')
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 positive when the journal item is a debit and negative when if it is a 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_partner_id(self, cr, uid, ids, move_id, partner_id, account_id=None, debit=0, credit=0, date=False, journal=False, context=None):
659         partner_obj = self.pool.get('res.partner')
660         payment_term_obj = self.pool.get('account.payment.term')
661         journal_obj = self.pool.get('account.journal')
662         fiscal_pos_obj = self.pool.get('account.fiscal.position')
663         val = {}
664         val['date_maturity'] = False
665
666         if not partner_id:
667             return {'value':val}
668         if not date:
669             date = datetime.now().strftime('%Y-%m-%d')
670         jt = False
671         if journal:
672             jt = journal_obj.browse(cr, uid, journal, context=context).type
673         part = partner_obj.browse(cr, uid, partner_id, context=context)
674
675         payment_term_id = False
676         if jt and jt in ('purchase', 'purchase_refund') and part.property_supplier_payment_term:
677             payment_term_id = part.property_supplier_payment_term.id
678         elif jt and part.property_payment_term:
679             payment_term_id = part.property_payment_term.id
680         if payment_term_id:
681             res = payment_term_obj.compute(cr, uid, payment_term_id, 100, date)
682             if res:
683                 val['date_maturity'] = res[0][0]
684         if not account_id:
685             id1 = part.property_account_payable.id
686             id2 =  part.property_account_receivable.id
687             if jt:
688                 if jt in ('sale', 'purchase_refund'):
689                     val['account_id'] = fiscal_pos_obj.map_account(cr, uid, part and part.property_account_position or False, id2)
690                 elif jt in ('purchase', 'sale_refund'):
691                     val['account_id'] = fiscal_pos_obj.map_account(cr, uid, part and part.property_account_position or False, id1)
692                 elif jt in ('general', 'bank', 'cash'):
693                     if part.customer:
694                         val['account_id'] = fiscal_pos_obj.map_account(cr, uid, part and part.property_account_position or False, id2)
695                     elif part.supplier:
696                         val['account_id'] = fiscal_pos_obj.map_account(cr, uid, part and part.property_account_position or False, id1)
697                 if val.get('account_id', False):
698                     d = self.onchange_account_id(cr, uid, ids, account_id=val['account_id'], partner_id=part.id, context=context)
699                     val.update(d['value'])
700         return {'value':val}
701
702     def onchange_account_id(self, cr, uid, ids, account_id=False, partner_id=False, context=None):
703         account_obj = self.pool.get('account.account')
704         partner_obj = self.pool.get('res.partner')
705         fiscal_pos_obj = self.pool.get('account.fiscal.position')
706         val = {}
707         if account_id:
708             res = account_obj.browse(cr, uid, account_id, context=context)
709             tax_ids = res.tax_ids
710             if tax_ids and partner_id:
711                 part = partner_obj.browse(cr, uid, partner_id, context=context)
712                 tax_id = fiscal_pos_obj.map_tax(cr, uid, part and part.property_account_position or False, tax_ids)[0]
713             else:
714                 tax_id = tax_ids and tax_ids[0].id or False
715             val['account_tax_id'] = tax_id
716         return {'value': val}
717     #
718     # type: the type if reconciliation (no logic behind this field, for info)
719     #
720     # writeoff; entry generated for the difference between the lines
721     #
722     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
723         if context is None:
724             context = {}
725         if context and context.get('next_partner_only', False):
726             if not context.get('partner_id', False):
727                 partner = self.list_partners_to_reconcile(cr, uid, context=context)
728                 if partner:
729                     partner = partner[0]
730             else:
731                 partner = context.get('partner_id', False)
732             if not partner:
733                 return []
734             args.append(('partner_id', '=', partner[0]))
735         return super(account_move_line, self).search(cr, uid, args, offset, limit, order, context, count)
736
737     def list_partners_to_reconcile(self, cr, uid, context=None):
738         cr.execute(
739              """SELECT partner_id FROM (
740                 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
741                 FROM account_move_line l
742                 RIGHT JOIN account_account a ON (a.id = l.account_id)
743                 RIGHT JOIN res_partner p ON (l.partner_id = p.id)
744                     WHERE a.reconcile IS TRUE
745                     AND l.reconcile_id IS NULL
746                     AND l.state <> 'draft'
747                     GROUP BY l.partner_id, p.last_reconciliation_date
748                 ) AS s
749                 WHERE debit > 0 AND credit > 0 AND (last_reconciliation_date IS NULL OR max_date > last_reconciliation_date)
750                 ORDER BY last_reconciliation_date""")
751         ids = [x[0] for x in cr.fetchall()]
752         if not ids: 
753             return []
754
755         # To apply the ir_rules
756         partner_obj = self.pool.get('res.partner')
757         ids = partner_obj.search(cr, uid, [('id', 'in', ids)], context=context)
758         return partner_obj.name_get(cr, uid, ids, context=context)
759
760     def reconcile_partial(self, cr, uid, ids, type='auto', context=None, writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False):
761         move_rec_obj = self.pool.get('account.move.reconcile')
762         merges = []
763         unmerge = []
764         total = 0.0
765         merges_rec = []
766         company_list = []
767         if context is None:
768             context = {}
769         for line in self.browse(cr, uid, ids, context=context):
770             if company_list and not line.company_id.id in company_list:
771                 raise osv.except_osv(_('Warning!'), _('To reconcile the entries company should be the same for all entries.'))
772             company_list.append(line.company_id.id)
773
774         for line in self.browse(cr, uid, ids, context=context):
775             if line.account_id.currency_id:
776                 currency_id = line.account_id.currency_id
777             else:
778                 currency_id = line.company_id.currency_id
779             if line.reconcile_id:
780                 raise osv.except_osv(_('Warning'), _("Journal Item '%s' (id: %s), Move '%s' is already reconciled!") % (line.name, line.id, line.move_id.name)) 
781             if line.reconcile_partial_id:
782                 for line2 in line.reconcile_partial_id.line_partial_ids:
783                     if not line2.reconcile_id:
784                         if line2.id not in merges:
785                             merges.append(line2.id)
786                         if line2.account_id.currency_id:
787                             total += line2.amount_currency
788                         else:
789                             total += (line2.debit or 0.0) - (line2.credit or 0.0)
790                 merges_rec.append(line.reconcile_partial_id.id)
791             else:
792                 unmerge.append(line.id)
793                 if line.account_id.currency_id:
794                     total += line.amount_currency
795                 else:
796                     total += (line.debit or 0.0) - (line.credit or 0.0)
797         if self.pool.get('res.currency').is_zero(cr, uid, currency_id, total):
798             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)
799             return res
800         r_id = move_rec_obj.create(cr, uid, {
801             'type': type,
802             'line_partial_ids': map(lambda x: (4,x,False), merges+unmerge)
803         })
804         move_rec_obj.reconcile_partial_check(cr, uid, [r_id] + merges_rec, context=context)
805         return True
806
807     def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context=None):
808         account_obj = self.pool.get('account.account')
809         move_obj = self.pool.get('account.move')
810         move_rec_obj = self.pool.get('account.move.reconcile')
811         partner_obj = self.pool.get('res.partner')
812         currency_obj = self.pool.get('res.currency')
813         lines = self.browse(cr, uid, ids, context=context)
814         unrec_lines = filter(lambda x: not x['reconcile_id'], lines)
815         credit = debit = 0.0
816         currency = 0.0
817         account_id = False
818         partner_id = False
819         if context is None:
820             context = {}
821         company_list = []
822         for line in self.browse(cr, uid, ids, context=context):
823             if company_list and not line.company_id.id in company_list:
824                 raise osv.except_osv(_('Warning!'), _('To reconcile the entries company should be the same for all entries.'))
825             company_list.append(line.company_id.id)
826         for line in unrec_lines:
827             if line.state <> 'valid':
828                 raise osv.except_osv(_('Error!'),
829                         _('Entry "%s" is not valid !') % line.name)
830             credit += line['credit']
831             debit += line['debit']
832             currency += line['amount_currency'] or 0.0
833             account_id = line['account_id']['id']
834             partner_id = (line['partner_id'] and line['partner_id']['id']) or False
835         writeoff = debit - credit
836
837         # Ifdate_p in context => take this date
838         if context.has_key('date_p') and context['date_p']:
839             date=context['date_p']
840         else:
841             date = time.strftime('%Y-%m-%d')
842
843         cr.execute('SELECT account_id, reconcile_id '\
844                    'FROM account_move_line '\
845                    'WHERE id IN %s '\
846                    'GROUP BY account_id,reconcile_id',
847                    (tuple(ids), ))
848         r = cr.fetchall()
849         #TODO: move this check to a constraint in the account_move_reconcile object
850         if not unrec_lines:
851             raise osv.except_osv(_('Error!'), _('Entry is already reconciled.'))
852         account = account_obj.browse(cr, uid, account_id, context=context)
853         if r[0][1] != None:
854             raise osv.except_osv(_('Error!'), _('Some entries are already reconciled.'))
855
856         if context.get('fy_closing'):
857             # We don't want to generate any write-off when being called from the
858             # wizard used to close a fiscal year (and it doesn't give us any
859             # writeoff_acc_id).
860             pass
861         elif (not currency_obj.is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \
862            (account.currency_id and (not currency_obj.is_zero(cr, uid, account.currency_id, currency))):
863             if not writeoff_acc_id:
864                 raise osv.except_osv(_('Warning!'), _('You have to provide an account for the write off/exchange difference entry.'))
865             if writeoff > 0:
866                 debit = writeoff
867                 credit = 0.0
868                 self_credit = writeoff
869                 self_debit = 0.0
870             else:
871                 debit = 0.0
872                 credit = -writeoff
873                 self_credit = 0.0
874                 self_debit = -writeoff
875             # If comment exist in context, take it
876             if 'comment' in context and context['comment']:
877                 libelle = context['comment']
878             else:
879                 libelle = _('Write-Off')
880
881             cur_obj = self.pool.get('res.currency')
882             cur_id = False
883             amount_currency_writeoff = 0.0
884             if context.get('company_currency_id',False) != context.get('currency_id',False):
885                 cur_id = context.get('currency_id',False)
886                 for line in unrec_lines:
887                     if line.currency_id and line.currency_id.id == context.get('currency_id',False):
888                         amount_currency_writeoff += line.amount_currency
889                     else:
890                         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})
891                         amount_currency_writeoff += (line.debit > 0) and tmp_amount or -tmp_amount
892
893             writeoff_lines = [
894                 (0, 0, {
895                     'name': libelle,
896                     'debit': self_debit,
897                     'credit': self_credit,
898                     'account_id': account_id,
899                     'date': date,
900                     'partner_id': partner_id,
901                     'currency_id': cur_id or (account.currency_id.id or False),
902                     'amount_currency': amount_currency_writeoff and -1 * amount_currency_writeoff or (account.currency_id.id and -1 * currency or 0.0)
903                 }),
904                 (0, 0, {
905                     'name': libelle,
906                     'debit': debit,
907                     'credit': credit,
908                     'account_id': writeoff_acc_id,
909                     'analytic_account_id': context.get('analytic_id', False),
910                     'date': date,
911                     'partner_id': partner_id,
912                     'currency_id': cur_id or (account.currency_id.id or False),
913                     'amount_currency': amount_currency_writeoff and amount_currency_writeoff or (account.currency_id.id and currency or 0.0)
914                 })
915             ]
916
917             writeoff_move_id = move_obj.create(cr, uid, {
918                 'period_id': writeoff_period_id,
919                 'journal_id': writeoff_journal_id,
920                 'date':date,
921                 'state': 'draft',
922                 'line_id': writeoff_lines
923             })
924
925             writeoff_line_ids = self.search(cr, uid, [('move_id', '=', writeoff_move_id), ('account_id', '=', account_id)])
926             if account_id == writeoff_acc_id:
927                 writeoff_line_ids = [writeoff_line_ids[1]]
928             ids += writeoff_line_ids
929
930         r_id = move_rec_obj.create(cr, uid, {
931             'type': type,
932             'line_id': map(lambda x: (4, x, False), ids),
933             'line_partial_ids': map(lambda x: (3, x, False), ids)
934         })
935         wf_service = netsvc.LocalService("workflow")
936         # the id of the move.reconcile is written in the move.line (self) by the create method above
937         # because of the way the line_id are defined: (4, x, False)
938         for id in ids:
939             wf_service.trg_trigger(uid, 'account.move.line', id, cr)
940
941         if lines and lines[0]:
942             partner_id = lines[0].partner_id and lines[0].partner_id.id or False
943             if partner_id and not partner_obj.has_something_to_reconcile(cr, uid, partner_id, context=context):
944                 partner_obj.mark_as_reconciled(cr, uid, [partner_id], context=context)
945         return r_id
946
947     def view_header_get(self, cr, user, view_id, view_type, context=None):
948         if context is None:
949             context = {}
950         context = self.convert_to_period(cr, user, context=context)
951         if context.get('account_id', False):
952             cr.execute('SELECT code FROM account_account WHERE id = %s', (context['account_id'], ))
953             res = cr.fetchone()
954             if res:
955                 res = _('Entries: ')+ (res[0] or '')
956             return res
957         if (not context.get('journal_id', False)) or (not context.get('period_id', False)):
958             return False
959         if context.get('search_default_journal_id', False):
960             context['journal_id'] = context.get('search_default_journal_id')
961         cr.execute('SELECT code FROM account_journal WHERE id = %s', (context['journal_id'], ))
962         j = cr.fetchone()[0] or ''
963         cr.execute('SELECT code FROM account_period WHERE id = %s', (context['period_id'], ))
964         p = cr.fetchone()[0] or ''
965         if j or p:
966             return j + (p and (':' + p) or '')
967         return False
968
969     def onchange_date(self, cr, user, ids, date, context=None):
970         """
971         Returns a dict that contains new values and context
972         @param cr: A database cursor
973         @param user: ID of the user currently logged in
974         @param date: latest value from user input for field date
975         @param args: other arguments
976         @param context: context arguments, like lang, time zone
977         @return: Returns a dict which contains new values, and context
978         """
979         res = {}
980         if context is None:
981             context = {}
982         period_pool = self.pool.get('account.period')
983         ctx = dict(context, account_period_prefer_normal=True)
984         pids = period_pool.find(cr, user, date, context=ctx)
985         if pids:
986             res.update({
987                 'period_id':pids[0]
988             })
989             context.update({
990                 'period_id':pids[0]
991             })
992         return {
993             'value':res,
994             'context':context,
995         }
996
997     def _check_moves(self, cr, uid, context=None):
998         # use the first move ever created for this journal and period
999         if context is None:
1000             context = {}
1001         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']))
1002         res = cr.fetchone()
1003         if res:
1004             if res[1] != 'draft':
1005                 raise osv.except_osv(_('User Error!'),
1006                        _('The account move (%s) for centralisation ' \
1007                                 'has been confirmed.') % res[2])
1008         return res
1009
1010     def _remove_move_reconcile(self, cr, uid, move_ids=None, opening_reconciliation=False, context=None):
1011         # Function remove move rencocile ids related with moves
1012         obj_move_line = self.pool.get('account.move.line')
1013         obj_move_rec = self.pool.get('account.move.reconcile')
1014         unlink_ids = []
1015         if not move_ids:
1016             return True
1017         recs = obj_move_line.read(cr, uid, move_ids, ['reconcile_id', 'reconcile_partial_id'])
1018         full_recs = filter(lambda x: x['reconcile_id'], recs)
1019         rec_ids = [rec['reconcile_id'][0] for rec in full_recs]
1020         part_recs = filter(lambda x: x['reconcile_partial_id'], recs)
1021         part_rec_ids = [rec['reconcile_partial_id'][0] for rec in part_recs]
1022         unlink_ids += rec_ids
1023         unlink_ids += part_rec_ids
1024         if unlink_ids:
1025             if opening_reconciliation:
1026                 obj_move_rec.write(cr, uid, unlink_ids, {'opening_reconciliation': False})
1027             obj_move_rec.unlink(cr, uid, unlink_ids)
1028         return True
1029
1030     def unlink(self, cr, uid, ids, context=None, check=True):
1031         if context is None:
1032             context = {}
1033         move_obj = self.pool.get('account.move')
1034         self._update_check(cr, uid, ids, context)
1035         result = False
1036         move_ids = set()
1037         for line in self.browse(cr, uid, ids, context=context):
1038             move_ids.add(line.move_id.id)
1039             context['journal_id'] = line.journal_id.id
1040             context['period_id'] = line.period_id.id
1041             result = super(account_move_line, self).unlink(cr, uid, [line.id], context=context)
1042         move_ids = list(move_ids)
1043         if check and move_ids:
1044             move_obj.validate(cr, uid, move_ids, context=context)
1045         return result
1046
1047     def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
1048         if context is None:
1049             context={}
1050         move_obj = self.pool.get('account.move')
1051         account_obj = self.pool.get('account.account')
1052         journal_obj = self.pool.get('account.journal')
1053         if isinstance(ids, (int, long)):
1054             ids = [ids]
1055         if vals.get('account_tax_id', False):
1056             raise osv.except_osv(_('Unable to change tax!'), _('You cannot change the tax, you should remove and recreate lines.'))
1057         if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']:
1058             raise osv.except_osv(_('Bad Account!'), _('You cannot use an inactive account.'))
1059         if update_check:
1060             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):
1061                 self._update_check(cr, uid, ids, context)
1062
1063         todo_date = None
1064         if vals.get('date', False):
1065             todo_date = vals['date']
1066             del vals['date']
1067
1068         for line in self.browse(cr, uid, ids, context=context):
1069             ctx = context.copy()
1070             if ('journal_id' not in ctx):
1071                 if line.move_id:
1072                    ctx['journal_id'] = line.move_id.journal_id.id
1073                 else:
1074                     ctx['journal_id'] = line.journal_id.id
1075             if ('period_id' not in ctx):
1076                 if line.move_id:
1077                     ctx['period_id'] = line.move_id.period_id.id
1078                 else:
1079                     ctx['period_id'] = line.period_id.id
1080             #Check for centralisation
1081             journal = journal_obj.browse(cr, uid, ctx['journal_id'], context=ctx)
1082             if journal.centralisation:
1083                 self._check_moves(cr, uid, context=ctx)
1084         result = super(account_move_line, self).write(cr, uid, ids, vals, context)
1085         if check:
1086             done = []
1087             for line in self.browse(cr, uid, ids):
1088                 if line.move_id.id not in done:
1089                     done.append(line.move_id.id)
1090                     move_obj.validate(cr, uid, [line.move_id.id], context)
1091                     if todo_date:
1092                         move_obj.write(cr, uid, [line.move_id.id], {'date': todo_date}, context=context)
1093         return result
1094
1095     def _update_journal_check(self, cr, uid, journal_id, period_id, context=None):
1096         journal_obj = self.pool.get('account.journal')
1097         period_obj = self.pool.get('account.period')
1098         jour_period_obj = self.pool.get('account.journal.period')
1099         cr.execute('SELECT state FROM account_journal_period WHERE journal_id = %s AND period_id = %s', (journal_id, period_id))
1100         result = cr.fetchall()
1101         journal = journal_obj.browse(cr, uid, journal_id, context=context)
1102         period = period_obj.browse(cr, uid, period_id, context=context)
1103         for (state,) in result:
1104             if state == 'done':
1105                 raise osv.except_osv(_('Error!'), _('You can not add/modify entries in a closed period %s of journal %s.' % (period.name,journal.name)))                
1106         if not result:
1107             jour_period_obj.create(cr, uid, {
1108                 'name': (journal.code or journal.name)+':'+(period.name or ''),
1109                 'journal_id': journal.id,
1110                 'period_id': period.id
1111             })
1112         return True
1113
1114     def _update_check(self, cr, uid, ids, context=None):
1115         done = {}
1116         for line in self.browse(cr, uid, ids, context=context):
1117             err_msg = _('Move name (id): %s (%s)') % (line.move_id.name, str(line.move_id.id))
1118             if line.move_id.state <> 'draft' and (not line.journal_id.entry_posted):
1119                 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)
1120             if line.reconcile_id:
1121                 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)
1122             t = (line.journal_id.id, line.period_id.id)
1123             if t not in done:
1124                 self._update_journal_check(cr, uid, line.journal_id.id, line.period_id.id, context)
1125                 done[t] = True
1126         return True
1127
1128     def create(self, cr, uid, vals, context=None, check=True):
1129         account_obj = self.pool.get('account.account')
1130         tax_obj = self.pool.get('account.tax')
1131         move_obj = self.pool.get('account.move')
1132         cur_obj = self.pool.get('res.currency')
1133         journal_obj = self.pool.get('account.journal')
1134         if context is None:
1135             context = {}
1136         if vals.get('move_id', False):
1137             move = self.pool.get('account.move').browse(cr, uid, vals['move_id'], context=context)
1138             if move.company_id:
1139                 vals['company_id'] = move.company_id.id
1140             if move.date and not vals.get('date'):
1141                 vals['date'] = move.date
1142         if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']:
1143             raise osv.except_osv(_('Bad Account!'), _('You cannot use an inactive account.'))
1144         if 'journal_id' in vals and vals['journal_id']:
1145             context['journal_id'] = vals['journal_id']
1146         if 'period_id' in vals and vals['period_id']:
1147             context['period_id'] = vals['period_id']
1148         if ('journal_id' not in context) and ('move_id' in vals) and vals['move_id']:
1149             m = move_obj.browse(cr, uid, vals['move_id'])
1150             context['journal_id'] = m.journal_id.id
1151             context['period_id'] = m.period_id.id
1152         #we need to treat the case where a value is given in the context for period_id as a string
1153         if 'period_id' in context and not isinstance(context.get('period_id', ''), (int, long)):
1154             period_candidate_ids = self.pool.get('account.period').name_search(cr, uid, name=context.get('period_id',''))
1155             if len(period_candidate_ids) != 1:
1156                 raise osv.except_osv(_('Error!'), _('No period found or more than one period found for the given date.'))
1157             context['period_id'] = period_candidate_ids[0][0]
1158         if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
1159             context['journal_id'] = context.get('search_default_journal_id')
1160         self._update_journal_check(cr, uid, context['journal_id'], context['period_id'], context)
1161         move_id = vals.get('move_id', False)
1162         journal = journal_obj.browse(cr, uid, context['journal_id'], context=context)
1163         vals['journal_id'] = vals.get('journal_id') or context.get('journal_id')
1164         vals['period_id'] = vals.get('period_id') or context.get('period_id')
1165         vals['date'] = vals.get('date') or context.get('date')
1166         if not move_id:
1167             if journal.centralisation:
1168                 #Check for centralisation
1169                 res = self._check_moves(cr, uid, context)
1170                 if res:
1171                     vals['move_id'] = res[0]
1172             if not vals.get('move_id', False):
1173                 if journal.sequence_id:
1174                     #name = self.pool.get('ir.sequence').next_by_id(cr, uid, journal.sequence_id.id)
1175                     v = {
1176                         'date': vals.get('date', time.strftime('%Y-%m-%d')),
1177                         'period_id': context['period_id'],
1178                         'journal_id': context['journal_id']
1179                     }
1180                     if vals.get('ref', ''):
1181                         v.update({'ref': vals['ref']})
1182                     move_id = move_obj.create(cr, uid, v, context)
1183                     vals['move_id'] = move_id
1184                 else:
1185                     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.'))
1186         ok = not (journal.type_control_ids or journal.account_control_ids)
1187         if ('account_id' in vals):
1188             account = account_obj.browse(cr, uid, vals['account_id'], context=context)
1189             if journal.type_control_ids:
1190                 type = account.user_type
1191                 for t in journal.type_control_ids:
1192                     if type.code == t.code:
1193                         ok = True
1194                         break
1195             if journal.account_control_ids and not ok:
1196                 for a in journal.account_control_ids:
1197                     if a.id == vals['account_id']:
1198                         ok = True
1199                         break
1200             # Automatically convert in the account's secondary currency if there is one and
1201             # the provided values were not already multi-currency
1202             if account.currency_id and (vals.get('amount_currency', False) is False) and account.currency_id.id != account.company_id.currency_id.id:
1203                 vals['currency_id'] = account.currency_id.id
1204                 ctx = {}
1205                 if 'date' in vals:
1206                     ctx['date'] = vals['date']
1207                 vals['amount_currency'] = cur_obj.compute(cr, uid, account.company_id.currency_id.id,
1208                     account.currency_id.id, vals.get('debit', 0.0)-vals.get('credit', 0.0), context=ctx)
1209         if not ok:
1210             raise osv.except_osv(_('Bad Account!'), _('You cannot use this general account in this journal, check the tab \'Entry Controls\' on the related journal.'))
1211
1212         if vals.get('analytic_account_id',False):
1213             if journal.analytic_journal_id:
1214                 vals['analytic_lines'] = [(0,0, {
1215                         'name': vals['name'],
1216                         'date': vals.get('date', time.strftime('%Y-%m-%d')),
1217                         'account_id': vals.get('analytic_account_id', False),
1218                         'unit_amount': vals.get('quantity', 1.0),
1219                         'amount': vals.get('debit', 0.0) or vals.get('credit', 0.0),
1220                         'general_account_id': vals.get('account_id', False),
1221                         'journal_id': journal.analytic_journal_id.id,
1222                         'ref': vals.get('ref', False),
1223                         'user_id': uid
1224             })]
1225
1226         result = super(account_move_line, self).create(cr, uid, vals, context=context)
1227         # CREATE Taxes
1228         if vals.get('account_tax_id', False):
1229             tax_id = tax_obj.browse(cr, uid, vals['account_tax_id'])
1230             total = vals['debit'] - vals['credit']
1231             if journal.type in ('purchase_refund', 'sale_refund'):
1232                 base_code = 'ref_base_code_id'
1233                 tax_code = 'ref_tax_code_id'
1234                 account_id = 'account_paid_id'
1235                 base_sign = 'ref_base_sign'
1236                 tax_sign = 'ref_tax_sign'
1237             else:
1238                 base_code = 'base_code_id'
1239                 tax_code = 'tax_code_id'
1240                 account_id = 'account_collected_id'
1241                 base_sign = 'base_sign'
1242                 tax_sign = 'tax_sign'
1243             tmp_cnt = 0
1244             for tax in tax_obj.compute_all(cr, uid, [tax_id], total, 1.00, force_excluded=True).get('taxes'):
1245                 #create the base movement
1246                 if tmp_cnt == 0:
1247                     if tax[base_code]:
1248                         tmp_cnt += 1
1249                         self.write(cr, uid,[result], {
1250                             'tax_code_id': tax[base_code],
1251                             'tax_amount': tax[base_sign] * abs(total)
1252                         })
1253                 else:
1254                     data = {
1255                         'move_id': vals['move_id'],
1256                         'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''),
1257                         'date': vals['date'],
1258                         'partner_id': vals.get('partner_id',False),
1259                         'ref': vals.get('ref',False),
1260                         'account_tax_id': False,
1261                         'tax_code_id': tax[base_code],
1262                         'tax_amount': tax[base_sign] * abs(total),
1263                         'account_id': vals['account_id'],
1264                         'credit': 0.0,
1265                         'debit': 0.0,
1266                     }
1267                     if data['tax_code_id']:
1268                         self.create(cr, uid, data, context)
1269                 #create the Tax movement
1270                 data = {
1271                     'move_id': vals['move_id'],
1272                     'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''),
1273                     'date': vals['date'],
1274                     'partner_id': vals.get('partner_id',False),
1275                     'ref': vals.get('ref',False),
1276                     'account_tax_id': False,
1277                     'tax_code_id': tax[tax_code],
1278                     'tax_amount': tax[tax_sign] * abs(tax['amount']),
1279                     'account_id': tax[account_id] or vals['account_id'],
1280                     'credit': tax['amount']<0 and -tax['amount'] or 0.0,
1281                     'debit': tax['amount']>0 and tax['amount'] or 0.0,
1282                 }
1283                 if data['tax_code_id']:
1284                     self.create(cr, uid, data, context)
1285             del vals['account_tax_id']
1286
1287         if check and ((not context.get('no_store_function')) or journal.entry_posted):
1288             tmp = move_obj.validate(cr, uid, [vals['move_id']], context)
1289             if journal.entry_posted and tmp:
1290                 move_obj.button_validate(cr,uid, [vals['move_id']], context)
1291         return result
1292
1293     def list_periods(self, cr, uid, context=None):
1294         ids = self.pool.get('account.period').search(cr,uid,[])
1295         return self.pool.get('account.period').name_get(cr, uid, ids, context=context)
1296
1297     def list_journals(self, cr, uid, context=None):
1298         ng = dict(self.pool.get('account.journal').name_search(cr,uid,'',[]))
1299         ids = ng.keys()
1300         result = []
1301         for journal in self.pool.get('account.journal').browse(cr, uid, ids, context=context):
1302             result.append((journal.id,ng[journal.id],journal.type,
1303                 bool(journal.currency),bool(journal.analytic_journal_id)))
1304         return result
1305
1306 account_move_line()
1307
1308 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: