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