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