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