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