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