[FIX]Wrong condition and message
[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         for line in voucher_brw.line_ids:
1086             #create one move line per voucher line where amount is not 0.0
1087             if not line.amount and not float_compare(line.move_line_id.invoice.amount_total,0.0,precision_rounding=0.0):
1088                 continue
1089             # convert the amount set on the voucher line into the currency of the voucher's company
1090             amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1091             # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1092             # currency rate difference
1093             if line.amount == line.amount_unreconciled:
1094                 if not line.move_line_id:
1095                     raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1096                 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1097                 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1098             else:
1099                 currency_rate_difference = 0.0
1100             move_line = {
1101                 'journal_id': voucher_brw.journal_id.id,
1102                 'period_id': voucher_brw.period_id.id,
1103                 'name': line.name or '/',
1104                 'account_id': line.account_id.id,
1105                 'move_id': move_id,
1106                 'partner_id': voucher_brw.partner_id.id,
1107                 '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,
1108                 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1109                 'quantity': 1,
1110                 'credit': 0.0,
1111                 'debit': 0.0,
1112                 'date': voucher_brw.date
1113             }
1114             if amount < 0:
1115                 amount = -amount
1116                 if line.type == 'dr':
1117                     line.type = 'cr'
1118                 else:
1119                     line.type = 'dr'
1120
1121             if (line.type=='dr'):
1122                 tot_line += amount
1123                 move_line['debit'] = amount
1124             else:
1125                 tot_line -= amount
1126                 move_line['credit'] = amount
1127
1128             if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1129                 move_line.update({
1130                     'account_tax_id': voucher_brw.tax_id.id,
1131                 })
1132
1133             if move_line.get('account_tax_id', False):
1134                 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1135                 if not (tax_data.base_code_id and tax_data.tax_code_id):
1136                     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))
1137
1138             # compute the amount in foreign currency
1139             foreign_currency_diff = 0.0
1140             amount_currency = False
1141             if line.move_line_id:
1142                 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1143                 # We want to set it on the account move line as soon as the original line had a foreign currency
1144                 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1145                     # we compute the amount in that foreign currency.
1146                     if line.move_line_id.currency_id.id == current_currency:
1147                         # if the voucher and the voucher line share the same currency, there is no computation to do
1148                         sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1149                         amount_currency = sign * (line.amount)
1150                     elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1151                         # if the rate is specified on the voucher, we must use it
1152                         voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1153                         amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1154                     else:
1155                         # otherwise we use the rates of the system (giving the voucher date in the context)
1156                         amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1157                 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1158                     sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1159                     foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1160
1161             move_line['amount_currency'] = amount_currency
1162             voucher_line = move_line_obj.create(cr, uid, move_line)
1163             rec_ids = [voucher_line, line.move_line_id.id]
1164
1165             if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1166                 # Change difference entry in company currency
1167                 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1168                 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1169                 move_line_obj.create(cr, uid, exch_lines[1], context)
1170                 rec_ids.append(new_id)
1171
1172             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):
1173                 # Change difference entry in voucher currency
1174                 move_line_foreign_currency = {
1175                     'journal_id': line.voucher_id.journal_id.id,
1176                     'period_id': line.voucher_id.period_id.id,
1177                     'name': _('change')+': '+(line.name or '/'),
1178                     'account_id': line.account_id.id,
1179                     'move_id': move_id,
1180                     'partner_id': line.voucher_id.partner_id.id,
1181                     'currency_id': line.move_line_id.currency_id.id,
1182                     'amount_currency': -1 * foreign_currency_diff,
1183                     'quantity': 1,
1184                     'credit': 0.0,
1185                     'debit': 0.0,
1186                     'date': line.voucher_id.date,
1187                 }
1188                 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1189                 rec_ids.append(new_id)
1190
1191             if line.move_line_id.id:
1192                 rec_lst_ids.append(rec_ids)
1193
1194         return (tot_line, rec_lst_ids)
1195
1196     def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1197         '''
1198         Set a dict to be use to create the writeoff move line.
1199
1200         :param voucher_id: Id of voucher what we are creating account_move.
1201         :param line_total: Amount remaining to be allocated on lines.
1202         :param move_id: Id of account move where this line will be added.
1203         :param name: Description of account move line.
1204         :param company_currency: id of currency of the company to which the voucher belong
1205         :param current_currency: id of currency of the voucher
1206         :return: mapping between fieldname and value of account move line to create
1207         :rtype: dict
1208         '''
1209         currency_obj = self.pool.get('res.currency')
1210         move_line = {}
1211
1212         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1213         current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1214
1215         if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1216             diff = line_total
1217             account_id = False
1218             write_off_name = ''
1219             if voucher_brw.payment_option == 'with_writeoff':
1220                 account_id = voucher_brw.writeoff_acc_id.id
1221                 write_off_name = voucher_brw.comment
1222             elif voucher_brw.type in ('sale', 'receipt'):
1223                 account_id = voucher_brw.partner_id.property_account_receivable.id
1224             else:
1225                 account_id = voucher_brw.partner_id.property_account_payable.id
1226             sign = voucher_brw.type == 'payment' and -1 or 1
1227             move_line = {
1228                 'name': write_off_name or name,
1229                 'account_id': account_id,
1230                 'move_id': move_id,
1231                 'partner_id': voucher_brw.partner_id.id,
1232                 'date': voucher_brw.date,
1233                 'credit': diff > 0 and diff or 0.0,
1234                 'debit': diff < 0 and -diff or 0.0,
1235                 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1236                 'currency_id': company_currency <> current_currency and current_currency or False,
1237                 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1238             }
1239
1240         return move_line
1241
1242     def _get_company_currency(self, cr, uid, voucher_id, context=None):
1243         '''
1244         Get the currency of the actual company.
1245
1246         :param voucher_id: Id of the voucher what i want to obtain company currency.
1247         :return: currency id of the company of the voucher
1248         :rtype: int
1249         '''
1250         return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1251
1252     def _get_current_currency(self, cr, uid, voucher_id, context=None):
1253         '''
1254         Get the currency of the voucher.
1255
1256         :param voucher_id: Id of the voucher what i want to obtain current currency.
1257         :return: currency id of the voucher
1258         :rtype: int
1259         '''
1260         voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1261         return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1262
1263     def action_move_line_create(self, cr, uid, ids, context=None):
1264         '''
1265         Confirm the vouchers given in ids and create the journal entries for each of them
1266         '''
1267         if context is None:
1268             context = {}
1269         move_pool = self.pool.get('account.move')
1270         move_line_pool = self.pool.get('account.move.line')
1271         for voucher in self.browse(cr, uid, ids, context=context):
1272             if voucher.move_id:
1273                 continue
1274             company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1275             current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1276             # we select the context to use accordingly if it's a multicurrency case or not
1277             context = self._sel_context(cr, uid, voucher.id, context)
1278             # But for the operations made by _convert_amount, we always need to give the date in the context
1279             ctx = context.copy()
1280             ctx.update({'date': voucher.date})
1281             # Create the account move record.
1282             move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1283             # Get the name of the account_move just created
1284             name = move_pool.browse(cr, uid, move_id, context=context).name
1285             # Create the first line of the voucher
1286             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)
1287             move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1288             line_total = move_line_brw.debit - move_line_brw.credit
1289             rec_list_ids = []
1290             if voucher.type == 'sale':
1291                 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1292             elif voucher.type == 'purchase':
1293                 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1294             # Create one move line per voucher line where amount is not 0.0
1295             line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1296
1297             # Create the writeoff line if needed
1298             ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1299             if ml_writeoff:
1300                 move_line_pool.create(cr, uid, ml_writeoff, context)
1301             # We post the voucher.
1302             self.write(cr, uid, [voucher.id], {
1303                 'move_id': move_id,
1304                 'state': 'posted',
1305                 'number': name,
1306             })
1307             if voucher.journal_id.entry_posted:
1308                 move_pool.post(cr, uid, [move_id], context={})
1309             # We automatically reconcile the account move lines.
1310             reconcile = False
1311             for rec_ids in rec_list_ids:
1312                 if len(rec_ids) >= 2:
1313                     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)
1314         return True
1315
1316     def copy(self, cr, uid, id, default=None, context=None):
1317         if default is None:
1318             default = {}
1319         default.update({
1320             'state': 'draft',
1321             'number': False,
1322             'move_id': False,
1323             'line_cr_ids': False,
1324             'line_dr_ids': False,
1325             'reference': False
1326         })
1327         if 'date' not in default:
1328             default['date'] = time.strftime('%Y-%m-%d')
1329         return super(account_voucher, self).copy(cr, uid, id, default, context)
1330
1331
1332 class account_voucher_line(osv.osv):
1333     _name = 'account.voucher.line'
1334     _description = 'Voucher Lines'
1335     _order = "move_line_id"
1336
1337     # If the payment is in the same currency than the invoice, we keep the same amount
1338     # Otherwise, we compute from company currency to payment currency
1339     def _compute_balance(self, cr, uid, ids, name, args, context=None):
1340         currency_pool = self.pool.get('res.currency')
1341         rs_data = {}
1342         for line in self.browse(cr, uid, ids, context=context):
1343             ctx = context.copy()
1344             ctx.update({'date': line.voucher_id.date})
1345             res = {}
1346             company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1347             voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1348             move_line = line.move_line_id or False
1349
1350             if not move_line:
1351                 res['amount_original'] = 0.0
1352                 res['amount_unreconciled'] = 0.0
1353             elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1354                 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1355                 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)
1356             elif move_line and move_line.credit > 0:
1357                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1358                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1359             else:
1360                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1361                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1362
1363             rs_data[line.id] = res
1364         return rs_data
1365
1366     def _currency_id(self, cr, uid, ids, name, args, context=None):
1367         '''
1368         This function returns the currency id of a voucher line. It's either the currency of the
1369         associated move line (if any) or the currency of the voucher or the company currency.
1370         '''
1371         res = {}
1372         for line in self.browse(cr, uid, ids, context=context):
1373             move_line = line.move_line_id
1374             if move_line:
1375                 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1376             else:
1377                 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1378         return res
1379
1380     _columns = {
1381         'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1382         'name':fields.char('Description', size=256),
1383         'account_id':fields.many2one('account.account','Account', required=True),
1384         'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1385         'untax_amount':fields.float('Untax Amount'),
1386         'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1387         'reconcile': fields.boolean('Full Reconcile'),
1388         'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1389         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
1390         'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1391         'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1392         'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1393         'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1394         'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1395         'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1396         'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1397     }
1398     _defaults = {
1399         'name': '',
1400     }
1401
1402     def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1403         vals = {'amount': 0.0}
1404         if reconcile:
1405             vals = { 'amount': amount_unreconciled}
1406         return {'value': vals}
1407
1408     def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1409         vals = {}
1410         if amount:
1411             vals['reconcile'] = (amount == amount_unreconciled)
1412         return {'value': vals}
1413
1414     def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1415         """
1416         Returns a dict that contains new values and context
1417
1418         @param move_line_id: latest value from user input for field move_line_id
1419         @param args: other arguments
1420         @param context: context arguments, like lang, time zone
1421
1422         @return: Returns a dict which contains new values, and context
1423         """
1424         res = {}
1425         move_line_pool = self.pool.get('account.move.line')
1426         if move_line_id:
1427             move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1428             if move_line.credit:
1429                 ttype = 'dr'
1430             else:
1431                 ttype = 'cr'
1432             res.update({
1433                 'account_id': move_line.account_id.id,
1434                 'type': ttype,
1435                 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1436             })
1437         return {
1438             'value':res,
1439         }
1440
1441     def default_get(self, cr, user, fields_list, context=None):
1442         """
1443         Returns default values for fields
1444         @param fields_list: list of fields, for which default values are required to be read
1445         @param context: context arguments, like lang, time zone
1446
1447         @return: Returns a dict that contains default values for fields
1448         """
1449         if context is None:
1450             context = {}
1451         journal_id = context.get('journal_id', False)
1452         partner_id = context.get('partner_id', False)
1453         journal_pool = self.pool.get('account.journal')
1454         partner_pool = self.pool.get('res.partner')
1455         values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1456         if (not journal_id) or ('account_id' not in fields_list):
1457             return values
1458         journal = journal_pool.browse(cr, user, journal_id, context=context)
1459         account_id = False
1460         ttype = 'cr'
1461         if journal.type in ('sale', 'sale_refund'):
1462             account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1463             ttype = 'cr'
1464         elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1465             account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1466             ttype = 'dr'
1467         elif partner_id:
1468             partner = partner_pool.browse(cr, user, partner_id, context=context)
1469             if context.get('type') == 'payment':
1470                 ttype = 'dr'
1471                 account_id = partner.property_account_payable.id
1472             elif context.get('type') == 'receipt':
1473                 account_id = partner.property_account_receivable.id
1474
1475         values.update({
1476             'account_id':account_id,
1477             'type':ttype
1478         })
1479         return values
1480 account_voucher_line()
1481
1482 class account_bank_statement(osv.osv):
1483     _inherit = 'account.bank.statement'
1484
1485     def button_confirm_bank(self, cr, uid, ids, context=None):
1486         voucher_obj = self.pool.get('account.voucher')
1487         voucher_ids = []
1488         for statement in self.browse(cr, uid, ids, context=context):
1489             voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1490         if voucher_ids:
1491             voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1492         return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1493
1494     def button_cancel(self, cr, uid, ids, context=None):
1495         voucher_obj = self.pool.get('account.voucher')
1496         for st in self.browse(cr, uid, ids, context=context):
1497             voucher_ids = []
1498             for line in st.line_ids:
1499                 if line.voucher_id:
1500                     voucher_ids.append(line.voucher_id.id)
1501             voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1502         return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1503
1504     def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1505         voucher_obj = self.pool.get('account.voucher')
1506         wf_service = netsvc.LocalService("workflow")
1507         move_line_obj = self.pool.get('account.move.line')
1508         bank_st_line_obj = self.pool.get('account.bank.statement.line')
1509         st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1510         if st_line.voucher_id:
1511             voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1512             if st_line.voucher_id.state == 'cancel':
1513                 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1514             wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1515
1516             v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1517             bank_st_line_obj.write(cr, uid, [st_line_id], {
1518                 'move_ids': [(4, v.move_id.id, False)]
1519             })
1520
1521             return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1522         return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1523
1524     def write(self, cr, uid, ids, vals, context=None):
1525         # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1526         # Because the voucher keeps in memory the journal it was created with.
1527         for bk_st in self.browse(cr, uid, ids, context=context):
1528             if vals.get('journal_id') and bk_st.line_ids:
1529                 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1530                     raise osv.except_osv(_('Unable to change journal !'), _('You can not change the journal as you already reconciled some statement lines!'))
1531         return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1532
1533 account_bank_statement()
1534
1535 class account_bank_statement_line(osv.osv):
1536     _inherit = 'account.bank.statement.line'
1537
1538     def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1539         res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1540         if 'value' not in res:
1541             res['value'] = {}
1542         res['value'].update({'voucher_id' : False})
1543         return res
1544
1545     def onchange_amount(self, cr, uid, ids, amount, context=None):
1546         return {'value' :  {'voucher_id' : False}}
1547
1548     def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1549         if not ids:
1550             return {}
1551         res = {}
1552         for line in self.browse(cursor, user, ids, context=context):
1553             if line.voucher_id:
1554                 res[line.id] = line.voucher_id.amount#
1555             else:
1556                 res[line.id] = 0.0
1557         return res
1558
1559     def _check_amount(self, cr, uid, ids, context=None):
1560         for obj in self.browse(cr, uid, ids, context=context):
1561             if obj.voucher_id:
1562                 diff = abs(obj.amount) - obj.voucher_id.amount
1563                 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1564                     return False
1565         return True
1566
1567     _constraints = [
1568         (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1569     ]
1570
1571     _columns = {
1572         'amount_reconciled': fields.function(_amount_reconciled,
1573             string='Amount reconciled', type='float'),
1574         'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1575     }
1576
1577     def unlink(self, cr, uid, ids, context=None):
1578         voucher_obj = self.pool.get('account.voucher')
1579         statement_line = self.browse(cr, uid, ids, context=context)
1580         unlink_ids = []
1581         for st_line in statement_line:
1582             if st_line.voucher_id:
1583                 unlink_ids.append(st_line.voucher_id.id)
1584         voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1585         return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1586
1587 account_bank_statement_line()
1588
1589 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1590     results = []
1591     for operation in operations:
1592         result = None
1593         if not isinstance(operation, (list, tuple)):
1594             result = target_osv.read(cr, uid, operation, fields, context=context)
1595         elif operation[0] == 0:
1596             # may be necessary to check if all the fields are here and get the default values?
1597             result = operation[2]
1598         elif operation[0] == 1:
1599             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1600             if not result: result = {}
1601             result.update(operation[2])
1602         elif operation[0] == 4:
1603             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1604         if result != None:
1605             results.append(result)
1606     return results
1607
1608
1609 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: