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