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