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