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