[REF] Including decimal precision on amount_residual calculated to avoid account...
[odoo/odoo.git] / addons / account_voucher / account_voucher.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import time
23 from lxml import etree
24
25 import netsvc
26 from osv import osv, fields
27 import decimal_precision as dp
28 from tools.translate import _
29
30
31 class account_voucher(osv.osv):
32     def _check_paid(self, cr, uid, ids, name, args, context=None):
33         res = {}
34         for voucher in self.browse(cr, uid, ids, context=context):
35             ok = True
36             for line in voucher.move_ids:
37                 if (line.account_id.type, 'in', ('receivable', 'payable')) and not line.reconcile_id:
38                     ok = False
39             res[voucher.id] = ok
40         return res
41
42
43
44     def _get_type(self, cr, uid, context=None):
45         if context is None:
46             context = {}
47         return context.get('type', False)
48
49     def _get_period(self, cr, uid, context=None):
50         if context is None: context = {}
51         if context.get('period_id', False):
52             return context.get('period_id')
53         periods = self.pool.get('account.period').find(cr, uid)
54         return periods and periods[0] or False
55
56     def _get_journal(self, cr, uid, context=None):
57         if context is None: context = {}
58         journal_pool = self.pool.get('account.journal')
59         invoice_pool = self.pool.get('account.invoice')
60         if context.get('invoice_id', False):
61             currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
62             journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
63             return journal_id and journal_id[0] or False
64         if context.get('journal_id', False):
65             return context.get('journal_id')
66         if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
67             return context.get('search_default_journal_id')
68
69         ttype = context.get('type', 'bank')
70         if ttype in ('payment', 'receipt'):
71             ttype = 'bank'
72         res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
73         return res and res[0] or False
74
75     def _get_tax(self, cr, uid, context=None):
76         if context is None: context = {}
77         journal_pool = self.pool.get('account.journal')
78         journal_id = context.get('journal_id', False)
79         if not journal_id:
80             ttype = context.get('type', 'bank')
81             res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
82             if not res:
83                 return False
84             journal_id = res[0]
85
86         if not journal_id:
87             return False
88         journal = journal_pool.browse(cr, uid, journal_id, context=context)
89         account_id = journal.default_credit_account_id or journal.default_debit_account_id
90         if account_id and account_id.tax_ids:
91             tax_id = account_id.tax_ids[0].id
92             return tax_id
93         return False
94
95     def _get_currency(self, cr, uid, context=None):
96         if context is None: context = {}
97         journal_pool = self.pool.get('account.journal')
98         journal_id = context.get('journal_id', False)
99         if journal_id:
100             journal = journal_pool.browse(cr, uid, journal_id, context=context)
101 #            currency_id = journal.company_id.currency_id.id
102             if journal.currency:
103                 return journal.currency.id
104         return False
105
106     def _get_partner(self, cr, uid, context=None):
107         if context is None: context = {}
108         return context.get('partner_id', False)
109
110     def _get_reference(self, cr, uid, context=None):
111         if context is None: context = {}
112         return context.get('reference', False)
113
114     def _get_narration(self, cr, uid, context=None):
115         if context is None: context = {}
116         return context.get('narration', False)
117
118     def _get_amount(self, cr, uid, context=None):
119         if context is None:
120             context= {}
121         return context.get('amount', 0.0)
122
123     def name_get(self, cr, uid, ids, context=None):
124         if not ids:
125             return []
126         if context is None: context = {}
127         return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
128
129     def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
130         mod_obj = self.pool.get('ir.model.data')
131         if context is None: context = {}
132         if not view_id and context.get('invoice_type', False):
133             if context.get('invoice_type', False) in ('out_invoice', 'out_refund'):
134                 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
135             else:
136                 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
137             result = result and result[1] or False
138             view_id = result
139         if not view_id and view_type == 'form' and context.get('line_type', False):
140             if context.get('line_type', False) == 'customer':
141                 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
142             else:
143                 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
144             result = result and result[1] or False
145             view_id = result
146
147         res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
148         doc = etree.XML(res['arch'])
149         nodes = doc.xpath("//field[@name='partner_id']")
150         if context.get('type', 'sale') in ('purchase', 'payment'):
151             for node in nodes:
152                 node.set('domain', "[('supplier', '=', True)]")
153             res['arch'] = etree.tostring(doc)
154         return res
155
156     def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount):
157         debit = credit = 0.0
158         for l in line_dr_ids:
159             debit += l['amount']
160         for l in line_cr_ids:
161             credit += l['amount']
162         return abs(amount - abs(credit - debit))
163
164     def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, context=None):
165         context = context or {}
166         if not line_dr_ids and not line_cr_ids:
167             return {'value':{}}
168         line_osv = self.pool.get("account.voucher.line")
169         line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
170         line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
171         return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount)}}
172
173     def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
174         if not ids: return {}
175         res = {}
176         debit = credit = 0.0
177         for voucher in self.browse(cr, uid, ids, context=context):
178             for l in voucher.line_dr_ids:
179                 debit += l.amount
180             for l in voucher.line_cr_ids:
181                 credit += l.amount
182             res[voucher.id] =  abs(voucher.amount - abs(credit - debit))
183         return res
184
185     _name = 'account.voucher'
186     _description = 'Accounting Voucher'
187     _order = "date desc, id desc"
188 #    _rec_name = 'number'
189     _columns = {
190         'type':fields.selection([
191             ('sale','Sale'),
192             ('purchase','Purchase'),
193             ('payment','Payment'),
194             ('receipt','Receipt'),
195         ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
196         'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
197         'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
198         'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
199         'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
200         'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
201         'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
202             domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
203         'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
204             domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
205         'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
206         'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
207 #        'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
208         'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
209         'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
210         'state':fields.selection(
211             [('draft','Draft'),
212              ('proforma','Pro-forma'),
213              ('posted','Posted'),
214              ('cancel','Cancelled')
215             ], 'State', readonly=True, size=32,
216             help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Voucher. \
217                         \n* The \'Pro-forma\' when voucher is in Pro-forma state,voucher does not have an voucher number. \
218                         \n* The \'Posted\' state is used when user create voucher,a voucher number is generated and voucher entries are created in account \
219                         \n* The \'Cancelled\' state is used when user cancel voucher.'),
220         'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
221         'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
222         'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
223         'number': fields.char('Number', size=32, readonly=True,),
224         'move_id':fields.many2one('account.move', 'Account Entry'),
225         'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
226         'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
227         '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'),
228         'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
229         'pay_now':fields.selection([
230             ('pay_now','Pay Directly'),
231             ('pay_later','Pay Later or Group Funds'),
232         ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
233         'tax_id':fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}),
234         'pre_line':fields.boolean('Previous Payments ?', required=False),
235         'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
236         'payment_option':fields.selection([
237                                            ('without_writeoff', 'Keep Open'),
238                                            ('with_writeoff', 'Reconcile Payment Balance'),
239                                            ], 'Payment Difference', required=True, readonly=True, states={'draft': [('readonly', False)]}),
240         'exchange_acc_id': fields.many2one('account.account', 'Exchange Diff. Account', readonly=True, states={'draft': [('readonly', False)]}),
241         'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
242         'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
243         'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
244         'writeoff_amount': fields.function(_get_writeoff_amount, string='Reconcile Amount', type='float', readonly=True),
245     }
246     _defaults = {
247         'period_id': _get_period,
248         'partner_id': _get_partner,
249         'journal_id':_get_journal,
250         'currency_id': _get_currency,
251         'reference': _get_reference,
252         'narration':_get_narration,
253         'amount': _get_amount,
254         'type':_get_type,
255         'state': 'draft',
256         'pay_now': 'pay_later',
257         'name': '',
258         'date': lambda *a: time.strftime('%Y-%m-%d'),
259         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
260         'tax_id': _get_tax,
261         'payment_option': 'without_writeoff',
262         'comment': _('Write-Off'),
263     }
264
265     def compute_tax(self, cr, uid, ids, context=None):
266         tax_pool = self.pool.get('account.tax')
267         partner_pool = self.pool.get('res.partner')
268         position_pool = self.pool.get('account.fiscal.position')
269         voucher_line_pool = self.pool.get('account.voucher.line')
270         voucher_pool = self.pool.get('account.voucher')
271         if context is None: context = {}
272
273         for voucher in voucher_pool.browse(cr, uid, ids, context=context):
274             voucher_amount = 0.0
275             for line in voucher.line_ids:
276                 voucher_amount += line.untax_amount or line.amount
277                 line.amount = line.untax_amount or line.amount
278                 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
279
280             if not voucher.tax_id:
281                 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
282                 continue
283
284             tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
285             partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
286             taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
287             tax = tax_pool.browse(cr, uid, taxes, context=context)
288
289             total = voucher_amount
290             total_tax = 0.0
291
292             if not tax[0].price_include:
293                 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_amount, 1).get('taxes', []):
294                     total_tax += tax_line.get('amount', 0.0)
295                 total += total_tax
296             else:
297                 for line in voucher.line_ids:
298                     line_total = 0.0
299                     line_tax = 0.0
300
301                     for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
302                         line_tax += tax_line.get('amount', 0.0)
303                         line_total += tax_line.get('price_unit')
304                     total_tax += line_tax
305                     untax_amount = line.untax_amount or line.amount
306                     voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
307
308             self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
309         return True
310
311     def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
312         context = context or {}
313         tax_pool = self.pool.get('account.tax')
314         partner_pool = self.pool.get('res.partner')
315         position_pool = self.pool.get('account.fiscal.position')
316         line_pool = self.pool.get('account.voucher.line')
317         res = {
318             'tax_amount': False,
319             'amount': False,
320         }
321         voucher_total = 0.0
322
323         line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
324
325         total = 0.0
326         total_tax = 0.0
327         for line in line_ids:
328             line_amount = 0.0
329             line_amount = line.get('amount',0.0)
330             voucher_total += line_amount
331
332         total = voucher_total
333         total_tax = 0.0
334         if tax_id:
335             tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
336             if partner_id:
337                 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
338                 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
339                 tax = tax_pool.browse(cr, uid, taxes, context=context)
340
341             if not tax[0].price_include:
342                 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_total, 1).get('taxes', []):
343                     total_tax += tax_line.get('amount')
344                 total += total_tax
345
346         res.update({
347             'amount':total or voucher_total,
348             'tax_amount':total_tax
349         })
350         return {
351             'value':res
352         }
353
354     def onchange_term_id(self, cr, uid, ids, term_id, amount):
355         term_pool = self.pool.get('account.payment.term')
356         terms = False
357         due_date = False
358         default = {'date_due':False}
359         if term_id and amount:
360             terms = term_pool.compute(cr, uid, term_id, amount)
361         if terms:
362             due_date = terms[-1][0]
363             default.update({
364                 'date_due':due_date
365             })
366         return {'value':default}
367
368     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, context=None):
369         """price
370         Returns a dict that contains new values and context
371
372         @param partner_id: latest value from user input for field partner_id
373         @param args: other arguments
374         @param context: context arguments, like lang, time zone
375
376         @return: Returns a dict which contains new values, and context
377         """
378         default = {
379             'value':{},
380         }
381
382         if not partner_id or not journal_id:
383             return default
384
385         partner_pool = self.pool.get('res.partner')
386         journal_pool = self.pool.get('account.journal')
387
388         journal = journal_pool.browse(cr, uid, journal_id, context=context)
389         partner = partner_pool.browse(cr, uid, partner_id, context=context)
390         account_id = False
391         tr_type = False
392         if journal.type in ('sale','sale_refund'):
393             account_id = partner.property_account_receivable.id
394             tr_type = 'sale'
395         elif journal.type in ('purchase', 'purchase_refund','expense'):
396             account_id = partner.property_account_payable.id
397             tr_type = 'purchase'
398         else:
399             if not journal.default_credit_account_id or not journal.default_debit_account_id:
400                 raise osv.except_osv(_('Error !'), _('Please define default credit/debit account on the %s !') % (journal.name))
401             account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
402             tr_type = 'receipt'
403
404         default['value']['account_id'] = account_id
405         default['value']['type'] = ttype or tr_type
406
407         vals = self.onchange_journal(cr, uid, ids, journal_id, line_ids, tax_id, partner_id, context)
408         default['value'].update(vals.get('value'))
409
410         return default
411
412     def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
413         """
414         Returns a dict that contains new values and context
415
416         @param partner_id: latest value from user input for field partner_id
417         @param args: other arguments
418         @param context: context arguments, like lang, time zone
419
420         @return: Returns a dict which contains new values, and context
421         """
422         if context is None:
423             context = {}
424         if not journal_id:
425             return {}
426         context_multi_currency = context.copy()
427         if date:
428             context_multi_currency.update({'date': date})
429
430         line_pool = self.pool.get('account.voucher.line')
431         line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
432         if line_ids:
433             line_pool.unlink(cr, uid, line_ids)
434
435         currency_pool = self.pool.get('res.currency')
436         move_line_pool = self.pool.get('account.move.line')
437         partner_pool = self.pool.get('res.partner')
438         journal_pool = self.pool.get('account.journal')
439
440         vals = self.onchange_journal(cr, uid, ids, journal_id, [], False, partner_id, context)
441         vals = vals.get('value')
442
443         journal = journal_pool.browse(cr, uid, journal_id, context=context)
444         currency_id = vals.get('currency_id', currency_id)
445         default = {
446             'value':{'line_ids':[], 'line_dr_ids':[], 'line_cr_ids':[], 'pre_line': False, 'currency_id':currency_id},
447         }
448         currency_id = currency_id or journal.company_id.currency_id.id
449
450         if not partner_id:
451             return default
452
453         if not partner_id and ids:
454             line_ids = line_pool.search(cr, uid, [('voucher_id', '=', ids[0])])
455             if line_ids:
456                 line_pool.unlink(cr, uid, line_ids)
457             return default
458
459         partner = partner_pool.browse(cr, uid, partner_id, context=context)
460         account_id = False
461         if journal.type in ('sale','sale_refund'):
462             account_id = partner.property_account_receivable.id
463         elif journal.type in ('purchase', 'purchase_refund','expense'):
464             account_id = partner.property_account_payable.id
465         else:
466             account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
467
468         default['value']['account_id'] = account_id
469
470         if journal.type not in ('cash', 'bank'):
471             return default
472
473         total_credit = 0.0
474         total_debit = 0.0
475         account_type = 'receivable'
476         if ttype == 'payment':
477             account_type = 'payable'
478             total_debit = price or 0.0
479         else:
480             total_credit = price or 0.0
481             account_type = 'receivable'
482
483         if not context.get('move_line_ids', False):
484             ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
485         else:
486             ids = context['move_line_ids']
487         ids.reverse()
488         moves = move_line_pool.browse(cr, uid, ids, context=context)
489
490         #company_currency = journal.company_id.currency_id.id
491         #if company_currency != currency_id and ttype == 'payment':
492         #    total_debit = currency_pool.compute(cr, uid, currency_id, company_currency, total_debit, context=context_multi_currency)
493         #elif company_currency != currency_id and ttype == 'receipt':
494         #    total_credit = currency_pool.compute(cr, uid, currency_id, company_currency, total_credit, context=context_multi_currency)
495
496         company_currency = journal.company_id.currency_id.id
497         for line in moves:
498             if line.credit and line.reconcile_partial_id and ttype == 'receipt':
499                 continue
500             if line.debit and line.reconcile_partial_id and ttype == 'payment':
501                 continue
502
503             if line.currency_id and currency_id==line.currency_id.id:
504                 total_credit += line.amount_currency <0 and -line.amount_currency or 0.0
505                 total_debit += line.amount_currency >0 and line.amount_currency or 0.0
506             else:
507                 total_credit += currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or 0.0)
508                 total_debit += currency_pool.compute(cr, uid, company_currency, currency_id, line.debit or 0.0)
509
510         for line in moves:
511             if line.credit and line.reconcile_partial_id and ttype == 'receipt':
512                 continue
513             if line.debit and line.reconcile_partial_id and ttype == 'payment':
514                 continue
515
516             if line.currency_id and currency_id==line.currency_id.id:
517                 amount_original = abs(line.amount_currency)
518                 amount_unreconciled = abs(line.amount_residual_currency)
519             else:
520                 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
521                 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
522
523             #original_amount = line.credit or line.debit or 0.0
524             #amount_unreconciled = currency_pool.compute(cr, uid, line.currency_id and line.currency_id.id or company_currency, currency_id, abs(line.amount_residual_currency), context=context_multi_currency)
525             rs = {
526                 'name':line.move_id.name,
527                 'type': line.credit and 'dr' or 'cr',
528                 'move_line_id':line.id,
529                 'account_id':line.account_id.id,
530                 'amount_original': amount_original,
531                 'date_original':line.date,
532                 'date_due':line.date_maturity,
533                 'amount_unreconciled': amount_unreconciled,
534
535             }
536
537             if line.credit:
538                 amount = min(amount_unreconciled, total_debit)
539                 rs['amount'] = amount
540                 total_debit -= amount
541             else:
542                 amount = min(amount_unreconciled, total_credit)
543                 rs['amount'] = amount
544                 total_credit -= amount
545
546             default['value']['line_ids'].append(rs)
547             if rs['type'] == 'cr':
548                 default['value']['line_cr_ids'].append(rs)
549             else:
550                 default['value']['line_dr_ids'].append(rs)
551
552             if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
553                 default['value']['pre_line'] = 1
554             elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
555                 default['value']['pre_line'] = 1
556             default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price)
557         return default
558
559     def onchange_date(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
560         """
561         @param date: latest value from user input for field date
562         @param args: other arguments
563         @param context: context arguments, like lang, time zone
564         @return: Returns a dict which contains new values, and context
565         """
566         period_pool = self.pool.get('account.period')
567         res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=context)
568         pids = period_pool.search(cr, uid, [('date_start', '<=', date), ('date_stop', '>=', date)])
569         if pids:
570             if not 'value' in res:
571                 res['value'] = {}
572             res['value'].update({'period_id':pids[0]})
573         return res
574
575     def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, context=None):
576         if not journal_id:
577             return False
578         journal_pool = self.pool.get('account.journal')
579         journal = journal_pool.browse(cr, uid, journal_id, context=context)
580         account_id = journal.default_credit_account_id or journal.default_debit_account_id
581         tax_id = False
582         if account_id and account_id.tax_ids:
583             tax_id = account_id.tax_ids[0].id
584
585         vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
586         vals['value'].update({'tax_id':tax_id})
587         currency_id = False #journal.company_id.currency_id.id
588         if journal.currency:
589             currency_id = journal.currency.id
590         vals['value'].update({'currency_id':currency_id})
591         return vals
592
593     def proforma_voucher(self, cr, uid, ids, context=None):
594         self.action_move_line_create(cr, uid, ids, context=context)
595         return True
596
597     def action_cancel_draft(self, cr, uid, ids, context=None):
598         wf_service = netsvc.LocalService("workflow")
599         for voucher_id in ids:
600             wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
601         self.write(cr, uid, ids, {'state':'draft'})
602         return True
603
604     def cancel_voucher(self, cr, uid, ids, context=None):
605         reconcile_pool = self.pool.get('account.move.reconcile')
606         move_pool = self.pool.get('account.move')
607
608         for voucher in self.browse(cr, uid, ids, context=context):
609             recs = []
610             for line in voucher.move_ids:
611                 if line.reconcile_id:
612                     recs += [line.reconcile_id.id]
613                 if line.reconcile_partial_id:
614                     recs += [line.reconcile_partial_id.id]
615
616             reconcile_pool.unlink(cr, uid, recs)
617
618             if voucher.move_id:
619                 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
620                 move_pool.unlink(cr, uid, [voucher.move_id.id])
621         res = {
622             'state':'cancel',
623             'move_id':False,
624         }
625         self.write(cr, uid, ids, res)
626         return True
627
628     def unlink(self, cr, uid, ids, context=None):
629         for t in self.read(cr, uid, ids, ['state'], context=context):
630             if t['state'] not in ('draft', 'cancel'):
631                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Voucher(s) which are already opened or paid !'))
632         return super(account_voucher, self).unlink(cr, uid, ids, context=context)
633
634     # TODO: may be we can remove this method if not used anyware
635     def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
636         res = {}
637         if not partner_id:
638             return res
639         res = {'account_id':False}
640         partner_pool = self.pool.get('res.partner')
641         journal_pool = self.pool.get('account.journal')
642         if pay_now == 'pay_later':
643             partner = partner_pool.browse(cr, uid, partner_id)
644             journal = journal_pool.browse(cr, uid, journal_id)
645             if journal.type in ('sale','sale_refund'):
646                 account_id = partner.property_account_receivable.id
647             elif journal.type in ('purchase', 'purchase_refund','expense'):
648                 account_id = partner.property_account_payable.id
649             else:
650                 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
651             res['account_id'] = account_id
652         return {'value':res}
653
654     def _sel_context(self, cr, uid, voucher_id,context=None):
655         """
656         Select if context will be multicurrency or not.
657
658         :param voucher_id: Id of the actual voucher
659         :return Dict with new context
660         """
661         company_currency = self._get_company_currency(cr, uid, voucher_id, context)
662         current_currency = self._get_current_currency(cr, uid, voucher_id, context)
663         context_multi_currency = context.copy()
664         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
665         context_multi_currency.update({'date': voucher_brw.date})
666         if current_currency <> company_currency: context = context_multi_currency
667         return context
668
669     def first_move_line_get(self, cr, uid, voucher_id, move_id, context=None):
670         '''
671         Set a dict to be use to create the first account move line of voucher.
672
673         @param cr: A database cursor
674         @param uid: ID of the user currently logged in
675         @param voucher_id: Id of voucher what we are creating account_move.
676         @param move_id: Id of account move where this line will be added.
677         @param context: optional context dictionary
678         @return: dictionary which contains information regarding account move line
679         '''
680         move_line_obj = self.pool.get('account.move.line')
681         currency_obj = self.pool.get('res.currency')
682         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
683         company_currency = self._get_company_currency(cr, uid, voucher_brw.id, context)
684         current_currency = self._get_current_currency(cr, uid, voucher_brw.id, context)
685         context = self._sel_context(cr,uid,voucher_brw.id,context)
686         debit = credit = 0.0
687         # TODO: is there any other alternative then the voucher type ??
688         # ANSWER: We can have payment and receipt "In Advance". 
689         # TODO: Make this logic available.
690         # -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
691         if voucher_brw.type in ('purchase', 'payment'):
692             credit = currency_obj.compute(cr, uid, current_currency, company_currency, voucher_brw.amount, context)
693         elif voucher_brw.type in ('sale', 'receipt'):
694             debit = currency_obj.compute(cr, uid, current_currency, company_currency, voucher_brw.amount, context)
695         if debit < 0: credit = -debit; debit = 0.0
696         if credit < 0: debit = -credit; credit = 0.0
697         sign = debit - credit < 0 and -1 or 1
698         #set the first line of the voucher
699         move_line = {
700                 'name': voucher_brw.name or '/',
701                 'debit': debit,
702                 'credit': credit,
703                 'account_id': voucher_brw.account_id.id,
704                 'move_id': move_id,
705                 'journal_id': voucher_brw.journal_id.id,
706                 'period_id': voucher_brw.period_id.id,
707                 'partner_id': voucher_brw.partner_id.id,
708                 'currency_id': company_currency <> current_currency and  current_currency or False,
709                 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
710                 'date': voucher_brw.date,
711                 'date_maturity': voucher_brw.date_due
712             }
713         return move_line
714
715     def account_move_get(self, cr, uid, voucher_id, context=None):
716         '''
717         This method create the account move related to voucher.
718
719         @param cr: A database cursor
720         @param uid: ID of the user currently logged in
721         @param voucher_id: Id of voucher what we are creating account_move.
722         @param context: optional context dictionary
723         @return: dictionary which contains information regarding account move
724         '''
725         move_obj = self.pool.get('account.move')
726         seq_obj = self.pool.get('ir.sequence')
727         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
728         if voucher_brw.number:
729             name = voucher_brw.number
730         elif voucher_brw.journal_id.sequence_id:
731             name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id)
732         else:
733             raise osv.except_osv(_('Error !'), 
734                         _('Please define a sequence on the journal !'))
735         if not voucher_brw.reference:
736             ref = name.replace('/','')
737         else:
738             ref = voucher_brw.reference
739
740         move = {
741             'name': name,
742             'journal_id': voucher_brw.journal_id.id,
743             'narration': voucher_brw.narration,
744             'date': voucher_brw.date,
745             'ref': ref,
746             'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
747         }
748         return move
749
750     def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, context = None):
751         '''
752         Generate two lines when the amount residual is due to difference on exchange.
753
754         @param line: browse object of the voucher.line
755         @param move_id: Account move wher the move lines will be.
756         @param amount_residual: Amount to be posted.
757         @param context: Context wher we are working
758         @return: Tuple with 2 dicts account move line  in pos [0] and counterpart in  pos [1].
759         '''
760         company_currency = self._get_company_currency(cr, uid, line.voucher_id.id, context)
761         current_currency = self._get_current_currency(cr, uid, line.voucher_id.id, context)
762         if not line.voucher_id.exchange_acc_id.id:
763             raise osv.except_osv(_('Error!'), _('You must provide an account for the exchange difference.'))
764         move_line = {
765             'journal_id': line.voucher_id.journal_id.id,
766             'period_id': line.voucher_id.period_id.id,
767             'name': _('change')+': '+(line.name or '/'),
768             'account_id': line.account_id.id,
769             'move_id': move_id,
770             'partner_id': line.voucher_id.partner_id.id,
771             'currency_id': company_currency <> current_currency and current_currency or False,
772             'amount_currency': 0.0,
773             'quantity': 1,
774             'credit': amount_residual > 0 and amount_residual or 0.0,
775             'debit': amount_residual < 0 and -amount_residual or 0.0,
776             'date': line.voucher_id.date,
777         }
778         move_line_counterpart = {
779             'journal_id': line.voucher_id.journal_id.id,
780             'period_id': line.voucher_id.period_id.id,
781             'name': _('change')+': '+(line.name or '/'),
782             'account_id': line.voucher_id.exchange_acc_id.id,
783             'move_id': move_id,
784             'amount_currency': 0.0,
785             'partner_id': line.voucher_id.partner_id.id,
786             'currency_id': company_currency <> current_currency and current_currency or False,
787             'quantity': 1,
788             'debit': amount_residual > 0 and amount_residual or 0.0,
789             'credit': amount_residual < 0 and -amount_residual or 0.0,
790             'date': line.voucher_id.date,
791         }
792         return (move_line,move_line_counterpart)
793
794     def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, context=None):
795         '''
796         Create all ther rest of account move lines on accout move object.
797         It returns Tuple with tot_line what is total of difference between debit and credit and 
798         a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
799         
800         @param voucher_id: Voucher id what we are working with
801         @param line_total: Total of the first line.
802         @param move_id: Account move wher this lines will be joined.
803         @return Tuple with data to evaluate reconcilation process.
804         '''
805         move_line_obj = self.pool.get('account.move.line')
806         currency_obj = self.pool.get('res.currency')
807         tot_line = line_total
808         rec_lst_ids = []
809
810         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
811         company_currency = self._get_company_currency(cr, uid, voucher_brw.id, context)
812         current_currency = self._get_current_currency(cr, uid, voucher_brw.id, context)
813         context = self._sel_context(cr,uid,voucher_brw.id,context)
814         for line in voucher_brw.line_ids:
815             #create one move line per voucher line where amount is not 0.0
816             if not line.amount:
817                 continue
818             #we check if the voucher line is fully paid or not and create a move line to balance the payment and initial invoice if needed
819             if line.amount == line.amount_unreconciled:
820                 amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.untax_amount or line.amount, context)
821                 amount_residual = line.move_line_id.amount_residual - amount #residual amount in company currency
822             else:
823                 amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.untax_amount or line.amount, context)
824                 amount_residual = 0.0
825             move_line = {
826                 'journal_id': voucher_brw.journal_id.id,
827                 'period_id': voucher_brw.period_id.id,
828                 'name': line.name or '/',
829                 'account_id': line.account_id.id,
830                 'move_id': move_id,
831                 'partner_id': voucher_brw.partner_id.id,
832                 'currency_id': company_currency <> current_currency and current_currency or False,
833                 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
834                 'quantity': 1,
835                 'credit': 0.0,
836                 'debit': 0.0,
837                 'date': voucher_brw.date
838             }
839             if amount < 0:
840                 amount = -amount
841                 if line.type == 'dr':
842                     line.type = 'cr'
843                 else:
844                     line.type = 'dr'
845
846             if (line.type=='dr'):
847                 tot_line += amount
848                 move_line['debit'] = amount
849             else:
850                 tot_line -= amount
851                 move_line['credit'] = amount
852
853             if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
854                 move_line.update({
855                     'account_tax_id': voucher_brw.tax_id.id,
856                 })
857
858             if move_line.get('account_tax_id', False):
859                 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
860                 if not (tax_data.base_code_id and tax_data.tax_code_id):
861                     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))
862
863             sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
864             move_line['amount_currency'] = company_currency <> current_currency and sign * line.amount or 0.0
865             voucher_line = move_line_obj.create(cr, uid, move_line)
866             rec_ids = [voucher_line, line.move_line_id.id]
867             print "What the fuck %s"% (self.pool.get('decimal.precision').precision_get(cr,uid,'Account'))
868             if round(amount_residual,self.pool.get('decimal.precision').precision_get(cr,uid,'Account')): # Change difference entry
869                 print "Amount Residual %s " % str(amount_residual)
870                 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, 
871                                             amount_residual ,context)
872                 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
873                 move_line_obj.create(cr, uid, exch_lines[1], context)
874                 rec_ids.append(new_id)
875
876             if line.move_line_id.id:
877                 rec_lst_ids.append(rec_ids)
878
879         return (tot_line, rec_lst_ids)
880
881
882     def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, context= None):
883         '''
884         Set a dict to be use to create the writeoff move line.
885
886         @param voucher_id: Id of voucher what we are creating account_move.
887         @param line_total: Amount total of the first account move line of the voucher.
888         @param move_id: Id of account move where this line will be added.
889         @param name: Description of account move line.
890         @param context: optional context dictionary
891         @return: dictionary which contains information regarding account move line
892         '''
893         move_line_obj = self.pool.get('account.move.line')
894         currency_obj = self.pool.get('res.currency')
895         move_line = {}
896
897         voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
898         company_currency = self._get_company_currency(cr, uid, voucher_brw.id, context)
899         current_currency = self._get_current_currency(cr, uid, voucher_brw.id, context)
900         context = self._sel_context(cr,uid,voucher_brw.id,context)
901         current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
902
903         if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
904             diff = line_total
905             account_id = False
906             write_off_name = ''
907             if voucher_brw.payment_option == 'with_writeoff':
908                 account_id = voucher_brw.writeoff_acc_id.id
909                 write_off_name = voucher_brw.comment
910             elif voucher_brw.type in ('sale', 'receipt'):
911                 account_id = voucher_brw.partner_id.property_account_receivable.id
912             else:
913                 account_id = voucher_brw.partner_id.property_account_payable.id
914             move_line = {
915                 'name': write_off_name or name,
916                 'account_id': account_id,
917                 'move_id': move_id,
918                 'partner_id': voucher_brw.partner_id.id,
919                 'date': voucher_brw.date,
920                 'credit': diff > 0 and diff or 0.0,
921                 'debit': diff < 0 and -diff or 0.0,
922                 #'amount_currency': company_currency <> current_currency and currency_obj.compute(cr, uid, company_currency, current_currency, diff * -1, context=context_multi_currency) or 0.0,
923                 #'currency_id': company_currency <> current_currency and current_currency or False,
924             }
925
926         return move_line
927
928     def _get_company_currency(self, cr, uid, voucher_id, context=None):
929         '''
930         Get the courrency of the actual company.
931
932         @param voucher_id: Id of the voucher what i want to obtain company currency.
933         @return id of currency
934         '''
935         return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
936         
937     def _get_current_currency(self, cr, uid, voucher_id, context=None):
938         '''
939         Get currency we are working with.
940
941         @param voucher_id: Id of the voucher what i want to obtain current currency.
942         @return id of currency
943         '''
944         voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
945         return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
946         
947     def action_move_line_create(self, cr, uid, ids, context=None):
948         '''
949         Create account move for account voucher.
950
951         '''
952         if context is None:
953             context = {}
954         move_pool = self.pool.get('account.move')
955         move_line_pool = self.pool.get('account.move.line')
956         currency_pool = self.pool.get('res.currency')
957         tax_obj = self.pool.get('account.tax')
958         seq_obj = self.pool.get('ir.sequence')
959         for voucher in self.browse(cr, uid, ids, context=context):
960
961             if voucher.move_id:
962                 continue
963             company_currency = self._get_company_currency(cr, uid, voucher.id, context)
964             current_currency = self._get_current_currency(cr, uid, voucher.id, context)
965             current_currency_obj = voucher.currency_id or voucher.journal_id.company_id.currency_id
966             #Create the account move record.
967             move_id = move_pool.create(cr, uid, self.account_move_get(cr,uid,voucher.id))
968             # Get the name of the acc_move just created
969             name = move_pool.browse(cr, uid, move_id, context=context).name
970             #Create the first line of the voucher, the payment made
971             move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, context), context)
972             move_line_brw = move_line_pool.browse(cr,uid,move_line_id, context)
973             line_total = move_line_brw.debit - move_line_brw.credit
974             rec_list_ids = []
975             if voucher.type == 'sale':
976                 line_total = line_total - currency_pool.compute(cr, uid, current_currency, company_currency, voucher.tax_amount, context=context_multi_currency)
977             elif voucher.type == 'purchase':
978                 line_total = line_total + currency_pool.compute(cr, uid, current_currency, company_currency, voucher.tax_amount, context=context_multi_currency)
979             ############################################################
980             #create one move line per voucher line where amount is not 0.0
981             line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, context)
982
983             #create the writeoff line if needed
984             ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, context)
985             if ml_writeoff:
986                 ml_writeoff_id = move_line_pool.create(cr, uid, ml_writeoff, context)
987             #We put posted the voucher.
988             self.write(cr, uid, [voucher.id], {
989                 'move_id': move_id,
990                 'state': 'posted',
991                 'number': name,
992             })
993             if voucher.journal_id.entry_posted:
994                 move_pool.post(cr, uid, [move_id], context={})
995             #We automatically reconcile the account move lines.
996             for rec_ids in rec_list_ids:
997                 if len(rec_ids) >= 2:
998                     move_line_pool.reconcile_partial(cr, uid, rec_ids, writeoff_acc_id=voucher.exchange_acc_id.id, writeoff_period_id=voucher.period_id.id, writeoff_journal_id=voucher.journal_id.id)
999         return True
1000
1001     def copy(self, cr, uid, id, default={}, context=None):
1002         default.update({
1003             'state': 'draft',
1004             'number': False,
1005             'move_id': False,
1006             'line_cr_ids': False,
1007             'line_dr_ids': False,
1008             'reference': False
1009         })
1010         if 'date' not in default:
1011             default['date'] = time.strftime('%Y-%m-%d')
1012         return super(account_voucher, self).copy(cr, uid, id, default, context)
1013
1014 account_voucher()
1015
1016 class account_voucher_line(osv.osv):
1017     _name = 'account.voucher.line'
1018     _description = 'Voucher Lines'
1019     _order = "move_line_id"
1020
1021     # If the payment is in the same currency than the invoice, we keep the same amount
1022     # Otherwise, we compute from company currency to payment currency
1023     def _compute_balance(self, cr, uid, ids, name, args, context=None):
1024         currency_pool = self.pool.get('res.currency')
1025         rs_data = {}
1026         for line in self.browse(cr, uid, ids, context=context):
1027             ctx = context.copy()
1028             ctx.update({'date': line.voucher_id.date})
1029             res = {}
1030             company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1031             voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1032             move_line = line.move_line_id or False
1033
1034             if not move_line:
1035                 res['amount_original'] = 0.0
1036                 res['amount_unreconciled'] = 0.0
1037             elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1038                 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1039                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, move_line.currency_id and move_line.currency_id.id or company_currency, voucher_currency, abs(move_line.amount_residual_currency), context=ctx)
1040             elif move_line and move_line.credit > 0:
1041                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1042                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1043             else:
1044                 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1045                 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1046
1047             rs_data[line.id] = res
1048         return rs_data
1049
1050     _columns = {
1051         'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1052         'name':fields.char('Description', size=256),
1053         'account_id':fields.many2one('account.account','Account', required=True),
1054         'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1055         'untax_amount':fields.float('Untax Amount'),
1056         'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1057         'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1058         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
1059         'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1060         'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1061         'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1062         'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True),
1063         'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True),
1064         'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1065     }
1066     _defaults = {
1067         'name': ''
1068     }
1069
1070     def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1071         """
1072         Returns a dict that contains new values and context
1073
1074         @param move_line_id: latest value from user input for field move_line_id
1075         @param args: other arguments
1076         @param context: context arguments, like lang, time zone
1077
1078         @return: Returns a dict which contains new values, and context
1079         """
1080         res = {}
1081         move_line_pool = self.pool.get('account.move.line')
1082         if move_line_id:
1083             move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1084             if move_line.credit:
1085                 ttype = 'dr'
1086             else:
1087                 ttype = 'cr'
1088             account_id = move_line.account_id.id
1089             res.update({
1090                 'account_id':account_id,
1091                 'type': ttype
1092             })
1093         return {
1094             'value':res,
1095         }
1096
1097     def default_get(self, cr, user, fields_list, context=None):
1098         """
1099         Returns default values for fields
1100         @param fields_list: list of fields, for which default values are required to be read
1101         @param context: context arguments, like lang, time zone
1102
1103         @return: Returns a dict that contains default values for fields
1104         """
1105         if context is None:
1106             context = {}
1107         journal_id = context.get('journal_id', False)
1108         partner_id = context.get('partner_id', False)
1109         journal_pool = self.pool.get('account.journal')
1110         partner_pool = self.pool.get('res.partner')
1111         values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1112         if (not journal_id) or ('account_id' not in fields_list):
1113             return values
1114         journal = journal_pool.browse(cr, user, journal_id, context=context)
1115         account_id = False
1116         ttype = 'cr'
1117         if journal.type in ('sale', 'sale_refund'):
1118             account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1119             ttype = 'cr'
1120         elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1121             account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1122             ttype = 'dr'
1123         elif partner_id:
1124             partner = partner_pool.browse(cr, user, partner_id, context=context)
1125             if context.get('type') == 'payment':
1126                 ttype = 'dr'
1127                 account_id = partner.property_account_payable.id
1128             elif context.get('type') == 'receipt':
1129                 account_id = partner.property_account_receivable.id
1130
1131         values.update({
1132             'account_id':account_id,
1133             'type':ttype
1134         })
1135         return values
1136 account_voucher_line()
1137
1138 class account_bank_statement(osv.osv):
1139     _inherit = 'account.bank.statement'
1140
1141     def button_cancel(self, cr, uid, ids, context=None):
1142         voucher_obj = self.pool.get('account.voucher')
1143         for st in self.browse(cr, uid, ids, context=context):
1144             voucher_ids = []
1145             for line in st.line_ids:
1146                 if line.voucher_id:
1147                     voucher_ids.append(line.voucher_id.id)
1148             voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1149         return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1150
1151     def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1152         voucher_obj = self.pool.get('account.voucher')
1153         wf_service = netsvc.LocalService("workflow")
1154         move_line_obj = self.pool.get('account.move.line')
1155         bank_st_line_obj = self.pool.get('account.bank.statement.line')
1156         st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1157         if st_line.voucher_id:
1158             voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1159             if st_line.voucher_id.state == 'cancel':
1160                 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1161             wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1162
1163             v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1164             bank_st_line_obj.write(cr, uid, [st_line_id], {
1165                 'move_ids': [(4, v.move_id.id, False)]
1166             })
1167
1168             return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1169         return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1170
1171 account_bank_statement()
1172
1173 class account_bank_statement_line(osv.osv):
1174     _inherit = 'account.bank.statement.line'
1175
1176     def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1177         if not ids:
1178             return {}
1179
1180         res = {}
1181 #        company_currency_id = False
1182         for line in self.browse(cursor, user, ids, context=context):
1183 #            if not company_currency_id:
1184 #                company_currency_id = line.company_id.id
1185             if line.voucher_id:
1186                 res[line.id] = line.voucher_id.amount#
1187 #                        res_currency_obj.compute(cursor, user,
1188 #                        company_currency_id, line.statement_id.currency.id,
1189 #                        line.voucher_id.amount, context=context)
1190             else:
1191                 res[line.id] = 0.0
1192         return res
1193
1194     def _check_amount(self, cr, uid, ids, context=None):
1195         for obj in self.browse(cr, uid, ids, context=context):
1196             if obj.voucher_id:
1197                 diff = abs(obj.amount) - obj.voucher_id.amount
1198                 if not self.pool.get('res.currency').is_zero(cr, uid, obj.voucher_id.currency_id, diff):
1199                     return False
1200         return True
1201
1202     _constraints = [
1203         (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1204     ]
1205
1206     _columns = {
1207         'amount_reconciled': fields.function(_amount_reconciled,
1208             string='Amount reconciled', type='float'),
1209         'voucher_id': fields.many2one('account.voucher', 'Payment'),
1210
1211     }
1212
1213     def unlink(self, cr, uid, ids, context=None):
1214         voucher_obj = self.pool.get('account.voucher')
1215         statement_line = self.browse(cr, uid, ids, context=context)
1216         unlink_ids = []
1217         for st_line in statement_line:
1218             if st_line.voucher_id:
1219                 unlink_ids.append(st_line.voucher_id.id)
1220         voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1221         return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1222
1223 account_bank_statement_line()
1224
1225 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1226     results = []
1227     for operation in operations:
1228         result = None
1229         if not isinstance(operation, (list, tuple)):
1230             result = target_osv.read(cr, uid, operation, fields, context=context)
1231         elif operation[0] == 0:
1232             # may be necessary to check if all the fields are here and get the default values?
1233             result = operation[2]
1234         elif operation[0] == 1:
1235             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1236             result.update(operation[2])
1237         elif operation[0] == 4:
1238             result = target_osv.read(cr, uid, operation[1], fields, context=context)
1239         if result != None:
1240             results.append(result)
1241     return results
1242
1243 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: