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