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