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