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