[REF] ThreadView widget renamed to mail_thread. Also added some footers where they...
[odoo/odoo.git] / addons / account_voucher / account_voucher.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 time
23 from lxml import etree
24
25 import netsvc
26 from osv import osv, fields
27 import decimal_precision as dp
28 from tools.translate import _
29
30 class res_company(osv.osv):
31     _inherit = "res.company"
32     _columns = {
33         'income_currency_exchange_account_id': fields.many2one(
34             'account.account',
35             string="Income Currency Rate",
36             domain="[('type', '=', 'other')]",),
37         'expense_currency_exchange_account_id': fields.many2one(
38             'account.account',
39             string="Expense Currency Rate",
40             domain="[('type', '=', 'other')]",),
41     }
42
43 res_company()
44
45 class account_voucher(osv.osv):
46     def _check_paid(self, cr, uid, ids, name, args, context=None):
47         res = {}
48         for voucher in self.browse(cr, uid, ids, context=context):
49             paid = False
50             for line in voucher.move_ids:
51                 if (line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id:
52                     paid = True
53             res[voucher.id] = paid
54         return res
55
56     def _get_type(self, cr, uid, context=None):
57         if context is None:
58             context = {}
59         return context.get('type', False)
60
61     def _get_period(self, cr, uid, context=None):
62         if context is None: context = {}
63         if context.get('period_id', False):
64             return context.get('period_id')
65         periods = self.pool.get('account.period').find(cr, uid)
66         return periods and periods[0] or False
67
68     def _make_journal_search(self, cr, uid, ttype, context=None):
69         journal_pool = self.pool.get('account.journal')
70         return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
71
72     def _get_journal(self, cr, uid, context=None):
73         if context is None: context = {}
74         invoice_pool = self.pool.get('account.invoice')
75         journal_pool = self.pool.get('account.journal')
76         if context.get('invoice_id', False):
77             currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
78             journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
79             return journal_id and journal_id[0] or False
80         if context.get('journal_id', False):
81             return context.get('journal_id')
82         if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
83             return context.get('search_default_journal_id')
84
85         ttype = context.get('type', 'bank')
86         if ttype in ('payment', 'receipt'):
87             ttype = 'bank'
88         res = self._make_journal_search(cr, uid, ttype, context=context)
89         return res and res[0] or False
90
91     def _get_tax(self, cr, uid, context=None):
92         if context is None: context = {}
93         journal_pool = self.pool.get('account.journal')
94         journal_id = context.get('journal_id', False)
95         if not journal_id:
96             ttype = context.get('type', 'bank')
97             res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
98             if not res:
99                 return False
100             journal_id = res[0]
101
102         if not journal_id:
103             return False
104         journal = journal_pool.browse(cr, uid, journal_id, context=context)
105         account_id = journal.default_credit_account_id or journal.default_debit_account_id
106         if account_id and account_id.tax_ids:
107             tax_id = account_id.tax_ids[0].id
108             return tax_id
109         return False
110
111     def _get_payment_rate_currency(self, cr, uid, context=None):
112         """
113         Return the default value for field payment_rate_currency_id: the currency of the journal
114         if there is one, otherwise the currency of the user's company
115         """
116         if context is None: context = {}
117         journal_pool = self.pool.get('account.journal')
118         journal_id = context.get('journal_id', False)
119         if journal_id:
120             journal = journal_pool.browse(cr, uid, journal_id, context=context)
121             if journal.currency:
122                 return journal.currency.id
123         #no journal given in the context, use company currency as default
124         return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
125
126     def _get_currency(self, cr, uid, context=None):
127         if context is None: context = {}
128         journal_pool = self.pool.get('account.journal')
129         journal_id = context.get('journal_id', False)
130         if journal_id:
131             journal = journal_pool.browse(cr, uid, journal_id, context=context)
132             if journal.currency:
133                 return journal.currency.id
134         return False
135
136     def _get_partner(self, cr, uid, context=None):
137         if context is None: context = {}
138         return context.get('partner_id', False)
139
140     def _get_reference(self, cr, uid, context=None):
141         if context is None: context = {}
142         return context.get('reference', False)
143
144     def _get_narration(self, cr, uid, context=None):
145         if context is None: context = {}
146         return context.get('narration', False)
147
148     def _get_amount(self, cr, uid, context=None):
149         if context is None:
150             context= {}
151         return context.get('amount', 0.0)
152
153     def name_get(self, cr, uid, ids, context=None):
154         if not ids:
155             return []
156         if context is None: context = {}
157         return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
158
159     def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
160         mod_obj = self.pool.get('ir.model.data')
161         if context is None: context = {}
162
163         if view_type == 'form':
164             if not view_id and context.get('invoice_type'):
165                 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
166                     result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
167                 else:
168                     result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
169                 result = result and result[1] or False
170                 view_id = result
171             if not view_id and context.get('line_type'):
172                 if context.get('line_type') == 'customer':
173                     result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
174                 else:
175                     result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
176                 result = result and result[1] or False
177                 view_id = result
178
179         res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
180         doc = etree.XML(res['arch'])
181
182         if context.get('type', 'sale') in ('purchase', 'payment'):
183             nodes = doc.xpath("//field[@name='partner_id']")
184             for node in nodes:
185                 node.set('domain', "[('supplier', '=', True)]")
186         res['arch'] = etree.tostring(doc)
187         return res
188
189     def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount):
190         debit = credit = 0.0
191         for l in line_dr_ids:
192             debit += l['amount']
193         for l in line_cr_ids:
194             credit += l['amount']
195         return abs(amount - abs(credit - debit))
196
197     def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, context=None):
198         context = context or {}
199         if not line_dr_ids and not line_cr_ids:
200             return {'value':{}}
201         line_osv = self.pool.get("account.voucher.line")
202         line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
203         line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
204
205         #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
206         is_multi_currency = False
207         if voucher_currency:
208             # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
209             is_multi_currency = True
210         else:
211             #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
212             for voucher_line in line_dr_ids+line_cr_ids:
213                 company_currency = False
214                 company_currency = voucher_line.get('move_line_id', False) and self.pool.get('account.move.line').browse(cr, uid, voucher_line.get('move_line_id'), context=context).company_id.currency_id.id
215                 if voucher_line.get('currency_id', company_currency) != company_currency:
216                     is_multi_currency = True
217                     break
218         return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount), 'is_multi_currency': is_multi_currency}}
219
220     def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
221         if not ids: return {}
222         currency_obj = self.pool.get('res.currency')
223         res = {}
224         debit = credit = 0.0
225         for voucher in self.browse(cr, uid, ids, context=context):
226             for l in voucher.line_dr_ids:
227                 debit += l.amount
228             for l in voucher.line_cr_ids:
229                 credit += l.amount
230             currency = voucher.currency_id or voucher.company_id.currency_id
231             res[voucher.id] =  currency_obj.round(cr, uid, currency, abs(voucher.amount - abs(credit - debit)))
232         return res
233
234     def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
235         if not ids: return {}
236         res = {}
237         rate = 1.0
238         for voucher in self.browse(cr, uid, ids, context=context):
239             if voucher.currency_id:
240                 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
241                     rate =  1 / voucher.payment_rate
242                 else:
243                     ctx = context.copy()
244                     ctx.update({'date': voucher.date})
245                     voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
246                     company_currency_rate = voucher.company_id.currency_id.rate
247                     rate = voucher_rate * company_currency_rate
248             res[voucher.id] =  voucher.amount / rate
249         return res
250
251     _name = 'account.voucher'
252     _description = 'Accounting Voucher'
253     _inherit = ['mail.thread']
254     _order = "date desc, id desc"
255 #    _rec_name = 'number'
256     _columns = {
257         'type':fields.selection([
258             ('sale','Sale'),
259             ('purchase','Purchase'),
260             ('payment','Payment'),
261             ('receipt','Receipt'),
262         ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
263         'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
264         'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
265         'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
266         'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
267         'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
268         'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
269             domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
270         'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
271             domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
272         'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
273         'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
274 #        'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
275         'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
276         'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
277         'state':fields.selection(
278             [('draft','Draft'),
279              ('cancel','Cancelled'),
280              ('proforma','Pro-forma'),
281              ('posted','Posted')
282             ], 'Status', readonly=True, size=32,
283             help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Voucher. \
284                         \n* The \'Pro-forma\' when voucher is in Pro-forma state,voucher does not have an voucher number. \
285                         \n* The \'Posted\' state is used when user create voucher,a voucher number is generated and voucher entries are created in account \
286                         \n* The \'Cancelled\' state is used when user cancel voucher.'),
287         'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
288         'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
289         'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
290         'number': fields.char('Number', size=32, readonly=True,),
291         'move_id':fields.many2one('account.move', 'Account Entry'),
292         'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
293         'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
294         'audit': fields.related('move_id','to_check', type='boolean', help='Check this box if you are unsure of that journal entry and if you want to note it as \'to be reviewed\' by an accounting expert.', relation='account.move', string='To Review'),
295         'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
296         'pay_now':fields.selection([
297             ('pay_now','Pay Directly'),
298             ('pay_later','Pay Later or Group Funds'),
299         ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
300         'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
301         'pre_line':fields.boolean('Previous Payments ?', required=False),
302         'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
303         'payment_option':fields.selection([
304                                            ('without_writeoff', 'Keep Open'),
305                                            ('with_writeoff', 'Reconcile Payment Balance'),
306                                            ], 'Payment Difference', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="This field helps you to choose what you want to do with the eventual difference between the paid amount and the sum of allocated amounts. You can either choose to keep open this difference on the partner's account, or reconcile it with the payment(s)"),
307         'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
308         'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
309         'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
310         'writeoff_amount': fields.function(_get_writeoff_amount, string='Difference Amount', type='float', readonly=True, help="Computed as the difference between the amount stated in the voucher and the sum of allocation on the voucher lines."),
311         'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
312         'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
313             help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field)  and the voucher currency.'),
314         'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
315         'is_multi_currency': fields.boolean('Multi Currency Voucher', help='Fields with internal purpose only that depicts if the voucher is a multi currency one or not'),
316     }
317     _defaults = {
318         'period_id': _get_period,
319         'partner_id': _get_partner,
320         'journal_id':_get_journal,
321         'currency_id': _get_currency,
322         'reference': _get_reference,
323         'narration':_get_narration,
324         'amount': _get_amount,
325         'type':_get_type,
326         'state': 'draft',
327         'pay_now': 'pay_now',
328         'name': '',
329         'date': lambda *a: time.strftime('%Y-%m-%d'),
330         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
331         'tax_id': _get_tax,
332         'payment_option': 'without_writeoff',
333         'comment': _('Write-Off'),
334         'payment_rate': 1.0,
335         'payment_rate_currency_id': _get_payment_rate_currency,
336     }
337
338     def create(self, cr, uid, vals, context=None):
339         voucher =  super(account_voucher, self).create(cr, uid, vals, context=context)
340         self.create_send_note(cr, uid, [voucher], context=context)
341         return voucher
342
343     def compute_tax(self, cr, uid, ids, context=None):
344         tax_pool = self.pool.get('account.tax')
345         partner_pool = self.pool.get('res.partner')
346         position_pool = self.pool.get('account.fiscal.position')
347         voucher_line_pool = self.pool.get('account.voucher.line')
348         voucher_pool = self.pool.get('account.voucher')
349         if context is None: context = {}
350
351         for voucher in voucher_pool.browse(cr, uid, ids, context=context):
352             voucher_amount = 0.0
353             for line in voucher.line_ids:
354                 voucher_amount += line.untax_amount or line.amount
355                 line.amount = line.untax_amount or line.amount
356                 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
357
358             if not voucher.tax_id:
359                 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
360                 continue
361
362             tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
363             partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
364             taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
365             tax = tax_pool.browse(cr, uid, taxes, context=context)
366
367             total = voucher_amount
368             total_tax = 0.0
369
370             if not tax[0].price_include:
371                 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_amount, 1).get('taxes', []):
372                     total_tax += tax_line.get('amount', 0.0)
373                 total += total_tax
374             else:
375                 for line in voucher.line_ids:
376                     line_total = 0.0
377                     line_tax = 0.0
378
379                     for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
380                         line_tax += tax_line.get('amount', 0.0)
381                         line_total += tax_line.get('price_unit')
382                     total_tax += line_tax
383                     untax_amount = line.untax_amount or line.amount
384                     voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
385
386             self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
387         return True
388
389     def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
390         context = context or {}
391         tax_pool = self.pool.get('account.tax')
392         partner_pool = self.pool.get('res.partner')
393         position_pool = self.pool.get('account.fiscal.position')
394         line_pool = self.pool.get('account.voucher.line')
395         res = {
396             'tax_amount': False,
397             'amount': False,
398         }
399         voucher_total = 0.0
400
401         line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
402
403         for line in line_ids:
404             line_amount = 0.0
405             line_amount = line.get('amount',0.0)
406             voucher_total += line_amount
407
408         total = voucher_total
409         total_tax = 0.0
410         if tax_id:
411             tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
412             if partner_id:
413                 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
414                 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
415                 tax = tax_pool.browse(cr, uid, taxes, context=context)
416
417             if not tax[0].price_include:
418                 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_total, 1).get('taxes', []):
419                     total_tax += tax_line.get('amount')
420                 total += total_tax
421
422         res.update({
423             'amount':total or voucher_total,
424             'tax_amount':total_tax
425         })
426         return {
427             'value':res
428         }
429
430     def onchange_term_id(self, cr, uid, ids, term_id, amount):
431         term_pool = self.pool.get('account.payment.term')
432         terms = False
433         due_date = False
434         default = {'date_due':False}
435         if term_id and amount:
436             terms = term_pool.compute(cr, uid, term_id, amount)
437         if terms:
438             due_date = terms[-1][0]
439             default.update({
440                 'date_due':due_date
441             })
442         return {'value':default}
443
444     def onchange_journal_voucher(self, cr, uid, ids, line_ids=False, tax_id=False, price=0.0, partner_id=False, journal_id=False, ttype=False, company_id=False, context=None):
445         """price
446         Returns a dict that contains new values and context
447
448         @param partner_id: latest value from user input for field partner_id
449         @param args: other arguments
450         @param context: context arguments, like lang, time zone
451
452         @return: Returns a dict which contains new values, and context
453         """
454         default = {
455             'value':{},
456         }
457
458         if not partner_id or not journal_id:
459             return default
460
461         partner_pool = self.pool.get('res.partner')
462         journal_pool = self.pool.get('account.journal')
463
464         journal = journal_pool.browse(cr, uid, journal_id, context=context)
465         partner = partner_pool.browse(cr, uid, partner_id, context=context)
466         account_id = False
467         tr_type = False
468         if journal.type in ('sale','sale_refund'):
469             account_id = partner.property_account_receivable.id
470             tr_type = 'sale'
471         elif journal.type in ('purchase', 'purchase_refund','expense'):
472             account_id = partner.property_account_payable.id
473             tr_type = 'purchase'
474         else:
475             if not journal.default_credit_account_id or not journal.default_debit_account_id:
476                 raise osv.except_osv(_('Error !'), _('Please define default credit/debit accounts on the journal "%s" !') % (journal.name))
477             account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
478             tr_type = 'receipt'
479
480         default['value']['account_id'] = account_id
481         default['value']['type'] = ttype or tr_type
482
483         vals = self.onchange_journal(cr, uid, ids, journal_id, line_ids, tax_id, partner_id, time.strftime('%Y-%m-%d'), price, ttype, company_id, context)
484         default['value'].update(vals.get('value'))
485
486         return default
487
488     def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
489         res =  {'value': {'paid_amount_in_company_currency': amount}}
490         company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
491         if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
492             voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
493             if company_currency.id == payment_rate_currency_id:
494                 company_rate = rate
495             else:
496                 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
497             res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
498         return res
499
500     def onchange_amount(self, cr, uid, ids, amount, rate, partner_id, journal_id, currency_id, ttype, date, payment_rate_currency_id, company_id, context=None):
501         if context is None:
502             context = {}
503         res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
504         ctx = context.copy()
505         ctx.update({'date': date})
506         vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
507         for key in vals.keys():
508             res[key].update(vals[key])
509         return res
510
511     def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
512         if context is None:
513             context = {}
514         #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
515         currency_obj = self.pool.get('res.currency')
516         journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
517         company_id = journal.company_id.id
518         payment_rate = 1.0
519         payment_rate_currency_id = currency_id
520         ctx = context.copy()
521         ctx.update({'date': date})
522         o2m_to_loop = False
523         if ttype == 'receipt':
524             o2m_to_loop = 'line_cr_ids'
525         elif ttype == 'payment':
526             o2m_to_loop = 'line_dr_ids'
527         if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
528             for voucher_line in vals['value'][o2m_to_loop]:
529                 if voucher_line['currency_id'] != currency_id:
530                     # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
531                     # is not in the voucher currency
532                     payment_rate_currency_id = voucher_line['currency_id']
533                     tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
534                     voucher_currency_id = currency_id or journal.company_id.currency_id.id
535                     payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
536                     break
537         res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
538         for key in res.keys():
539             vals[key].update(res[key])
540         vals['value'].update({'payment_rate': payment_rate})
541         if payment_rate_currency_id:
542             vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
543         return vals
544
545     def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
546         if not journal_id:
547             return {}
548         res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
549         vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
550         for key in vals.keys():
551             res[key].update(vals[key])
552         return res
553
554     def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
555         """
556         Returns a dict that contains new values and context
557
558         @param partner_id: latest value from user input for field partner_id
559         @param args: other arguments
560         @param context: context arguments, like lang, time zone
561
562         @return: Returns a dict which contains new values, and context
563         """
564         def _remove_noise_in_o2m():
565             """if the line is partially reconciled, then we must pay attention to display it only once and
566                 in the good o2m.
567                 This function returns True if the line is considered as noise and should not be displayed
568             """
569             if line.reconcile_partial_id:
570                 sign = 1 if ttype == 'receipt' else -1
571                 if currency_id == line.currency_id.id:
572                     if line.amount_residual_currency * sign <= 0:
573                         return True
574                 else:
575                     if line.amount_residual * sign <= 0:
576                         return True
577             return False
578
579         if context is None:
580             context = {}
581         context_multi_currency = context.copy()
582         if date:
583             context_multi_currency.update({'date': date})
584
585         currency_pool = self.pool.get('res.currency')
586         move_line_pool = self.pool.get('account.move.line')
587         partner_pool = self.pool.get('res.partner')
588         journal_pool = self.pool.get('account.journal')
589         line_pool = self.pool.get('account.voucher.line')
590
591         #set default values
592         default = {
593             'value': {'line_ids': [] ,'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
594         }
595
596         #drop existing lines
597         line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
598         if line_ids:
599             line_pool.unlink(cr, uid, line_ids)
600
601         if not partner_id or not journal_id:
602             return default
603
604         journal = journal_pool.browse(cr, uid, journal_id, context=context)
605         partner = partner_pool.browse(cr, uid, partner_id, context=context)
606         currency_id = currency_id or journal.company_id.currency_id.id
607         account_id = False
608         if journal.type in ('sale','sale_refund'):
609             account_id = partner.property_account_receivable.id
610         elif journal.type in ('purchase', 'purchase_refund','expense'):
611             account_id = partner.property_account_payable.id
612         else:
613             account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
614
615         default['value']['account_id'] = account_id
616
617         if journal.type not in ('cash', 'bank'):
618             return default
619
620         total_credit = 0.0
621         total_debit = 0.0
622         account_type = 'receivable'
623         if ttype == 'payment':
624             account_type = 'payable'
625             total_debit = price or 0.0
626         else:
627             total_credit = price or 0.0
628             account_type = 'receivable'
629
630         if not context.get('move_line_ids', False):
631             ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
632         else:
633             ids = context['move_line_ids']
634         invoice_id = context.get('invoice_id', False)
635         company_currency = journal.company_id.currency_id.id
636         move_line_found = False
637
638         #order the lines by most old first
639         ids.reverse()
640         account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
641
642         #compute the total debit/credit and look for a matching open amount or invoice
643         for line in account_move_lines:
644             if _remove_noise_in_o2m():
645                 continue
646
647             if invoice_id:
648                 if line.invoice.id == invoice_id:
649                     #if the invoice linked to the voucher line is equal to the invoice_id in context
650                     #then we assign the amount on that line, whatever the other voucher lines
651                     move_line_found = line.id
652                     break
653             elif currency_id == company_currency:
654                 #otherwise treatments is the same but with other field names
655                 if line.amount_residual == price:
656                     #if the amount residual is equal the amount voucher, we assign it to that voucher
657                     #line, whatever the other voucher lines
658                     move_line_found = line.id
659                     break
660                 #otherwise we will split the voucher amount on each line (by most old first)
661                 total_credit += line.credit or 0.0
662                 total_debit += line.debit or 0.0
663             elif currency_id == line.currency_id.id:
664                 if line.amount_residual_currency == price:
665                     move_line_found = line.id
666                     break
667                 total_credit += line.credit and line.amount_currency or 0.0
668                 total_debit += line.debit and line.amount_currency or 0.0
669
670         #voucher line creation
671         for line in account_move_lines:
672             if _remove_noise_in_o2m():
673                 continue
674
675             if line.currency_id and currency_id==line.currency_id.id:
676                 amount_original = abs(line.amount_currency)
677                 amount_unreconciled = abs(line.amount_residual_currency)
678             else:
679                 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
680                 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
681             line_currency_id = line.currency_id and line.currency_id.id or company_currency
682             rs = {
683                 'name':line.move_id.name,
684                 'type': line.credit and 'dr' or 'cr',
685                 'move_line_id':line.id,
686                 'account_id':line.account_id.id,
687                 'amount_original': amount_original,
688                 'amount': (move_line_found == line.id) and min(price, amount_unreconciled) or 0.0,
689                 'date_original':line.date,
690                 'date_due':line.date_maturity,
691                 'amount_unreconciled': amount_unreconciled,
692                 'currency_id': line_currency_id,
693             }
694
695             #split voucher amount by most old first, but only for lines in the same currency
696             if not move_line_found:
697                 if currency_id == line_currency_id:
698                     if line.credit:
699                         amount = min(amount_unreconciled, abs(total_debit))
700                         rs['amount'] = amount
701                         total_debit -= amount
702                     else:
703                         amount = min(amount_unreconciled, abs(total_credit))
704                         rs['amount'] = amount
705                         total_credit -= amount
706
707             if rs['amount_unreconciled'] == rs['amount']:
708                 rs['reconcile'] = True
709
710             if rs['type'] == 'cr':
711                 default['value']['line_cr_ids'].append(rs)
712             else:
713                 default['value']['line_dr_ids'].append(rs)
714
715             if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
716                 default['value']['pre_line'] = 1
717             elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
718                 default['value']['pre_line'] = 1
719             default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price)
720         return default
721
722     def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
723         if context is None:
724             context = {}
725         res = {'value': {}}
726         #set the default payment rate of the voucher and compute the paid amount in company currency
727         if currency_id and currency_id == payment_rate_currency_id:
728             ctx = context.copy()
729             ctx.update({'date': date})
730             vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
731             for key in vals.keys():
732                 res[key].update(vals[key])
733         return res
734
735     def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
736         """
737         @param date: latest value from user input for field date
738         @param args: other arguments
739         @param context: context arguments, like lang, time zone
740         @return: Returns a dict which contains new values, and context
741         """
742         if context is None:
743             context ={}
744         res = {'value': {}}
745         #set the period of the voucher
746         period_pool = self.pool.get('account.period')
747         currency_obj = self.pool.get('res.currency')
748         ctx = context.copy()
749         ctx.update({'company_id': company_id})
750         pids = period_pool.find(cr, uid, date, context=ctx)
751         if pids:
752             res['value'].update({'period_id':pids[0]})
753         if payment_rate_currency_id:
754             ctx.update({'date': date})
755             payment_rate = 1.0
756             if payment_rate_currency_id != currency_id:
757                 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
758                 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
759                 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
760             vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
761             vals['value'].update({'payment_rate': payment_rate})
762             for key in vals.keys():
763                 res[key].update(vals[key])
764         return res
765
766     def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
767         if not journal_id:
768             return False
769         journal_pool = self.pool.get('account.journal')
770         journal = journal_pool.browse(cr, uid, journal_id, context=context)
771         account_id = journal.default_credit_account_id or journal.default_debit_account_id
772         tax_id = False
773         if account_id and account_id.tax_ids:
774             tax_id = account_id.tax_ids[0].id
775
776         vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
777         vals['value'].update({'tax_id':tax_id,'amount': amount})
778         currency_id = False
779         if journal.currency:
780             currency_id = journal.currency.id
781         vals['value'].update({'currency_id': currency_id})
782         res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
783         for key in res.keys():
784             vals[key].update(res[key])
785         return vals
786
787     def proforma_voucher(self, cr, uid, ids, context=None):
788         self.action_move_line_create(cr, uid, ids, context=context)
789         return {'type': 'ir.actions.act_window_close'}
790
791     def action_cancel_draft(self, cr, uid, ids, context=None):
792         wf_service = netsvc.LocalService("workflow")
793         for voucher_id in ids:
794             wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
795         self.write(cr, uid, ids, {'state':'draft'})
796         return True
797
798     def cancel_voucher(self, cr, uid, ids, context=None):
799         reconcile_pool = self.pool.get('account.move.reconcile')
800         move_pool = self.pool.get('account.move')
801
802         for voucher in self.browse(cr, uid, ids, context=context):
803             recs = []
804             for line in voucher.move_ids:
805                 if line.reconcile_id:
806                     recs += [line.reconcile_id.id]
807                 if line.reconcile_partial_id:
808                     recs += [line.reconcile_partial_id.id]
809
810             reconcile_pool.unlink(cr, uid, recs)
811
812             if voucher.move_id:
813                 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
814                 move_pool.unlink(cr, uid, [voucher.move_id.id])
815         res = {
816             'state':'cancel',
817             'move_id':False,
818         }
819         self.write(cr, uid, ids, res)
820         return True
821
822     def unlink(self, cr, uid, ids, context=None):
823         for t in self.read(cr, uid, ids, ['state'], context=context):
824             if t['state'] not in ('draft', 'cancel'):
825                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Voucher(s) which are already opened or paid !'))
826         return super(account_voucher, self).unlink(cr, uid, ids, context=context)
827
828     def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
829         res = {}
830         if not partner_id:
831             return res
832         res = {'account_id':False}
833         partner_pool = self.pool.get('res.partner')
834         journal_pool = self.pool.get('account.journal')
835         if pay_now == 'pay_later':
836             partner = partner_pool.browse(cr, uid, partner_id)
837             journal = journal_pool.browse(cr, uid, journal_id)
838             if journal.type in ('sale','sale_refund'):
839                 account_id = partner.property_account_receivable.id
840             elif journal.type in ('purchase', 'purchase_refund','expense'):
841                 account_id = partner.property_account_payable.id
842             else:
843                 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
844             res['account_id'] = account_id
845         return {'value':res}
846
847     def _sel_context(self, cr, uid, voucher_id,context=None):
848         """
849         Select the context to use accordingly if it needs to be multicurrency or not.
850
851         :param voucher_id: Id of the actual voucher
852         :return: The returned context will be the same as given in parameter if the voucher currency is the same
853                  than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
854                  the date of the voucher.
855         :rtype: dict
856         """
857         company_currency = self._get_company_currency(cr, uid, voucher_id, context)
858         current_currency = self._get_current_currency(cr, uid, voucher_id, context)
859         if current_currency <> company_currency:
860             context_multi_currency = context.copy()
861             voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
862             context_multi_currency.update({'date': voucher_brw.date})
863             return context_multi_currency
864         return context
865
866     def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
867         '''
868         Return a dict to be use to create the first account move line of given voucher.
869
870         :param voucher_id: Id of voucher what we are creating account_move.
871         :param move_id: Id of account move where this line will be added.
872         :param company_currency: id of currency of the company to which the voucher belong
873         :param current_currency: id of currency of the voucher
874         :return: mapping between fieldname and value of account move line to create
875         :rtype: dict
876         '''
877         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
878         debit = credit = 0.0
879         # TODO: is there any other alternative then the voucher type ??
880         # ANSWER: We can have payment and receipt "In Advance".
881         # TODO: Make this logic available.
882         # -for sale, purchase we have but for the payment and receipt we do not have as based on the bank/cash journal we can not know its payment or receipt
883         if voucher_brw.type in ('purchase', 'payment'):
884             credit = voucher_brw.paid_amount_in_company_currency
885         elif voucher_brw.type in ('sale', 'receipt'):
886             debit = voucher_brw.paid_amount_in_company_currency
887         if debit < 0: credit = -debit; debit = 0.0
888         if credit < 0: debit = -credit; credit = 0.0
889         sign = debit - credit < 0 and -1 or 1
890         #set the first line of the voucher
891         move_line = {
892                 'name': voucher_brw.name or '/',
893                 'debit': debit,
894                 'credit': credit,
895                 'account_id': voucher_brw.account_id.id,
896                 'move_id': move_id,
897                 'journal_id': voucher_brw.journal_id.id,
898                 'period_id': voucher_brw.period_id.id,
899                 'partner_id': voucher_brw.partner_id.id,
900                 'currency_id': company_currency <> current_currency and  current_currency or False,
901                 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
902                 'date': voucher_brw.date,
903                 'date_maturity': voucher_brw.date_due
904             }
905         return move_line
906
907     def account_move_get(self, cr, uid, voucher_id, context=None):
908         '''
909         This method prepare the creation of the account move related to the given voucher.
910
911         :param voucher_id: Id of voucher for which we are creating account_move.
912         :return: mapping between fieldname and value of account move to create
913         :rtype: dict
914         '''
915         seq_obj = self.pool.get('ir.sequence')
916         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
917         if voucher_brw.number:
918             name = voucher_brw.number
919         elif voucher_brw.journal_id.sequence_id:
920             name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
921         else:
922             raise osv.except_osv(_('Error !'),
923                         _('Please define a sequence on the journal !'))
924         if not voucher_brw.reference:
925             ref = name.replace('/','')
926         else:
927             ref = voucher_brw.reference
928
929         move = {
930             'name': name,
931             'journal_id': voucher_brw.journal_id.id,
932             'narration': voucher_brw.narration,
933             'date': voucher_brw.date,
934             'ref': ref,
935             'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
936         }
937         return move
938
939     def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
940         '''
941         Prepare the two lines in company currency due to currency rate difference.
942
943         :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
944             entries
945         :param move_id: Account move wher the move lines will be.
946         :param amount_residual: Amount to be posted.
947         :param company_currency: id of currency of the company to which the voucher belong
948         :param current_currency: id of currency of the voucher
949         :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
950         :rtype: tuple of dict
951         '''
952         if amount_residual > 0:
953             account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
954             if not account_id:
955                 raise osv.except_osv(_('Warning'),_("Unable to create accounting entry for currency rate difference. You have to configure the field 'Income Currency Rate' on the company! "))
956         else:
957             account_id = line.voucher_id.company_id.income_currency_exchange_account_id
958             if not account_id:
959                 raise osv.except_osv(_('Warning'),_("Unable to create accounting entry for currency rate difference. You have to configure the field 'Expense Currency Rate' on the company! "))
960         # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
961         # the receivable/payable account may have a secondary currency, which render this field mandatory
962         account_currency_id = company_currency <> current_currency and current_currency or False
963         move_line = {
964             'journal_id': line.voucher_id.journal_id.id,
965             'period_id': line.voucher_id.period_id.id,
966             'name': _('change')+': '+(line.name or '/'),
967             'account_id': line.account_id.id,
968             'move_id': move_id,
969             'partner_id': line.voucher_id.partner_id.id,
970             'currency_id': account_currency_id,
971             'amount_currency': 0.0,
972             'quantity': 1,
973             'credit': amount_residual > 0 and amount_residual or 0.0,
974             'debit': amount_residual < 0 and -amount_residual or 0.0,
975             'date': line.voucher_id.date,
976         }
977         move_line_counterpart = {
978             'journal_id': line.voucher_id.journal_id.id,
979             'period_id': line.voucher_id.period_id.id,
980             'name': _('change')+': '+(line.name or '/'),
981             'account_id': account_id.id,
982             'move_id': move_id,
983             'amount_currency': 0.0,
984             'partner_id': line.voucher_id.partner_id.id,
985             'currency_id': account_currency_id,
986             'quantity': 1,
987             'debit': amount_residual > 0 and amount_residual or 0.0,
988             'credit': amount_residual < 0 and -amount_residual or 0.0,
989             'date': line.voucher_id.date,
990         }
991         return (move_line, move_line_counterpart)
992
993     def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
994         '''
995         This function convert the amount given in company currency. It takes either the rate in the voucher (if the
996         payment_rate_currency_id is relevant) either the rate encoded in the system.
997
998         :param amount: float. The amount to convert
999         :param voucher: id of the voucher on which we want the conversion
1000         :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1001             field in order to select the good rate to use.
1002         :return: the amount in the currency of the voucher's company
1003         :rtype: float
1004         '''
1005         currency_obj = self.pool.get('res.currency')
1006         voucher = self.browse(cr, uid, voucher_id, context=context)
1007         res = amount
1008         if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1009             # the rate specified on the voucher is for the company currency
1010             res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1011         else:
1012             # the rate specified on the voucher is not relevant, we use all the rates in the system
1013             res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1014         return res
1015
1016     def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1017         '''
1018         Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1019         It returns Tuple with tot_line what is total of difference between debit and credit and
1020         a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1021
1022         :param voucher_id: Voucher id what we are working with
1023         :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1024         :param move_id: Account move wher those lines will be joined.
1025         :param company_currency: id of currency of the company to which the voucher belong
1026         :param current_currency: id of currency of the voucher
1027         :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1028         :rtype: tuple(float, list of int)
1029         '''
1030         if context is None:
1031             context = {}
1032         move_line_obj = self.pool.get('account.move.line')
1033         currency_obj = self.pool.get('res.currency')
1034         tax_obj = self.pool.get('account.tax')
1035         tot_line = line_total
1036         rec_lst_ids = []
1037
1038         voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1039         ctx = context.copy()
1040         ctx.update({'date': voucher_brw.date})
1041         for line in voucher_brw.line_ids:
1042             #create one move line per voucher line where amount is not 0.0
1043             if not line.amount:
1044                 continue
1045             # convert the amount set on the voucher line into the currency of the voucher's company
1046             amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1047             # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1048             # currency rate difference
1049             if line.amount == line.amount_unreconciled:
1050                 currency_rate_difference = line.move_line_id.amount_residual - amount
1051             else:
1052                 currency_rate_difference = 0.0
1053             move_line = {
1054                 'journal_id': voucher_brw.journal_id.id,
1055                 'period_id': voucher_brw.period_id.id,
1056                 'name': line.name or '/',
1057                 'account_id': line.account_id.id,
1058                 'move_id': move_id,
1059                 'partner_id': voucher_brw.partner_id.id,
1060                 'currency_id': line.move_line_id and (company_currency <> line.move_line_id.currency_id.id and line.move_line_id.currency_id.id) or False,
1061                 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1062                 'quantity': 1,
1063                 'credit': 0.0,
1064                 'debit': 0.0,
1065                 'date': voucher_brw.date
1066             }
1067             if amount < 0:
1068                 amount = -amount
1069                 if line.type == 'dr':
1070                     line.type = 'cr'
1071                 else:
1072                     line.type = 'dr'
1073
1074             if (line.type=='dr'):
1075                 tot_line += amount
1076                 move_line['debit'] = amount
1077             else:
1078                 tot_line -= amount
1079                 move_line['credit'] = amount
1080
1081             if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1082                 move_line.update({
1083                     'account_tax_id': voucher_brw.tax_id.id,
1084                 })
1085
1086             if move_line.get('account_tax_id', False):
1087                 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1088                 if not (tax_data.base_code_id and tax_data.tax_code_id):
1089                     raise osv.except_osv(_('No Account Base Code and Account Tax Code!'),_("You have to configure account base code and account tax code on the '%s' tax!") % (tax_data.name))
1090
1091             # compute the amount in foreign currency
1092             foreign_currency_diff = 0.0
1093             amount_currency = False
1094             if line.move_line_id:
1095                 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1096                 # We want to set it on the account move line as soon as the original line had a foreign currency
1097                 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1098                     # we compute the amount in that foreign currency.
1099                     if line.move_line_id.currency_id.id == current_currency:
1100                         # if the voucher and the voucher line share the same currency, there is no computation to do
1101                         sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1102                         amount_currency = sign * (line.amount)
1103                     elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1104                         # if the rate is specified on the voucher, we must use it
1105                         voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1106                         amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1107                     else:
1108                         # otherwise we use the rates of the system (giving the voucher date in the context)
1109                         amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1110                 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1111                     foreign_currency_diff = line.move_line_id.amount_residual_currency + amount_currency
1112
1113             move_line['amount_currency'] = amount_currency
1114             voucher_line = move_line_obj.create(cr, uid, move_line)
1115             rec_ids = [voucher_line, line.move_line_id.id]
1116
1117             if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1118                 # Change difference entry in company currency
1119                 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1120                 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1121                 move_line_obj.create(cr, uid, exch_lines[1], context)
1122                 rec_ids.append(new_id)
1123
1124             if line.move_line_id and line.move_line_id.currency_id and not currency_obj.is_zero(cr, uid, line.move_line_id.currency_id, foreign_currency_diff):
1125                 # Change difference entry in voucher currency
1126                 move_line_foreign_currency = {
1127                     'journal_id': line.voucher_id.journal_id.id,
1128                     'period_id': line.voucher_id.period_id.id,
1129                     'name': _('change')+': '+(line.name or '/'),
1130                     'account_id': line.account_id.id,
1131                     'move_id': move_id,
1132                     'partner_id': line.voucher_id.partner_id.id,
1133                     'currency_id': line.move_line_id.currency_id.id,
1134                     'amount_currency': -1 * foreign_currency_diff,
1135                     'quantity': 1,
1136                     'credit': 0.0,
1137                     'debit': 0.0,
1138                     'date': line.voucher_id.date,
1139                 }
1140                 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1141                 rec_ids.append(new_id)
1142
1143             if line.move_line_id.id:
1144                 rec_lst_ids.append(rec_ids)
1145
1146         return (tot_line, rec_lst_ids)
1147
1148     def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1149         '''
1150         Set a dict to be use to create the writeoff move line.
1151
1152         :param voucher_id: Id of voucher what we are creating account_move.
1153         :param line_total: Amount remaining to be allocated on lines.
1154         :param move_id: Id of account move where this line will be added.
1155         :param name: Description of account move line.
1156         :param company_currency: id of currency of the company to which the voucher belong
1157         :param current_currency: id of currency of the voucher
1158         :return: mapping between fieldname and value of account move line to create
1159         :rtype: dict
1160         '''
1161         currency_obj = self.pool.get('res.currency')
1162         move_line = {}
1163
1164         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1165         current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1166
1167         if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1168             diff = line_total
1169             account_id = False
1170             write_off_name = ''
1171             if voucher_brw.payment_option == 'with_writeoff':
1172                 account_id = voucher_brw.writeoff_acc_id.id
1173                 write_off_name = voucher_brw.comment
1174             elif voucher_brw.type in ('sale', 'receipt'):
1175                 account_id = voucher_brw.partner_id.property_account_receivable.id
1176             else:
1177                 account_id = voucher_brw.partner_id.property_account_payable.id
1178             move_line = {
1179                 'name': write_off_name or name,
1180                 'account_id': account_id,
1181                 'move_id': move_id,
1182                 'partner_id': voucher_brw.partner_id.id,
1183                 'date': voucher_brw.date,
1184                 'credit': diff > 0 and diff or 0.0,
1185                 'debit': diff < 0 and -diff or 0.0,
1186                 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
1187                 'currency_id': company_currency <> current_currency and current_currency or False,
1188                 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1189             }
1190
1191         return move_line
1192
1193     def _get_company_currency(self, cr, uid, voucher_id, context=None):
1194         '''
1195         Get the currency of the actual company.
1196
1197         :param voucher_id: Id of the voucher what i want to obtain company currency.
1198         :return: currency id of the company of the voucher
1199         :rtype: int
1200         '''
1201         return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1202
1203     def _get_current_currency(self, cr, uid, voucher_id, context=None):
1204         '''
1205         Get the currency of the voucher.
1206
1207         :param voucher_id: Id of the voucher what i want to obtain current currency.
1208         :return: currency id of the voucher
1209         :rtype: int
1210         '''
1211         voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1212         return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1213
1214     def action_move_line_create(self, cr, uid, ids, context=None):
1215         '''
1216         Confirm the vouchers given in ids and create the journal entries for each of them
1217         '''
1218         if context is None:
1219             context = {}
1220         move_pool = self.pool.get('account.move')
1221         move_line_pool = self.pool.get('account.move.line')
1222         for voucher in self.browse(cr, uid, ids, context=context):
1223             if voucher.move_id:
1224                 continue
1225             company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1226             current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1227             # we select the context to use accordingly if it's a multicurrency case or not
1228             context = self._sel_context(cr, uid, voucher.id, context)
1229             # But for the operations made by _convert_amount, we always need to give the date in the context
1230             ctx = context.copy()
1231             ctx.update({'date': voucher.date})
1232             # Create the account move record.
1233             move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1234             # Get the name of the account_move just created
1235             name = move_pool.browse(cr, uid, move_id, context=context).name
1236             # Create the first line of the voucher
1237             move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, company_currency, current_currency, context), context)
1238             move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1239             line_total = move_line_brw.debit - move_line_brw.credit
1240             rec_list_ids = []
1241             if voucher.type == 'sale':
1242                 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1243             elif voucher.type == 'purchase':
1244                 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1245             # Create one move line per voucher line where amount is not 0.0
1246             line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1247
1248             # Create the writeoff line if needed
1249             ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1250             if ml_writeoff:
1251                 move_line_pool.create(cr, uid, ml_writeoff, context)
1252             # We post the voucher.
1253             self.write(cr, uid, [voucher.id], {
1254                 'move_id': move_id,
1255                 'state': 'posted',
1256                 'number': name,
1257             })
1258             self.post_send_note(cr, uid, [voucher.id], context=context)
1259             if voucher.journal_id.entry_posted:
1260                 move_pool.post(cr, uid, [move_id], context={})
1261             # We automatically reconcile the account move lines.
1262             reconcile = False
1263             for rec_ids in rec_list_ids:
1264                 if len(rec_ids) >= 2:
1265                     reconcile = move_line_pool.reconcile_partial(cr, uid, rec_ids, writeoff_acc_id=voucher.writeoff_acc_id.id, writeoff_period_id=voucher.period_id.id, writeoff_journal_id=voucher.journal_id.id)
1266             if reconcile:
1267                 self.reconcile_send_note(cr, uid, [voucher.id], context=context)
1268         return True
1269
1270     def copy(self, cr, uid, id, default={}, context=None):
1271         default.update({
1272             'state': 'draft',
1273             'number': False,
1274             'move_id': False,
1275             'line_cr_ids': False,
1276             'line_dr_ids': False,
1277             'reference': False
1278         })
1279         if 'date' not in default:
1280             default['date'] = time.strftime('%Y-%m-%d')
1281         return super(account_voucher, self).copy(cr, uid, id, default, context)
1282
1283     # -----------------------------------------
1284     # OpenChatter notifications and need_action
1285     # -----------------------------------------
1286     _document_type = {
1287         'sale': 'Sales Receipt',
1288         'purchase': 'Purchase Receipt',
1289         'payment': 'Supplier Payment',
1290         'receipt': 'Customer Payment',
1291         False: 'Payment',
1292     }
1293
1294     def create_send_note(self, cr, uid, ids, context=None):
1295         for obj in self.browse(cr, uid, ids, context=context):
1296             message = "%s <b>created</b>." % self._document_type[obj.type or False]
1297             self.message_append_note(cr, uid, [obj.id], body=message, context=context)
1298
1299     def post_send_note(self, cr, uid, ids, context=None):
1300         for obj in self.browse(cr, uid, ids, context=context):
1301             message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
1302             self.message_append_note(cr, uid, [obj.id], body=message, context=context)
1303
1304     def reconcile_send_note(self, cr, uid, ids, context=None):
1305         for obj in self.browse(cr, uid, ids, context=context):
1306             message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
1307             self.message_append_note(cr, uid, [obj.id], body=message, context=context)
1308
1309 account_voucher()
1310
1311 class account_voucher_line(osv.osv):
1312     _name = 'account.voucher.line'
1313     _description = 'Voucher Lines'
1314     _order = "move_line_id"
1315
1316     # If the payment is in the same currency than the invoice, we keep the same amount
1317     # Otherwise, we compute from company currency to payment currency
1318     def _compute_balance(self, cr, uid, ids, name, args, context=None):
1319         currency_pool = self.pool.get('res.currency')
1320         rs_data = {}
1321         for line in self.browse(cr, uid, ids, context=context):
1322             ctx = context.copy()
1323             ctx.update({'date': line.voucher_id.date})
1324             res = {}
1325             company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1326             voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1327             move_line = line.move_line_id or False
1328
1329             if not move_line:
1330                 res['amount_original'] = 0.0
1331                 res['amount_unreconciled'] = 0.0
1332             elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1333                 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1334                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, move_line.currency_id and move_line.currency_id.id or company_currency, voucher_currency, abs(move_line.amount_residual_currency), context=ctx)
1335             elif move_line and move_line.credit > 0:
1336                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1337                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1338             else:
1339                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1340                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1341
1342             rs_data[line.id] = res
1343         return rs_data
1344
1345     def _currency_id(self, cr, uid, ids, name, args, context=None):
1346         '''
1347         This function returns the currency id of a voucher line. It's either the currency of the
1348         associated move line (if any) or the currency of the voucher or the company currency.
1349         '''
1350         res = {}
1351         for line in self.browse(cr, uid, ids, context=context):
1352             move_line = line.move_line_id
1353             if move_line:
1354                 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1355             else:
1356                 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1357         return res
1358
1359     _columns = {
1360         'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1361         'name':fields.char('Description', size=256),
1362         'account_id':fields.many2one('account.account','Account', required=True),
1363         'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1364         'untax_amount':fields.float('Untax Amount'),
1365         'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1366         'reconcile': fields.boolean('Full Reconcile'),
1367         'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1368         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
1369         'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1370         'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1371         'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1372         'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1373         'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1374         'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1375         'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1376     }
1377     _defaults = {
1378         'name': '',
1379     }
1380
1381     def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1382         vals = { 'amount': 0.0}
1383         if reconcile:
1384             vals = { 'amount': amount_unreconciled}
1385         return {'value': vals}
1386
1387     def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1388         vals = {}
1389         if amount:
1390             vals['reconcile'] = (amount == amount_unreconciled)
1391         return {'value': vals}
1392
1393     def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1394         """
1395         Returns a dict that contains new values and context
1396
1397         @param move_line_id: latest value from user input for field move_line_id
1398         @param args: other arguments
1399         @param context: context arguments, like lang, time zone
1400
1401         @return: Returns a dict which contains new values, and context
1402         """
1403         res = {}
1404         move_line_pool = self.pool.get('account.move.line')
1405         if move_line_id:
1406             move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1407             if move_line.credit:
1408                 ttype = 'dr'
1409             else:
1410                 ttype = 'cr'
1411             res.update({
1412                 'account_id': move_line.account_id.id,
1413                 'type': ttype,
1414                 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1415             })
1416         return {
1417             'value':res,
1418         }
1419
1420     def default_get(self, cr, user, fields_list, context=None):
1421         """
1422         Returns default values for fields
1423         @param fields_list: list of fields, for which default values are required to be read
1424         @param context: context arguments, like lang, time zone
1425
1426         @return: Returns a dict that contains default values for fields
1427         """
1428         if context is None:
1429             context = {}
1430         journal_id = context.get('journal_id', False)
1431         partner_id = context.get('partner_id', False)
1432         journal_pool = self.pool.get('account.journal')
1433         partner_pool = self.pool.get('res.partner')
1434         values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1435         if (not journal_id) or ('account_id' not in fields_list):
1436             return values
1437         journal = journal_pool.browse(cr, user, journal_id, context=context)
1438         account_id = False
1439         ttype = 'cr'
1440         if journal.type in ('sale', 'sale_refund'):
1441             account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1442             ttype = 'cr'
1443         elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1444             account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1445             ttype = 'dr'
1446         elif partner_id:
1447             partner = partner_pool.browse(cr, user, partner_id, context=context)
1448             if context.get('type') == 'payment':
1449                 ttype = 'dr'
1450                 account_id = partner.property_account_payable.id
1451             elif context.get('type') == 'receipt':
1452                 account_id = partner.property_account_receivable.id
1453
1454         values.update({
1455             'account_id':account_id,
1456             'type':ttype
1457         })
1458         return values
1459 account_voucher_line()
1460
1461 class account_bank_statement(osv.osv):
1462     _inherit = 'account.bank.statement'
1463
1464     def button_cancel(self, cr, uid, ids, context=None):
1465         voucher_obj = self.pool.get('account.voucher')
1466         for st in self.browse(cr, uid, ids, context=context):
1467             voucher_ids = []
1468             for line in st.line_ids:
1469                 if line.voucher_id:
1470                     voucher_ids.append(line.voucher_id.id)
1471             voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1472         return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1473
1474     def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1475         voucher_obj = self.pool.get('account.voucher')
1476         wf_service = netsvc.LocalService("workflow")
1477         move_line_obj = self.pool.get('account.move.line')
1478         bank_st_line_obj = self.pool.get('account.bank.statement.line')
1479         st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1480         if st_line.voucher_id:
1481             voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1482             if st_line.voucher_id.state == 'cancel':
1483                 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1484             wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1485
1486             v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1487             bank_st_line_obj.write(cr, uid, [st_line_id], {
1488                 'move_ids': [(4, v.move_id.id, False)]
1489             })
1490
1491             return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1492         return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1493
1494 account_bank_statement()
1495
1496 class account_bank_statement_line(osv.osv):
1497     _inherit = 'account.bank.statement.line'
1498
1499     def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1500         if not ids:
1501             return {}
1502         res = {}
1503         for line in self.browse(cursor, user, ids, context=context):
1504             if line.voucher_id:
1505                 res[line.id] = line.voucher_id.amount#
1506             else:
1507                 res[line.id] = 0.0
1508         return res
1509
1510     def _check_amount(self, cr, uid, ids, context=None):
1511         for obj in self.browse(cr, uid, ids, context=context):
1512             if obj.voucher_id:
1513                 diff = abs(obj.amount) - obj.voucher_id.amount
1514                 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1515                     return False
1516         return True
1517
1518     _constraints = [
1519         (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1520     ]
1521
1522     _columns = {
1523         'amount_reconciled': fields.function(_amount_reconciled,
1524             string='Amount reconciled', type='float'),
1525         'voucher_id': fields.many2one('account.voucher', 'Payment'),
1526     }
1527
1528     def unlink(self, cr, uid, ids, context=None):
1529         voucher_obj = self.pool.get('account.voucher')
1530         statement_line = self.browse(cr, uid, ids, context=context)
1531         unlink_ids = []
1532         for st_line in statement_line:
1533             if st_line.voucher_id:
1534                 unlink_ids.append(st_line.voucher_id.id)
1535         voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1536         return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1537
1538 account_bank_statement_line()
1539
1540 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1541     results = []
1542     for operation in operations:
1543         result = None
1544         if not isinstance(operation, (list, tuple)):
1545             result = target_osv.read(cr, uid, operation, fields, context=context)
1546         elif operation[0] == 0:
1547             # may be necessary to check if all the fields are here and get the default values?
1548             result = operation[2]
1549         elif operation[0] == 1:
1550             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1551             if not result: result = {}
1552             result.update(operation[2])
1553         elif operation[0] == 4:
1554             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1555         if result != None:
1556             results.append(result)
1557     return results
1558
1559
1560 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: