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