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