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