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