535f76b8d64a69c6263801c7b0c5449261c1c097
[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         if not journal_id:
515             return {}
516         res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
517         vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
518         for key in vals.keys():
519             res[key].update(vals[key])
520         return res
521
522     def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
523         """
524         Returns a dict that contains new values and context
525
526         @param partner_id: latest value from user input for field partner_id
527         @param args: other arguments
528         @param context: context arguments, like lang, time zone
529
530         @return: Returns a dict which contains new values, and context
531         """
532         if context is None:
533             context = {}
534         context_multi_currency = context.copy()
535         if date:
536             context_multi_currency.update({'date': date})
537
538         currency_pool = self.pool.get('res.currency')
539         move_line_pool = self.pool.get('account.move.line')
540         partner_pool = self.pool.get('res.partner')
541         journal_pool = self.pool.get('account.journal')
542         line_pool = self.pool.get('account.voucher.line')
543
544         #set default values
545         default = {
546             'value': {'line_ids': [] ,'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
547         }
548
549         #drop existing lines
550         line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
551         if line_ids:
552             line_pool.unlink(cr, uid, line_ids)
553
554         if not partner_id or not journal_id:
555             return default
556
557         journal = journal_pool.browse(cr, uid, journal_id, context=context)
558         partner = partner_pool.browse(cr, uid, partner_id, context=context)
559         currency_id = currency_id or journal.company_id.currency_id.id
560         account_id = False
561         if journal.type in ('sale','sale_refund'):
562             account_id = partner.property_account_receivable.id
563         elif journal.type in ('purchase', 'purchase_refund','expense'):
564             account_id = partner.property_account_payable.id
565         else:
566             account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
567
568         default['value']['account_id'] = account_id
569
570         if journal.type not in ('cash', 'bank'):
571             return default
572
573         total_credit = 0.0
574         total_debit = 0.0
575         account_type = 'receivable'
576         if ttype == 'payment':
577             account_type = 'payable'
578             total_debit = price or 0.0
579         else:
580             total_credit = price or 0.0
581             account_type = 'receivable'
582
583         if not context.get('move_line_ids', False):
584             ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
585         else:
586             ids = context['move_line_ids']
587         invoice_id = context.get('invoice_id', False)
588         company_currency = journal.company_id.currency_id.id
589         move_line_found = False
590
591         #order the lines by most old first
592         ids.reverse()
593         account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
594
595         for line in account_move_lines:
596             if line.credit and line.reconcile_partial_id and ttype == 'receipt':
597                 continue
598             if line.debit and line.reconcile_partial_id and ttype == 'payment':
599                 continue
600             if invoice_id:
601                 if line.invoice.id == invoice_id:
602                     #if the invoice linked to the voucher line is equal to the invoice_id in context
603                     #then we assign the amount on that line, whatever the other voucher lines
604                     move_line_found = line.id
605                     break
606             elif currency_id == company_currency:
607                 #otherwise treatments is the same but with other field names
608                 if line.amount_residual == price:
609                     #if the amount residual is equal the amount voucher, we assign it to that voucher
610                     #line, whatever the other voucher lines
611                     move_line_found = line.id
612                     break
613                 #otherwise we will split the voucher amount on each line (by most old first)
614                 total_credit += line.credit or 0.0
615                 total_debit += line.debit or 0.0
616             elif currency_id == line.currency_id.id:
617                 if line.amount_residual_currency == price:
618                     move_line_found = line.id
619                     break
620                 total_credit += line.credit and line.amount_currency or 0.0
621                 total_debit += line.debit and line.amount_currency or 0.0
622
623         #voucher line creation
624         for line in account_move_lines:
625             if line.credit and line.reconcile_partial_id and ttype == 'receipt':
626                 continue
627             if line.debit and line.reconcile_partial_id and ttype == 'payment':
628                 continue
629             if line.currency_id and currency_id==line.currency_id.id:
630                 amount_original = abs(line.amount_currency)
631                 amount_unreconciled = abs(line.amount_residual_currency)
632             else:
633                 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
634                 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
635             line_currency_id = line.currency_id and line.currency_id.id or company_currency
636             rs = {
637                 'name':line.move_id.name,
638                 'type': line.credit and 'dr' or 'cr',
639                 'move_line_id':line.id,
640                 'account_id':line.account_id.id,
641                 'amount_original': amount_original,
642                 'amount': (move_line_found == line.id) and min(price, amount_unreconciled) or 0.0,
643                 'date_original':line.date,
644                 'date_due':line.date_maturity,
645                 'amount_unreconciled': amount_unreconciled,
646                 'currency_id': line_currency_id,
647             }
648
649             #split voucher amount by most old first, but only for lines in the same currency
650             if not move_line_found:
651                 if currency_id == line_currency_id:
652                     if line.credit:
653                         amount = min(amount_unreconciled, abs(total_debit))
654                         rs['amount'] = amount
655                         total_debit -= amount
656                     else:
657                         amount = min(amount_unreconciled, abs(total_credit))
658                         rs['amount'] = amount
659                         total_credit -= amount
660
661             if rs['amount_unreconciled'] == rs['amount']:
662                 rs['reconcile'] = True
663
664             if rs['type'] == 'cr':
665                 default['value']['line_cr_ids'].append(rs)
666             else:
667                 default['value']['line_dr_ids'].append(rs)
668
669             if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
670                 default['value']['pre_line'] = 1
671             elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
672                 default['value']['pre_line'] = 1
673             default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price)
674         return default
675
676     def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
677         if context is None:
678             context = {}
679         res = {'value': {}}
680         #set the default payment rate of the voucher and compute the paid amount in company currency
681         if currency_id and currency_id == payment_rate_currency_id:
682             ctx = context.copy()
683             ctx.update({'date': date})
684             vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
685             for key in vals.keys():
686                 res[key].update(vals[key])
687         return res
688
689     def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
690         """
691         @param date: latest value from user input for field date
692         @param args: other arguments
693         @param context: context arguments, like lang, time zone
694         @return: Returns a dict which contains new values, and context
695         """
696         if context is None:
697             context ={}
698         res = {'value': {}}
699         #set the period of the voucher
700         period_pool = self.pool.get('account.period')
701         currency_obj = self.pool.get('res.currency')
702         ctx = context.copy()
703         ctx.update({'company_id': company_id})
704         pids = period_pool.find(cr, uid, date, context=ctx)
705         if pids:
706             res['value'].update({'period_id':pids[0]})
707         if payment_rate_currency_id:
708             ctx.update({'date': date})
709             payment_rate = 1.0
710             if payment_rate_currency_id != currency_id:
711                 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
712                 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
713                 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
714             vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
715             vals['value'].update({'payment_rate': payment_rate})
716             for key in vals.keys():
717                 res[key].update(vals[key])
718         return res
719
720     def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
721         if not journal_id:
722             return False
723         journal_pool = self.pool.get('account.journal')
724         journal = journal_pool.browse(cr, uid, journal_id, context=context)
725         account_id = journal.default_credit_account_id or journal.default_debit_account_id
726         tax_id = False
727         if account_id and account_id.tax_ids:
728             tax_id = account_id.tax_ids[0].id
729
730         vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
731         vals['value'].update({'tax_id':tax_id,'amount': amount})
732         currency_id = False
733         if journal.currency:
734             currency_id = journal.currency.id
735         vals['value'].update({'currency_id': currency_id})
736         res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
737         for key in res.keys():
738             vals[key].update(res[key])
739         return vals
740
741     def proforma_voucher(self, cr, uid, ids, context=None):
742         self.action_move_line_create(cr, uid, ids, context=context)
743         return True
744
745     def action_cancel_draft(self, cr, uid, ids, context=None):
746         wf_service = netsvc.LocalService("workflow")
747         for voucher_id in ids:
748             wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
749         self.write(cr, uid, ids, {'state':'draft'})
750         return True
751
752     def cancel_voucher(self, cr, uid, ids, context=None):
753         reconcile_pool = self.pool.get('account.move.reconcile')
754         move_pool = self.pool.get('account.move')
755
756         for voucher in self.browse(cr, uid, ids, context=context):
757             recs = []
758             for line in voucher.move_ids:
759                 if line.reconcile_id:
760                     recs += [line.reconcile_id.id]
761                 if line.reconcile_partial_id:
762                     recs += [line.reconcile_partial_id.id]
763
764             reconcile_pool.unlink(cr, uid, recs)
765
766             if voucher.move_id:
767                 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
768                 move_pool.unlink(cr, uid, [voucher.move_id.id])
769         res = {
770             'state':'cancel',
771             'move_id':False,
772         }
773         self.write(cr, uid, ids, res)
774         return True
775
776     def unlink(self, cr, uid, ids, context=None):
777         for t in self.read(cr, uid, ids, ['state'], context=context):
778             if t['state'] not in ('draft', 'cancel'):
779                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Voucher(s) which are already opened or paid !'))
780         return super(account_voucher, self).unlink(cr, uid, ids, context=context)
781
782     def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
783         res = {}
784         if not partner_id:
785             return res
786         res = {'account_id':False}
787         partner_pool = self.pool.get('res.partner')
788         journal_pool = self.pool.get('account.journal')
789         if pay_now == 'pay_later':
790             partner = partner_pool.browse(cr, uid, partner_id)
791             journal = journal_pool.browse(cr, uid, journal_id)
792             if journal.type in ('sale','sale_refund'):
793                 account_id = partner.property_account_receivable.id
794             elif journal.type in ('purchase', 'purchase_refund','expense'):
795                 account_id = partner.property_account_payable.id
796             else:
797                 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
798             res['account_id'] = account_id
799         return {'value':res}
800
801     def _sel_context(self, cr, uid, voucher_id,context=None):
802         """
803         Select the context to use accordingly if it needs to be multicurrency or not.
804
805         :param voucher_id: Id of the actual voucher
806         :return: The returned context will be the same as given in parameter if the voucher currency is the same
807                  than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
808                  the date of the voucher.
809         :rtype: dict
810         """
811         company_currency = self._get_company_currency(cr, uid, voucher_id, context)
812         current_currency = self._get_current_currency(cr, uid, voucher_id, context)
813         if current_currency <> company_currency:
814             context_multi_currency = context.copy()
815             voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
816             context_multi_currency.update({'date': voucher_brw.date})
817             return context_multi_currency
818         return context
819
820     def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
821         '''
822         Return a dict to be use to create the first account move line of given voucher.
823
824         :param voucher_id: Id of voucher what we are creating account_move.
825         :param move_id: Id of account move where this line will be added.
826         :param company_currency: id of currency of the company to which the voucher belong
827         :param current_currency: id of currency of the voucher
828         :return: mapping between fieldname and value of account move line to create
829         :rtype: dict
830         '''
831         move_line_obj = self.pool.get('account.move.line')
832         currency_obj = self.pool.get('res.currency')
833         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
834         debit = credit = 0.0
835         # TODO: is there any other alternative then the voucher type ??
836         # ANSWER: We can have payment and receipt "In Advance".
837         # TODO: Make this logic available.
838         # -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
839         if voucher_brw.type in ('purchase', 'payment'):
840             credit = voucher_brw.paid_amount_in_company_currency
841         elif voucher_brw.type in ('sale', 'receipt'):
842             debit = voucher_brw.paid_amount_in_company_currency
843         if debit < 0: credit = -debit; debit = 0.0
844         if credit < 0: debit = -credit; credit = 0.0
845         sign = debit - credit < 0 and -1 or 1
846         #set the first line of the voucher
847         move_line = {
848                 'name': voucher_brw.name or '/',
849                 'debit': debit,
850                 'credit': credit,
851                 'account_id': voucher_brw.account_id.id,
852                 'move_id': move_id,
853                 'journal_id': voucher_brw.journal_id.id,
854                 'period_id': voucher_brw.period_id.id,
855                 'partner_id': voucher_brw.partner_id.id,
856                 'currency_id': company_currency <> current_currency and  current_currency or False,
857                 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
858                 'date': voucher_brw.date,
859                 'date_maturity': voucher_brw.date_due
860             }
861         return move_line
862
863     def account_move_get(self, cr, uid, voucher_id, context=None):
864         '''
865         This method prepare the creation of the account move related to the given voucher.
866
867         :param voucher_id: Id of voucher for which we are creating account_move.
868         :return: mapping between fieldname and value of account move to create
869         :rtype: dict
870         '''
871         move_obj = self.pool.get('account.move')
872         seq_obj = self.pool.get('ir.sequence')
873         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
874         if voucher_brw.number:
875             name = voucher_brw.number
876         elif voucher_brw.journal_id.sequence_id:
877             name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id)
878         else:
879             raise osv.except_osv(_('Error !'),
880                         _('Please define a sequence on the journal !'))
881         if not voucher_brw.reference:
882             ref = name.replace('/','')
883         else:
884             ref = voucher_brw.reference
885
886         move = {
887             'name': name,
888             'journal_id': voucher_brw.journal_id.id,
889             'narration': voucher_brw.narration,
890             'date': voucher_brw.date,
891             'ref': ref,
892             'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
893         }
894         return move
895
896     def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
897         '''
898         Prepare the two lines in company currency due to currency rate difference.
899
900         :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
901             entries
902         :param move_id: Account move wher the move lines will be.
903         :param amount_residual: Amount to be posted.
904         :param company_currency: id of currency of the company to which the voucher belong
905         :param current_currency: id of currency of the voucher
906         :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
907         :rtype: tuple of dict
908         '''
909         if amount_residual > 0:
910             account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
911             if not account_id:
912                 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! "))
913         else:
914             account_id = line.voucher_id.company_id.income_currency_exchange_account_id
915             if not account_id:
916                 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! "))
917         # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
918         # the receivable/payable account may have a secondary currency, which render this field mandatory
919         account_currency_id = company_currency <> current_currency and current_currency or False
920         move_line = {
921             'journal_id': line.voucher_id.journal_id.id,
922             'period_id': line.voucher_id.period_id.id,
923             'name': _('change')+': '+(line.name or '/'),
924             'account_id': line.account_id.id,
925             'move_id': move_id,
926             'partner_id': line.voucher_id.partner_id.id,
927             'currency_id': account_currency_id,
928             'amount_currency': 0.0,
929             'quantity': 1,
930             'credit': amount_residual > 0 and amount_residual or 0.0,
931             'debit': amount_residual < 0 and -amount_residual or 0.0,
932             'date': line.voucher_id.date,
933         }
934         move_line_counterpart = {
935             'journal_id': line.voucher_id.journal_id.id,
936             'period_id': line.voucher_id.period_id.id,
937             'name': _('change')+': '+(line.name or '/'),
938             'account_id': account_id.id,
939             'move_id': move_id,
940             'amount_currency': 0.0,
941             'partner_id': line.voucher_id.partner_id.id,
942             'currency_id': account_currency_id,
943             'quantity': 1,
944             'debit': amount_residual > 0 and amount_residual or 0.0,
945             'credit': amount_residual < 0 and -amount_residual or 0.0,
946             'date': line.voucher_id.date,
947         }
948         return (move_line, move_line_counterpart)
949
950     def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
951         '''
952         This function convert the amount given in company currency. It takes either the rate in the voucher (if the
953         payment_rate_currency_id is relevant) either the rate encoded in the system.
954
955         :param amount: float. The amount to convert
956         :param voucher: id of the voucher on which we want the conversion
957         :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
958             field in order to select the good rate to use.
959         :return: the amount in the currency of the voucher's company
960         :rtype: float
961         '''
962         currency_obj = self.pool.get('res.currency')
963         voucher = self.browse(cr, uid, voucher_id, context=context)
964         res = amount
965         if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
966             # the rate specified on the voucher is for the company currency
967             rate_between_voucher_and_base = voucher.currency_id.rate or 1.0
968             rate_between_base_and_company = voucher.payment_rate or 1.0
969             res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount / rate_between_voucher_and_base * rate_between_base_and_company))
970         else:
971             # the rate specified on the voucher is not relevant, we use all the rates in the system
972             res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
973         return res
974
975     def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
976         '''
977         Create one account move line, on the given account move, per voucher line where amount is not 0.0.
978         It returns Tuple with tot_line what is total of difference between debit and credit and
979         a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
980
981         :param voucher_id: Voucher id what we are working with
982         :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
983         :param move_id: Account move wher those lines will be joined.
984         :param company_currency: id of currency of the company to which the voucher belong
985         :param current_currency: id of currency of the voucher
986         :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
987         :rtype: tuple(float, list of int)
988         '''
989         if context is None:
990             context = {}
991         move_line_obj = self.pool.get('account.move.line')
992         currency_obj = self.pool.get('res.currency')
993         tot_line = line_total
994         rec_lst_ids = []
995
996         voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
997         ctx = context.copy()
998         ctx.update({'date': voucher_brw.date})
999         for line in voucher_brw.line_ids:
1000             #create one move line per voucher line where amount is not 0.0
1001             if not line.amount:
1002                 continue
1003             # convert the amount set on the voucher line into the currency of the voucher's company
1004             amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1005             # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1006             # currency rate difference
1007             if line.amount == line.amount_unreconciled:
1008                 currency_rate_difference = line.move_line_id.amount_residual - amount
1009             else:
1010                 currency_rate_difference = 0.0
1011             move_line = {
1012                 'journal_id': voucher_brw.journal_id.id,
1013                 'period_id': voucher_brw.period_id.id,
1014                 'name': line.name or '/',
1015                 'account_id': line.account_id.id,
1016                 'move_id': move_id,
1017                 'partner_id': voucher_brw.partner_id.id,
1018                 '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,
1019                 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1020                 'quantity': 1,
1021                 'credit': 0.0,
1022                 'debit': 0.0,
1023                 'date': voucher_brw.date
1024             }
1025             if amount < 0:
1026                 amount = -amount
1027                 if line.type == 'dr':
1028                     line.type = 'cr'
1029                 else:
1030                     line.type = 'dr'
1031
1032             if (line.type=='dr'):
1033                 tot_line += amount
1034                 move_line['debit'] = amount
1035             else:
1036                 tot_line -= amount
1037                 move_line['credit'] = amount
1038
1039             if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1040                 move_line.update({
1041                     'account_tax_id': voucher_brw.tax_id.id,
1042                 })
1043
1044             if move_line.get('account_tax_id', False):
1045                 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1046                 if not (tax_data.base_code_id and tax_data.tax_code_id):
1047                     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))
1048
1049             # compute the amount in foreign currency
1050             foreign_currency_diff = 0.0
1051             amount_currency = False
1052             if line.move_line_id:
1053                 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1054                 # We want to set it on the account move line as soon as the original line had a foreign currency
1055                 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1056                     # we compute the amount in that foreign currency. 
1057                     if line.move_line_id.currency_id.id == current_currency:
1058                         # if the voucher and the voucher line share the same currency, there is no computation to do
1059                         sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1060                         amount_currency = sign * (line.amount)
1061                     elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1062                         # if the rate is specified on the voucher, we must use it
1063                         voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1064                         amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1065                     else:
1066                         # otherwise we use the rates of the system (giving the voucher date in the context)
1067                         amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1068                 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1069                     foreign_currency_diff = line.move_line_id.amount_residual_currency + amount_currency
1070
1071             move_line['amount_currency'] = amount_currency
1072             voucher_line = move_line_obj.create(cr, uid, move_line)
1073             rec_ids = [voucher_line, line.move_line_id.id]
1074
1075             if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1076                 # Change difference entry in company currency
1077                 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1078                 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1079                 move_line_obj.create(cr, uid, exch_lines[1], context)
1080                 rec_ids.append(new_id)
1081
1082             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):
1083                 # Change difference entry in voucher currency
1084                 move_line_foreign_currency = {
1085                     'journal_id': line.voucher_id.journal_id.id,
1086                     'period_id': line.voucher_id.period_id.id,
1087                     'name': _('change')+': '+(line.name or '/'),
1088                     'account_id': line.account_id.id,
1089                     'move_id': move_id,
1090                     'partner_id': line.voucher_id.partner_id.id,
1091                     'currency_id': line.move_line_id.currency_id.id,
1092                     'amount_currency': -1 * foreign_currency_diff,
1093                     'quantity': 1,
1094                     'credit': 0.0,
1095                     'debit': 0.0,
1096                     'date': line.voucher_id.date,
1097                 }
1098                 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1099                 rec_ids.append(new_id)
1100
1101             if line.move_line_id.id:
1102                 rec_lst_ids.append(rec_ids)
1103
1104         return (tot_line, rec_lst_ids)
1105
1106     def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1107         '''
1108         Set a dict to be use to create the writeoff move line.
1109
1110         :param voucher_id: Id of voucher what we are creating account_move.
1111         :param line_total: Amount remaining to be allocated on lines.
1112         :param move_id: Id of account move where this line will be added.
1113         :param name: Description of account move line.
1114         :param company_currency: id of currency of the company to which the voucher belong
1115         :param current_currency: id of currency of the voucher
1116         :return: mapping between fieldname and value of account move line to create
1117         :rtype: dict
1118         '''
1119         move_line_obj = self.pool.get('account.move.line')
1120         currency_obj = self.pool.get('res.currency')
1121         move_line = {}
1122
1123         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1124         current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1125
1126         if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1127             diff = line_total
1128             account_id = False
1129             write_off_name = ''
1130             if voucher_brw.payment_option == 'with_writeoff':
1131                 account_id = voucher_brw.writeoff_acc_id.id
1132                 write_off_name = voucher_brw.comment
1133             elif voucher_brw.type in ('sale', 'receipt'):
1134                 account_id = voucher_brw.partner_id.property_account_receivable.id
1135             else:
1136                 account_id = voucher_brw.partner_id.property_account_payable.id
1137             move_line = {
1138                 'name': write_off_name or name,
1139                 'account_id': account_id,
1140                 'move_id': move_id,
1141                 'partner_id': voucher_brw.partner_id.id,
1142                 'date': voucher_brw.date,
1143                 'credit': diff > 0 and diff or 0.0,
1144                 'debit': diff < 0 and -diff or 0.0,
1145                 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
1146                 'currency_id': company_currency <> current_currency and current_currency or False,
1147                 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1148             }
1149
1150         return move_line
1151
1152     def _get_company_currency(self, cr, uid, voucher_id, context=None):
1153         '''
1154         Get the currency of the actual company.
1155
1156         :param voucher_id: Id of the voucher what i want to obtain company currency.
1157         :return: currency id of the company of the voucher
1158         :rtype: int
1159         '''
1160         return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1161
1162     def _get_current_currency(self, cr, uid, voucher_id, context=None):
1163         '''
1164         Get the currency of the voucher.
1165
1166         :param voucher_id: Id of the voucher what i want to obtain current currency.
1167         :return: currency id of the voucher
1168         :rtype: int
1169         '''
1170         voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1171         return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1172
1173     def action_move_line_create(self, cr, uid, ids, context=None):
1174         '''
1175         Confirm the vouchers given in ids and create the journal entries for each of them
1176         '''
1177         if context is None:
1178             context = {}
1179         move_pool = self.pool.get('account.move')
1180         move_line_pool = self.pool.get('account.move.line')
1181         currency_pool = self.pool.get('res.currency')
1182         tax_obj = self.pool.get('account.tax')
1183         seq_obj = self.pool.get('ir.sequence')
1184         for voucher in self.browse(cr, uid, ids, context=context):
1185             if voucher.move_id:
1186                 continue
1187             company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1188             current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1189             # we select the context to use accordingly if it's a multicurrency case or not
1190             context = self._sel_context(cr, uid, voucher.id, context)
1191             # But for the operations made by _convert_amount, we always need to give the date in the context
1192             ctx = context.copy()
1193             ctx.update({'date': voucher.date})
1194             # Create the account move record.
1195             move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1196             # Get the name of the account_move just created
1197             name = move_pool.browse(cr, uid, move_id, context=context).name
1198             # Create the first line of the voucher
1199             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)
1200             move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1201             line_total = move_line_brw.debit - move_line_brw.credit
1202             rec_list_ids = []
1203             if voucher.type == 'sale':
1204                 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1205             elif voucher.type == 'purchase':
1206                 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1207             # Create one move line per voucher line where amount is not 0.0
1208             line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1209
1210             # Create the writeoff line if needed
1211             ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1212             if ml_writeoff:
1213                 ml_writeoff_id = move_line_pool.create(cr, uid, ml_writeoff, context)
1214             # We post the voucher.
1215             self.write(cr, uid, [voucher.id], {
1216                 'move_id': move_id,
1217                 'state': 'posted',
1218                 'number': name,
1219             })
1220             if voucher.journal_id.entry_posted:
1221                 move_pool.post(cr, uid, [move_id], context={})
1222             # We automatically reconcile the account move lines.
1223             for rec_ids in rec_list_ids:
1224                 if len(rec_ids) >= 2:
1225                     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)
1226         return True
1227
1228     def copy(self, cr, uid, id, default={}, context=None):
1229         default.update({
1230             'state': 'draft',
1231             'number': False,
1232             'move_id': False,
1233             'line_cr_ids': False,
1234             'line_dr_ids': False,
1235             'reference': False
1236         })
1237         if 'date' not in default:
1238             default['date'] = time.strftime('%Y-%m-%d')
1239         return super(account_voucher, self).copy(cr, uid, id, default, context)
1240
1241 account_voucher()
1242
1243 class account_voucher_line(osv.osv):
1244     _name = 'account.voucher.line'
1245     _description = 'Voucher Lines'
1246     _order = "move_line_id"
1247
1248     # If the payment is in the same currency than the invoice, we keep the same amount
1249     # Otherwise, we compute from company currency to payment currency
1250     def _compute_balance(self, cr, uid, ids, name, args, context=None):
1251         currency_pool = self.pool.get('res.currency')
1252         rs_data = {}
1253         for line in self.browse(cr, uid, ids, context=context):
1254             ctx = context.copy()
1255             ctx.update({'date': line.voucher_id.date})
1256             res = {}
1257             company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1258             voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1259             move_line = line.move_line_id or False
1260
1261             if not move_line:
1262                 res['amount_original'] = 0.0
1263                 res['amount_unreconciled'] = 0.0
1264             elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1265                 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1266                 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)
1267             elif move_line and move_line.credit > 0:
1268                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1269                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1270             else:
1271                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1272                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1273
1274             rs_data[line.id] = res
1275         return rs_data
1276
1277     def _currency_id(self, cr, uid, ids, name, args, context=None):
1278         '''
1279         This function returns the currency id of a voucher line. It's either the currency of the 
1280         associated move line (if any) or the currency of the voucher or the company currency.
1281         '''
1282         res = {}
1283         for line in self.browse(cr, uid, ids, context=context):
1284             move_line = line.move_line_id
1285             if move_line:
1286                 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1287             else:
1288                 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1289         return res
1290
1291     _columns = {
1292         'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1293         'name':fields.char('Description', size=256),
1294         'account_id':fields.many2one('account.account','Account', required=True),
1295         'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1296         'untax_amount':fields.float('Untax Amount'),
1297         'amount':fields.float('Allocation', digits_compute=dp.get_precision('Account')),
1298         'reconcile': fields.boolean('Full Reconcile'),
1299         'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1300         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
1301         'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1302         'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1303         'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1304         'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True),
1305         'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True),
1306         'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1307         'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1308     }
1309     _defaults = {
1310         'name': '',
1311     }
1312
1313     def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1314         vals = { 'amount': 0.0}
1315         if reconcile:
1316             vals = { 'amount': amount_unreconciled}
1317         return {'value': vals}
1318
1319     def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1320         vals = {}
1321         if amount:
1322             vals['reconcile'] = (amount == amount_unreconciled)
1323         return {'value': vals}
1324
1325     def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1326         """
1327         Returns a dict that contains new values and context
1328
1329         @param move_line_id: latest value from user input for field move_line_id
1330         @param args: other arguments
1331         @param context: context arguments, like lang, time zone
1332
1333         @return: Returns a dict which contains new values, and context
1334         """
1335         res = {}
1336         move_line_pool = self.pool.get('account.move.line')
1337         if move_line_id:
1338             move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1339             if move_line.credit:
1340                 ttype = 'dr'
1341             else:
1342                 ttype = 'cr'
1343             res.update({
1344                 'account_id': move_line.account_id.id,
1345                 'type': ttype,
1346                 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1347             })
1348         return {
1349             'value':res,
1350         }
1351
1352     def default_get(self, cr, user, fields_list, context=None):
1353         """
1354         Returns default values for fields
1355         @param fields_list: list of fields, for which default values are required to be read
1356         @param context: context arguments, like lang, time zone
1357
1358         @return: Returns a dict that contains default values for fields
1359         """
1360         if context is None:
1361             context = {}
1362         journal_id = context.get('journal_id', False)
1363         partner_id = context.get('partner_id', False)
1364         journal_pool = self.pool.get('account.journal')
1365         partner_pool = self.pool.get('res.partner')
1366         values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1367         if (not journal_id) or ('account_id' not in fields_list):
1368             return values
1369         journal = journal_pool.browse(cr, user, journal_id, context=context)
1370         account_id = False
1371         ttype = 'cr'
1372         if journal.type in ('sale', 'sale_refund'):
1373             account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1374             ttype = 'cr'
1375         elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1376             account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1377             ttype = 'dr'
1378         elif partner_id:
1379             partner = partner_pool.browse(cr, user, partner_id, context=context)
1380             if context.get('type') == 'payment':
1381                 ttype = 'dr'
1382                 account_id = partner.property_account_payable.id
1383             elif context.get('type') == 'receipt':
1384                 account_id = partner.property_account_receivable.id
1385
1386         values.update({
1387             'account_id':account_id,
1388             'type':ttype
1389         })
1390         return values
1391 account_voucher_line()
1392
1393 class account_bank_statement(osv.osv):
1394     _inherit = 'account.bank.statement'
1395
1396     def button_cancel(self, cr, uid, ids, context=None):
1397         voucher_obj = self.pool.get('account.voucher')
1398         for st in self.browse(cr, uid, ids, context=context):
1399             voucher_ids = []
1400             for line in st.line_ids:
1401                 if line.voucher_id:
1402                     voucher_ids.append(line.voucher_id.id)
1403             voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1404         return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1405
1406     def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1407         voucher_obj = self.pool.get('account.voucher')
1408         wf_service = netsvc.LocalService("workflow")
1409         move_line_obj = self.pool.get('account.move.line')
1410         bank_st_line_obj = self.pool.get('account.bank.statement.line')
1411         st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1412         if st_line.voucher_id:
1413             voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1414             if st_line.voucher_id.state == 'cancel':
1415                 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1416             wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1417
1418             v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1419             bank_st_line_obj.write(cr, uid, [st_line_id], {
1420                 'move_ids': [(4, v.move_id.id, False)]
1421             })
1422
1423             return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1424         return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1425
1426 account_bank_statement()
1427
1428 class account_bank_statement_line(osv.osv):
1429     _inherit = 'account.bank.statement.line'
1430
1431     def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1432         if not ids:
1433             return {}
1434         res = {}
1435         for line in self.browse(cursor, user, ids, context=context):
1436             if line.voucher_id:
1437                 res[line.id] = line.voucher_id.amount#
1438             else:
1439                 res[line.id] = 0.0
1440         return res
1441
1442     def _check_amount(self, cr, uid, ids, context=None):
1443         for obj in self.browse(cr, uid, ids, context=context):
1444             if obj.voucher_id:
1445                 diff = abs(obj.amount) - obj.voucher_id.amount
1446                 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1447                     return False
1448         return True
1449
1450     _constraints = [
1451         (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1452     ]
1453
1454     _columns = {
1455         'amount_reconciled': fields.function(_amount_reconciled,
1456             string='Amount reconciled', type='float'),
1457         'voucher_id': fields.many2one('account.voucher', 'Payment'),
1458     }
1459
1460     def unlink(self, cr, uid, ids, context=None):
1461         voucher_obj = self.pool.get('account.voucher')
1462         statement_line = self.browse(cr, uid, ids, context=context)
1463         unlink_ids = []
1464         for st_line in statement_line:
1465             if st_line.voucher_id:
1466                 unlink_ids.append(st_line.voucher_id.id)
1467         voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1468         return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1469
1470 account_bank_statement_line()
1471
1472 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1473     results = []
1474     for operation in operations:
1475         result = None
1476         if not isinstance(operation, (list, tuple)):
1477             result = target_osv.read(cr, uid, operation, fields, context=context)
1478         elif operation[0] == 0:
1479             # may be necessary to check if all the fields are here and get the default values?
1480             result = operation[2]
1481         elif operation[0] == 1:
1482             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1483             if not result: result = {}
1484             result.update(operation[2])
1485         elif operation[0] == 4:
1486             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1487         if result != None:
1488             results.append(result)
1489     return results
1490
1491 class res_company(osv.osv):
1492     _inherit = "res.company"
1493     _columns = {
1494         'income_currency_exchange_account_id': fields.many2one(
1495             'account.account',
1496             string="Income Currency Rate",
1497             domain="[('type', '=', 'other')]",),
1498         'expense_currency_exchange_account_id': fields.many2one(
1499             'account.account',
1500             string="Expense Currency Rate",
1501             domain="[('type', '=', 'other')]",),
1502     }
1503
1504 res_company()
1505
1506 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: