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