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