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