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