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