[MERGE] merged the last branch of development related to the voucher multicurrencies...
[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                 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1146             }
1147
1148         return move_line
1149
1150     def _get_company_currency(self, cr, uid, voucher_id, context=None):
1151         '''
1152         Get the currency of the actual company.
1153
1154         :param voucher_id: Id of the voucher what i want to obtain company currency.
1155         :return: currency id of the company of the voucher
1156         :rtype: int
1157         '''
1158         return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1159
1160     def _get_current_currency(self, cr, uid, voucher_id, context=None):
1161         '''
1162         Get the currency of the voucher.
1163
1164         :param voucher_id: Id of the voucher what i want to obtain current currency.
1165         :return: currency id of the voucher
1166         :rtype: int
1167         '''
1168         voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1169         return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1170
1171     def action_move_line_create(self, cr, uid, ids, context=None):
1172         '''
1173         Confirm the vouchers given in ids and create the journal entries for each of them
1174         '''
1175         if context is None:
1176             context = {}
1177         move_pool = self.pool.get('account.move')
1178         move_line_pool = self.pool.get('account.move.line')
1179         currency_pool = self.pool.get('res.currency')
1180         tax_obj = self.pool.get('account.tax')
1181         seq_obj = self.pool.get('ir.sequence')
1182         for voucher in self.browse(cr, uid, ids, context=context):
1183             if voucher.move_id:
1184                 continue
1185             company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1186             current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1187             # we select the context to use accordingly if it's a multicurrency case or not
1188             context = self._sel_context(cr, uid, voucher.id, context)
1189             # But for the operations made by _convert_amount, we always need to give the date in the context
1190             ctx = context.copy()
1191             ctx.update({'date': voucher.date})
1192             # Create the account move record.
1193             move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1194             # Get the name of the account_move just created
1195             name = move_pool.browse(cr, uid, move_id, context=context).name
1196             # Create the first line of the voucher
1197             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)
1198             move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1199             line_total = move_line_brw.debit - move_line_brw.credit
1200             rec_list_ids = []
1201             if voucher.type == 'sale':
1202                 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1203             elif voucher.type == 'purchase':
1204                 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1205             # Create one move line per voucher line where amount is not 0.0
1206             line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1207
1208             # Create the writeoff line if needed
1209             ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1210             if ml_writeoff:
1211                 ml_writeoff_id = move_line_pool.create(cr, uid, ml_writeoff, context)
1212             # We post the voucher.
1213             self.write(cr, uid, [voucher.id], {
1214                 'move_id': move_id,
1215                 'state': 'posted',
1216                 'number': name,
1217             })
1218             if voucher.journal_id.entry_posted:
1219                 move_pool.post(cr, uid, [move_id], context={})
1220             # We automatically reconcile the account move lines.
1221             for rec_ids in rec_list_ids:
1222                 if len(rec_ids) >= 2:
1223                     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)
1224         return True
1225
1226     def copy(self, cr, uid, id, default={}, context=None):
1227         default.update({
1228             'state': 'draft',
1229             'number': False,
1230             'move_id': False,
1231             'line_cr_ids': False,
1232             'line_dr_ids': False,
1233             'reference': False
1234         })
1235         if 'date' not in default:
1236             default['date'] = time.strftime('%Y-%m-%d')
1237         return super(account_voucher, self).copy(cr, uid, id, default, context)
1238
1239 account_voucher()
1240
1241 class account_voucher_line(osv.osv):
1242     _name = 'account.voucher.line'
1243     _description = 'Voucher Lines'
1244     _order = "move_line_id"
1245
1246     # If the payment is in the same currency than the invoice, we keep the same amount
1247     # Otherwise, we compute from company currency to payment currency
1248     def _compute_balance(self, cr, uid, ids, name, args, context=None):
1249         currency_pool = self.pool.get('res.currency')
1250         rs_data = {}
1251         for line in self.browse(cr, uid, ids, context=context):
1252             ctx = context.copy()
1253             ctx.update({'date': line.voucher_id.date})
1254             res = {}
1255             company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1256             voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1257             move_line = line.move_line_id or False
1258
1259             if not move_line:
1260                 res['amount_original'] = 0.0
1261                 res['amount_unreconciled'] = 0.0
1262             elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1263                 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1264                 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)
1265             elif move_line and move_line.credit > 0:
1266                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1267                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1268             else:
1269                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1270                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1271
1272             rs_data[line.id] = res
1273         return rs_data
1274
1275     def _currency_id(self, cr, uid, ids, name, args, context=None):
1276         '''
1277         This function returns the currency id of a voucher line. It's either the currency of the 
1278         associated move line (if any) or the currency of the voucher or the company currency.
1279         '''
1280         res = {}
1281         for line in self.browse(cr, uid, ids, context=context):
1282             move_line = line.move_line_id
1283             if move_line:
1284                 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1285             else:
1286                 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1287         return res
1288
1289     _columns = {
1290         'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1291         'name':fields.char('Description', size=256),
1292         'account_id':fields.many2one('account.account','Account', required=True),
1293         'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1294         'untax_amount':fields.float('Untax Amount'),
1295         'amount':fields.float('Allocation', digits_compute=dp.get_precision('Account')),
1296         'reconcile': fields.boolean('Full Reconcile'),
1297         'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1298         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
1299         'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1300         'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1301         'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1302         'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True),
1303         'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True),
1304         'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1305         'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1306     }
1307     _defaults = {
1308         'name': '',
1309     }
1310
1311     def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1312         vals = { 'amount': 0.0}
1313         if reconcile:
1314             vals = { 'amount': amount_unreconciled}
1315         return {'value': vals}
1316
1317     def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1318         vals = {}
1319         if amount:
1320             vals['reconcile'] = (amount == amount_unreconciled)
1321         return {'value': vals}
1322
1323     def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1324         """
1325         Returns a dict that contains new values and context
1326
1327         @param move_line_id: latest value from user input for field move_line_id
1328         @param args: other arguments
1329         @param context: context arguments, like lang, time zone
1330
1331         @return: Returns a dict which contains new values, and context
1332         """
1333         res = {}
1334         move_line_pool = self.pool.get('account.move.line')
1335         if move_line_id:
1336             move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1337             if move_line.credit:
1338                 ttype = 'dr'
1339             else:
1340                 ttype = 'cr'
1341             res.update({
1342                 'account_id': move_line.account_id.id,
1343                 'type': ttype,
1344                 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1345             })
1346         return {
1347             'value':res,
1348         }
1349
1350     def default_get(self, cr, user, fields_list, context=None):
1351         """
1352         Returns default values for fields
1353         @param fields_list: list of fields, for which default values are required to be read
1354         @param context: context arguments, like lang, time zone
1355
1356         @return: Returns a dict that contains default values for fields
1357         """
1358         if context is None:
1359             context = {}
1360         journal_id = context.get('journal_id', False)
1361         partner_id = context.get('partner_id', False)
1362         journal_pool = self.pool.get('account.journal')
1363         partner_pool = self.pool.get('res.partner')
1364         values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1365         if (not journal_id) or ('account_id' not in fields_list):
1366             return values
1367         journal = journal_pool.browse(cr, user, journal_id, context=context)
1368         account_id = False
1369         ttype = 'cr'
1370         if journal.type in ('sale', 'sale_refund'):
1371             account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1372             ttype = 'cr'
1373         elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1374             account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1375             ttype = 'dr'
1376         elif partner_id:
1377             partner = partner_pool.browse(cr, user, partner_id, context=context)
1378             if context.get('type') == 'payment':
1379                 ttype = 'dr'
1380                 account_id = partner.property_account_payable.id
1381             elif context.get('type') == 'receipt':
1382                 account_id = partner.property_account_receivable.id
1383
1384         values.update({
1385             'account_id':account_id,
1386             'type':ttype
1387         })
1388         return values
1389 account_voucher_line()
1390
1391 class account_bank_statement(osv.osv):
1392     _inherit = 'account.bank.statement'
1393
1394     def button_cancel(self, cr, uid, ids, context=None):
1395         voucher_obj = self.pool.get('account.voucher')
1396         for st in self.browse(cr, uid, ids, context=context):
1397             voucher_ids = []
1398             for line in st.line_ids:
1399                 if line.voucher_id:
1400                     voucher_ids.append(line.voucher_id.id)
1401             voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1402         return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1403
1404     def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1405         voucher_obj = self.pool.get('account.voucher')
1406         wf_service = netsvc.LocalService("workflow")
1407         move_line_obj = self.pool.get('account.move.line')
1408         bank_st_line_obj = self.pool.get('account.bank.statement.line')
1409         st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1410         if st_line.voucher_id:
1411             voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1412             if st_line.voucher_id.state == 'cancel':
1413                 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1414             wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1415
1416             v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1417             bank_st_line_obj.write(cr, uid, [st_line_id], {
1418                 'move_ids': [(4, v.move_id.id, False)]
1419             })
1420
1421             return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1422         return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1423
1424 account_bank_statement()
1425
1426 class account_bank_statement_line(osv.osv):
1427     _inherit = 'account.bank.statement.line'
1428
1429     def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1430         if not ids:
1431             return {}
1432         res = {}
1433         for line in self.browse(cursor, user, ids, context=context):
1434             if line.voucher_id:
1435                 res[line.id] = line.voucher_id.amount#
1436             else:
1437                 res[line.id] = 0.0
1438         return res
1439
1440     def _check_amount(self, cr, uid, ids, context=None):
1441         for obj in self.browse(cr, uid, ids, context=context):
1442             if obj.voucher_id:
1443                 diff = abs(obj.amount) - obj.voucher_id.amount
1444                 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1445                     return False
1446         return True
1447
1448     _constraints = [
1449         (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1450     ]
1451
1452     _columns = {
1453         'amount_reconciled': fields.function(_amount_reconciled,
1454             string='Amount reconciled', type='float'),
1455         'voucher_id': fields.many2one('account.voucher', 'Payment'),
1456     }
1457
1458     def unlink(self, cr, uid, ids, context=None):
1459         voucher_obj = self.pool.get('account.voucher')
1460         statement_line = self.browse(cr, uid, ids, context=context)
1461         unlink_ids = []
1462         for st_line in statement_line:
1463             if st_line.voucher_id:
1464                 unlink_ids.append(st_line.voucher_id.id)
1465         voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1466         return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1467
1468 account_bank_statement_line()
1469
1470 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1471     results = []
1472     for operation in operations:
1473         result = None
1474         if not isinstance(operation, (list, tuple)):
1475             result = target_osv.read(cr, uid, operation, fields, context=context)
1476         elif operation[0] == 0:
1477             # may be necessary to check if all the fields are here and get the default values?
1478             result = operation[2]
1479         elif operation[0] == 1:
1480             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1481             if not result: result = {}
1482             result.update(operation[2])
1483         elif operation[0] == 4:
1484             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1485         if result != None:
1486             results.append(result)
1487     return results
1488
1489 class res_company(osv.osv):
1490     _inherit = "res.company"
1491     _columns = {
1492         'income_currency_exchange_account_id': fields.many2one(
1493             'account.account',
1494             string="Income Currency Rate",
1495             domain="[('type', '=', 'other')]",),
1496         'expense_currency_exchange_account_id': fields.many2one(
1497             'account.account',
1498             string="Expense Currency Rate",
1499             domain="[('type', '=', 'other')]",),
1500     }
1501
1502 res_company()
1503
1504 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: