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 res_company(osv.osv):
45 _inherit = "res.company"
47 'income_currency_exchange_account_id': fields.many2one(
49 string="Gain Exchange Rate Account",
50 domain="[('type', '=', 'other')]",),
51 'expense_currency_exchange_account_id': fields.many2one(
53 string="Loss Exchange Rate Account",
54 domain="[('type', '=', 'other')]",),
58 class account_config_settings(osv.osv_memory):
59 _inherit = 'account.config.settings'
61 'income_currency_exchange_account_id': fields.related(
62 'company_id', 'income_currency_exchange_account_id',
64 relation='account.account',
65 string="Gain Exchange Rate Account",
66 domain="[('type', '=', 'other')]"),
67 'expense_currency_exchange_account_id': fields.related(
68 'company_id', 'expense_currency_exchange_account_id',
70 relation='account.account',
71 string="Loss Exchange Rate Account",
72 domain="[('type', '=', 'other')]"),
74 def onchange_company_id(self, cr, uid, ids, company_id, context=None):
75 res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id, context=context)
77 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
78 res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False,
79 'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False})
81 res['value'].update({'income_currency_exchange_account_id': False,
82 'expense_currency_exchange_account_id': False})
85 class account_voucher(osv.osv):
86 def _check_paid(self, cr, uid, ids, name, args, context=None):
88 for voucher in self.browse(cr, uid, ids, context=context):
89 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
92 def _get_type(self, cr, uid, context=None):
95 return context.get('type', False)
97 def _get_period(self, cr, uid, context=None):
98 if context is None: context = {}
99 if context.get('period_id', False):
100 return context.get('period_id')
101 periods = self.pool.get('account.period').find(cr, uid, context=context)
102 return periods and periods[0] or False
104 def _make_journal_search(self, cr, uid, ttype, context=None):
105 journal_pool = self.pool.get('account.journal')
106 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
108 def _get_journal(self, cr, uid, context=None):
109 if context is None: context = {}
110 invoice_pool = self.pool.get('account.invoice')
111 journal_pool = self.pool.get('account.journal')
112 if context.get('invoice_id', False):
113 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
114 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
115 return journal_id and journal_id[0] or False
116 if context.get('journal_id', False):
117 return context.get('journal_id')
118 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
119 return context.get('search_default_journal_id')
121 ttype = context.get('type', 'bank')
122 if ttype in ('payment', 'receipt'):
124 res = self._make_journal_search(cr, uid, ttype, context=context)
125 return res and res[0] or False
127 def _get_tax(self, cr, uid, context=None):
128 if context is None: context = {}
129 journal_pool = self.pool.get('account.journal')
130 journal_id = context.get('journal_id', False)
132 ttype = context.get('type', 'bank')
133 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
140 journal = journal_pool.browse(cr, uid, journal_id, context=context)
141 account_id = journal.default_credit_account_id or journal.default_debit_account_id
142 if account_id and account_id.tax_ids:
143 tax_id = account_id.tax_ids[0].id
147 def _get_payment_rate_currency(self, cr, uid, context=None):
149 Return the default value for field payment_rate_currency_id: the currency of the journal
150 if there is one, otherwise the currency of the user's company
152 if context is None: context = {}
153 journal_pool = self.pool.get('account.journal')
154 journal_id = context.get('journal_id', False)
156 journal = journal_pool.browse(cr, uid, journal_id, context=context)
158 return journal.currency.id
159 #no journal given in the context, use company currency as default
160 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
162 def _get_currency(self, cr, uid, context=None):
163 if context is None: context = {}
164 journal_pool = self.pool.get('account.journal')
165 journal_id = context.get('journal_id', False)
167 journal = journal_pool.browse(cr, uid, journal_id, context=context)
169 return journal.currency.id
170 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
172 def _get_partner(self, cr, uid, context=None):
173 if context is None: context = {}
174 return context.get('partner_id', False)
176 def _get_reference(self, cr, uid, context=None):
177 if context is None: context = {}
178 return context.get('reference', False)
180 def _get_narration(self, cr, uid, context=None):
181 if context is None: context = {}
182 return context.get('narration', False)
184 def _get_amount(self, cr, uid, context=None):
187 return context.get('amount', 0.0)
189 def name_get(self, cr, uid, ids, context=None):
192 if context is None: context = {}
193 return [(r['id'], (r['number'] or _('Voucher'))) for r in self.read(cr, uid, ids, ['number'], context, load='_classic_write')]
195 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
196 mod_obj = self.pool.get('ir.model.data')
197 if context is None: context = {}
199 if view_type == 'form':
200 if not view_id and context.get('invoice_type'):
201 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
202 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
204 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
205 result = result and result[1] or False
207 if not view_id and context.get('line_type'):
208 if context.get('line_type') == 'customer':
209 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
211 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
212 result = result and result[1] or False
215 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
216 doc = etree.XML(res['arch'])
218 if context.get('type', 'sale') in ('purchase', 'payment'):
219 nodes = doc.xpath("//field[@name='partner_id']")
221 node.set('context', "{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}")
222 if context.get('invoice_type','') in ('in_invoice', 'in_refund'):
223 node.set('string', _("Supplier"))
224 res['arch'] = etree.tostring(doc)
227 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
229 sign = type == 'payment' and -1 or 1
230 for l in line_dr_ids:
232 for l in line_cr_ids:
233 credit += l['amount']
234 return amount - sign * (credit - debit)
236 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
237 context = context or {}
238 if not line_dr_ids and not line_cr_ids:
239 return {'value':{'writeoff_amount': 0.0}}
240 line_osv = self.pool.get("account.voucher.line")
241 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
242 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
243 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
244 is_multi_currency = False
245 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to see the options
246 for voucher_line in line_dr_ids+line_cr_ids:
247 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')
248 if line_id and self.pool.get('account.move.line').browse(cr, uid, line_id, context=context).currency_id:
249 is_multi_currency = True
251 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
253 def _get_journal_currency(self, cr, uid, ids, name, args, context=None):
255 for voucher in self.browse(cr, uid, ids, context=context):
256 res[voucher.id] = voucher.journal_id.currency and voucher.journal_id.currency.id or voucher.company_id.currency_id.id
259 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
260 if not ids: return {}
261 currency_obj = self.pool.get('res.currency')
264 for voucher in self.browse(cr, uid, ids, context=context):
265 sign = voucher.type == 'payment' and -1 or 1
266 for l in voucher.line_dr_ids:
268 for l in voucher.line_cr_ids:
270 currency = voucher.currency_id or voucher.company_id.currency_id
271 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
274 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
279 for v in self.browse(cr, uid, ids, context=context):
280 ctx.update({'date': v.date})
281 #make a new call to browse in order to have the right date in the context, to get the right currency rate
282 voucher = self.browse(cr, uid, v.id, context=ctx)
284 'voucher_special_currency': voucher.payment_rate_currency_id and voucher.payment_rate_currency_id.id or False,
285 'voucher_special_currency_rate': voucher.currency_id.rate * voucher.payment_rate,})
286 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)
289 def _get_currency_help_label(self, cr, uid, currency_id, payment_rate, payment_rate_currency_id, context=None):
291 This function builds a string to help the users to understand the behavior of the payment rate fields they can specify on the voucher.
292 This string is only used to improve the usability in the voucher form view and has no other effect.
294 :param currency_id: the voucher currency
295 :type currency_id: integer
296 :param payment_rate: the value of the payment_rate field of the voucher
297 :type payment_rate: float
298 :param payment_rate_currency_id: the value of the payment_rate_currency_id field of the voucher
299 :type payment_rate_currency_id: integer
300 :return: translated string giving a tip on what's the effect of the current payment rate specified
303 rml_parser = report_sxw.rml_parse(cr, uid, 'currency_help_label', context=context)
304 currency_pool = self.pool.get('res.currency')
305 currency_str = payment_rate_str = ''
307 currency_str = rml_parser.formatLang(1, currency_obj=currency_pool.browse(cr, uid, currency_id, context=context))
308 if payment_rate_currency_id:
309 payment_rate_str = rml_parser.formatLang(payment_rate, currency_obj=currency_pool.browse(cr, uid, payment_rate_currency_id, context=context))
310 currency_help_label = _('At the operation date, the exchange rate was\n%s = %s') % (currency_str, payment_rate_str)
311 return currency_help_label
313 def _fnct_currency_help_label(self, cr, uid, ids, name, args, context=None):
315 for voucher in self.browse(cr, uid, ids, context=context):
316 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)
319 _name = 'account.voucher'
320 _description = 'Accounting Voucher'
321 _inherit = ['mail.thread']
322 _order = "date desc, id desc"
323 # _rec_name = 'number'
326 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
331 'type':fields.selection([
333 ('purchase','Purchase'),
334 ('payment','Payment'),
335 ('receipt','Receipt'),
336 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
337 'name':fields.char('Memo', readonly=True, states={'draft':[('readonly',False)]}),
338 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]},
339 help="Effective date for accounting entries", copy=False),
340 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
341 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
342 'line_ids':fields.one2many('account.voucher.line', 'voucher_id', 'Voucher Lines',
343 readonly=True, copy=True,
344 states={'draft':[('readonly',False)]}),
345 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
346 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
347 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
348 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
349 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
350 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
351 'currency_id': fields.function(_get_journal_currency, type='many2one', relation='res.currency', string='Currency', readonly=True, required=True),
352 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
353 'state':fields.selection(
355 ('cancel','Cancelled'),
356 ('proforma','Pro-forma'),
358 ], 'Status', readonly=True, track_visibility='onchange', copy=False,
359 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
360 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
361 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
362 \n* The \'Cancelled\' status is used when user cancel voucher.'),
363 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
364 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
365 'reference': fields.char('Ref #', readonly=True, states={'draft':[('readonly',False)]},
366 help="Transaction reference number.", copy=False),
367 'number': fields.char('Number', readonly=True, copy=False),
368 'move_id':fields.many2one('account.move', 'Account Entry', copy=False),
369 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
370 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
371 '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'),
372 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
373 'pay_now':fields.selection([
374 ('pay_now','Pay Directly'),
375 ('pay_later','Pay Later or Group Funds'),
376 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
377 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
378 'pre_line':fields.boolean('Previous Payments ?', required=False),
379 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
380 'payment_option':fields.selection([
381 ('without_writeoff', 'Keep Open'),
382 ('with_writeoff', 'Reconcile Payment Balance'),
383 ], '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)"),
384 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
385 'comment': fields.char('Counterpart Comment', required=True, readonly=True, states={'draft': [('readonly', False)]}),
386 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
387 '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."),
388 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
389 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
390 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
391 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
392 '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'),
393 '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"),
396 'period_id': _get_period,
397 'partner_id': _get_partner,
398 'journal_id':_get_journal,
399 'currency_id': _get_currency,
400 'reference': _get_reference,
401 'narration':_get_narration,
402 'amount': _get_amount,
405 'pay_now': 'pay_now',
407 'date': lambda *a: time.strftime('%Y-%m-%d'),
408 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
410 'payment_option': 'without_writeoff',
411 'comment': _('Write-Off'),
413 'payment_rate_currency_id': _get_payment_rate_currency,
416 def compute_tax(self, cr, uid, ids, context=None):
417 tax_pool = self.pool.get('account.tax')
418 partner_pool = self.pool.get('res.partner')
419 position_pool = self.pool.get('account.fiscal.position')
420 voucher_line_pool = self.pool.get('account.voucher.line')
421 voucher_pool = self.pool.get('account.voucher')
422 if context is None: context = {}
424 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
426 for line in voucher.line_ids:
427 voucher_amount += line.untax_amount or line.amount
428 line.amount = line.untax_amount or line.amount
429 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
431 if not voucher.tax_id:
432 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
435 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
436 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
437 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
438 tax = tax_pool.browse(cr, uid, taxes, context=context)
440 total = voucher_amount
443 if not tax[0].price_include:
444 for line in voucher.line_ids:
445 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
446 total_tax += tax_line.get('amount', 0.0)
449 for line in voucher.line_ids:
453 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
454 line_tax += tax_line.get('amount', 0.0)
455 line_total += tax_line.get('price_unit')
456 total_tax += line_tax
457 untax_amount = line.untax_amount or line.amount
458 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
460 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
463 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
464 context = context or {}
465 tax_pool = self.pool.get('account.tax')
466 partner_pool = self.pool.get('res.partner')
467 position_pool = self.pool.get('account.fiscal.position')
468 line_pool = self.pool.get('account.voucher.line')
477 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
480 for line in line_ids:
482 line_amount = line.get('amount',0.0)
485 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
487 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
488 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
489 tax = tax_pool.browse(cr, uid, taxes, context=context)
491 if not tax[0].price_include:
492 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
493 total_tax += tax_line.get('amount')
495 voucher_total += line_amount
496 total = voucher_total + total_tax
499 'amount': total or voucher_total,
500 'tax_amount': total_tax
506 def onchange_term_id(self, cr, uid, ids, term_id, amount):
507 term_pool = self.pool.get('account.payment.term')
510 default = {'date_due':False}
511 if term_id and amount:
512 terms = term_pool.compute(cr, uid, term_id, amount)
514 due_date = terms[-1][0]
518 return {'value':default}
520 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):
522 Returns a dict that contains new values and context
524 @param partner_id: latest value from user input for field partner_id
525 @param args: other arguments
526 @param context: context arguments, like lang, time zone
528 @return: Returns a dict which contains new values, and context
534 if not partner_id or not journal_id:
537 partner_pool = self.pool.get('res.partner')
538 journal_pool = self.pool.get('account.journal')
540 journal = journal_pool.browse(cr, uid, journal_id, context=context)
541 partner = partner_pool.browse(cr, uid, partner_id, context=context)
544 if journal.type in ('sale','sale_refund'):
545 account_id = partner.property_account_receivable.id
547 elif journal.type in ('purchase', 'purchase_refund','expense'):
548 account_id = partner.property_account_payable.id
551 if not journal.default_credit_account_id or not journal.default_debit_account_id:
552 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
553 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
556 default['value']['account_id'] = account_id
557 default['value']['type'] = ttype or tr_type
559 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)
560 default['value'].update(vals.get('value'))
564 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
565 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)}}
566 if rate and amount and currency_id:
567 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
568 #context should contain the date, the payment currency and the payment rate specified on the voucher
569 amount_in_company_currency = self.pool.get('res.currency').compute(cr, uid, currency_id, company_currency.id, amount, context=context)
570 res['value']['paid_amount_in_company_currency'] = amount_in_company_currency
573 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):
577 ctx.update({'date': date})
578 #read the voucher rate with the right date in the context
579 currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
580 voucher_rate = self.pool.get('res.currency').read(cr, uid, [currency_id], ['rate'], context=ctx)[0]['rate']
582 'voucher_special_currency': payment_rate_currency_id,
583 'voucher_special_currency_rate': rate * voucher_rate})
584 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=ctx)
585 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
586 for key in vals.keys():
587 res[key].update(vals[key])
590 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
593 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
594 currency_obj = self.pool.get('res.currency')
595 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
596 company_id = journal.company_id.id
598 currency_id = currency_id or journal.company_id.currency_id.id
599 payment_rate_currency_id = currency_id
601 ctx.update({'date': date})
603 if ttype == 'receipt':
604 o2m_to_loop = 'line_cr_ids'
605 elif ttype == 'payment':
606 o2m_to_loop = 'line_dr_ids'
607 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
608 for voucher_line in vals['value'][o2m_to_loop]:
609 if voucher_line['currency_id'] != currency_id:
610 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
611 # is not in the voucher currency
612 payment_rate_currency_id = voucher_line['currency_id']
613 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
614 payment_rate = tmp / currency_obj.browse(cr, uid, currency_id, context=ctx).rate
616 vals['value'].update({
617 'payment_rate': payment_rate,
618 'currency_id': currency_id,
619 'payment_rate_currency_id': payment_rate_currency_id
621 #read the voucher rate with the right date in the context
622 voucher_rate = self.pool.get('res.currency').read(cr, uid, [currency_id], ['rate'], context=ctx)[0]['rate']
624 'voucher_special_currency_rate': payment_rate * voucher_rate,
625 'voucher_special_currency': payment_rate_currency_id})
626 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
627 for key in res.keys():
628 vals[key].update(res[key])
631 def basic_onchange_partner(self, cr, uid, ids, partner_id, journal_id, ttype, context=None):
632 partner_pool = self.pool.get('res.partner')
633 journal_pool = self.pool.get('account.journal')
634 res = {'value': {'account_id': False}}
635 if not partner_id or not journal_id:
638 journal = journal_pool.browse(cr, uid, journal_id, context=context)
639 partner = partner_pool.browse(cr, uid, partner_id, context=context)
641 if journal.type in ('sale','sale_refund'):
642 account_id = partner.property_account_receivable.id
643 elif journal.type in ('purchase', 'purchase_refund','expense'):
644 account_id = partner.property_account_payable.id
646 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
648 res['value']['account_id'] = account_id
651 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
656 #TODO: comment me and use me directly in the sales/purchases views
657 res = self.basic_onchange_partner(cr, uid, ids, partner_id, journal_id, ttype, context=context)
658 if ttype in ['sale', 'purchase']:
661 # 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
662 ctx.update({'date': date})
663 vals = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=ctx)
664 vals2 = self.recompute_payment_rate(cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=context)
665 for key in vals.keys():
666 res[key].update(vals[key])
667 for key in vals2.keys():
668 res[key].update(vals2[key])
669 #TODO: can probably be removed now
670 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
671 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
672 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
673 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
674 # onchange returns a value for them
676 del(res['value']['line_dr_ids'])
677 del(res['value']['pre_line'])
678 del(res['value']['payment_rate'])
679 elif ttype == 'purchase':
680 del(res['value']['line_cr_ids'])
681 del(res['value']['pre_line'])
682 del(res['value']['payment_rate'])
685 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
687 Returns a dict that contains new values and context
689 @param partner_id: latest value from user input for field partner_id
690 @param args: other arguments
691 @param context: context arguments, like lang, time zone
693 @return: Returns a dict which contains new values, and context
695 def _remove_noise_in_o2m():
696 """if the line is partially reconciled, then we must pay attention to display it only once and
698 This function returns True if the line is considered as noise and should not be displayed
700 if line.reconcile_partial_id:
701 if currency_id == line.currency_id.id:
702 if line.amount_residual_currency <= 0:
705 if line.amount_residual <= 0:
711 context_multi_currency = context.copy()
713 currency_pool = self.pool.get('res.currency')
714 move_line_pool = self.pool.get('account.move.line')
715 partner_pool = self.pool.get('res.partner')
716 journal_pool = self.pool.get('account.journal')
717 line_pool = self.pool.get('account.voucher.line')
721 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
725 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
727 line_pool.unlink(cr, uid, line_ids)
729 if not partner_id or not journal_id:
732 journal = journal_pool.browse(cr, uid, journal_id, context=context)
733 partner = partner_pool.browse(cr, uid, partner_id, context=context)
734 currency_id = currency_id or journal.company_id.currency_id.id
739 if context.get('account_id'):
740 account_type = self.pool['account.account'].browse(cr, uid, context['account_id'], context=context).type
741 if ttype == 'payment':
743 account_type = 'payable'
744 total_debit = price or 0.0
746 total_credit = price or 0.0
748 account_type = 'receivable'
750 if not context.get('move_line_ids', False):
751 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
753 ids = context['move_line_ids']
754 invoice_id = context.get('invoice_id', False)
755 company_currency = journal.company_id.currency_id.id
756 move_lines_found = []
758 #order the lines by most old first
760 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
762 #compute the total debit/credit and look for a matching open amount or invoice
763 for line in account_move_lines:
764 if _remove_noise_in_o2m():
768 if line.invoice.id == invoice_id:
769 #if the invoice linked to the voucher line is equal to the invoice_id in context
770 #then we assign the amount on that line, whatever the other voucher lines
771 move_lines_found.append(line.id)
772 elif currency_id == company_currency:
773 #otherwise treatments is the same but with other field names
774 if line.amount_residual == price:
775 #if the amount residual is equal the amount voucher, we assign it to that voucher
776 #line, whatever the other voucher lines
777 move_lines_found.append(line.id)
779 #otherwise we will split the voucher amount on each line (by most old first)
780 total_credit += line.credit or 0.0
781 total_debit += line.debit or 0.0
782 elif currency_id == line.currency_id.id:
783 if line.amount_residual_currency == price:
784 move_lines_found.append(line.id)
786 total_credit += line.credit and line.amount_currency or 0.0
787 total_debit += line.debit and line.amount_currency or 0.0
789 remaining_amount = price
790 #voucher line creation
791 for line in account_move_lines:
793 if _remove_noise_in_o2m():
796 if line.currency_id and currency_id == line.currency_id.id:
797 amount_original = abs(line.amount_currency)
798 amount_unreconciled = abs(line.amount_residual_currency)
800 #always use the amount booked in the company currency as the basis of the conversion into the voucher currency
801 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0, context=context_multi_currency)
802 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual), context=context_multi_currency)
803 line_currency_id = line.currency_id and line.currency_id.id or company_currency
805 'name':line.move_id.name,
806 'type': line.credit and 'dr' or 'cr',
807 'move_line_id':line.id,
808 'account_id':line.account_id.id,
809 'amount_original': amount_original,
810 'amount': (line.id in move_lines_found) and min(abs(remaining_amount), amount_unreconciled) or 0.0,
811 'date_original':line.date,
812 'date_due':line.date_maturity,
813 'amount_unreconciled': amount_unreconciled,
814 'currency_id': line_currency_id,
816 remaining_amount -= rs['amount']
817 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
818 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
819 if not move_lines_found:
820 if currency_id == line_currency_id:
822 amount = min(amount_unreconciled, abs(total_debit))
823 rs['amount'] = amount
824 total_debit -= amount
826 amount = min(amount_unreconciled, abs(total_credit))
827 rs['amount'] = amount
828 total_credit -= amount
830 if rs['amount_unreconciled'] == rs['amount']:
831 rs['reconcile'] = True
833 if rs['type'] == 'cr':
834 default['value']['line_cr_ids'].append(rs)
836 default['value']['line_dr_ids'].append(rs)
838 if len(default['value']['line_cr_ids']) > 0:
839 default['value']['pre_line'] = 1
840 elif len(default['value']['line_dr_ids']) > 0:
841 default['value']['pre_line'] = 1
842 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
845 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
850 #set the default payment rate of the voucher and compute the paid amount in company currency
852 ctx.update({'date': date})
853 #read the voucher rate with the right date in the context
854 voucher_rate = self.pool.get('res.currency').read(cr, uid, [currency_id], ['rate'], context=ctx)[0]['rate']
856 'voucher_special_currency_rate': payment_rate * voucher_rate,
857 'voucher_special_currency': payment_rate_currency_id})
858 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
859 for key in vals.keys():
860 res[key].update(vals[key])
863 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
865 @param date: latest value from user input for field date
866 @param args: other arguments
867 @param context: context arguments, like lang, time zone
868 @return: Returns a dict which contains new values, and context
873 #set the period of the voucher
874 period_pool = self.pool.get('account.period')
875 currency_obj = self.pool.get('res.currency')
877 ctx.update({'company_id': company_id, 'account_period_prefer_normal': True})
878 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
879 pids = period_pool.find(cr, uid, date, context=ctx)
881 res['value'].update({'period_id':pids[0]})
882 if payment_rate_currency_id:
883 ctx.update({'date': date})
885 if payment_rate_currency_id != currency_id:
886 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
887 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
888 vals = self.onchange_payment_rate_currency(cr, uid, ids, voucher_currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
889 vals['value'].update({'payment_rate': payment_rate})
890 for key in vals.keys():
891 res[key].update(vals[key])
894 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
899 journal_pool = self.pool.get('account.journal')
900 journal = journal_pool.browse(cr, uid, journal_id, context=context)
901 account_id = journal.default_credit_account_id or journal.default_debit_account_id
903 if account_id and account_id.tax_ids:
904 tax_id = account_id.tax_ids[0].id
907 if ttype in ('sale', 'purchase'):
908 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
909 vals['value'].update({'tax_id':tax_id,'amount': amount})
912 currency_id = journal.currency.id
914 currency_id = journal.company_id.currency_id.id
915 vals['value'].update({'currency_id': currency_id})
916 #in case we want to register the payment directly from an invoice, it's confusing to allow to switch the journal
917 #without seeing that the amount is expressed in the journal currency, and not in the invoice currency. So to avoid
918 #this common mistake, we simply reset the amount to 0 if the currency is not the invoice currency.
919 if context.get('payment_expected_currency') and currency_id != context.get('payment_expected_currency'):
920 vals['value']['amount'] = 0
923 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
924 for key in res.keys():
925 vals[key].update(res[key])
928 def button_proforma_voucher(self, cr, uid, ids, context=None):
929 self.signal_proforma_voucher(cr, uid, ids)
930 return {'type': 'ir.actions.act_window_close'}
932 def proforma_voucher(self, cr, uid, ids, context=None):
933 self.action_move_line_create(cr, uid, ids, context=context)
936 def action_cancel_draft(self, cr, uid, ids, context=None):
937 self.create_workflow(cr, uid, ids)
938 self.write(cr, uid, ids, {'state':'draft'})
941 def cancel_voucher(self, cr, uid, ids, context=None):
942 reconcile_pool = self.pool.get('account.move.reconcile')
943 move_pool = self.pool.get('account.move')
944 move_line_pool = self.pool.get('account.move.line')
945 for voucher in self.browse(cr, uid, ids, context=context):
946 # refresh to make sure you don't unlink an already removed move
948 for line in voucher.move_ids:
949 if line.reconcile_id:
950 move_lines = [move_line.id for move_line in line.reconcile_id.line_id]
951 move_lines.remove(line.id)
952 reconcile_pool.unlink(cr, uid, [line.reconcile_id.id])
953 if len(move_lines) >= 2:
954 move_line_pool.reconcile_partial(cr, uid, move_lines, 'auto',context=context)
956 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
957 move_pool.unlink(cr, uid, [voucher.move_id.id])
962 self.write(cr, uid, ids, res)
965 def unlink(self, cr, uid, ids, context=None):
966 for t in self.read(cr, uid, ids, ['state'], context=context):
967 if t['state'] not in ('draft', 'cancel'):
968 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
969 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
971 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
976 partner_pool = self.pool.get('res.partner')
977 journal_pool = self.pool.get('account.journal')
978 if pay_now == 'pay_later':
979 partner = partner_pool.browse(cr, uid, partner_id)
980 journal = journal_pool.browse(cr, uid, journal_id)
981 if journal.type in ('sale','sale_refund'):
982 account_id = partner.property_account_receivable.id
983 elif journal.type in ('purchase', 'purchase_refund','expense'):
984 account_id = partner.property_account_payable.id
986 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
988 res['account_id'] = account_id
991 def _sel_context(self, cr, uid, voucher_id, context=None):
993 Select the context to use accordingly if it needs to be multicurrency or not.
995 :param voucher_id: Id of the actual voucher
996 :return: The returned context will be the same as given in parameter if the voucher currency is the same
997 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
998 the date of the voucher.
1001 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
1002 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
1003 if current_currency <> company_currency:
1004 context_multi_currency = context.copy()
1005 voucher = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1006 context_multi_currency.update({'date': voucher.date})
1007 return context_multi_currency
1010 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
1012 Return a dict to be use to create the first account move line of given voucher.
1014 :param voucher_id: Id of voucher what we are creating account_move.
1015 :param move_id: Id of account move where this line will be added.
1016 :param company_currency: id of currency of the company to which the voucher belong
1017 :param current_currency: id of currency of the voucher
1018 :return: mapping between fieldname and value of account move line to create
1021 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1022 debit = credit = 0.0
1023 # TODO: is there any other alternative then the voucher type ??
1024 # ANSWER: We can have payment and receipt "In Advance".
1025 # TODO: Make this logic available.
1026 # -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
1027 if voucher.type in ('purchase', 'payment'):
1028 credit = voucher.paid_amount_in_company_currency
1029 elif voucher.type in ('sale', 'receipt'):
1030 debit = voucher.paid_amount_in_company_currency
1031 if debit < 0: credit = -debit; debit = 0.0
1032 if credit < 0: debit = -credit; credit = 0.0
1033 sign = debit - credit < 0 and -1 or 1
1034 #set the first line of the voucher
1036 'name': voucher.name or '/',
1039 'account_id': voucher.account_id.id,
1041 'journal_id': voucher.journal_id.id,
1042 'period_id': voucher.period_id.id,
1043 'partner_id': voucher.partner_id.id,
1044 'currency_id': company_currency <> current_currency and current_currency or False,
1045 'amount_currency': company_currency <> current_currency and sign * voucher.amount or 0.0,
1046 'date': voucher.date,
1047 'date_maturity': voucher.date_due
1051 def account_move_get(self, cr, uid, voucher_id, context=None):
1053 This method prepare the creation of the account move related to the given voucher.
1055 :param voucher_id: Id of voucher for which we are creating account_move.
1056 :return: mapping between fieldname and value of account move to create
1059 seq_obj = self.pool.get('ir.sequence')
1060 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1062 name = voucher.number
1063 elif voucher.journal_id.sequence_id:
1064 if not voucher.journal_id.sequence_id.active:
1065 raise osv.except_osv(_('Configuration Error !'),
1066 _('Please activate the sequence of selected journal !'))
1068 c.update({'fiscalyear_id': voucher.period_id.fiscalyear_id.id})
1069 name = seq_obj.next_by_id(cr, uid, voucher.journal_id.sequence_id.id, context=c)
1071 raise osv.except_osv(_('Error!'),
1072 _('Please define a sequence on the journal.'))
1073 if not voucher.reference:
1074 ref = name.replace('/','')
1076 ref = voucher.reference
1080 'journal_id': voucher.journal_id.id,
1081 'narration': voucher.narration,
1082 'date': voucher.date,
1084 'period_id': voucher.period_id.id,
1088 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
1090 Prepare the two lines in company currency due to currency rate difference.
1092 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
1094 :param move_id: Account move wher the move lines will be.
1095 :param amount_residual: Amount to be posted.
1096 :param company_currency: id of currency of the company to which the voucher belong
1097 :param current_currency: id of currency of the voucher
1098 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
1099 :rtype: tuple of dict
1101 if amount_residual > 0:
1102 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1104 model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'action_account_form')
1105 msg = _("You should configure the 'Loss Exchange Rate Account' to manage automatically the booking of accounting entries related to differences between exchange rates.")
1106 raise openerp.exceptions.RedirectWarning(msg, action_id, _('Go to the configuration panel'))
1108 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1110 model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'action_account_form')
1111 msg = _("You should configure the 'Gain Exchange Rate Account' to manage automatically the booking of accounting entries related to differences between exchange rates.")
1112 raise openerp.exceptions.RedirectWarning(msg, action_id, _('Go to the configuration panel'))
1113 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1114 # the receivable/payable account may have a secondary currency, which render this field mandatory
1115 if line.account_id.currency_id:
1116 account_currency_id = line.account_id.currency_id.id
1118 account_currency_id = company_currency <> current_currency and current_currency or False
1120 'journal_id': line.voucher_id.journal_id.id,
1121 'period_id': line.voucher_id.period_id.id,
1122 'name': _('change')+': '+(line.name or '/'),
1123 'account_id': line.account_id.id,
1125 'partner_id': line.voucher_id.partner_id.id,
1126 'currency_id': account_currency_id,
1127 'amount_currency': 0.0,
1129 'credit': amount_residual > 0 and amount_residual or 0.0,
1130 'debit': amount_residual < 0 and -amount_residual or 0.0,
1131 'date': line.voucher_id.date,
1133 move_line_counterpart = {
1134 'journal_id': line.voucher_id.journal_id.id,
1135 'period_id': line.voucher_id.period_id.id,
1136 'name': _('change')+': '+(line.name or '/'),
1137 'account_id': account_id.id,
1139 'amount_currency': 0.0,
1140 'partner_id': line.voucher_id.partner_id.id,
1141 'currency_id': account_currency_id,
1143 'debit': amount_residual > 0 and amount_residual or 0.0,
1144 'credit': amount_residual < 0 and -amount_residual or 0.0,
1145 'date': line.voucher_id.date,
1147 return (move_line, move_line_counterpart)
1149 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1151 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1152 payment_rate_currency_id is relevant) either the rate encoded in the system.
1154 :param amount: float. The amount to convert
1155 :param voucher: id of the voucher on which we want the conversion
1156 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1157 field in order to select the good rate to use.
1158 :return: the amount in the currency of the voucher's company
1163 currency_obj = self.pool.get('res.currency')
1164 voucher = self.browse(cr, uid, voucher_id, context=context)
1165 return currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1167 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1169 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1170 It returns Tuple with tot_line what is total of difference between debit and credit and
1171 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1173 :param voucher_id: Voucher id what we are working with
1174 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1175 :param move_id: Account move wher those lines will be joined.
1176 :param company_currency: id of currency of the company to which the voucher belong
1177 :param current_currency: id of currency of the voucher
1178 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1179 :rtype: tuple(float, list of int)
1183 move_line_obj = self.pool.get('account.move.line')
1184 currency_obj = self.pool.get('res.currency')
1185 tax_obj = self.pool.get('account.tax')
1186 tot_line = line_total
1189 date = self.read(cr, uid, [voucher_id], ['date'], context=context)[0]['date']
1190 ctx = context.copy()
1191 ctx.update({'date': date})
1192 voucher = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context=ctx)
1193 voucher_currency = voucher.journal_id.currency or voucher.company_id.currency_id
1195 'voucher_special_currency_rate': voucher_currency.rate * voucher.payment_rate ,
1196 'voucher_special_currency': voucher.payment_rate_currency_id and voucher.payment_rate_currency_id.id or False,})
1197 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1198 for line in voucher.line_ids:
1199 #create one move line per voucher line where amount is not 0.0
1200 # AND (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
1201 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)):
1203 # convert the amount set on the voucher line into the currency of the voucher's company
1204 # 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
1205 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher.id, context=ctx)
1206 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1207 # currency rate difference
1208 if line.amount == line.amount_unreconciled:
1209 if not line.move_line_id:
1210 raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1211 sign = voucher.type in ('payment', 'purchase') and -1 or 1
1212 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1214 currency_rate_difference = 0.0
1216 'journal_id': voucher.journal_id.id,
1217 'period_id': voucher.period_id.id,
1218 'name': line.name or '/',
1219 'account_id': line.account_id.id,
1221 'partner_id': voucher.partner_id.id,
1222 '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,
1223 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1227 'date': voucher.date
1231 if line.type == 'dr':
1236 if (line.type=='dr'):
1238 move_line['debit'] = amount
1241 move_line['credit'] = amount
1243 if voucher.tax_id and voucher.type in ('sale', 'purchase'):
1245 'account_tax_id': voucher.tax_id.id,
1248 if move_line.get('account_tax_id', False):
1249 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1250 if not (tax_data.base_code_id and tax_data.tax_code_id):
1251 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))
1253 # compute the amount in foreign currency
1254 foreign_currency_diff = 0.0
1255 amount_currency = False
1256 if line.move_line_id:
1257 # We want to set it on the account move line as soon as the original line had a foreign currency
1258 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1259 # we compute the amount in that foreign currency.
1260 if line.move_line_id.currency_id.id == current_currency:
1261 # if the voucher and the voucher line share the same currency, there is no computation to do
1262 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1263 amount_currency = sign * (line.amount)
1265 # if the rate is specified on the voucher, it will be used thanks to the special keys in the context
1266 # otherwise we use the rates of the system
1267 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1268 if line.amount == line.amount_unreconciled:
1269 sign = voucher.type in ('payment', 'purchase') and -1 or 1
1270 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1272 move_line['amount_currency'] = amount_currency
1273 voucher_line = move_line_obj.create(cr, uid, move_line)
1274 rec_ids = [voucher_line, line.move_line_id.id]
1276 if not currency_obj.is_zero(cr, uid, voucher.company_id.currency_id, currency_rate_difference):
1277 # Change difference entry in company currency
1278 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1279 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1280 move_line_obj.create(cr, uid, exch_lines[1], context)
1281 rec_ids.append(new_id)
1283 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):
1284 # Change difference entry in voucher currency
1285 move_line_foreign_currency = {
1286 'journal_id': line.voucher_id.journal_id.id,
1287 'period_id': line.voucher_id.period_id.id,
1288 'name': _('change')+': '+(line.name or '/'),
1289 'account_id': line.account_id.id,
1291 'partner_id': line.voucher_id.partner_id.id,
1292 'currency_id': line.move_line_id.currency_id.id,
1293 'amount_currency': -1 * foreign_currency_diff,
1297 'date': line.voucher_id.date,
1299 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1300 rec_ids.append(new_id)
1301 if line.move_line_id.id:
1302 rec_lst_ids.append(rec_ids)
1303 return (tot_line, rec_lst_ids)
1305 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1307 Set a dict to be use to create the writeoff move line.
1309 :param voucher_id: Id of voucher what we are creating account_move.
1310 :param line_total: Amount remaining to be allocated on lines.
1311 :param move_id: Id of account move where this line will be added.
1312 :param name: Description of account move line.
1313 :param company_currency: id of currency of the company to which the voucher belong
1314 :param current_currency: id of currency of the voucher
1315 :return: mapping between fieldname and value of account move line to create
1318 currency_obj = self.pool.get('res.currency')
1321 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1322 current_currency_obj = voucher.currency_id or voucher.journal_id.company_id.currency_id
1324 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1328 if voucher.payment_option == 'with_writeoff':
1329 account_id = voucher.writeoff_acc_id.id
1330 write_off_name = voucher.comment
1331 elif voucher.type in ('sale', 'receipt'):
1332 account_id = voucher.partner_id.property_account_receivable.id
1334 account_id = voucher.partner_id.property_account_payable.id
1335 sign = voucher.type == 'payment' and -1 or 1
1337 'name': write_off_name or name,
1338 'account_id': account_id,
1340 'partner_id': voucher.partner_id.id,
1341 'date': voucher.date,
1342 'credit': diff > 0 and diff or 0.0,
1343 'debit': diff < 0 and -diff or 0.0,
1344 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher.writeoff_amount) or 0.0,
1345 'currency_id': company_currency <> current_currency and current_currency or False,
1346 'analytic_account_id': voucher.analytic_id and voucher.analytic_id.id or False,
1351 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1353 Get the currency of the actual company.
1355 :param voucher_id: Id of the voucher what i want to obtain company currency.
1356 :return: currency id of the company of the voucher
1359 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1361 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1363 Get the currency of the voucher.
1365 :param voucher_id: Id of the voucher what i want to obtain current currency.
1366 :return: currency id of the voucher
1369 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1370 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1372 def action_move_line_create(self, cr, uid, ids, context=None):
1374 Confirm the vouchers given in ids and create the journal entries for each of them
1378 move_pool = self.pool.get('account.move')
1379 move_line_pool = self.pool.get('account.move.line')
1380 for voucher in self.browse(cr, uid, ids, context=context):
1381 local_context = dict(context, force_company=voucher.journal_id.company_id.id)
1384 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1385 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1386 # we select the context to use accordingly if it's a multicurrency case or not
1387 context = self._sel_context(cr, uid, voucher.id, context)
1388 # But for the operations made by _convert_amount, we always need to give the date in the context
1389 ctx = context.copy()
1390 ctx.update({'date': voucher.date})
1391 # Create the account move record.
1392 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1393 # Get the name of the account_move just created
1394 name = move_pool.browse(cr, uid, move_id, context=context).name
1395 # Create the first line of the voucher
1396 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)
1397 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1398 line_total = move_line_brw.debit - move_line_brw.credit
1400 if voucher.type == 'sale':
1401 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1402 elif voucher.type == 'purchase':
1403 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1404 # Create one move line per voucher line where amount is not 0.0
1405 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1407 # Create the writeoff line if needed
1408 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, local_context)
1410 move_line_pool.create(cr, uid, ml_writeoff, local_context)
1411 # We post the voucher.
1412 self.write(cr, uid, [voucher.id], {
1417 if voucher.journal_id.entry_posted:
1418 move_pool.post(cr, uid, [move_id], context={})
1419 # We automatically reconcile the account move lines.
1421 for rec_ids in rec_list_ids:
1422 if len(rec_ids) >= 2:
1423 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)
1426 class account_voucher_line(osv.osv):
1427 _name = 'account.voucher.line'
1428 _description = 'Voucher Lines'
1429 _order = "move_line_id"
1431 # If the payment is in the same currency than the invoice, we keep the same amount
1432 # Otherwise, we compute from invoice currency to payment currency
1433 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1434 currency_pool = self.pool.get('res.currency')
1436 for line in self.browse(cr, uid, ids, context=context):
1437 ctx = context.copy()
1438 ctx.update({'date': line.voucher_id.date})
1439 voucher_rate = self.pool.get('res.currency').read(cr, uid, line.voucher_id.currency_id.id, ['rate'], context=ctx)['rate']
1441 'voucher_special_currency': line.voucher_id.payment_rate_currency_id and line.voucher_id.payment_rate_currency_id.id or False,
1442 'voucher_special_currency_rate': line.voucher_id.payment_rate * voucher_rate})
1444 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1445 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1446 move_line = line.move_line_id or False
1449 res['amount_original'] = 0.0
1450 res['amount_unreconciled'] = 0.0
1451 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1452 res['amount_original'] = abs(move_line.amount_currency)
1453 res['amount_unreconciled'] = abs(move_line.amount_residual_currency)
1455 #always use the amount booked in the company currency as the basis of the conversion into the voucher currency
1456 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit or move_line.debit or 0.0, context=ctx)
1457 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1459 rs_data[line.id] = res
1462 def _currency_id(self, cr, uid, ids, name, args, context=None):
1464 This function returns the currency id of a voucher line. It's either the currency of the
1465 associated move line (if any) or the currency of the voucher or the company currency.
1468 for line in self.browse(cr, uid, ids, context=context):
1469 move_line = line.move_line_id
1471 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1473 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1477 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1478 'name':fields.char('Description',),
1479 'account_id':fields.many2one('account.account','Account', required=True),
1480 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1481 'untax_amount':fields.float('Untax Amount'),
1482 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1483 'reconcile': fields.boolean('Full Reconcile'),
1484 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1485 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1486 'move_line_id': fields.many2one('account.move.line', 'Journal Item', copy=False),
1487 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1488 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1489 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1490 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1491 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1492 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1498 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1499 vals = {'amount': 0.0}
1501 vals = { 'amount': amount_unreconciled}
1502 return {'value': vals}
1504 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1507 vals['reconcile'] = (amount == amount_unreconciled)
1508 return {'value': vals}
1510 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1512 Returns a dict that contains new values and context
1514 @param move_line_id: latest value from user input for field move_line_id
1515 @param args: other arguments
1516 @param context: context arguments, like lang, time zone
1518 @return: Returns a dict which contains new values, and context
1521 move_line_pool = self.pool.get('account.move.line')
1523 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1524 if move_line.credit:
1529 'account_id': move_line.account_id.id,
1531 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1537 def default_get(self, cr, user, fields_list, context=None):
1539 Returns default values for fields
1540 @param fields_list: list of fields, for which default values are required to be read
1541 @param context: context arguments, like lang, time zone
1543 @return: Returns a dict that contains default values for fields
1547 journal_id = context.get('journal_id', False)
1548 partner_id = context.get('partner_id', False)
1549 journal_pool = self.pool.get('account.journal')
1550 partner_pool = self.pool.get('res.partner')
1551 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1552 if (not journal_id) or ('account_id' not in fields_list):
1554 journal = journal_pool.browse(cr, user, journal_id, context=context)
1557 if journal.type in ('sale', 'sale_refund'):
1558 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1560 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1561 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1564 partner = partner_pool.browse(cr, user, partner_id, context=context)
1565 if context.get('type') == 'payment':
1567 account_id = partner.property_account_payable.id
1568 elif context.get('type') == 'receipt':
1569 account_id = partner.property_account_receivable.id
1572 'account_id':account_id,
1577 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1579 for operation in operations:
1581 if not isinstance(operation, (list, tuple)):
1582 result = target_osv.read(cr, uid, operation, fields, context=context)
1583 elif operation[0] == 0:
1584 # may be necessary to check if all the fields are here and get the default values?
1585 result = operation[2]
1586 elif operation[0] == 1:
1587 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1588 if not result: result = {}
1589 result.update(operation[2])
1590 elif operation[0] == 4:
1591 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1593 results.append(result)
1597 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: