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