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