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