1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
23 from lxml import etree
25 from openerp.osv import fields, osv
26 import openerp.addons.decimal_precision as dp
27 from openerp.tools.translate import _
28 from openerp.tools import float_compare
29 from openerp.report import report_sxw
32 class res_currency(osv.osv):
33 _inherit = "res.currency"
35 def _get_current_rate(self, cr, uid, ids, raise_on_no_rate=True, context=None):
38 res = super(res_currency, self)._get_current_rate(cr, uid, ids, raise_on_no_rate, context=context)
39 if context.get('voucher_special_currency') in ids and context.get('voucher_special_currency_rate'):
40 res[context.get('voucher_special_currency')] = context.get('voucher_special_currency_rate')
44 class account_voucher(osv.osv):
45 def _check_paid(self, cr, uid, ids, name, args, context=None):
47 for voucher in self.browse(cr, uid, ids, context=context):
48 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
51 def _get_type(self, cr, uid, context=None):
54 return context.get('type', False)
56 def _get_period(self, cr, uid, context=None):
57 if context is None: context = {}
58 if context.get('period_id', False):
59 return context.get('period_id')
60 periods = self.pool.get('account.period').find(cr, uid, context=context)
61 return periods and periods[0] or False
63 def _make_journal_search(self, cr, uid, ttype, context=None):
64 journal_pool = self.pool.get('account.journal')
65 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
67 def _get_journal(self, cr, uid, context=None):
68 if context is None: context = {}
69 invoice_pool = self.pool.get('account.invoice')
70 journal_pool = self.pool.get('account.journal')
71 if context.get('invoice_id', False):
72 invoice = invoice_pool.browse(cr, uid, context['invoice_id'], context=context)
73 journal_id = journal_pool.search(cr, uid, [
74 ('currency', '=', invoice.currency_id.id), ('company_id', '=', invoice.company_id.id)
75 ], limit=1, context=context)
76 return journal_id and journal_id[0] or False
77 if context.get('journal_id', False):
78 return context.get('journal_id')
79 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
80 return context.get('search_default_journal_id')
82 ttype = context.get('type', 'bank')
83 if ttype in ('payment', 'receipt'):
85 res = self._make_journal_search(cr, uid, ttype, context=context)
86 return res and res[0] or False
88 def _get_tax(self, cr, uid, context=None):
89 if context is None: context = {}
90 journal_pool = self.pool.get('account.journal')
91 journal_id = context.get('journal_id', False)
93 ttype = context.get('type', 'bank')
94 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
101 journal = journal_pool.browse(cr, uid, journal_id, context=context)
102 account_id = journal.default_credit_account_id or journal.default_debit_account_id
103 if account_id and account_id.tax_ids:
104 tax_id = account_id.tax_ids[0].id
108 def _get_payment_rate_currency(self, cr, uid, context=None):
110 Return the default value for field payment_rate_currency_id: the currency of the journal
111 if there is one, otherwise the currency of the user's company
113 if context is None: context = {}
114 journal_pool = self.pool.get('account.journal')
115 journal_id = context.get('journal_id', False)
117 journal = journal_pool.browse(cr, uid, journal_id, context=context)
119 return journal.currency.id
120 #no journal given in the context, use company currency as default
121 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
123 def _get_currency(self, cr, uid, context=None):
124 if context is None: context = {}
125 journal_pool = self.pool.get('account.journal')
126 journal_id = context.get('journal_id', False)
128 if isinstance(journal_id, (list, tuple)):
129 # sometimes journal_id is a pair (id, display_name)
130 journal_id = journal_id[0]
131 journal = journal_pool.browse(cr, uid, journal_id, context=context)
133 return journal.currency.id
134 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
136 def _get_partner(self, cr, uid, context=None):
137 if context is None: context = {}
138 return context.get('partner_id', False)
140 def _get_reference(self, cr, uid, context=None):
141 if context is None: context = {}
142 return context.get('reference', False)
144 def _get_narration(self, cr, uid, context=None):
145 if context is None: context = {}
146 return context.get('narration', False)
148 def _get_amount(self, cr, uid, context=None):
151 return context.get('amount', 0.0)
153 def name_get(self, cr, uid, ids, context=None):
156 if context is None: context = {}
157 return [(r['id'], (r['number'] or _('Voucher'))) for r in self.read(cr, uid, ids, ['number'], context, load='_classic_write')]
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 = {}
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')
168 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
169 result = result and result[1] or False
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')
175 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
176 result = result and result[1] or False
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'])
182 if context.get('type', 'sale') in ('purchase', 'payment'):
183 nodes = doc.xpath("//field[@name='partner_id']")
185 node.set('context', "{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}")
186 if context.get('invoice_type','') in ('in_invoice', 'in_refund'):
187 node.set('string', _("Supplier"))
188 res['arch'] = etree.tostring(doc)
191 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
193 sign = type == 'payment' and -1 or 1
194 for l in line_dr_ids:
195 if isinstance(l, dict):
197 for l in line_cr_ids:
198 if isinstance(l, dict):
199 credit += l['amount']
200 return amount - sign * (credit - debit)
202 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
203 context = context or {}
204 if not line_dr_ids and not line_cr_ids:
205 return {'value':{'writeoff_amount': 0.0}}
206 # resolve lists of commands into lists of dicts
207 line_dr_ids = self.resolve_2many_commands(cr, uid, 'line_dr_ids', line_dr_ids, ['amount'], context)
208 line_cr_ids = self.resolve_2many_commands(cr, uid, 'line_cr_ids', line_cr_ids, ['amount'], context)
209 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
210 is_multi_currency = False
211 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to see the options
212 for voucher_line in line_dr_ids+line_cr_ids:
213 line_id = voucher_line.get('id') and self.pool.get('account.voucher.line').browse(cr, uid, voucher_line['id'], context=context).move_line_id.id or voucher_line.get('move_line_id')
214 if line_id and self.pool.get('account.move.line').browse(cr, uid, line_id, context=context).currency_id:
215 is_multi_currency = True
217 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
219 def _get_journal_currency(self, cr, uid, ids, name, args, context=None):
221 for voucher in self.browse(cr, uid, ids, context=context):
222 res[voucher.id] = voucher.journal_id.currency and voucher.journal_id.currency.id or voucher.company_id.currency_id.id
225 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
226 if not ids: return {}
227 currency_obj = self.pool.get('res.currency')
230 for voucher in self.browse(cr, uid, ids, context=context):
231 sign = voucher.type == 'payment' and -1 or 1
232 for l in voucher.line_dr_ids:
234 for l in voucher.line_cr_ids:
236 currency = voucher.currency_id or voucher.company_id.currency_id
237 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
240 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
245 for v in self.browse(cr, uid, ids, context=context):
246 ctx.update({'date': v.date})
247 #make a new call to browse in order to have the right date in the context, to get the right currency rate
248 voucher = self.browse(cr, uid, v.id, context=ctx)
250 'voucher_special_currency': voucher.payment_rate_currency_id and voucher.payment_rate_currency_id.id or False,
251 'voucher_special_currency_rate': voucher.currency_id.rate * voucher.payment_rate,})
252 res[voucher.id] = self.pool.get('res.currency').compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, voucher.amount, context=ctx)
255 def _get_currency_help_label(self, cr, uid, currency_id, payment_rate, payment_rate_currency_id, context=None):
257 This function builds a string to help the users to understand the behavior of the payment rate fields they can specify on the voucher.
258 This string is only used to improve the usability in the voucher form view and has no other effect.
260 :param currency_id: the voucher currency
261 :type currency_id: integer
262 :param payment_rate: the value of the payment_rate field of the voucher
263 :type payment_rate: float
264 :param payment_rate_currency_id: the value of the payment_rate_currency_id field of the voucher
265 :type payment_rate_currency_id: integer
266 :return: translated string giving a tip on what's the effect of the current payment rate specified
269 rml_parser = report_sxw.rml_parse(cr, uid, 'currency_help_label', context=context)
270 currency_pool = self.pool.get('res.currency')
271 currency_str = payment_rate_str = ''
273 currency_str = rml_parser.formatLang(1, currency_obj=currency_pool.browse(cr, uid, currency_id, context=context))
274 if payment_rate_currency_id:
275 payment_rate_str = rml_parser.formatLang(payment_rate, currency_obj=currency_pool.browse(cr, uid, payment_rate_currency_id, context=context))
276 currency_help_label = _('At the operation date, the exchange rate was\n%s = %s') % (currency_str, payment_rate_str)
277 return currency_help_label
279 def _fnct_currency_help_label(self, cr, uid, ids, name, args, context=None):
281 for voucher in self.browse(cr, uid, ids, context=context):
282 res[voucher.id] = self._get_currency_help_label(cr, uid, voucher.currency_id.id, voucher.payment_rate, voucher.payment_rate_currency_id.id, context=context)
285 _name = 'account.voucher'
286 _description = 'Accounting Voucher'
287 _inherit = ['mail.thread']
288 _order = "date desc, id desc"
289 # _rec_name = 'number'
292 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
297 'type':fields.selection([
299 ('purchase','Purchase'),
300 ('payment','Payment'),
301 ('receipt','Receipt'),
302 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
303 'name':fields.char('Memo', readonly=True, states={'draft':[('readonly',False)]}),
304 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]},
305 help="Effective date for accounting entries", copy=False),
306 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
307 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
308 'line_ids':fields.one2many('account.voucher.line', 'voucher_id', 'Voucher Lines',
309 readonly=True, copy=True,
310 states={'draft':[('readonly',False)]}),
311 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
312 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
313 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
314 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
315 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
316 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
317 'currency_id': fields.function(_get_journal_currency, type='many2one', relation='res.currency', string='Currency', readonly=True, required=True),
318 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
319 'state':fields.selection(
321 ('cancel','Cancelled'),
322 ('proforma','Pro-forma'),
324 ], 'Status', readonly=True, track_visibility='onchange', copy=False,
325 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
326 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
327 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
328 \n* The \'Cancelled\' status is used when user cancel voucher.'),
329 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
330 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True),
331 'reference': fields.char('Ref #', readonly=True, states={'draft':[('readonly',False)]},
332 help="Transaction reference number.", copy=False),
333 'number': fields.char('Number', readonly=True, copy=False),
334 'move_id':fields.many2one('account.move', 'Account Entry', copy=False),
335 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
336 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
337 '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'),
338 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
339 'pay_now':fields.selection([
340 ('pay_now','Pay Directly'),
341 ('pay_later','Pay Later or Group Funds'),
342 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
343 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
344 'pre_line':fields.boolean('Previous Payments ?', required=False),
345 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
346 'payment_option':fields.selection([
347 ('without_writeoff', 'Keep it open'),
348 ('with_writeoff', 'Reconcile payment balance'),
349 ], '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)"),
350 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
351 'comment': fields.char('Counterpart Comment', required=True, readonly=True, states={'draft': [('readonly', False)]}),
352 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
353 '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."),
354 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
355 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
356 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
357 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
358 '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'),
359 'currency_help_label': fields.function(_fnct_currency_help_label, type='text', string="Helping Sentence", help="This sentence helps you to know how to specify the payment rate by giving you the direct effect it has"),
362 'period_id': _get_period,
363 'partner_id': _get_partner,
364 'journal_id':_get_journal,
365 'currency_id': _get_currency,
366 'reference': _get_reference,
367 'narration':_get_narration,
368 'amount': _get_amount,
371 'pay_now': 'pay_now',
373 'date': lambda *a: time.strftime('%Y-%m-%d'),
374 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
376 'payment_option': 'without_writeoff',
377 'comment': _('Write-Off'),
379 'payment_rate_currency_id': _get_payment_rate_currency,
382 def compute_tax(self, cr, uid, ids, context=None):
383 tax_pool = self.pool.get('account.tax')
384 partner_pool = self.pool.get('res.partner')
385 position_pool = self.pool.get('account.fiscal.position')
386 voucher_line_pool = self.pool.get('account.voucher.line')
387 voucher_pool = self.pool.get('account.voucher')
388 if context is None: context = {}
390 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
392 for line in voucher.line_ids:
393 voucher_amount += line.untax_amount or line.amount
394 line.amount = line.untax_amount or line.amount
395 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
397 if not voucher.tax_id:
398 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
401 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
402 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
403 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
404 tax = tax_pool.browse(cr, uid, taxes, context=context)
406 total = voucher_amount
409 if not tax[0].price_include:
410 for line in voucher.line_ids:
411 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
412 total_tax += tax_line.get('amount', 0.0)
415 for line in voucher.line_ids:
419 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
420 line_tax += tax_line.get('amount', 0.0)
421 line_total += tax_line.get('price_unit')
422 total_tax += line_tax
423 untax_amount = line.untax_amount or line.amount
424 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
426 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
429 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
430 context = context or {}
431 tax_pool = self.pool.get('account.tax')
432 partner_pool = self.pool.get('res.partner')
433 position_pool = self.pool.get('account.fiscal.position')
442 # resolve the list of commands into a list of dicts
443 line_ids = self.resolve_2many_commands(cr, uid, 'line_ids', line_ids, ['amount'], context)
446 for line in line_ids:
448 line_amount = line.get('amount',0.0)
451 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
453 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
454 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
455 tax = tax_pool.browse(cr, uid, taxes, context=context)
457 if not tax[0].price_include:
458 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
459 total_tax += tax_line.get('amount')
461 voucher_total += line_amount
462 total = voucher_total + total_tax
465 'amount': total or voucher_total,
466 'tax_amount': total_tax
472 def onchange_term_id(self, cr, uid, ids, term_id, amount):
473 term_pool = self.pool.get('account.payment.term')
476 default = {'date_due':False}
477 if term_id and amount:
478 terms = term_pool.compute(cr, uid, term_id, amount)
480 due_date = terms[-1][0]
484 return {'value':default}
486 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):
488 Returns a dict that contains new values and context
490 @param partner_id: latest value from user input for field partner_id
491 @param args: other arguments
492 @param context: context arguments, like lang, time zone
494 @return: Returns a dict which contains new values, and context
500 if not partner_id or not journal_id:
503 partner_pool = self.pool.get('res.partner')
504 journal_pool = self.pool.get('account.journal')
506 journal = journal_pool.browse(cr, uid, journal_id, context=context)
507 partner = partner_pool.browse(cr, uid, partner_id, context=context)
510 if journal.type in ('sale','sale_refund'):
511 account_id = partner.property_account_receivable.id
513 elif journal.type in ('purchase', 'purchase_refund','expense'):
514 account_id = partner.property_account_payable.id
517 if not journal.default_credit_account_id or not journal.default_debit_account_id:
518 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
519 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
522 default['value']['account_id'] = account_id
523 default['value']['type'] = ttype or tr_type
525 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)
526 default['value'].update(vals.get('value'))
530 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
531 res = {'value': {'paid_amount_in_company_currency': amount, 'currency_help_label': self._get_currency_help_label(cr, uid, currency_id, rate, payment_rate_currency_id, context=context)}}
532 if rate and amount and currency_id:
533 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
534 #context should contain the date, the payment currency and the payment rate specified on the voucher
535 amount_in_company_currency = self.pool.get('res.currency').compute(cr, uid, currency_id, company_currency.id, amount, context=context)
536 res['value']['paid_amount_in_company_currency'] = amount_in_company_currency
539 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):
543 ctx.update({'date': date})
544 #read the voucher rate with the right date in the context
545 currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
546 voucher_rate = self.pool.get('res.currency').read(cr, uid, [currency_id], ['rate'], context=ctx)[0]['rate']
548 'voucher_special_currency': payment_rate_currency_id,
549 'voucher_special_currency_rate': rate * voucher_rate})
550 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=ctx)
551 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
552 for key in vals.keys():
553 res[key].update(vals[key])
556 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
559 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
560 currency_obj = self.pool.get('res.currency')
561 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
562 company_id = journal.company_id.id
564 currency_id = currency_id or journal.company_id.currency_id.id
565 payment_rate_currency_id = currency_id
567 ctx.update({'date': date})
569 if ttype == 'receipt':
570 o2m_to_loop = 'line_cr_ids'
571 elif ttype == 'payment':
572 o2m_to_loop = 'line_dr_ids'
573 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
574 for voucher_line in vals['value'][o2m_to_loop]:
575 if not isinstance(voucher_line, dict):
577 if voucher_line['currency_id'] != currency_id:
578 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
579 # is not in the voucher currency
580 payment_rate_currency_id = voucher_line['currency_id']
581 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
582 payment_rate = tmp / currency_obj.browse(cr, uid, currency_id, context=ctx).rate
584 vals['value'].update({
585 'payment_rate': payment_rate,
586 'currency_id': currency_id,
587 'payment_rate_currency_id': payment_rate_currency_id
589 #read the voucher rate with the right date in the context
590 voucher_rate = self.pool.get('res.currency').read(cr, uid, [currency_id], ['rate'], context=ctx)[0]['rate']
592 'voucher_special_currency_rate': payment_rate * voucher_rate,
593 'voucher_special_currency': payment_rate_currency_id})
594 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
595 for key in res.keys():
596 vals[key].update(res[key])
599 def basic_onchange_partner(self, cr, uid, ids, partner_id, journal_id, ttype, context=None):
600 partner_pool = self.pool.get('res.partner')
601 journal_pool = self.pool.get('account.journal')
602 res = {'value': {'account_id': False}}
603 if not partner_id or not journal_id:
606 journal = journal_pool.browse(cr, uid, journal_id, context=context)
607 partner = partner_pool.browse(cr, uid, partner_id, context=context)
609 if journal.type in ('sale','sale_refund'):
610 account_id = partner.property_account_receivable.id
611 elif journal.type in ('purchase', 'purchase_refund','expense'):
612 account_id = partner.property_account_payable.id
614 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
616 res['value']['account_id'] = account_id
619 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
624 #TODO: comment me and use me directly in the sales/purchases views
625 res = self.basic_onchange_partner(cr, uid, ids, partner_id, journal_id, ttype, context=context)
626 if ttype in ['sale', 'purchase']:
629 # not passing the payment_rate currency and the payment_rate in the context but it's ok because they are reset in recompute_payment_rate
630 ctx.update({'date': date})
631 vals = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=ctx)
632 vals2 = self.recompute_payment_rate(cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=context)
633 for key in vals.keys():
634 res[key].update(vals[key])
635 for key in vals2.keys():
636 res[key].update(vals2[key])
637 #TODO: can probably be removed now
638 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
639 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
640 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
641 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
642 # onchange returns a value for them
644 del(res['value']['line_dr_ids'])
645 del(res['value']['pre_line'])
646 del(res['value']['payment_rate'])
647 elif ttype == 'purchase':
648 del(res['value']['line_cr_ids'])
649 del(res['value']['pre_line'])
650 del(res['value']['payment_rate'])
653 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
655 Returns a dict that contains new values and context
657 @param partner_id: latest value from user input for field partner_id
658 @param args: other arguments
659 @param context: context arguments, like lang, time zone
661 @return: Returns a dict which contains new values, and context
663 def _remove_noise_in_o2m():
664 """if the line is partially reconciled, then we must pay attention to display it only once and
666 This function returns True if the line is considered as noise and should not be displayed
668 if line.reconcile_partial_id:
669 if currency_id == line.currency_id.id:
670 if line.amount_residual_currency <= 0:
673 if line.amount_residual <= 0:
679 context_multi_currency = context.copy()
681 currency_pool = self.pool.get('res.currency')
682 move_line_pool = self.pool.get('account.move.line')
683 partner_pool = self.pool.get('res.partner')
684 journal_pool = self.pool.get('account.journal')
685 line_pool = self.pool.get('account.voucher.line')
689 'value': {'line_dr_ids': [], 'line_cr_ids': [], 'pre_line': False},
692 # drop existing lines
693 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])])
694 for line in line_pool.browse(cr, uid, line_ids, context=context):
695 if line.type == 'cr':
696 default['value']['line_cr_ids'].append((2, line.id))
698 default['value']['line_dr_ids'].append((2, line.id))
700 if not partner_id or not journal_id:
703 journal = journal_pool.browse(cr, uid, journal_id, context=context)
704 partner = partner_pool.browse(cr, uid, partner_id, context=context)
705 currency_id = currency_id or journal.company_id.currency_id.id
710 if context.get('account_id'):
711 account_type = self.pool['account.account'].browse(cr, uid, context['account_id'], context=context).type
712 if ttype == 'payment':
714 account_type = 'payable'
715 total_debit = price or 0.0
717 total_credit = price or 0.0
719 account_type = 'receivable'
721 if not context.get('move_line_ids', False):
722 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
724 ids = context['move_line_ids']
725 invoice_id = context.get('invoice_id', False)
726 company_currency = journal.company_id.currency_id.id
727 move_lines_found = []
729 #order the lines by most old first
731 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
733 #compute the total debit/credit and look for a matching open amount or invoice
734 for line in account_move_lines:
735 if _remove_noise_in_o2m():
739 if line.invoice.id == invoice_id:
740 #if the invoice linked to the voucher line is equal to the invoice_id in context
741 #then we assign the amount on that line, whatever the other voucher lines
742 move_lines_found.append(line.id)
743 elif currency_id == company_currency:
744 #otherwise treatments is the same but with other field names
745 if line.amount_residual == price:
746 #if the amount residual is equal the amount voucher, we assign it to that voucher
747 #line, whatever the other voucher lines
748 move_lines_found.append(line.id)
750 #otherwise we will split the voucher amount on each line (by most old first)
751 total_credit += line.credit or 0.0
752 total_debit += line.debit or 0.0
753 elif currency_id == line.currency_id.id:
754 if line.amount_residual_currency == price:
755 move_lines_found.append(line.id)
757 total_credit += line.credit and line.amount_currency or 0.0
758 total_debit += line.debit and line.amount_currency or 0.0
760 remaining_amount = price
761 #voucher line creation
762 for line in account_move_lines:
764 if _remove_noise_in_o2m():
767 if line.currency_id and currency_id == line.currency_id.id:
768 amount_original = abs(line.amount_currency)
769 amount_unreconciled = abs(line.amount_residual_currency)
771 #always use the amount booked in the company currency as the basis of the conversion into the voucher currency
772 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0, context=context_multi_currency)
773 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual), context=context_multi_currency)
774 line_currency_id = line.currency_id and line.currency_id.id or company_currency
776 'name':line.move_id.name,
777 'type': line.credit and 'dr' or 'cr',
778 'move_line_id':line.id,
779 'account_id':line.account_id.id,
780 'amount_original': amount_original,
781 'amount': (line.id in move_lines_found) and min(abs(remaining_amount), amount_unreconciled) or 0.0,
782 'date_original':line.date,
783 'date_due':line.date_maturity,
784 'amount_unreconciled': amount_unreconciled,
785 'currency_id': line_currency_id,
787 remaining_amount -= rs['amount']
788 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
789 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
790 if not move_lines_found:
791 if currency_id == line_currency_id:
793 amount = min(amount_unreconciled, abs(total_debit))
794 rs['amount'] = amount
795 total_debit -= amount
797 amount = min(amount_unreconciled, abs(total_credit))
798 rs['amount'] = amount
799 total_credit -= amount
801 if rs['amount_unreconciled'] == rs['amount']:
802 rs['reconcile'] = True
804 if rs['type'] == 'cr':
805 default['value']['line_cr_ids'].append(rs)
807 default['value']['line_dr_ids'].append(rs)
809 if len(default['value']['line_cr_ids']) > 0:
810 default['value']['pre_line'] = 1
811 elif len(default['value']['line_dr_ids']) > 0:
812 default['value']['pre_line'] = 1
813 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
816 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
821 #set the default payment rate of the voucher and compute the paid amount in company currency
823 ctx.update({'date': date})
824 #read the voucher rate with the right date in the context
825 voucher_rate = self.pool.get('res.currency').read(cr, uid, [currency_id], ['rate'], context=ctx)[0]['rate']
827 'voucher_special_currency_rate': payment_rate * voucher_rate,
828 'voucher_special_currency': payment_rate_currency_id})
829 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
830 for key in vals.keys():
831 res[key].update(vals[key])
834 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
836 @param date: latest value from user input for field date
837 @param args: other arguments
838 @param context: context arguments, like lang, time zone
839 @return: Returns a dict which contains new values, and context
844 #set the period of the voucher
845 period_pool = self.pool.get('account.period')
846 currency_obj = self.pool.get('res.currency')
848 ctx.update({'company_id': company_id, 'account_period_prefer_normal': True})
849 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
850 pids = period_pool.find(cr, uid, date, context=ctx)
852 res['value'].update({'period_id':pids[0]})
853 if payment_rate_currency_id:
854 ctx.update({'date': date})
856 if payment_rate_currency_id != currency_id:
857 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
858 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
859 vals = self.onchange_payment_rate_currency(cr, uid, ids, voucher_currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
860 vals['value'].update({'payment_rate': payment_rate})
861 for key in vals.keys():
862 res[key].update(vals[key])
865 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
870 journal_pool = self.pool.get('account.journal')
871 journal = journal_pool.browse(cr, uid, journal_id, context=context)
872 account_id = journal.default_credit_account_id or journal.default_debit_account_id
874 if account_id and account_id.tax_ids:
875 tax_id = account_id.tax_ids[0].id
878 if ttype in ('sale', 'purchase'):
879 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
880 vals['value'].update({'tax_id':tax_id,'amount': amount})
883 currency_id = journal.currency.id
885 currency_id = journal.company_id.currency_id.id
887 period_ids = self.pool['account.period'].find(cr, uid, context=dict(context, company_id=company_id))
888 vals['value'].update({
889 'currency_id': currency_id,
890 'payment_rate_currency_id': currency_id,
891 'period_id': period_ids and period_ids[0] or False
893 #in case we want to register the payment directly from an invoice, it's confusing to allow to switch the journal
894 #without seeing that the amount is expressed in the journal currency, and not in the invoice currency. So to avoid
895 #this common mistake, we simply reset the amount to 0 if the currency is not the invoice currency.
896 if context.get('payment_expected_currency') and currency_id != context.get('payment_expected_currency'):
897 vals['value']['amount'] = 0
900 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
901 for key in res.keys():
902 vals[key].update(res[key])
905 def onchange_company(self, cr, uid, ids, partner_id, journal_id, currency_id, company_id, context=None):
907 If the company changes, check that the journal is in the right company.
908 If not, fetch a new journal.
910 journal_pool = self.pool['account.journal']
911 journal = journal_pool.browse(cr, uid, journal_id, context=context)
912 if journal.company_id.id != company_id:
913 # can not guess type of journal, better remove it
914 return {'value': {'journal_id': False}}
917 def button_proforma_voucher(self, cr, uid, ids, context=None):
918 self.signal_workflow(cr, uid, ids, 'proforma_voucher')
919 return {'type': 'ir.actions.act_window_close'}
921 def proforma_voucher(self, cr, uid, ids, context=None):
922 self.action_move_line_create(cr, uid, ids, context=context)
925 def action_cancel_draft(self, cr, uid, ids, context=None):
926 self.create_workflow(cr, uid, ids)
927 self.write(cr, uid, ids, {'state':'draft'})
930 def cancel_voucher(self, cr, uid, ids, context=None):
931 reconcile_pool = self.pool.get('account.move.reconcile')
932 move_pool = self.pool.get('account.move')
933 move_line_pool = self.pool.get('account.move.line')
934 for voucher in self.browse(cr, uid, ids, context=context):
935 # refresh to make sure you don't unlink an already removed move
937 for line in voucher.move_ids:
938 # refresh to make sure you don't unreconcile an already unreconciled entry
940 if line.reconcile_id:
941 move_lines = [move_line.id for move_line in line.reconcile_id.line_id]
942 move_lines.remove(line.id)
943 reconcile_pool.unlink(cr, uid, [line.reconcile_id.id])
944 if len(move_lines) >= 2:
945 move_line_pool.reconcile_partial(cr, uid, move_lines, 'auto',context=context)
947 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
948 move_pool.unlink(cr, uid, [voucher.move_id.id])
953 self.write(cr, uid, ids, res)
956 def unlink(self, cr, uid, ids, context=None):
957 for t in self.read(cr, uid, ids, ['state'], context=context):
958 if t['state'] not in ('draft', 'cancel'):
959 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
960 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
962 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
967 partner_pool = self.pool.get('res.partner')
968 journal_pool = self.pool.get('account.journal')
969 if pay_now == 'pay_later':
970 partner = partner_pool.browse(cr, uid, partner_id)
971 journal = journal_pool.browse(cr, uid, journal_id)
972 if journal.type in ('sale','sale_refund'):
973 account_id = partner.property_account_receivable.id
974 elif journal.type in ('purchase', 'purchase_refund','expense'):
975 account_id = partner.property_account_payable.id
977 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
979 res['account_id'] = account_id
982 def _sel_context(self, cr, uid, voucher_id, context=None):
984 Select the context to use accordingly if it needs to be multicurrency or not.
986 :param voucher_id: Id of the actual voucher
987 :return: The returned context will be the same as given in parameter if the voucher currency is the same
988 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
989 the date of the voucher.
992 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
993 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
994 if current_currency <> company_currency:
995 context_multi_currency = context.copy()
996 voucher = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
997 context_multi_currency.update({'date': voucher.date})
998 return context_multi_currency
1001 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
1003 Return a dict to be use to create the first account move line of given voucher.
1005 :param voucher_id: Id of voucher what we are creating account_move.
1006 :param move_id: Id of account move where this line will be added.
1007 :param company_currency: id of currency of the company to which the voucher belong
1008 :param current_currency: id of currency of the voucher
1009 :return: mapping between fieldname and value of account move line to create
1012 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1013 debit = credit = 0.0
1014 # TODO: is there any other alternative then the voucher type ??
1015 # ANSWER: We can have payment and receipt "In Advance".
1016 # TODO: Make this logic available.
1017 # -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
1018 if voucher.type in ('purchase', 'payment'):
1019 credit = voucher.paid_amount_in_company_currency
1020 elif voucher.type in ('sale', 'receipt'):
1021 debit = voucher.paid_amount_in_company_currency
1022 if debit < 0: credit = -debit; debit = 0.0
1023 if credit < 0: debit = -credit; credit = 0.0
1024 sign = debit - credit < 0 and -1 or 1
1025 #set the first line of the voucher
1027 'name': voucher.name or '/',
1030 'account_id': voucher.account_id.id,
1032 'journal_id': voucher.journal_id.id,
1033 'period_id': voucher.period_id.id,
1034 'partner_id': voucher.partner_id.id,
1035 'currency_id': company_currency <> current_currency and current_currency or False,
1036 'amount_currency': (sign * abs(voucher.amount) # amount < 0 for refunds
1037 if company_currency != current_currency else 0.0),
1038 'date': voucher.date,
1039 'date_maturity': voucher.date_due
1043 def account_move_get(self, cr, uid, voucher_id, context=None):
1045 This method prepare the creation of the account move related to the given voucher.
1047 :param voucher_id: Id of voucher for which we are creating account_move.
1048 :return: mapping between fieldname and value of account move to create
1051 seq_obj = self.pool.get('ir.sequence')
1052 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1054 name = voucher.number
1055 elif voucher.journal_id.sequence_id:
1056 if not voucher.journal_id.sequence_id.active:
1057 raise osv.except_osv(_('Configuration Error !'),
1058 _('Please activate the sequence of selected journal !'))
1060 c.update({'fiscalyear_id': voucher.period_id.fiscalyear_id.id})
1061 name = seq_obj.next_by_id(cr, uid, voucher.journal_id.sequence_id.id, context=c)
1063 raise osv.except_osv(_('Error!'),
1064 _('Please define a sequence on the journal.'))
1065 if not voucher.reference:
1066 ref = name.replace('/','')
1068 ref = voucher.reference
1072 'journal_id': voucher.journal_id.id,
1073 'narration': voucher.narration,
1074 'date': voucher.date,
1076 'period_id': voucher.period_id.id,
1080 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
1082 Prepare the two lines in company currency due to currency rate difference.
1084 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
1086 :param move_id: Account move wher the move lines will be.
1087 :param amount_residual: Amount to be posted.
1088 :param company_currency: id of currency of the company to which the voucher belong
1089 :param current_currency: id of currency of the voucher
1090 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
1091 :rtype: tuple of dict
1093 if amount_residual > 0:
1094 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1096 model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'action_account_form')
1097 msg = _("You should configure the 'Loss Exchange Rate Account' to manage automatically the booking of accounting entries related to differences between exchange rates.")
1098 raise openerp.exceptions.RedirectWarning(msg, action_id, _('Go to the configuration panel'))
1100 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1102 model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'action_account_form')
1103 msg = _("You should configure the 'Gain Exchange Rate Account' to manage automatically the booking of accounting entries related to differences between exchange rates.")
1104 raise openerp.exceptions.RedirectWarning(msg, action_id, _('Go to the configuration panel'))
1105 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1106 # the receivable/payable account may have a secondary currency, which render this field mandatory
1107 if line.account_id.currency_id:
1108 account_currency_id = line.account_id.currency_id.id
1110 account_currency_id = company_currency <> current_currency and current_currency or False
1112 'journal_id': line.voucher_id.journal_id.id,
1113 'period_id': line.voucher_id.period_id.id,
1114 'name': _('change')+': '+(line.name or '/'),
1115 'account_id': line.account_id.id,
1117 'partner_id': line.voucher_id.partner_id.id,
1118 'currency_id': account_currency_id,
1119 'amount_currency': 0.0,
1121 'credit': amount_residual > 0 and amount_residual or 0.0,
1122 'debit': amount_residual < 0 and -amount_residual or 0.0,
1123 'date': line.voucher_id.date,
1125 move_line_counterpart = {
1126 'journal_id': line.voucher_id.journal_id.id,
1127 'period_id': line.voucher_id.period_id.id,
1128 'name': _('change')+': '+(line.name or '/'),
1129 'account_id': account_id.id,
1131 'amount_currency': 0.0,
1132 'partner_id': line.voucher_id.partner_id.id,
1133 'currency_id': account_currency_id,
1135 'debit': amount_residual > 0 and amount_residual or 0.0,
1136 'credit': amount_residual < 0 and -amount_residual or 0.0,
1137 'date': line.voucher_id.date,
1139 return (move_line, move_line_counterpart)
1141 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1143 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1144 payment_rate_currency_id is relevant) either the rate encoded in the system.
1146 :param amount: float. The amount to convert
1147 :param voucher: id of the voucher on which we want the conversion
1148 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1149 field in order to select the good rate to use.
1150 :return: the amount in the currency of the voucher's company
1155 currency_obj = self.pool.get('res.currency')
1156 voucher = self.browse(cr, uid, voucher_id, context=context)
1157 return currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1159 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1161 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1162 It returns Tuple with tot_line what is total of difference between debit and credit and
1163 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1165 :param voucher_id: Voucher id what we are working with
1166 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1167 :param move_id: Account move wher those lines will be joined.
1168 :param company_currency: id of currency of the company to which the voucher belong
1169 :param current_currency: id of currency of the voucher
1170 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1171 :rtype: tuple(float, list of int)
1175 move_line_obj = self.pool.get('account.move.line')
1176 currency_obj = self.pool.get('res.currency')
1177 tax_obj = self.pool.get('account.tax')
1178 tot_line = line_total
1181 date = self.read(cr, uid, [voucher_id], ['date'], context=context)[0]['date']
1182 ctx = context.copy()
1183 ctx.update({'date': date})
1184 voucher = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context=ctx)
1185 voucher_currency = voucher.journal_id.currency or voucher.company_id.currency_id
1187 'voucher_special_currency_rate': voucher_currency.rate * voucher.payment_rate ,
1188 'voucher_special_currency': voucher.payment_rate_currency_id and voucher.payment_rate_currency_id.id or False,})
1189 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1190 for line in voucher.line_ids:
1191 #create one move line per voucher line where amount is not 0.0
1192 # AND (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
1193 if not line.amount and not (line.move_line_id and not float_compare(line.move_line_id.debit, line.move_line_id.credit, precision_digits=prec) and not float_compare(line.move_line_id.debit, 0.0, precision_digits=prec)):
1195 # convert the amount set on the voucher line into the currency of the voucher's company
1196 # this calls res_curreny.compute() with the right context, so that it will take either the rate on the voucher if it is relevant or will use the default behaviour
1197 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher.id, context=ctx)
1198 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1199 # currency rate difference
1200 if line.amount == line.amount_unreconciled:
1201 if not line.move_line_id:
1202 raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1203 sign = line.type =='dr' and -1 or 1
1204 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1206 currency_rate_difference = 0.0
1208 'journal_id': voucher.journal_id.id,
1209 'period_id': voucher.period_id.id,
1210 'name': line.name or '/',
1211 'account_id': line.account_id.id,
1213 'partner_id': voucher.partner_id.id,
1214 '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,
1215 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1219 'date': voucher.date
1223 if line.type == 'dr':
1228 if (line.type=='dr'):
1230 move_line['debit'] = amount
1233 move_line['credit'] = amount
1235 if voucher.tax_id and voucher.type in ('sale', 'purchase'):
1237 'account_tax_id': voucher.tax_id.id,
1240 if move_line.get('account_tax_id', False):
1241 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1242 if not (tax_data.base_code_id and tax_data.tax_code_id):
1243 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))
1245 # compute the amount in foreign currency
1246 foreign_currency_diff = 0.0
1247 amount_currency = False
1248 if line.move_line_id:
1249 # We want to set it on the account move line as soon as the original line had a foreign currency
1250 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1251 # we compute the amount in that foreign currency.
1252 if line.move_line_id.currency_id.id == current_currency:
1253 # if the voucher and the voucher line share the same currency, there is no computation to do
1254 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1255 amount_currency = sign * (line.amount)
1257 # if the rate is specified on the voucher, it will be used thanks to the special keys in the context
1258 # otherwise we use the rates of the system
1259 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1260 if line.amount == line.amount_unreconciled:
1261 foreign_currency_diff = line.move_line_id.amount_residual_currency - abs(amount_currency)
1263 move_line['amount_currency'] = amount_currency
1264 voucher_line = move_line_obj.create(cr, uid, move_line)
1265 rec_ids = [voucher_line, line.move_line_id.id]
1267 if not currency_obj.is_zero(cr, uid, voucher.company_id.currency_id, currency_rate_difference):
1268 # Change difference entry in company currency
1269 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1270 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1271 move_line_obj.create(cr, uid, exch_lines[1], context)
1272 rec_ids.append(new_id)
1274 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):
1275 # Change difference entry in voucher currency
1276 move_line_foreign_currency = {
1277 'journal_id': line.voucher_id.journal_id.id,
1278 'period_id': line.voucher_id.period_id.id,
1279 'name': _('change')+': '+(line.name or '/'),
1280 'account_id': line.account_id.id,
1282 'partner_id': line.voucher_id.partner_id.id,
1283 'currency_id': line.move_line_id.currency_id.id,
1284 'amount_currency': -1 * foreign_currency_diff,
1288 'date': line.voucher_id.date,
1290 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1291 rec_ids.append(new_id)
1292 if line.move_line_id.id:
1293 rec_lst_ids.append(rec_ids)
1294 return (tot_line, rec_lst_ids)
1296 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1298 Set a dict to be use to create the writeoff move line.
1300 :param voucher_id: Id of voucher what we are creating account_move.
1301 :param line_total: Amount remaining to be allocated on lines.
1302 :param move_id: Id of account move where this line will be added.
1303 :param name: Description of account move line.
1304 :param company_currency: id of currency of the company to which the voucher belong
1305 :param current_currency: id of currency of the voucher
1306 :return: mapping between fieldname and value of account move line to create
1309 currency_obj = self.pool.get('res.currency')
1312 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1313 current_currency_obj = voucher.currency_id or voucher.journal_id.company_id.currency_id
1315 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1319 if voucher.payment_option == 'with_writeoff':
1320 account_id = voucher.writeoff_acc_id.id
1321 write_off_name = voucher.comment
1322 elif voucher.partner_id:
1323 if voucher.type in ('sale', 'receipt'):
1324 account_id = voucher.partner_id.property_account_receivable.id
1326 account_id = voucher.partner_id.property_account_payable.id
1328 # fallback on account of voucher
1329 account_id = voucher.account_id.id
1330 sign = voucher.type == 'payment' and -1 or 1
1332 'name': write_off_name or name,
1333 'account_id': account_id,
1335 'partner_id': voucher.partner_id.id,
1336 'date': voucher.date,
1337 'credit': diff > 0 and diff or 0.0,
1338 'debit': diff < 0 and -diff or 0.0,
1339 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher.writeoff_amount) or 0.0,
1340 'currency_id': company_currency <> current_currency and current_currency or False,
1341 'analytic_account_id': voucher.analytic_id and voucher.analytic_id.id or False,
1346 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1348 Get the currency of the actual company.
1350 :param voucher_id: Id of the voucher what i want to obtain company currency.
1351 :return: currency id of the company of the voucher
1354 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1356 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1358 Get the currency of the voucher.
1360 :param voucher_id: Id of the voucher what i want to obtain current currency.
1361 :return: currency id of the voucher
1364 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1365 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1367 def action_move_line_create(self, cr, uid, ids, context=None):
1369 Confirm the vouchers given in ids and create the journal entries for each of them
1373 move_pool = self.pool.get('account.move')
1374 move_line_pool = self.pool.get('account.move.line')
1375 for voucher in self.browse(cr, uid, ids, context=context):
1376 local_context = dict(context, force_company=voucher.journal_id.company_id.id)
1379 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1380 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1381 # we select the context to use accordingly if it's a multicurrency case or not
1382 context = self._sel_context(cr, uid, voucher.id, context)
1383 # But for the operations made by _convert_amount, we always need to give the date in the context
1384 ctx = context.copy()
1385 ctx.update({'date': voucher.date})
1386 # Create the account move record.
1387 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1388 # Get the name of the account_move just created
1389 name = move_pool.browse(cr, uid, move_id, context=context).name
1390 # Create the first line of the voucher
1391 move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, company_currency, current_currency, local_context), local_context)
1392 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1393 line_total = move_line_brw.debit - move_line_brw.credit
1395 if voucher.type == 'sale':
1396 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1397 elif voucher.type == 'purchase':
1398 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1399 # Create one move line per voucher line where amount is not 0.0
1400 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1402 # Create the writeoff line if needed
1403 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, local_context)
1405 move_line_pool.create(cr, uid, ml_writeoff, local_context)
1406 # We post the voucher.
1407 self.write(cr, uid, [voucher.id], {
1412 if voucher.journal_id.entry_posted:
1413 move_pool.post(cr, uid, [move_id], context={})
1414 # We automatically reconcile the account move lines.
1416 for rec_ids in rec_list_ids:
1417 if len(rec_ids) >= 2:
1418 reconcile = move_line_pool.reconcile_partial(cr, uid, rec_ids, writeoff_acc_id=voucher.writeoff_acc_id.id, writeoff_period_id=voucher.period_id.id, writeoff_journal_id=voucher.journal_id.id)
1421 class account_voucher_line(osv.osv):
1422 _name = 'account.voucher.line'
1423 _description = 'Voucher Lines'
1424 _order = "move_line_id"
1426 # If the payment is in the same currency than the invoice, we keep the same amount
1427 # Otherwise, we compute from invoice currency to payment currency
1428 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1429 currency_pool = self.pool.get('res.currency')
1431 for line in self.browse(cr, uid, ids, context=context):
1432 ctx = context.copy()
1433 ctx.update({'date': line.voucher_id.date})
1434 voucher_rate = self.pool.get('res.currency').read(cr, uid, line.voucher_id.currency_id.id, ['rate'], context=ctx)['rate']
1436 'voucher_special_currency': line.voucher_id.payment_rate_currency_id and line.voucher_id.payment_rate_currency_id.id or False,
1437 'voucher_special_currency_rate': line.voucher_id.payment_rate * voucher_rate})
1439 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1440 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1441 move_line = line.move_line_id or False
1444 res['amount_original'] = 0.0
1445 res['amount_unreconciled'] = 0.0
1446 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1447 res['amount_original'] = abs(move_line.amount_currency)
1448 res['amount_unreconciled'] = abs(move_line.amount_residual_currency)
1450 #always use the amount booked in the company currency as the basis of the conversion into the voucher currency
1451 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit or move_line.debit or 0.0, context=ctx)
1452 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1454 rs_data[line.id] = res
1457 def _currency_id(self, cr, uid, ids, name, args, context=None):
1459 This function returns the currency id of a voucher line. It's either the currency of the
1460 associated move line (if any) or the currency of the voucher or the company currency.
1463 for line in self.browse(cr, uid, ids, context=context):
1464 move_line = line.move_line_id
1466 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1468 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1472 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1473 'name':fields.char('Description',),
1474 'account_id':fields.many2one('account.account','Account', required=True),
1475 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1476 'untax_amount':fields.float('Untax Amount'),
1477 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1478 'reconcile': fields.boolean('Full Reconcile'),
1479 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1480 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1481 'move_line_id': fields.many2one('account.move.line', 'Journal Item', copy=False),
1482 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1483 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1484 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1485 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1486 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1487 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1493 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1494 vals = {'amount': 0.0}
1496 vals = { 'amount': amount_unreconciled}
1497 return {'value': vals}
1499 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1502 vals['reconcile'] = (amount == amount_unreconciled)
1503 return {'value': vals}
1505 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1507 Returns a dict that contains new values and context
1509 @param move_line_id: latest value from user input for field move_line_id
1510 @param args: other arguments
1511 @param context: context arguments, like lang, time zone
1513 @return: Returns a dict which contains new values, and context
1516 move_line_pool = self.pool.get('account.move.line')
1518 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1519 if move_line.credit:
1524 'account_id': move_line.account_id.id,
1526 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1532 def default_get(self, cr, user, fields_list, context=None):
1534 Returns default values for fields
1535 @param fields_list: list of fields, for which default values are required to be read
1536 @param context: context arguments, like lang, time zone
1538 @return: Returns a dict that contains default values for fields
1542 journal_id = context.get('journal_id', False)
1543 partner_id = context.get('partner_id', False)
1544 journal_pool = self.pool.get('account.journal')
1545 partner_pool = self.pool.get('res.partner')
1546 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1547 if (not journal_id) or ('account_id' not in fields_list):
1549 journal = journal_pool.browse(cr, user, journal_id, context=context)
1552 if journal.type in ('sale', 'sale_refund'):
1553 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1555 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1556 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1559 partner = partner_pool.browse(cr, user, partner_id, context=context)
1560 if context.get('type') == 'payment':
1562 account_id = partner.property_account_payable.id
1563 elif context.get('type') == 'receipt':
1564 account_id = partner.property_account_receivable.id
1567 'account_id':account_id,
1572 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: