[FIX]account_voucher: change payment view to add some field and currency when it...
[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':{}}
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         if voucher_currency:
236             # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
237             is_multi_currency = True
238         else:
239             #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
240             for voucher_line in line_dr_ids+line_cr_ids:
241                 company_currency = False
242                 company_currency = voucher_line.get('move_line_id', False) and self.pool.get('account.move.line').browse(cr, uid, voucher_line.get('move_line_id'), context=context).company_id.currency_id.id
243                 if voucher_line.get('currency_id', company_currency) != company_currency:
244                     is_multi_currency = True
245                     break
246         return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
247
248     def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
249         if not ids: return {}
250         currency_obj = self.pool.get('res.currency')
251         res = {}
252         debit = credit = 0.0
253         for voucher in self.browse(cr, uid, ids, context=context):
254             sign = voucher.type == 'payment' and -1 or 1
255             for l in voucher.line_dr_ids:
256                 debit += l.amount
257             for l in voucher.line_cr_ids:
258                 credit += l.amount
259             currency = voucher.currency_id or voucher.company_id.currency_id
260             res[voucher.id] =  currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
261         return res
262
263     def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
264         if not ids: return {}
265         res = {}
266         rate = 1.0
267         for voucher in self.browse(cr, uid, ids, context=context):
268             if voucher.currency_id:
269                 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
270                     rate =  1 / voucher.payment_rate
271                 else:
272                     ctx = context.copy()
273                     ctx.update({'date': voucher.date})
274                     voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
275                     company_currency_rate = voucher.company_id.currency_id.rate
276                     rate = voucher_rate * company_currency_rate
277             res[voucher.id] =  voucher.amount / rate
278         return res
279
280     _name = 'account.voucher'
281     _description = 'Accounting Voucher'
282     _inherit = ['mail.thread']
283     _order = "date desc, id desc"
284 #    _rec_name = 'number'
285     _track = {
286         'state': {
287             'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
288         },
289     }
290
291     _columns = {
292         '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."),
293         'type':fields.selection([
294             ('sale','Sale'),
295             ('purchase','Purchase'),
296             ('payment','Payment'),
297             ('receipt','Receipt'),
298         ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
299         'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
300         'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
301         'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
302         'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
303         'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
304         'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
305             domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
306         'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
307             domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
308         'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
309         'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
310 #        'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
311         'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
312         'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
313         'state':fields.selection(
314             [('draft','Draft'),
315              ('cancel','Cancelled'),
316              ('proforma','Pro-forma'),
317              ('posted','Posted')
318             ], 'Status', readonly=True, size=32, track_visibility='onchange',
319             help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
320                         \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
321                         \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
322                         \n* The \'Cancelled\' status is used when user cancel voucher.'),
323         'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
324         'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
325         'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
326         'number': fields.char('Number', size=32, readonly=True,),
327         'move_id':fields.many2one('account.move', 'Account Entry'),
328         'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
329         'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
330         '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'),
331         'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
332         'pay_now':fields.selection([
333             ('pay_now','Pay Directly'),
334             ('pay_later','Pay Later or Group Funds'),
335         ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
336         'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
337         'pre_line':fields.boolean('Previous Payments ?', required=False),
338         'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
339         'payment_option':fields.selection([
340                                            ('without_writeoff', 'Keep Open'),
341                                            ('with_writeoff', 'Reconcile Payment Balance'),
342                                            ], '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)"),
343         'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
344         'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
345         'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
346         '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."),
347         'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
348         'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
349             help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field)  and the voucher currency.'),
350         'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
351         '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'),
352     }
353     _defaults = {
354         'active': True,
355         'period_id': _get_period,
356         'partner_id': _get_partner,
357         'journal_id':_get_journal,
358         'currency_id': _get_currency,
359         'reference': _get_reference,
360         'narration':_get_narration,
361         'amount': _get_amount,
362         'type':_get_type,
363         'state': 'draft',
364         'pay_now': 'pay_now',
365         'name': '',
366         'date': lambda *a: time.strftime('%Y-%m-%d'),
367         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
368         'tax_id': _get_tax,
369         'payment_option': 'without_writeoff',
370         'comment': _('Write-Off'),
371         'payment_rate': 1.0,
372         'payment_rate_currency_id': _get_payment_rate_currency,
373     }
374
375     def compute_tax(self, cr, uid, ids, context=None):
376         tax_pool = self.pool.get('account.tax')
377         partner_pool = self.pool.get('res.partner')
378         position_pool = self.pool.get('account.fiscal.position')
379         voucher_line_pool = self.pool.get('account.voucher.line')
380         voucher_pool = self.pool.get('account.voucher')
381         if context is None: context = {}
382
383         for voucher in voucher_pool.browse(cr, uid, ids, context=context):
384             voucher_amount = 0.0
385             for line in voucher.line_ids:
386                 voucher_amount += line.untax_amount or line.amount
387                 line.amount = line.untax_amount or line.amount
388                 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
389
390             if not voucher.tax_id:
391                 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
392                 continue
393
394             tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
395             partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
396             taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
397             tax = tax_pool.browse(cr, uid, taxes, context=context)
398
399             total = voucher_amount
400             total_tax = 0.0
401
402             if not tax[0].price_include:
403                 for line in voucher.line_ids:
404                     for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
405                         total_tax += tax_line.get('amount', 0.0)
406                 total += total_tax
407             else:
408                 for line in voucher.line_ids:
409                     line_total = 0.0
410                     line_tax = 0.0
411
412                     for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
413                         line_tax += tax_line.get('amount', 0.0)
414                         line_total += tax_line.get('price_unit')
415                     total_tax += line_tax
416                     untax_amount = line.untax_amount or line.amount
417                     voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
418
419             self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
420         return True
421
422     def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
423         context = context or {}
424         tax_pool = self.pool.get('account.tax')
425         partner_pool = self.pool.get('res.partner')
426         position_pool = self.pool.get('account.fiscal.position')
427         line_pool = self.pool.get('account.voucher.line')
428         res = {
429             'tax_amount': False,
430             'amount': False,
431         }
432         voucher_total = 0.0
433
434         line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
435
436         total_tax = 0.0
437         for line in line_ids:
438             line_amount = 0.0
439             line_amount = line.get('amount',0.0)
440
441             if tax_id:
442                 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
443                 if partner_id:
444                     partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
445                     taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
446                     tax = tax_pool.browse(cr, uid, taxes, context=context)
447
448                 if not tax[0].price_include:
449                     for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
450                         total_tax += tax_line.get('amount')
451
452             voucher_total += line_amount
453         total = voucher_total + total_tax
454
455         res.update({
456             'amount': total or voucher_total,
457             'tax_amount': total_tax
458         })
459         return {
460             'value': res
461         }
462
463     def onchange_term_id(self, cr, uid, ids, term_id, amount):
464         term_pool = self.pool.get('account.payment.term')
465         terms = False
466         due_date = False
467         default = {'date_due':False}
468         if term_id and amount:
469             terms = term_pool.compute(cr, uid, term_id, amount)
470         if terms:
471             due_date = terms[-1][0]
472             default.update({
473                 'date_due':due_date
474             })
475         return {'value':default}
476
477     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):
478         """price
479         Returns a dict that contains new values and context
480
481         @param partner_id: latest value from user input for field partner_id
482         @param args: other arguments
483         @param context: context arguments, like lang, time zone
484
485         @return: Returns a dict which contains new values, and context
486         """
487         default = {
488             'value':{},
489         }
490
491         if not partner_id or not journal_id:
492             return default
493
494         partner_pool = self.pool.get('res.partner')
495         journal_pool = self.pool.get('account.journal')
496
497         journal = journal_pool.browse(cr, uid, journal_id, context=context)
498         partner = partner_pool.browse(cr, uid, partner_id, context=context)
499         account_id = False
500         tr_type = False
501         if journal.type in ('sale','sale_refund'):
502             account_id = partner.property_account_receivable.id
503             tr_type = 'sale'
504         elif journal.type in ('purchase', 'purchase_refund','expense'):
505             account_id = partner.property_account_payable.id
506             tr_type = 'purchase'
507         else:
508             if not journal.default_credit_account_id or not journal.default_debit_account_id:
509                 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
510             account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
511             tr_type = 'receipt'
512
513         default['value']['account_id'] = account_id
514         default['value']['type'] = ttype or tr_type
515
516         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)
517         default['value'].update(vals.get('value'))
518
519         return default
520
521     def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
522         res =  {'value': {'paid_amount_in_company_currency': amount}}
523         company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
524         if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
525             voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
526             if company_currency.id == payment_rate_currency_id:
527                 company_rate = rate
528             else:
529                 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
530             res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
531         return res
532
533     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):
534         if context is None:
535             context = {}
536         res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
537         ctx = context.copy()
538         ctx.update({'date': date})
539         vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
540         for key in vals.keys():
541             res[key].update(vals[key])
542         return res
543
544     def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
545         if context is None:
546             context = {}
547         #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
548         currency_obj = self.pool.get('res.currency')
549         journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
550         company_id = journal.company_id.id
551         payment_rate = 1.0
552         payment_rate_currency_id = currency_id
553         ctx = context.copy()
554         ctx.update({'date': date})
555         o2m_to_loop = False
556         if ttype == 'receipt':
557             o2m_to_loop = 'line_cr_ids'
558         elif ttype == 'payment':
559             o2m_to_loop = 'line_dr_ids'
560         if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
561             for voucher_line in vals['value'][o2m_to_loop]:
562                 if voucher_line['currency_id'] != currency_id:
563                     # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
564                     # is not in the voucher currency
565                     payment_rate_currency_id = voucher_line['currency_id']
566                     tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
567                     voucher_currency_id = currency_id or journal.company_id.currency_id.id
568                     payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
569                     break
570         res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
571         for key in res.keys():
572             vals[key].update(res[key])
573         vals['value'].update({'payment_rate': payment_rate})
574         if payment_rate_currency_id:
575             vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
576         return vals
577
578     def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
579         if not journal_id:
580             return {}
581         res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
582         vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
583         for key in vals.keys():
584             res[key].update(vals[key])
585         #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not 
586         # [pre_line, line_cr_ids, payment_rate...] for type purchase.
587         # We should definitively split account.voucher object in two and make distinct on_change functions. In the 
588         # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the 
589         # onchange returns a value for them
590         if ttype == 'sale':
591             del(res['value']['line_dr_ids'])
592             del(res['value']['pre_line'])
593             del(res['value']['payment_rate'])
594         elif ttype == 'purchase':
595             del(res['value']['line_cr_ids'])
596             del(res['value']['pre_line'])
597             del(res['value']['payment_rate'])
598         return res
599
600     def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
601         """
602         Returns a dict that contains new values and context
603
604         @param partner_id: latest value from user input for field partner_id
605         @param args: other arguments
606         @param context: context arguments, like lang, time zone
607
608         @return: Returns a dict which contains new values, and context
609         """
610         def _remove_noise_in_o2m():
611             """if the line is partially reconciled, then we must pay attention to display it only once and
612                 in the good o2m.
613                 This function returns True if the line is considered as noise and should not be displayed
614             """
615             if line.reconcile_partial_id:
616                 if currency_id == line.currency_id.id:
617                     if line.amount_residual_currency <= 0:
618                         return True
619                 else:
620                     if line.amount_residual <= 0:
621                         return True
622             return False
623
624         if context is None:
625             context = {}
626         context_multi_currency = context.copy()
627         if date:
628             context_multi_currency.update({'date': date})
629
630         currency_pool = self.pool.get('res.currency')
631         move_line_pool = self.pool.get('account.move.line')
632         partner_pool = self.pool.get('res.partner')
633         journal_pool = self.pool.get('account.journal')
634         line_pool = self.pool.get('account.voucher.line')
635
636         #set default values
637         default = {
638             'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
639         }
640
641         #drop existing lines
642         line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
643         if line_ids:
644             line_pool.unlink(cr, uid, line_ids)
645
646         if not partner_id or not journal_id:
647             return default
648
649         journal = journal_pool.browse(cr, uid, journal_id, context=context)
650         partner = partner_pool.browse(cr, uid, partner_id, context=context)
651         currency_id = currency_id or journal.company_id.currency_id.id
652         account_id = False
653         if journal.type in ('sale','sale_refund'):
654             account_id = partner.property_account_receivable.id
655         elif journal.type in ('purchase', 'purchase_refund','expense'):
656             account_id = partner.property_account_payable.id
657         else:
658             account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
659
660         default['value']['account_id'] = account_id
661
662         if journal.type not in ('cash', 'bank'):
663             return default
664
665         total_credit = 0.0
666         total_debit = 0.0
667         account_type = 'receivable'
668         if ttype == 'payment':
669             account_type = 'payable'
670             total_debit = price or 0.0
671         else:
672             total_credit = price or 0.0
673             account_type = 'receivable'
674
675         if not context.get('move_line_ids', False):
676             ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
677         else:
678             ids = context['move_line_ids']
679         invoice_id = context.get('invoice_id', False)
680         company_currency = journal.company_id.currency_id.id
681         move_line_found = False
682
683         #order the lines by most old first
684         ids.reverse()
685         account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
686
687         #compute the total debit/credit and look for a matching open amount or invoice
688         for line in account_move_lines:
689             if _remove_noise_in_o2m():
690                 continue
691
692             if invoice_id:
693                 if line.invoice.id == invoice_id:
694                     #if the invoice linked to the voucher line is equal to the invoice_id in context
695                     #then we assign the amount on that line, whatever the other voucher lines
696                     move_line_found = line.id
697                     break
698             elif currency_id == company_currency:
699                 #otherwise treatments is the same but with other field names
700                 if line.amount_residual == price:
701                     #if the amount residual is equal the amount voucher, we assign it to that voucher
702                     #line, whatever the other voucher lines
703                     move_line_found = line.id
704                     break
705                 #otherwise we will split the voucher amount on each line (by most old first)
706                 total_credit += line.credit or 0.0
707                 total_debit += line.debit or 0.0
708             elif currency_id == line.currency_id.id:
709                 if line.amount_residual_currency == price:
710                     move_line_found = line.id
711                     break
712                 total_credit += line.credit and line.amount_currency or 0.0
713                 total_debit += line.debit and line.amount_currency or 0.0
714
715         #voucher line creation
716         for line in account_move_lines:
717
718             if _remove_noise_in_o2m():
719                 continue
720
721             if line.currency_id and currency_id==line.currency_id.id:
722                 amount_original = abs(line.amount_currency)
723                 amount_unreconciled = abs(line.amount_residual_currency)
724             else:
725                 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
726                 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
727             line_currency_id = line.currency_id and line.currency_id.id or company_currency
728             rs = {
729                 'name':line.move_id.name,
730                 'type': line.credit and 'dr' or 'cr',
731                 'move_line_id':line.id,
732                 'account_id':line.account_id.id,
733                 'amount_original': amount_original,
734                 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
735                 'date_original':line.date,
736                 'date_due':line.date_maturity,
737                 'amount_unreconciled': amount_unreconciled,
738                 'currency_id': line_currency_id,
739             }
740             #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
741             #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
742             if not move_line_found:
743                 if currency_id == line_currency_id:
744                     if line.credit:
745                         amount = min(amount_unreconciled, abs(total_debit))
746                         rs['amount'] = amount
747                         total_debit -= amount
748                     else:
749                         amount = min(amount_unreconciled, abs(total_credit))
750                         rs['amount'] = amount
751                         total_credit -= amount
752
753             if rs['amount_unreconciled'] == rs['amount']:
754                 rs['reconcile'] = True
755
756             if rs['type'] == 'cr':
757                 default['value']['line_cr_ids'].append(rs)
758             else:
759                 default['value']['line_dr_ids'].append(rs)
760
761             if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
762                 default['value']['pre_line'] = 1
763             elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
764                 default['value']['pre_line'] = 1
765             default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
766         return default
767
768     def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
769         if context is None:
770             context = {}
771         res = {'value': {}}
772         #set the default payment rate of the voucher and compute the paid amount in company currency
773         if currency_id and currency_id == payment_rate_currency_id:
774             ctx = context.copy()
775             ctx.update({'date': date})
776             vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
777             for key in vals.keys():
778                 res[key].update(vals[key])
779         return res
780
781     def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
782         """
783         @param date: latest value from user input for field date
784         @param args: other arguments
785         @param context: context arguments, like lang, time zone
786         @return: Returns a dict which contains new values, and context
787         """
788         if context is None:
789             context ={}
790         res = {'value': {}}
791         #set the period of the voucher
792         period_pool = self.pool.get('account.period')
793         currency_obj = self.pool.get('res.currency')
794         ctx = context.copy()
795         ctx.update({'company_id': company_id, 'account_period_prefer_normal': True})
796         pids = period_pool.find(cr, uid, date, context=ctx)
797         if pids:
798             res['value'].update({'period_id':pids[0]})
799         if payment_rate_currency_id:
800             ctx.update({'date': date})
801             payment_rate = 1.0
802             if payment_rate_currency_id != currency_id:
803                 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
804                 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
805                 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
806             vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
807             vals['value'].update({'payment_rate': payment_rate})
808             for key in vals.keys():
809                 res[key].update(vals[key])
810         return res
811
812     def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
813         if not journal_id:
814             return False
815         journal_pool = self.pool.get('account.journal')
816         journal = journal_pool.browse(cr, uid, journal_id, context=context)
817         account_id = journal.default_credit_account_id or journal.default_debit_account_id
818         tax_id = False
819         if account_id and account_id.tax_ids:
820             tax_id = account_id.tax_ids[0].id
821
822         vals = {'value':{} }
823         if ttype in ('sale', 'purchase'):
824             vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
825             vals['value'].update({'tax_id':tax_id,'amount': amount})
826         currency_id = False
827         if journal.currency:
828             currency_id = journal.currency.id
829         else:
830             currency_id = journal.company_id.currency_id.id
831         vals['value'].update({'currency_id': currency_id})
832         res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
833         for key in res.keys():
834             vals[key].update(res[key])
835         return vals
836
837     def button_proforma_voucher(self, cr, uid, ids, context=None):
838         context = context or {}
839         wf_service = netsvc.LocalService("workflow")
840         for vid in ids:
841             wf_service.trg_validate(uid, 'account.voucher', vid, 'proforma_voucher', cr)
842         return {'type': 'ir.actions.act_window_close'}
843
844     def proforma_voucher(self, cr, uid, ids, context=None):
845         self.action_move_line_create(cr, uid, ids, context=context)
846         return True
847
848     def action_cancel_draft(self, cr, uid, ids, context=None):
849         wf_service = netsvc.LocalService("workflow")
850         for voucher_id in ids:
851             wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
852         self.write(cr, uid, ids, {'state':'draft'})
853         return True
854
855     def cancel_voucher(self, cr, uid, ids, context=None):
856         reconcile_pool = self.pool.get('account.move.reconcile')
857         move_pool = self.pool.get('account.move')
858
859         for voucher in self.browse(cr, uid, ids, context=context):
860             recs = []
861             for line in voucher.move_ids:
862                 if line.reconcile_id:
863                     recs += [line.reconcile_id.id]
864                 if line.reconcile_partial_id:
865                     recs += [line.reconcile_partial_id.id]
866
867             reconcile_pool.unlink(cr, uid, recs)
868
869             if voucher.move_id:
870                 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
871                 move_pool.unlink(cr, uid, [voucher.move_id.id])
872         res = {
873             'state':'cancel',
874             'move_id':False,
875         }
876         self.write(cr, uid, ids, res)
877         return True
878
879     def unlink(self, cr, uid, ids, context=None):
880         for t in self.read(cr, uid, ids, ['state'], context=context):
881             if t['state'] not in ('draft', 'cancel'):
882                 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
883         return super(account_voucher, self).unlink(cr, uid, ids, context=context)
884
885     def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
886         res = {}
887         if not partner_id:
888             return res
889         res = {'account_id':False}
890         partner_pool = self.pool.get('res.partner')
891         journal_pool = self.pool.get('account.journal')
892         if pay_now == 'pay_later':
893             partner = partner_pool.browse(cr, uid, partner_id)
894             journal = journal_pool.browse(cr, uid, journal_id)
895             if journal.type in ('sale','sale_refund'):
896                 account_id = partner.property_account_receivable.id
897             elif journal.type in ('purchase', 'purchase_refund','expense'):
898                 account_id = partner.property_account_payable.id
899             else:
900                 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
901             res['account_id'] = account_id
902         return {'value':res}
903
904     def _sel_context(self, cr, uid, voucher_id, context=None):
905         """
906         Select the context to use accordingly if it needs to be multicurrency or not.
907
908         :param voucher_id: Id of the actual voucher
909         :return: The returned context will be the same as given in parameter if the voucher currency is the same
910                  than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
911                  the date of the voucher.
912         :rtype: dict
913         """
914         company_currency = self._get_company_currency(cr, uid, voucher_id, context)
915         current_currency = self._get_current_currency(cr, uid, voucher_id, context)
916         if current_currency <> company_currency:
917             context_multi_currency = context.copy()
918             voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
919             context_multi_currency.update({'date': voucher_brw.date})
920             return context_multi_currency
921         return context
922
923     def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
924         '''
925         Return a dict to be use to create the first account move line of given voucher.
926
927         :param voucher_id: Id of voucher what we are creating account_move.
928         :param move_id: Id of account move where this line will be added.
929         :param company_currency: id of currency of the company to which the voucher belong
930         :param current_currency: id of currency of the voucher
931         :return: mapping between fieldname and value of account move line to create
932         :rtype: dict
933         '''
934         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
935         debit = credit = 0.0
936         # TODO: is there any other alternative then the voucher type ??
937         # ANSWER: We can have payment and receipt "In Advance".
938         # TODO: Make this logic available.
939         # -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
940         if voucher_brw.type in ('purchase', 'payment'):
941             credit = voucher_brw.paid_amount_in_company_currency
942         elif voucher_brw.type in ('sale', 'receipt'):
943             debit = voucher_brw.paid_amount_in_company_currency
944         if debit < 0: credit = -debit; debit = 0.0
945         if credit < 0: debit = -credit; credit = 0.0
946         sign = debit - credit < 0 and -1 or 1
947         #set the first line of the voucher
948         move_line = {
949                 'name': voucher_brw.name or '/',
950                 'debit': debit,
951                 'credit': credit,
952                 'account_id': voucher_brw.account_id.id,
953                 'move_id': move_id,
954                 'journal_id': voucher_brw.journal_id.id,
955                 'period_id': voucher_brw.period_id.id,
956                 'partner_id': voucher_brw.partner_id.id,
957                 'currency_id': company_currency <> current_currency and  current_currency or False,
958                 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
959                 'date': voucher_brw.date,
960                 'date_maturity': voucher_brw.date_due
961             }
962         return move_line
963
964     def account_move_get(self, cr, uid, voucher_id, context=None):
965         '''
966         This method prepare the creation of the account move related to the given voucher.
967
968         :param voucher_id: Id of voucher for which we are creating account_move.
969         :return: mapping between fieldname and value of account move to create
970         :rtype: dict
971         '''
972         seq_obj = self.pool.get('ir.sequence')
973         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
974         if voucher_brw.number:
975             name = voucher_brw.number
976         elif voucher_brw.journal_id.sequence_id:
977             if not voucher_brw.journal_id.sequence_id.active:
978                 raise osv.except_osv(_('Configuration Error !'),
979                     _('Please activate the sequence of selected journal !'))
980             c = dict(context)
981             c.update({'fiscalyear_id': voucher_brw.period_id.fiscalyear_id.id})
982             name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=c)
983         else:
984             raise osv.except_osv(_('Error!'),
985                         _('Please define a sequence on the journal.'))
986         if not voucher_brw.reference:
987             ref = name.replace('/','')
988         else:
989             ref = voucher_brw.reference
990
991         move = {
992             'name': name,
993             'journal_id': voucher_brw.journal_id.id,
994             'narration': voucher_brw.narration,
995             'date': voucher_brw.date,
996             'ref': ref,
997             'period_id': voucher_brw.period_id.id,
998         }
999         return move
1000
1001     def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
1002         '''
1003         Prepare the two lines in company currency due to currency rate difference.
1004
1005         :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
1006             entries
1007         :param move_id: Account move wher the move lines will be.
1008         :param amount_residual: Amount to be posted.
1009         :param company_currency: id of currency of the company to which the voucher belong
1010         :param current_currency: id of currency of the voucher
1011         :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
1012         :rtype: tuple of dict
1013         '''
1014         if amount_residual > 0:
1015             account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1016             if not account_id:
1017                 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."))
1018         else:
1019             account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1020             if not account_id:
1021                 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."))
1022         # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1023         # the receivable/payable account may have a secondary currency, which render this field mandatory
1024         account_currency_id = company_currency <> current_currency and current_currency or False
1025         move_line = {
1026             'journal_id': line.voucher_id.journal_id.id,
1027             'period_id': line.voucher_id.period_id.id,
1028             'name': _('change')+': '+(line.name or '/'),
1029             'account_id': line.account_id.id,
1030             'move_id': move_id,
1031             'partner_id': line.voucher_id.partner_id.id,
1032             'currency_id': account_currency_id,
1033             'amount_currency': 0.0,
1034             'quantity': 1,
1035             'credit': amount_residual > 0 and amount_residual or 0.0,
1036             'debit': amount_residual < 0 and -amount_residual or 0.0,
1037             'date': line.voucher_id.date,
1038         }
1039         move_line_counterpart = {
1040             'journal_id': line.voucher_id.journal_id.id,
1041             'period_id': line.voucher_id.period_id.id,
1042             'name': _('change')+': '+(line.name or '/'),
1043             'account_id': account_id.id,
1044             'move_id': move_id,
1045             'amount_currency': 0.0,
1046             'partner_id': line.voucher_id.partner_id.id,
1047             'currency_id': account_currency_id,
1048             'quantity': 1,
1049             'debit': amount_residual > 0 and amount_residual or 0.0,
1050             'credit': amount_residual < 0 and -amount_residual or 0.0,
1051             'date': line.voucher_id.date,
1052         }
1053         return (move_line, move_line_counterpart)
1054
1055     def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1056         '''
1057         This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1058         payment_rate_currency_id is relevant) either the rate encoded in the system.
1059
1060         :param amount: float. The amount to convert
1061         :param voucher: id of the voucher on which we want the conversion
1062         :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1063             field in order to select the good rate to use.
1064         :return: the amount in the currency of the voucher's company
1065         :rtype: float
1066         '''
1067         currency_obj = self.pool.get('res.currency')
1068         voucher = self.browse(cr, uid, voucher_id, context=context)
1069         res = amount
1070         if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1071             # the rate specified on the voucher is for the company currency
1072             res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1073         else:
1074             # the rate specified on the voucher is not relevant, we use all the rates in the system
1075             res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1076         return res
1077
1078     def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1079         '''
1080         Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1081         It returns Tuple with tot_line what is total of difference between debit and credit and
1082         a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1083
1084         :param voucher_id: Voucher id what we are working with
1085         :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1086         :param move_id: Account move wher those lines will be joined.
1087         :param company_currency: id of currency of the company to which the voucher belong
1088         :param current_currency: id of currency of the voucher
1089         :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1090         :rtype: tuple(float, list of int)
1091         '''
1092         if context is None:
1093             context = {}
1094         move_line_obj = self.pool.get('account.move.line')
1095         currency_obj = self.pool.get('res.currency')
1096         tax_obj = self.pool.get('account.tax')
1097         tot_line = line_total
1098         rec_lst_ids = []
1099
1100         voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1101         ctx = context.copy()
1102         ctx.update({'date': voucher_brw.date})
1103         prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1104         for line in voucher_brw.line_ids:
1105             #create one move line per voucher line where amount is not 0.0
1106             # AND (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
1107             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)):
1108                 continue
1109             # convert the amount set on the voucher line into the currency of the voucher's company
1110             amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1111             # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1112             # currency rate difference
1113             if line.amount == line.amount_unreconciled:
1114                 if not line.move_line_id:
1115                     raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1116                 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1117                 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1118             else:
1119                 currency_rate_difference = 0.0
1120             move_line = {
1121                 'journal_id': voucher_brw.journal_id.id,
1122                 'period_id': voucher_brw.period_id.id,
1123                 'name': line.name or '/',
1124                 'account_id': line.account_id.id,
1125                 'move_id': move_id,
1126                 'partner_id': voucher_brw.partner_id.id,
1127                 '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,
1128                 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1129                 'quantity': 1,
1130                 'credit': 0.0,
1131                 'debit': 0.0,
1132                 'date': voucher_brw.date
1133             }
1134             if amount < 0:
1135                 amount = -amount
1136                 if line.type == 'dr':
1137                     line.type = 'cr'
1138                 else:
1139                     line.type = 'dr'
1140
1141             if (line.type=='dr'):
1142                 tot_line += amount
1143                 move_line['debit'] = amount
1144             else:
1145                 tot_line -= amount
1146                 move_line['credit'] = amount
1147
1148             if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1149                 move_line.update({
1150                     'account_tax_id': voucher_brw.tax_id.id,
1151                 })
1152
1153             if move_line.get('account_tax_id', False):
1154                 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1155                 if not (tax_data.base_code_id and tax_data.tax_code_id):
1156                     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))
1157
1158             # compute the amount in foreign currency
1159             foreign_currency_diff = 0.0
1160             amount_currency = False
1161             if line.move_line_id:
1162                 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1163                 # We want to set it on the account move line as soon as the original line had a foreign currency
1164                 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1165                     # we compute the amount in that foreign currency.
1166                     if line.move_line_id.currency_id.id == current_currency:
1167                         # if the voucher and the voucher line share the same currency, there is no computation to do
1168                         sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1169                         amount_currency = sign * (line.amount)
1170                     elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1171                         # if the rate is specified on the voucher, we must use it
1172                         voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1173                         amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
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: