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