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