[REM] completion in Gantt chart tasks and projects, not supported by OpenERP and...
[odoo/odoo.git] / addons / account_voucher / account_voucher.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import time
23 from lxml import etree
24
25 import netsvc
26 from osv import osv, fields
27 import decimal_precision as dp
28 from tools.translate import _
29
30
31 class account_voucher(osv.osv):
32     def _check_paid(self, cr, uid, ids, name, args, context=None):
33         res = {}
34         for voucher in self.browse(cr, uid, ids, context=context):
35             ok = True
36             for line in voucher.move_ids:
37                 if (line.account_id.type, 'in', ('receivable', 'payable')) and not line.reconcile_id:
38                     ok = False
39             res[voucher.id] = ok
40         return res
41
42     def _get_type(self, cr, uid, context=None):
43         if context is None:
44             context = {}
45         return context.get('type', False)
46
47     def _get_period(self, cr, uid, context=None):
48         if context is None: context = {}
49         if context.get('period_id', False):
50             return context.get('period_id')
51         periods = self.pool.get('account.period').find(cr, uid)
52         return periods and periods[0] or False
53
54     def _get_journal(self, cr, uid, context=None):
55         if context is None: context = {}
56         journal_pool = self.pool.get('account.journal')
57         invoice_pool = self.pool.get('account.invoice')
58         if context.get('invoice_id', False):
59             currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
60             journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
61             return journal_id and journal_id[0] or False
62         if context.get('journal_id', False):
63             return context.get('journal_id')
64         if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
65             return context.get('search_default_journal_id')
66
67         ttype = context.get('type', 'bank')
68         if ttype in ('payment', 'receipt'):
69             ttype = 'bank'
70         res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
71         return res and res[0] or False
72
73     def _get_tax(self, cr, uid, context=None):
74         if context is None: context = {}
75         journal_pool = self.pool.get('account.journal')
76         journal_id = context.get('journal_id', False)
77         if not journal_id:
78             ttype = context.get('type', 'bank')
79             res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
80             if not res:
81                 return False
82             journal_id = res[0]
83
84         if not journal_id:
85             return False
86         journal = journal_pool.browse(cr, uid, journal_id, context=context)
87         account_id = journal.default_credit_account_id or journal.default_debit_account_id
88         if account_id and account_id.tax_ids:
89             tax_id = account_id.tax_ids[0].id
90             return tax_id
91         return False
92
93     def _get_payment_rate_currency(self, cr, uid, context=None):
94         """
95         Return the default value for field payment_rate_currency_id: the currency of the journal
96         if there is one, otherwise the currency of the user's company
97         """
98         if context is None: context = {}
99         journal_pool = self.pool.get('account.journal')
100         journal_id = context.get('journal_id', False)
101         if journal_id:
102             journal = journal_pool.browse(cr, uid, journal_id, context=context)
103             if journal.currency:
104                 return journal.currency.id
105         #no journal given in the context, use company currency as default
106         return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
107
108     def _get_currency(self, cr, uid, context=None):
109         if context is None: context = {}
110         journal_pool = self.pool.get('account.journal')
111         journal_id = context.get('journal_id', False)
112         if journal_id:
113             journal = journal_pool.browse(cr, uid, journal_id, context=context)
114             if journal.currency:
115                 return journal.currency.id
116         return False
117
118     def _get_partner(self, cr, uid, context=None):
119         if context is None: context = {}
120         return context.get('partner_id', False)
121
122     def _get_reference(self, cr, uid, context=None):
123         if context is None: context = {}
124         return context.get('reference', False)
125
126     def _get_narration(self, cr, uid, context=None):
127         if context is None: context = {}
128         return context.get('narration', False)
129
130     def _get_amount(self, cr, uid, context=None):
131         if context is None:
132             context= {}
133         return context.get('amount', 0.0)
134
135     def name_get(self, cr, uid, ids, context=None):
136         if not ids:
137             return []
138         if context is None: context = {}
139         return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
140
141     def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
142         mod_obj = self.pool.get('ir.model.data')
143         if context is None: context = {}
144
145         if view_type == 'form':
146             if not view_id and context.get('invoice_type'):
147                 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
148                     result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
149                 else:
150                     result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
151                 result = result and result[1] or False
152                 view_id = result
153             if not view_id and context.get('line_type'):
154                 if context.get('line_type') == 'customer':
155                     result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
156                 else:
157                     result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
158                 result = result and result[1] or False
159                 view_id = result
160
161         res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
162         doc = etree.XML(res['arch'])
163
164         if context.get('type', 'sale') in ('purchase', 'payment'):
165             nodes = doc.xpath("//field[@name='partner_id']")
166             for node in nodes:
167                 node.set('domain', "[('supplier', '=', True)]")
168         res['arch'] = etree.tostring(doc)
169         return res
170
171     def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount):
172         debit = credit = 0.0
173         for l in line_dr_ids:
174             debit += l['amount']
175         for l in line_cr_ids:
176             credit += l['amount']
177         return abs(amount - abs(credit - debit))
178
179     def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, context=None):
180         context = context or {}
181         if not line_dr_ids and not line_cr_ids:
182             return {'value':{}}
183         line_osv = self.pool.get("account.voucher.line")
184         line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
185         line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
186
187         #loop into the lines to see if there is an amount allocated on a voucher line with a currency different than the voucher currency
188         is_multi_currency = False
189         for voucher_line in line_dr_ids+line_cr_ids:
190             if voucher_line.get('currency_id',False) != voucher_currency:
191                 is_multi_currency = True
192                 break
193         return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount), 'is_multi_currency': is_multi_currency}}
194
195     def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
196         if not ids: return {}
197         currency_obj = self.pool.get('res.currency')
198         res = {}
199         debit = credit = 0.0
200         for voucher in self.browse(cr, uid, ids, context=context):
201             for l in voucher.line_dr_ids:
202                 debit += l.amount
203             for l in voucher.line_cr_ids:
204                 credit += l.amount
205             currency = voucher.currency_id or voucher.company_id.currency_id
206             res[voucher.id] =  currency_obj.round(cr, uid, currency, abs(voucher.amount - abs(credit - debit)))
207         return res
208
209     def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
210         if not ids: return {}
211         res = {}
212         voucher_rate = company_currency_rate = 1.0
213         for voucher in self.browse(cr, uid, ids, context=context):
214             if voucher.currency_id:
215                 ctx = context.copy()
216                 ctx.update({'date': voucher.date})
217                 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
218                 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
219                     company_currency_rate =  voucher.payment_rate
220                 else:
221                     company_currency_rate = voucher.company_id.currency_id.rate
222             res[voucher.id] =  voucher.amount / voucher_rate * company_currency_rate
223         return res
224
225     _name = 'account.voucher'
226     _description = 'Accounting Voucher'
227     _order = "date desc, id desc"
228 #    _rec_name = 'number'
229     _columns = {
230         'type':fields.selection([
231             ('sale','Sale'),
232             ('purchase','Purchase'),
233             ('payment','Payment'),
234             ('receipt','Receipt'),
235         ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
236         'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
237         'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
238         'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
239         'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
240         'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
241         'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
242             domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
243         'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
244             domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
245         'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
246         'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
247 #        'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
248         'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
249         'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
250         'state':fields.selection(
251             [('draft','Draft'),
252              ('proforma','Pro-forma'),
253              ('posted','Posted'),
254              ('cancel','Cancelled')
255             ], 'State', readonly=True, size=32,
256             help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Voucher. \
257                         \n* The \'Pro-forma\' when voucher is in Pro-forma state,voucher does not have an voucher number. \
258                         \n* The \'Posted\' state is used when user create voucher,a voucher number is generated and voucher entries are created in account \
259                         \n* The \'Cancelled\' state is used when user cancel voucher.'),
260         'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
261         'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
262         'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
263         'number': fields.char('Number', size=32, readonly=True,),
264         'move_id':fields.many2one('account.move', 'Account Entry'),
265         'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
266         'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
267         'audit': fields.related('move_id','to_check', type='boolean', help='Check this box if you are unsure of that journal entry and if you want to note it as \'to be reviewed\' by an accounting expert.', relation='account.move', string='To Review'),
268         'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
269         'pay_now':fields.selection([
270             ('pay_now','Pay Directly'),
271             ('pay_later','Pay Later or Group Funds'),
272         ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
273         'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
274         'pre_line':fields.boolean('Previous Payments ?', required=False),
275         'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
276         'payment_option':fields.selection([
277                                            ('without_writeoff', 'Keep Open'),
278                                            ('with_writeoff', 'Reconcile Payment Balance'),
279                                            ], 'Payment Difference', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="This field helps you to choose what you want to do with the eventual difference between the paid amount and the sum of allocated amounts. You can either choose to keep open this difference on the partner's account, or reconcile it with the payment(s)"),
280         'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
281         'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
282         'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
283         'writeoff_amount': fields.function(_get_writeoff_amount, string='Difference Amount', type='float', readonly=True, help="Computed as the difference between the amount stated in the voucher and the sum of allocation on the voucher lines."),
284         'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
285         'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
286             help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field)  and the voucher currency.'),
287         'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
288         'is_multi_currency': fields.boolean('Multi Currency Voucher', help='Fields with internal purpose only that depicts if the voucher is a multi currency one or not'),
289     }
290     _defaults = {
291         'period_id': _get_period,
292         'partner_id': _get_partner,
293         'journal_id':_get_journal,
294         'currency_id': _get_currency,
295         'reference': _get_reference,
296         'narration':_get_narration,
297         'amount': _get_amount,
298         'type':_get_type,
299         'state': 'draft',
300         'pay_now': 'pay_later',
301         'name': '',
302         'date': lambda *a: time.strftime('%Y-%m-%d'),
303         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
304         'tax_id': _get_tax,
305         'payment_option': 'without_writeoff',
306         'comment': _('Write-Off'),
307         'payment_rate': 1.0,
308         'payment_rate_currency_id': _get_payment_rate_currency,
309     }
310
311     def compute_tax(self, cr, uid, ids, context=None):
312         tax_pool = self.pool.get('account.tax')
313         partner_pool = self.pool.get('res.partner')
314         position_pool = self.pool.get('account.fiscal.position')
315         voucher_line_pool = self.pool.get('account.voucher.line')
316         voucher_pool = self.pool.get('account.voucher')
317         if context is None: context = {}
318
319         for voucher in voucher_pool.browse(cr, uid, ids, context=context):
320             voucher_amount = 0.0
321             for line in voucher.line_ids:
322                 voucher_amount += line.untax_amount or line.amount
323                 line.amount = line.untax_amount or line.amount
324                 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
325
326             if not voucher.tax_id:
327                 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
328                 continue
329
330             tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
331             partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
332             taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
333             tax = tax_pool.browse(cr, uid, taxes, context=context)
334
335             total = voucher_amount
336             total_tax = 0.0
337
338             if not tax[0].price_include:
339                 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_amount, 1).get('taxes', []):
340                     total_tax += tax_line.get('amount', 0.0)
341                 total += total_tax
342             else:
343                 for line in voucher.line_ids:
344                     line_total = 0.0
345                     line_tax = 0.0
346
347                     for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
348                         line_tax += tax_line.get('amount', 0.0)
349                         line_total += tax_line.get('price_unit')
350                     total_tax += line_tax
351                     untax_amount = line.untax_amount or line.amount
352                     voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
353
354             self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
355         return True
356
357     def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
358         context = context or {}
359         tax_pool = self.pool.get('account.tax')
360         partner_pool = self.pool.get('res.partner')
361         position_pool = self.pool.get('account.fiscal.position')
362         line_pool = self.pool.get('account.voucher.line')
363         res = {
364             'tax_amount': False,
365             'amount': False,
366         }
367         voucher_total = 0.0
368
369         line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
370
371         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 accounts on the journal "%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, time.strftime('%Y-%m-%d'), price, ttype, 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         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         seq_obj = self.pool.get('ir.sequence')
870         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
871         if voucher_brw.number:
872             name = voucher_brw.number
873         elif voucher_brw.journal_id.sequence_id:
874             name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id)
875         else:
876             raise osv.except_osv(_('Error !'),
877                         _('Please define a sequence on the journal !'))
878         if not voucher_brw.reference:
879             ref = name.replace('/','')
880         else:
881             ref = voucher_brw.reference
882
883         move = {
884             'name': name,
885             'journal_id': voucher_brw.journal_id.id,
886             'narration': voucher_brw.narration,
887             'date': voucher_brw.date,
888             'ref': ref,
889             'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
890         }
891         return move
892
893     def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
894         '''
895         Prepare the two lines in company currency due to currency rate difference.
896
897         :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
898             entries
899         :param move_id: Account move wher the move lines will be.
900         :param amount_residual: Amount to be posted.
901         :param company_currency: id of currency of the company to which the voucher belong
902         :param current_currency: id of currency of the voucher
903         :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
904         :rtype: tuple of dict
905         '''
906         if amount_residual > 0:
907             account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
908             if not account_id:
909                 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! "))
910         else:
911             account_id = line.voucher_id.company_id.income_currency_exchange_account_id
912             if not account_id:
913                 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! "))
914         # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
915         # the receivable/payable account may have a secondary currency, which render this field mandatory
916         account_currency_id = company_currency <> current_currency and current_currency or False
917         move_line = {
918             'journal_id': line.voucher_id.journal_id.id,
919             'period_id': line.voucher_id.period_id.id,
920             'name': _('change')+': '+(line.name or '/'),
921             'account_id': line.account_id.id,
922             'move_id': move_id,
923             'partner_id': line.voucher_id.partner_id.id,
924             'currency_id': account_currency_id,
925             'amount_currency': 0.0,
926             'quantity': 1,
927             'credit': amount_residual > 0 and amount_residual or 0.0,
928             'debit': amount_residual < 0 and -amount_residual or 0.0,
929             'date': line.voucher_id.date,
930         }
931         move_line_counterpart = {
932             'journal_id': line.voucher_id.journal_id.id,
933             'period_id': line.voucher_id.period_id.id,
934             'name': _('change')+': '+(line.name or '/'),
935             'account_id': account_id.id,
936             'move_id': move_id,
937             'amount_currency': 0.0,
938             'partner_id': line.voucher_id.partner_id.id,
939             'currency_id': account_currency_id,
940             'quantity': 1,
941             'debit': amount_residual > 0 and amount_residual or 0.0,
942             'credit': amount_residual < 0 and -amount_residual or 0.0,
943             'date': line.voucher_id.date,
944         }
945         return (move_line, move_line_counterpart)
946
947     def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
948         '''
949         This function convert the amount given in company currency. It takes either the rate in the voucher (if the
950         payment_rate_currency_id is relevant) either the rate encoded in the system.
951
952         :param amount: float. The amount to convert
953         :param voucher: id of the voucher on which we want the conversion
954         :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
955             field in order to select the good rate to use.
956         :return: the amount in the currency of the voucher's company
957         :rtype: float
958         '''
959         currency_obj = self.pool.get('res.currency')
960         voucher = self.browse(cr, uid, voucher_id, context=context)
961         res = amount
962         if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
963             # the rate specified on the voucher is for the company currency
964             rate_between_voucher_and_base = voucher.currency_id.rate or 1.0
965             rate_between_base_and_company = voucher.payment_rate or 1.0
966             res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount / rate_between_voucher_and_base * rate_between_base_and_company))
967         else:
968             # the rate specified on the voucher is not relevant, we use all the rates in the system
969             res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
970         return res
971
972     def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
973         '''
974         Create one account move line, on the given account move, per voucher line where amount is not 0.0.
975         It returns Tuple with tot_line what is total of difference between debit and credit and
976         a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
977
978         :param voucher_id: Voucher id what we are working with
979         :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
980         :param move_id: Account move wher those lines will be joined.
981         :param company_currency: id of currency of the company to which the voucher belong
982         :param current_currency: id of currency of the voucher
983         :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
984         :rtype: tuple(float, list of int)
985         '''
986         if context is None:
987             context = {}
988         move_line_obj = self.pool.get('account.move.line')
989         currency_obj = self.pool.get('res.currency')
990         tax_obj = self.pool.get('account.tax')
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         currency_obj = self.pool.get('res.currency')
1118         move_line = {}
1119
1120         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1121         current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1122
1123         if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1124             diff = line_total
1125             account_id = False
1126             write_off_name = ''
1127             if voucher_brw.payment_option == 'with_writeoff':
1128                 account_id = voucher_brw.writeoff_acc_id.id
1129                 write_off_name = voucher_brw.comment
1130             elif voucher_brw.type in ('sale', 'receipt'):
1131                 account_id = voucher_brw.partner_id.property_account_receivable.id
1132             else:
1133                 account_id = voucher_brw.partner_id.property_account_payable.id
1134             move_line = {
1135                 'name': write_off_name or name,
1136                 'account_id': account_id,
1137                 'move_id': move_id,
1138                 'partner_id': voucher_brw.partner_id.id,
1139                 'date': voucher_brw.date,
1140                 'credit': diff > 0 and diff or 0.0,
1141                 'debit': diff < 0 and -diff or 0.0,
1142                 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
1143                 'currency_id': company_currency <> current_currency and current_currency or False,
1144                 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1145             }
1146
1147         return move_line
1148
1149     def _get_company_currency(self, cr, uid, voucher_id, context=None):
1150         '''
1151         Get the currency of the actual company.
1152
1153         :param voucher_id: Id of the voucher what i want to obtain company currency.
1154         :return: currency id of the company of the voucher
1155         :rtype: int
1156         '''
1157         return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1158
1159     def _get_current_currency(self, cr, uid, voucher_id, context=None):
1160         '''
1161         Get the currency of the voucher.
1162
1163         :param voucher_id: Id of the voucher what i want to obtain current currency.
1164         :return: currency id of the voucher
1165         :rtype: int
1166         '''
1167         voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1168         return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1169
1170     def action_move_line_create(self, cr, uid, ids, context=None):
1171         '''
1172         Confirm the vouchers given in ids and create the journal entries for each of them
1173         '''
1174         if context is None:
1175             context = {}
1176         move_pool = self.pool.get('account.move')
1177         move_line_pool = self.pool.get('account.move.line')
1178         for voucher in self.browse(cr, uid, ids, context=context):
1179             if voucher.move_id:
1180                 continue
1181             company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1182             current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1183             # we select the context to use accordingly if it's a multicurrency case or not
1184             context = self._sel_context(cr, uid, voucher.id, context)
1185             # But for the operations made by _convert_amount, we always need to give the date in the context
1186             ctx = context.copy()
1187             ctx.update({'date': voucher.date})
1188             # Create the account move record.
1189             move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1190             # Get the name of the account_move just created
1191             name = move_pool.browse(cr, uid, move_id, context=context).name
1192             # Create the first line of the voucher
1193             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)
1194             move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1195             line_total = move_line_brw.debit - move_line_brw.credit
1196             rec_list_ids = []
1197             if voucher.type == 'sale':
1198                 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1199             elif voucher.type == 'purchase':
1200                 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1201             # Create one move line per voucher line where amount is not 0.0
1202             line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1203
1204             # Create the writeoff line if needed
1205             ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1206             if ml_writeoff:
1207                 move_line_pool.create(cr, uid, ml_writeoff, context)
1208             # We post the voucher.
1209             self.write(cr, uid, [voucher.id], {
1210                 'move_id': move_id,
1211                 'state': 'posted',
1212                 'number': name,
1213             })
1214             if voucher.journal_id.entry_posted:
1215                 move_pool.post(cr, uid, [move_id], context={})
1216             # We automatically reconcile the account move lines.
1217             for rec_ids in rec_list_ids:
1218                 if len(rec_ids) >= 2:
1219                     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)
1220         return True
1221
1222     def copy(self, cr, uid, id, default={}, context=None):
1223         default.update({
1224             'state': 'draft',
1225             'number': False,
1226             'move_id': False,
1227             'line_cr_ids': False,
1228             'line_dr_ids': False,
1229             'reference': False
1230         })
1231         if 'date' not in default:
1232             default['date'] = time.strftime('%Y-%m-%d')
1233         return super(account_voucher, self).copy(cr, uid, id, default, context)
1234
1235 account_voucher()
1236
1237 class account_voucher_line(osv.osv):
1238     _name = 'account.voucher.line'
1239     _description = 'Voucher Lines'
1240     _order = "move_line_id"
1241
1242     # If the payment is in the same currency than the invoice, we keep the same amount
1243     # Otherwise, we compute from company currency to payment currency
1244     def _compute_balance(self, cr, uid, ids, name, args, context=None):
1245         currency_pool = self.pool.get('res.currency')
1246         rs_data = {}
1247         for line in self.browse(cr, uid, ids, context=context):
1248             ctx = context.copy()
1249             ctx.update({'date': line.voucher_id.date})
1250             res = {}
1251             company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1252             voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1253             move_line = line.move_line_id or False
1254
1255             if not move_line:
1256                 res['amount_original'] = 0.0
1257                 res['amount_unreconciled'] = 0.0
1258             elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1259                 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1260                 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)
1261             elif move_line and move_line.credit > 0:
1262                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1263                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1264             else:
1265                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1266                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1267
1268             rs_data[line.id] = res
1269         return rs_data
1270
1271     def _currency_id(self, cr, uid, ids, name, args, context=None):
1272         '''
1273         This function returns the currency id of a voucher line. It's either the currency of the 
1274         associated move line (if any) or the currency of the voucher or the company currency.
1275         '''
1276         res = {}
1277         for line in self.browse(cr, uid, ids, context=context):
1278             move_line = line.move_line_id
1279             if move_line:
1280                 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1281             else:
1282                 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1283         return res
1284
1285     _columns = {
1286         'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1287         'name':fields.char('Description', size=256),
1288         'account_id':fields.many2one('account.account','Account', required=True),
1289         'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1290         'untax_amount':fields.float('Untax Amount'),
1291         'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1292         'reconcile': fields.boolean('Full Reconcile'),
1293         'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1294         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
1295         'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1296         'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1297         'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1298         'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True),
1299         'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True),
1300         'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1301         'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1302     }
1303     _defaults = {
1304         'name': '',
1305     }
1306
1307     def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1308         vals = { 'amount': 0.0}
1309         if reconcile:
1310             vals = { 'amount': amount_unreconciled}
1311         return {'value': vals}
1312
1313     def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1314         vals = {}
1315         if amount:
1316             vals['reconcile'] = (amount == amount_unreconciled)
1317         return {'value': vals}
1318
1319     def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1320         """
1321         Returns a dict that contains new values and context
1322
1323         @param move_line_id: latest value from user input for field move_line_id
1324         @param args: other arguments
1325         @param context: context arguments, like lang, time zone
1326
1327         @return: Returns a dict which contains new values, and context
1328         """
1329         res = {}
1330         move_line_pool = self.pool.get('account.move.line')
1331         if move_line_id:
1332             move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1333             if move_line.credit:
1334                 ttype = 'dr'
1335             else:
1336                 ttype = 'cr'
1337             res.update({
1338                 'account_id': move_line.account_id.id,
1339                 'type': ttype,
1340                 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1341             })
1342         return {
1343             'value':res,
1344         }
1345
1346     def default_get(self, cr, user, fields_list, context=None):
1347         """
1348         Returns default values for fields
1349         @param fields_list: list of fields, for which default values are required to be read
1350         @param context: context arguments, like lang, time zone
1351
1352         @return: Returns a dict that contains default values for fields
1353         """
1354         if context is None:
1355             context = {}
1356         journal_id = context.get('journal_id', False)
1357         partner_id = context.get('partner_id', False)
1358         journal_pool = self.pool.get('account.journal')
1359         partner_pool = self.pool.get('res.partner')
1360         values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1361         if (not journal_id) or ('account_id' not in fields_list):
1362             return values
1363         journal = journal_pool.browse(cr, user, journal_id, context=context)
1364         account_id = False
1365         ttype = 'cr'
1366         if journal.type in ('sale', 'sale_refund'):
1367             account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1368             ttype = 'cr'
1369         elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1370             account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1371             ttype = 'dr'
1372         elif partner_id:
1373             partner = partner_pool.browse(cr, user, partner_id, context=context)
1374             if context.get('type') == 'payment':
1375                 ttype = 'dr'
1376                 account_id = partner.property_account_payable.id
1377             elif context.get('type') == 'receipt':
1378                 account_id = partner.property_account_receivable.id
1379
1380         values.update({
1381             'account_id':account_id,
1382             'type':ttype
1383         })
1384         return values
1385 account_voucher_line()
1386
1387 class account_bank_statement(osv.osv):
1388     _inherit = 'account.bank.statement'
1389
1390     def button_cancel(self, cr, uid, ids, context=None):
1391         voucher_obj = self.pool.get('account.voucher')
1392         for st in self.browse(cr, uid, ids, context=context):
1393             voucher_ids = []
1394             for line in st.line_ids:
1395                 if line.voucher_id:
1396                     voucher_ids.append(line.voucher_id.id)
1397             voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1398         return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1399
1400     def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1401         voucher_obj = self.pool.get('account.voucher')
1402         wf_service = netsvc.LocalService("workflow")
1403         move_line_obj = self.pool.get('account.move.line')
1404         bank_st_line_obj = self.pool.get('account.bank.statement.line')
1405         st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1406         if st_line.voucher_id:
1407             voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1408             if st_line.voucher_id.state == 'cancel':
1409                 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1410             wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1411
1412             v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1413             bank_st_line_obj.write(cr, uid, [st_line_id], {
1414                 'move_ids': [(4, v.move_id.id, False)]
1415             })
1416
1417             return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1418         return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1419
1420 account_bank_statement()
1421
1422 class account_bank_statement_line(osv.osv):
1423     _inherit = 'account.bank.statement.line'
1424
1425     def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1426         if not ids:
1427             return {}
1428         res = {}
1429         for line in self.browse(cursor, user, ids, context=context):
1430             if line.voucher_id:
1431                 res[line.id] = line.voucher_id.amount#
1432             else:
1433                 res[line.id] = 0.0
1434         return res
1435
1436     def _check_amount(self, cr, uid, ids, context=None):
1437         for obj in self.browse(cr, uid, ids, context=context):
1438             if obj.voucher_id:
1439                 diff = abs(obj.amount) - obj.voucher_id.amount
1440                 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1441                     return False
1442         return True
1443
1444     _constraints = [
1445         (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1446     ]
1447
1448     _columns = {
1449         'amount_reconciled': fields.function(_amount_reconciled,
1450             string='Amount reconciled', type='float'),
1451         'voucher_id': fields.many2one('account.voucher', 'Payment'),
1452     }
1453
1454     def unlink(self, cr, uid, ids, context=None):
1455         voucher_obj = self.pool.get('account.voucher')
1456         statement_line = self.browse(cr, uid, ids, context=context)
1457         unlink_ids = []
1458         for st_line in statement_line:
1459             if st_line.voucher_id:
1460                 unlink_ids.append(st_line.voucher_id.id)
1461         voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1462         return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1463
1464 account_bank_statement_line()
1465
1466 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1467     results = []
1468     for operation in operations:
1469         result = None
1470         if not isinstance(operation, (list, tuple)):
1471             result = target_osv.read(cr, uid, operation, fields, context=context)
1472         elif operation[0] == 0:
1473             # may be necessary to check if all the fields are here and get the default values?
1474             result = operation[2]
1475         elif operation[0] == 1:
1476             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1477             if not result: result = {}
1478             result.update(operation[2])
1479         elif operation[0] == 4:
1480             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1481         if result != None:
1482             results.append(result)
1483     return results
1484
1485 class res_company(osv.osv):
1486     _inherit = "res.company"
1487     _columns = {
1488         'income_currency_exchange_account_id': fields.many2one(
1489             'account.account',
1490             string="Income Currency Rate",
1491             domain="[('type', '=', 'other')]",),
1492         'expense_currency_exchange_account_id': fields.many2one(
1493             'account.account',
1494             string="Expense Currency Rate",
1495             domain="[('type', '=', 'other')]",),
1496     }
1497
1498 res_company()
1499
1500 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: