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
30 class res_company(osv.osv):
31 _inherit = "res.company"
33 'income_currency_exchange_account_id': fields.many2one(
35 string="Gain Exchange Rate Account",
36 domain="[('type', '=', 'other')]",),
37 'expense_currency_exchange_account_id': fields.many2one(
39 string="Loss Exchange Rate Account",
40 domain="[('type', '=', 'other')]",),
45 class account_config_settings(osv.osv_memory):
46 _inherit = 'account.config.settings'
48 'income_currency_exchange_account_id': fields.related(
49 'company_id', 'income_currency_exchange_account_id',
51 relation='account.account',
52 string="Gain Exchange Rate Account",
53 domain="[('type', '=', 'other')]"),
54 'expense_currency_exchange_account_id': fields.related(
55 'company_id', 'expense_currency_exchange_account_id',
57 relation='account.account',
58 string="Loss Exchange Rate Account",
59 domain="[('type', '=', 'other')]"),
61 def onchange_company_id(self, cr, uid, ids, company_id, context=None):
62 res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id, context=context)
64 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
65 res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False,
66 'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False})
68 res['value'].update({'income_currency_exchange_account_id': False,
69 'expense_currency_exchange_account_id': False})
72 class account_voucher(osv.osv):
73 def _check_paid(self, cr, uid, ids, name, args, context=None):
75 for voucher in self.browse(cr, uid, ids, context=context):
76 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
79 def _get_type(self, cr, uid, context=None):
82 return context.get('type', False)
84 def _get_period(self, cr, uid, context=None):
85 if context is None: context = {}
86 if context.get('period_id', False):
87 return context.get('period_id')
88 periods = self.pool.get('account.period').find(cr, uid)
89 return periods and periods[0] or False
91 def _make_journal_search(self, cr, uid, ttype, context=None):
92 journal_pool = self.pool.get('account.journal')
93 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
95 def _get_journal(self, cr, uid, context=None):
96 if context is None: context = {}
97 invoice_pool = self.pool.get('account.invoice')
98 journal_pool = self.pool.get('account.journal')
99 if context.get('invoice_id', False):
100 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
101 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
102 return journal_id and journal_id[0] or False
103 if context.get('journal_id', False):
104 return context.get('journal_id')
105 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
106 return context.get('search_default_journal_id')
108 ttype = context.get('type', 'bank')
109 if ttype in ('payment', 'receipt'):
111 res = self._make_journal_search(cr, uid, ttype, context=context)
112 return res and res[0] or False
114 def _get_tax(self, cr, uid, context=None):
115 if context is None: context = {}
116 journal_pool = self.pool.get('account.journal')
117 journal_id = context.get('journal_id', False)
119 ttype = context.get('type', 'bank')
120 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
127 journal = journal_pool.browse(cr, uid, journal_id, context=context)
128 account_id = journal.default_credit_account_id or journal.default_debit_account_id
129 if account_id and account_id.tax_ids:
130 tax_id = account_id.tax_ids[0].id
134 def _get_payment_rate_currency(self, cr, uid, context=None):
136 Return the default value for field payment_rate_currency_id: the currency of the journal
137 if there is one, otherwise the currency of the user's company
139 if context is None: context = {}
140 journal_pool = self.pool.get('account.journal')
141 journal_id = context.get('journal_id', False)
143 journal = journal_pool.browse(cr, uid, journal_id, context=context)
145 return journal.currency.id
146 #no journal given in the context, use company currency as default
147 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
149 def _get_currency(self, cr, uid, context=None):
150 if context is None: context = {}
151 journal_pool = self.pool.get('account.journal')
152 journal_id = context.get('journal_id', False)
154 journal = journal_pool.browse(cr, uid, journal_id, context=context)
156 return journal.currency.id
159 def _get_partner(self, cr, uid, context=None):
160 if context is None: context = {}
161 return context.get('partner_id', False)
163 def _get_reference(self, cr, uid, context=None):
164 if context is None: context = {}
165 return context.get('reference', False)
167 def _get_narration(self, cr, uid, context=None):
168 if context is None: context = {}
169 return context.get('narration', False)
171 def _get_amount(self, cr, uid, context=None):
174 return context.get('amount', 0.0)
176 def name_get(self, cr, uid, ids, context=None):
179 if context is None: context = {}
180 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
182 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
183 mod_obj = self.pool.get('ir.model.data')
184 if context is None: context = {}
186 if view_type == 'form':
187 if not view_id and context.get('invoice_type'):
188 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
189 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
191 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
192 result = result and result[1] or False
194 if not view_id and context.get('line_type'):
195 if context.get('line_type') == 'customer':
196 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
198 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
199 result = result and result[1] or False
202 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
203 doc = etree.XML(res['arch'])
205 if context.get('type', 'sale') in ('purchase', 'payment'):
206 nodes = doc.xpath("//field[@name='partner_id']")
208 node.set('context', "{'search_default_supplier': 1}")
209 if context.get('invoice_type','') in ('in_invoice', 'in_refund'):
210 node.set('string', _("Supplier"))
211 res['arch'] = etree.tostring(doc)
214 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
216 sign = type == 'payment' and -1 or 1
217 for l in line_dr_ids:
219 for l in line_cr_ids:
220 credit += l['amount']
221 return amount - sign * (credit - debit)
223 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
224 context = context or {}
225 if not line_dr_ids and not line_cr_ids:
227 line_osv = self.pool.get("account.voucher.line")
228 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
229 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
231 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
232 is_multi_currency = False
234 # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
235 is_multi_currency = True
237 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
238 for voucher_line in line_dr_ids+line_cr_ids:
239 company_currency = False
240 company_currency = voucher_line.get('move_line_id', False) and self.pool.get('account.move.line').browse(cr, uid, voucher_line.get('move_line_id'), context=context).company_id.currency_id.id
241 if voucher_line.get('currency_id', company_currency) != company_currency:
242 is_multi_currency = True
244 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
246 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
247 if not ids: return {}
248 currency_obj = self.pool.get('res.currency')
251 for voucher in self.browse(cr, uid, ids, context=context):
252 sign = voucher.type == 'payment' and -1 or 1
253 for l in voucher.line_dr_ids:
255 for l in voucher.line_cr_ids:
257 currency = voucher.currency_id or voucher.company_id.currency_id
258 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
261 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
262 if not ids: return {}
265 for voucher in self.browse(cr, uid, ids, context=context):
266 if voucher.currency_id:
267 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
268 rate = 1 / voucher.payment_rate
271 ctx.update({'date': voucher.date})
272 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
273 company_currency_rate = voucher.company_id.currency_id.rate
274 rate = voucher_rate * company_currency_rate
275 res[voucher.id] = voucher.amount / rate
278 _name = 'account.voucher'
279 _description = 'Accounting Voucher'
280 _inherit = ['mail.thread']
281 _order = "date desc, id desc"
282 # _rec_name = 'number'
285 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
290 'active': fields.boolean('Active', help="By default, reconciliation vouchers made on draft bank statements are set as inactive, which allow to hide the customer/supplier payment while the bank statement isn't confirmed."),
291 'type':fields.selection([
293 ('purchase','Purchase'),
294 ('payment','Payment'),
295 ('receipt','Receipt'),
296 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
297 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
298 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
299 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
300 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
301 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
302 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
303 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
304 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
305 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
306 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
307 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
308 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
309 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
310 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
311 'state':fields.selection(
313 ('cancel','Cancelled'),
314 ('proforma','Pro-forma'),
316 ], 'Status', readonly=True, size=32, track_visibility='onchange',
317 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
318 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
319 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
320 \n* The \'Cancelled\' status is used when user cancel voucher.'),
321 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
322 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
323 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
324 'number': fields.char('Number', size=32, readonly=True,),
325 'move_id':fields.many2one('account.move', 'Account Entry'),
326 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
327 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
328 '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'),
329 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
330 'pay_now':fields.selection([
331 ('pay_now','Pay Directly'),
332 ('pay_later','Pay Later or Group Funds'),
333 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
334 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
335 'pre_line':fields.boolean('Previous Payments ?', required=False),
336 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
337 'payment_option':fields.selection([
338 ('without_writeoff', 'Keep Open'),
339 ('with_writeoff', 'Reconcile Payment Balance'),
340 ], '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)"),
341 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
342 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
343 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
344 '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."),
345 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
346 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
347 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
348 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
349 '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'),
353 'period_id': _get_period,
354 'partner_id': _get_partner,
355 'journal_id':_get_journal,
356 'currency_id': _get_currency,
357 'reference': _get_reference,
358 'narration':_get_narration,
359 'amount': _get_amount,
362 'pay_now': 'pay_now',
364 'date': lambda *a: time.strftime('%Y-%m-%d'),
365 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
367 'payment_option': 'without_writeoff',
368 'comment': _('Write-Off'),
370 'payment_rate_currency_id': _get_payment_rate_currency,
373 def compute_tax(self, cr, uid, ids, context=None):
374 tax_pool = self.pool.get('account.tax')
375 partner_pool = self.pool.get('res.partner')
376 position_pool = self.pool.get('account.fiscal.position')
377 voucher_line_pool = self.pool.get('account.voucher.line')
378 voucher_pool = self.pool.get('account.voucher')
379 if context is None: context = {}
381 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
383 for line in voucher.line_ids:
384 voucher_amount += line.untax_amount or line.amount
385 line.amount = line.untax_amount or line.amount
386 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
388 if not voucher.tax_id:
389 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
392 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
393 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
394 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
395 tax = tax_pool.browse(cr, uid, taxes, context=context)
397 total = voucher_amount
400 if not tax[0].price_include:
401 for line in voucher.line_ids:
402 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
403 total_tax += tax_line.get('amount', 0.0)
406 for line in voucher.line_ids:
410 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
411 line_tax += tax_line.get('amount', 0.0)
412 line_total += tax_line.get('price_unit')
413 total_tax += line_tax
414 untax_amount = line.untax_amount or line.amount
415 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
417 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
420 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
421 context = context or {}
422 tax_pool = self.pool.get('account.tax')
423 partner_pool = self.pool.get('res.partner')
424 position_pool = self.pool.get('account.fiscal.position')
425 line_pool = self.pool.get('account.voucher.line')
432 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
435 for line in line_ids:
437 line_amount = line.get('amount',0.0)
440 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
442 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
443 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
444 tax = tax_pool.browse(cr, uid, taxes, context=context)
446 if not tax[0].price_include:
447 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
448 total_tax += tax_line.get('amount')
450 voucher_total += line_amount
451 total = voucher_total + total_tax
454 'amount': total or voucher_total,
455 'tax_amount': total_tax
461 def onchange_term_id(self, cr, uid, ids, term_id, amount):
462 term_pool = self.pool.get('account.payment.term')
465 default = {'date_due':False}
466 if term_id and amount:
467 terms = term_pool.compute(cr, uid, term_id, amount)
469 due_date = terms[-1][0]
473 return {'value':default}
475 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):
477 Returns a dict that contains new values and context
479 @param partner_id: latest value from user input for field partner_id
480 @param args: other arguments
481 @param context: context arguments, like lang, time zone
483 @return: Returns a dict which contains new values, and context
489 if not partner_id or not journal_id:
492 partner_pool = self.pool.get('res.partner')
493 journal_pool = self.pool.get('account.journal')
495 journal = journal_pool.browse(cr, uid, journal_id, context=context)
496 partner = partner_pool.browse(cr, uid, partner_id, context=context)
499 if journal.type in ('sale','sale_refund'):
500 account_id = partner.property_account_receivable.id
502 elif journal.type in ('purchase', 'purchase_refund','expense'):
503 account_id = partner.property_account_payable.id
506 if not journal.default_credit_account_id or not journal.default_debit_account_id:
507 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
508 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
511 default['value']['account_id'] = account_id
512 default['value']['type'] = ttype or tr_type
514 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)
515 default['value'].update(vals.get('value'))
519 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
520 res = {'value': {'paid_amount_in_company_currency': amount}}
521 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
522 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
523 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
524 if company_currency.id == payment_rate_currency_id:
527 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
528 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
531 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):
534 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
536 ctx.update({'date': date})
537 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
538 for key in vals.keys():
539 res[key].update(vals[key])
542 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
545 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
546 currency_obj = self.pool.get('res.currency')
547 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
548 company_id = journal.company_id.id
550 payment_rate_currency_id = currency_id
552 ctx.update({'date': date})
554 if ttype == 'receipt':
555 o2m_to_loop = 'line_cr_ids'
556 elif ttype == 'payment':
557 o2m_to_loop = 'line_dr_ids'
558 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
559 for voucher_line in vals['value'][o2m_to_loop]:
560 if voucher_line['currency_id'] != currency_id:
561 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
562 # is not in the voucher currency
563 payment_rate_currency_id = voucher_line['currency_id']
564 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
565 voucher_currency_id = currency_id or journal.company_id.currency_id.id
566 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
568 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
569 for key in res.keys():
570 vals[key].update(res[key])
571 vals['value'].update({'payment_rate': payment_rate})
572 if payment_rate_currency_id:
573 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
576 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
579 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
580 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
581 for key in vals.keys():
582 res[key].update(vals[key])
583 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
584 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
585 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
586 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
587 # onchange returns a value for them
589 del(res['value']['line_dr_ids'])
590 del(res['value']['pre_line'])
591 del(res['value']['payment_rate'])
592 elif ttype == 'purchase':
593 del(res['value']['line_cr_ids'])
594 del(res['value']['pre_line'])
595 del(res['value']['payment_rate'])
598 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
600 Returns a dict that contains new values and context
602 @param partner_id: latest value from user input for field partner_id
603 @param args: other arguments
604 @param context: context arguments, like lang, time zone
606 @return: Returns a dict which contains new values, and context
608 def _remove_noise_in_o2m():
609 """if the line is partially reconciled, then we must pay attention to display it only once and
611 This function returns True if the line is considered as noise and should not be displayed
613 if line.reconcile_partial_id:
614 if currency_id == line.currency_id.id:
615 if line.amount_residual_currency <= 0:
618 if line.amount_residual <= 0:
624 context_multi_currency = context.copy()
626 context_multi_currency.update({'date': date})
628 currency_pool = self.pool.get('res.currency')
629 move_line_pool = self.pool.get('account.move.line')
630 partner_pool = self.pool.get('res.partner')
631 journal_pool = self.pool.get('account.journal')
632 line_pool = self.pool.get('account.voucher.line')
636 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
640 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
642 line_pool.unlink(cr, uid, line_ids)
644 if not partner_id or not journal_id:
647 journal = journal_pool.browse(cr, uid, journal_id, context=context)
648 partner = partner_pool.browse(cr, uid, partner_id, context=context)
649 currency_id = currency_id or journal.company_id.currency_id.id
651 if journal.type in ('sale','sale_refund'):
652 account_id = partner.property_account_receivable.id
653 elif journal.type in ('purchase', 'purchase_refund','expense'):
654 account_id = partner.property_account_payable.id
656 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
658 default['value']['account_id'] = account_id
660 if journal.type not in ('cash', 'bank'):
665 account_type = 'receivable'
666 if ttype == 'payment':
667 account_type = 'payable'
668 total_debit = price or 0.0
670 total_credit = price or 0.0
671 account_type = 'receivable'
673 if not context.get('move_line_ids', False):
674 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
676 ids = context['move_line_ids']
677 invoice_id = context.get('invoice_id', False)
678 company_currency = journal.company_id.currency_id.id
679 move_line_found = False
681 #order the lines by most old first
683 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
685 #compute the total debit/credit and look for a matching open amount or invoice
686 for line in account_move_lines:
687 if _remove_noise_in_o2m():
691 if line.invoice.id == invoice_id:
692 #if the invoice linked to the voucher line is equal to the invoice_id in context
693 #then we assign the amount on that line, whatever the other voucher lines
694 move_line_found = line.id
696 elif currency_id == company_currency:
697 #otherwise treatments is the same but with other field names
698 if line.amount_residual == price:
699 #if the amount residual is equal the amount voucher, we assign it to that voucher
700 #line, whatever the other voucher lines
701 move_line_found = line.id
703 #otherwise we will split the voucher amount on each line (by most old first)
704 total_credit += line.credit or 0.0
705 total_debit += line.debit or 0.0
706 elif currency_id == line.currency_id.id:
707 if line.amount_residual_currency == price:
708 move_line_found = line.id
710 total_credit += line.credit and line.amount_currency or 0.0
711 total_debit += line.debit and line.amount_currency or 0.0
713 #voucher line creation
714 for line in account_move_lines:
716 if _remove_noise_in_o2m():
719 if line.currency_id and currency_id==line.currency_id.id:
720 amount_original = abs(line.amount_currency)
721 amount_unreconciled = abs(line.amount_residual_currency)
723 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
724 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
725 line_currency_id = line.currency_id and line.currency_id.id or company_currency
727 'name':line.move_id.name,
728 'type': line.credit and 'dr' or 'cr',
729 'move_line_id':line.id,
730 'account_id':line.account_id.id,
731 'amount_original': amount_original,
732 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
733 'date_original':line.date,
734 'date_due':line.date_maturity,
735 'amount_unreconciled': amount_unreconciled,
736 'currency_id': line_currency_id,
738 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
739 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
740 if not move_line_found:
741 if currency_id == line_currency_id:
743 amount = min(amount_unreconciled, abs(total_debit))
744 rs['amount'] = amount
745 total_debit -= amount
747 amount = min(amount_unreconciled, abs(total_credit))
748 rs['amount'] = amount
749 total_credit -= amount
751 if rs['amount_unreconciled'] == rs['amount']:
752 rs['reconcile'] = True
754 if rs['type'] == 'cr':
755 default['value']['line_cr_ids'].append(rs)
757 default['value']['line_dr_ids'].append(rs)
759 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
760 default['value']['pre_line'] = 1
761 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
762 default['value']['pre_line'] = 1
763 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
766 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
770 #set the default payment rate of the voucher and compute the paid amount in company currency
771 if currency_id and currency_id == payment_rate_currency_id:
773 ctx.update({'date': date})
774 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
775 for key in vals.keys():
776 res[key].update(vals[key])
779 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
781 @param date: latest value from user input for field date
782 @param args: other arguments
783 @param context: context arguments, like lang, time zone
784 @return: Returns a dict which contains new values, and context
789 #set the period of the voucher
790 period_pool = self.pool.get('account.period')
791 currency_obj = self.pool.get('res.currency')
793 ctx.update({'company_id': company_id})
794 pids = period_pool.find(cr, uid, date, context=ctx)
796 res['value'].update({'period_id':pids[0]})
797 if payment_rate_currency_id:
798 ctx.update({'date': date})
800 if payment_rate_currency_id != currency_id:
801 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
802 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
803 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
804 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
805 vals['value'].update({'payment_rate': payment_rate})
806 for key in vals.keys():
807 res[key].update(vals[key])
810 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
813 journal_pool = self.pool.get('account.journal')
814 journal = journal_pool.browse(cr, uid, journal_id, context=context)
815 account_id = journal.default_credit_account_id or journal.default_debit_account_id
817 if account_id and account_id.tax_ids:
818 tax_id = account_id.tax_ids[0].id
821 if ttype in ('sale', 'purchase'):
822 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
823 vals['value'].update({'tax_id':tax_id,'amount': amount})
826 currency_id = journal.currency.id
827 vals['value'].update({'currency_id': currency_id})
828 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
829 for key in res.keys():
830 vals[key].update(res[key])
833 def button_proforma_voucher(self, cr, uid, ids, context=None):
834 self.signal_proforma_voucher(cr, uid, ids)
835 return {'type': 'ir.actions.act_window_close'}
837 def proforma_voucher(self, cr, uid, ids, context=None):
838 self.action_move_line_create(cr, uid, ids, context=context)
841 def action_cancel_draft(self, cr, uid, ids, context=None):
842 self.create_workflow(cr, uid, ids)
843 self.write(cr, uid, ids, {'state':'draft'})
846 def cancel_voucher(self, cr, uid, ids, context=None):
847 reconcile_pool = self.pool.get('account.move.reconcile')
848 move_pool = self.pool.get('account.move')
850 for voucher in self.browse(cr, uid, ids, context=context):
852 for line in voucher.move_ids:
853 if line.reconcile_id:
854 recs += [line.reconcile_id.id]
855 if line.reconcile_partial_id:
856 recs += [line.reconcile_partial_id.id]
858 reconcile_pool.unlink(cr, uid, recs)
861 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
862 move_pool.unlink(cr, uid, [voucher.move_id.id])
867 self.write(cr, uid, ids, res)
870 def unlink(self, cr, uid, ids, context=None):
871 for t in self.read(cr, uid, ids, ['state'], context=context):
872 if t['state'] not in ('draft', 'cancel'):
873 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
874 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
876 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
880 res = {'account_id':False}
881 partner_pool = self.pool.get('res.partner')
882 journal_pool = self.pool.get('account.journal')
883 if pay_now == 'pay_later':
884 partner = partner_pool.browse(cr, uid, partner_id)
885 journal = journal_pool.browse(cr, uid, journal_id)
886 if journal.type in ('sale','sale_refund'):
887 account_id = partner.property_account_receivable.id
888 elif journal.type in ('purchase', 'purchase_refund','expense'):
889 account_id = partner.property_account_payable.id
891 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
892 res['account_id'] = account_id
895 def _sel_context(self, cr, uid, voucher_id, context=None):
897 Select the context to use accordingly if it needs to be multicurrency or not.
899 :param voucher_id: Id of the actual voucher
900 :return: The returned context will be the same as given in parameter if the voucher currency is the same
901 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
902 the date of the voucher.
905 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
906 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
907 if current_currency <> company_currency:
908 context_multi_currency = context.copy()
909 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
910 context_multi_currency.update({'date': voucher_brw.date})
911 return context_multi_currency
914 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
916 Return a dict to be use to create the first account move line of given voucher.
918 :param voucher_id: Id of voucher what we are creating account_move.
919 :param move_id: Id of account move where this line will be added.
920 :param company_currency: id of currency of the company to which the voucher belong
921 :param current_currency: id of currency of the voucher
922 :return: mapping between fieldname and value of account move line to create
925 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
927 # TODO: is there any other alternative then the voucher type ??
928 # ANSWER: We can have payment and receipt "In Advance".
929 # TODO: Make this logic available.
930 # -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
931 if voucher_brw.type in ('purchase', 'payment'):
932 credit = voucher_brw.paid_amount_in_company_currency
933 elif voucher_brw.type in ('sale', 'receipt'):
934 debit = voucher_brw.paid_amount_in_company_currency
935 if debit < 0: credit = -debit; debit = 0.0
936 if credit < 0: debit = -credit; credit = 0.0
937 sign = debit - credit < 0 and -1 or 1
938 #set the first line of the voucher
940 'name': voucher_brw.name or '/',
943 'account_id': voucher_brw.account_id.id,
945 'journal_id': voucher_brw.journal_id.id,
946 'period_id': voucher_brw.period_id.id,
947 'partner_id': voucher_brw.partner_id.id,
948 'currency_id': company_currency <> current_currency and current_currency or False,
949 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
950 'date': voucher_brw.date,
951 'date_maturity': voucher_brw.date_due
955 def account_move_get(self, cr, uid, voucher_id, context=None):
957 This method prepare the creation of the account move related to the given voucher.
959 :param voucher_id: Id of voucher for which we are creating account_move.
960 :return: mapping between fieldname and value of account move to create
963 seq_obj = self.pool.get('ir.sequence')
964 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
965 if voucher_brw.number:
966 name = voucher_brw.number
967 elif voucher_brw.journal_id.sequence_id:
968 if not voucher_brw.journal_id.sequence_id.active:
969 raise osv.except_osv(_('Configuration Error !'),
970 _('Please activate the sequence of selected journal !'))
972 c.update({'fiscalyear_id': voucher_brw.period_id.fiscalyear_id.id})
973 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=c)
975 raise osv.except_osv(_('Error!'),
976 _('Please define a sequence on the journal.'))
977 if not voucher_brw.reference:
978 ref = name.replace('/','')
980 ref = voucher_brw.reference
984 'journal_id': voucher_brw.journal_id.id,
985 'narration': voucher_brw.narration,
986 'date': voucher_brw.date,
988 'period_id': voucher_brw.period_id.id,
992 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
994 Prepare the two lines in company currency due to currency rate difference.
996 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
998 :param move_id: Account move wher the move lines will be.
999 :param amount_residual: Amount to be posted.
1000 :param company_currency: id of currency of the company to which the voucher belong
1001 :param current_currency: id of currency of the voucher
1002 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
1003 :rtype: tuple of dict
1005 if amount_residual > 0:
1006 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1008 raise osv.except_osv(_('Insufficient Configuration!'),_("You should configure the 'Loss Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
1010 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1012 raise osv.except_osv(_('Insufficient Configuration!'),_("You should configure the 'Gain Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
1013 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1014 # the receivable/payable account may have a secondary currency, which render this field mandatory
1015 account_currency_id = company_currency <> current_currency and current_currency or False
1017 'journal_id': line.voucher_id.journal_id.id,
1018 'period_id': line.voucher_id.period_id.id,
1019 'name': _('change')+': '+(line.name or '/'),
1020 'account_id': line.account_id.id,
1022 'partner_id': line.voucher_id.partner_id.id,
1023 'currency_id': account_currency_id,
1024 'amount_currency': 0.0,
1026 'credit': amount_residual > 0 and amount_residual or 0.0,
1027 'debit': amount_residual < 0 and -amount_residual or 0.0,
1028 'date': line.voucher_id.date,
1030 move_line_counterpart = {
1031 'journal_id': line.voucher_id.journal_id.id,
1032 'period_id': line.voucher_id.period_id.id,
1033 'name': _('change')+': '+(line.name or '/'),
1034 'account_id': account_id.id,
1036 'amount_currency': 0.0,
1037 'partner_id': line.voucher_id.partner_id.id,
1038 'currency_id': account_currency_id,
1040 'debit': amount_residual > 0 and amount_residual or 0.0,
1041 'credit': amount_residual < 0 and -amount_residual or 0.0,
1042 'date': line.voucher_id.date,
1044 return (move_line, move_line_counterpart)
1046 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1048 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1049 payment_rate_currency_id is relevant) either the rate encoded in the system.
1051 :param amount: float. The amount to convert
1052 :param voucher: id of the voucher on which we want the conversion
1053 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1054 field in order to select the good rate to use.
1055 :return: the amount in the currency of the voucher's company
1058 currency_obj = self.pool.get('res.currency')
1059 voucher = self.browse(cr, uid, voucher_id, context=context)
1061 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1062 # the rate specified on the voucher is for the company currency
1063 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1065 # the rate specified on the voucher is not relevant, we use all the rates in the system
1066 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1069 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1071 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1072 It returns Tuple with tot_line what is total of difference between debit and credit and
1073 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1075 :param voucher_id: Voucher id what we are working with
1076 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1077 :param move_id: Account move wher those lines will be joined.
1078 :param company_currency: id of currency of the company to which the voucher belong
1079 :param current_currency: id of currency of the voucher
1080 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1081 :rtype: tuple(float, list of int)
1085 move_line_obj = self.pool.get('account.move.line')
1086 currency_obj = self.pool.get('res.currency')
1087 tax_obj = self.pool.get('account.tax')
1088 tot_line = line_total
1091 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1092 ctx = context.copy()
1093 ctx.update({'date': voucher_brw.date})
1094 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1095 for line in voucher_brw.line_ids:
1096 #create one move line per voucher line where amount is not 0.0
1097 # AND (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
1098 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_rounding=prec) and not float_compare(line.move_line_id.debit, 0.0, precision_rounding=prec)):
1100 # convert the amount set on the voucher line into the currency of the voucher's company
1101 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1102 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1103 # currency rate difference
1104 if line.amount == line.amount_unreconciled:
1105 if not line.move_line_id:
1106 raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1107 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1108 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1110 currency_rate_difference = 0.0
1112 'journal_id': voucher_brw.journal_id.id,
1113 'period_id': voucher_brw.period_id.id,
1114 'name': line.name or '/',
1115 'account_id': line.account_id.id,
1117 'partner_id': voucher_brw.partner_id.id,
1118 '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,
1119 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1123 'date': voucher_brw.date
1127 if line.type == 'dr':
1132 if (line.type=='dr'):
1134 move_line['debit'] = amount
1137 move_line['credit'] = amount
1139 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1141 'account_tax_id': voucher_brw.tax_id.id,
1144 if move_line.get('account_tax_id', False):
1145 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1146 if not (tax_data.base_code_id and tax_data.tax_code_id):
1147 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))
1149 # compute the amount in foreign currency
1150 foreign_currency_diff = 0.0
1151 amount_currency = False
1152 if line.move_line_id:
1153 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1154 # We want to set it on the account move line as soon as the original line had a foreign currency
1155 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1156 # we compute the amount in that foreign currency.
1157 if line.move_line_id.currency_id.id == current_currency:
1158 # if the voucher and the voucher line share the same currency, there is no computation to do
1159 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1160 amount_currency = sign * (line.amount)
1161 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1162 # if the rate is specified on the voucher, we must use it
1163 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1164 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1166 # otherwise we use the rates of the system (giving the voucher date in the context)
1167 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1168 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1169 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1170 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1172 move_line['amount_currency'] = amount_currency
1173 voucher_line = move_line_obj.create(cr, uid, move_line)
1174 rec_ids = [voucher_line, line.move_line_id.id]
1176 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1177 # Change difference entry in company currency
1178 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1179 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1180 move_line_obj.create(cr, uid, exch_lines[1], context)
1181 rec_ids.append(new_id)
1183 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):
1184 # Change difference entry in voucher currency
1185 move_line_foreign_currency = {
1186 'journal_id': line.voucher_id.journal_id.id,
1187 'period_id': line.voucher_id.period_id.id,
1188 'name': _('change')+': '+(line.name or '/'),
1189 'account_id': line.account_id.id,
1191 'partner_id': line.voucher_id.partner_id.id,
1192 'currency_id': line.move_line_id.currency_id.id,
1193 'amount_currency': -1 * foreign_currency_diff,
1197 'date': line.voucher_id.date,
1199 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1200 rec_ids.append(new_id)
1202 if line.move_line_id.id:
1203 rec_lst_ids.append(rec_ids)
1205 return (tot_line, rec_lst_ids)
1207 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1209 Set a dict to be use to create the writeoff move line.
1211 :param voucher_id: Id of voucher what we are creating account_move.
1212 :param line_total: Amount remaining to be allocated on lines.
1213 :param move_id: Id of account move where this line will be added.
1214 :param name: Description of account move line.
1215 :param company_currency: id of currency of the company to which the voucher belong
1216 :param current_currency: id of currency of the voucher
1217 :return: mapping between fieldname and value of account move line to create
1220 currency_obj = self.pool.get('res.currency')
1223 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1224 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1226 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1230 if voucher_brw.payment_option == 'with_writeoff':
1231 account_id = voucher_brw.writeoff_acc_id.id
1232 write_off_name = voucher_brw.comment
1233 elif voucher_brw.type in ('sale', 'receipt'):
1234 account_id = voucher_brw.partner_id.property_account_receivable.id
1236 account_id = voucher_brw.partner_id.property_account_payable.id
1237 sign = voucher_brw.type == 'payment' and -1 or 1
1239 'name': write_off_name or name,
1240 'account_id': account_id,
1242 'partner_id': voucher_brw.partner_id.id,
1243 'date': voucher_brw.date,
1244 'credit': diff > 0 and diff or 0.0,
1245 'debit': diff < 0 and -diff or 0.0,
1246 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1247 'currency_id': company_currency <> current_currency and current_currency or False,
1248 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1253 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1255 Get the currency of the actual company.
1257 :param voucher_id: Id of the voucher what i want to obtain company currency.
1258 :return: currency id of the company of the voucher
1261 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1263 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1265 Get the currency of the voucher.
1267 :param voucher_id: Id of the voucher what i want to obtain current currency.
1268 :return: currency id of the voucher
1271 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1272 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1274 def action_move_line_create(self, cr, uid, ids, context=None):
1276 Confirm the vouchers given in ids and create the journal entries for each of them
1280 move_pool = self.pool.get('account.move')
1281 move_line_pool = self.pool.get('account.move.line')
1282 for voucher in self.browse(cr, uid, ids, context=context):
1285 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1286 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1287 # we select the context to use accordingly if it's a multicurrency case or not
1288 context = self._sel_context(cr, uid, voucher.id, context)
1289 # But for the operations made by _convert_amount, we always need to give the date in the context
1290 ctx = context.copy()
1291 ctx.update({'date': voucher.date})
1292 # Create the account move record.
1293 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1294 # Get the name of the account_move just created
1295 name = move_pool.browse(cr, uid, move_id, context=context).name
1296 # Create the first line of the voucher
1297 move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, company_currency, current_currency, context), context)
1298 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1299 line_total = move_line_brw.debit - move_line_brw.credit
1301 if voucher.type == 'sale':
1302 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1303 elif voucher.type == 'purchase':
1304 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1305 # Create one move line per voucher line where amount is not 0.0
1306 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1308 # Create the writeoff line if needed
1309 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1311 move_line_pool.create(cr, uid, ml_writeoff, context)
1312 # We post the voucher.
1313 self.write(cr, uid, [voucher.id], {
1318 if voucher.journal_id.entry_posted:
1319 move_pool.post(cr, uid, [move_id], context={})
1320 # We automatically reconcile the account move lines.
1322 for rec_ids in rec_list_ids:
1323 if len(rec_ids) >= 2:
1324 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)
1327 def copy(self, cr, uid, id, default=None, context=None):
1334 'line_cr_ids': False,
1335 'line_dr_ids': False,
1338 if 'date' not in default:
1339 default['date'] = time.strftime('%Y-%m-%d')
1340 return super(account_voucher, self).copy(cr, uid, id, default, context)
1343 class account_voucher_line(osv.osv):
1344 _name = 'account.voucher.line'
1345 _description = 'Voucher Lines'
1346 _order = "move_line_id"
1348 # If the payment is in the same currency than the invoice, we keep the same amount
1349 # Otherwise, we compute from company currency to payment currency
1350 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1351 currency_pool = self.pool.get('res.currency')
1353 for line in self.browse(cr, uid, ids, context=context):
1354 ctx = context.copy()
1355 ctx.update({'date': line.voucher_id.date})
1357 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1358 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1359 move_line = line.move_line_id or False
1362 res['amount_original'] = 0.0
1363 res['amount_unreconciled'] = 0.0
1364 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1365 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1366 res['amount_unreconciled'] = currency_pool.compute(cr, uid, move_line.currency_id and move_line.currency_id.id or company_currency, voucher_currency, abs(move_line.amount_residual_currency), context=ctx)
1367 elif move_line and move_line.credit > 0:
1368 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1369 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1371 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1372 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1374 rs_data[line.id] = res
1377 def _currency_id(self, cr, uid, ids, name, args, context=None):
1379 This function returns the currency id of a voucher line. It's either the currency of the
1380 associated move line (if any) or the currency of the voucher or the company currency.
1383 for line in self.browse(cr, uid, ids, context=context):
1384 move_line = line.move_line_id
1386 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1388 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1392 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1393 'name':fields.char('Description', size=256),
1394 'account_id':fields.many2one('account.account','Account', required=True),
1395 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1396 'untax_amount':fields.float('Untax Amount'),
1397 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1398 'reconcile': fields.boolean('Full Reconcile'),
1399 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1400 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1401 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1402 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1403 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1404 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1405 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1406 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1407 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1413 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1414 vals = {'amount': 0.0}
1416 vals = { 'amount': amount_unreconciled}
1417 return {'value': vals}
1419 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1422 vals['reconcile'] = (amount == amount_unreconciled)
1423 return {'value': vals}
1425 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1427 Returns a dict that contains new values and context
1429 @param move_line_id: latest value from user input for field move_line_id
1430 @param args: other arguments
1431 @param context: context arguments, like lang, time zone
1433 @return: Returns a dict which contains new values, and context
1436 move_line_pool = self.pool.get('account.move.line')
1438 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1439 if move_line.credit:
1444 'account_id': move_line.account_id.id,
1446 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1452 def default_get(self, cr, user, fields_list, context=None):
1454 Returns default values for fields
1455 @param fields_list: list of fields, for which default values are required to be read
1456 @param context: context arguments, like lang, time zone
1458 @return: Returns a dict that contains default values for fields
1462 journal_id = context.get('journal_id', False)
1463 partner_id = context.get('partner_id', False)
1464 journal_pool = self.pool.get('account.journal')
1465 partner_pool = self.pool.get('res.partner')
1466 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1467 if (not journal_id) or ('account_id' not in fields_list):
1469 journal = journal_pool.browse(cr, user, journal_id, context=context)
1472 if journal.type in ('sale', 'sale_refund'):
1473 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1475 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1476 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1479 partner = partner_pool.browse(cr, user, partner_id, context=context)
1480 if context.get('type') == 'payment':
1482 account_id = partner.property_account_payable.id
1483 elif context.get('type') == 'receipt':
1484 account_id = partner.property_account_receivable.id
1487 'account_id':account_id,
1491 account_voucher_line()
1493 class account_bank_statement(osv.osv):
1494 _inherit = 'account.bank.statement'
1496 def button_confirm_bank(self, cr, uid, ids, context=None):
1497 voucher_obj = self.pool.get('account.voucher')
1499 for statement in self.browse(cr, uid, ids, context=context):
1500 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1502 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1503 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1505 def button_cancel(self, cr, uid, ids, context=None):
1506 voucher_obj = self.pool.get('account.voucher')
1507 for st in self.browse(cr, uid, ids, context=context):
1509 for line in st.line_ids:
1511 voucher_ids.append(line.voucher_id.id)
1512 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1513 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1515 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1516 voucher_obj = self.pool.get('account.voucher')
1517 move_line_obj = self.pool.get('account.move.line')
1518 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1519 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1520 if st_line.voucher_id:
1521 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1522 if st_line.voucher_id.state == 'cancel':
1523 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1524 voucher_obj.signal_proforma_voucher(cr, uid, [st_line.voucher_id.id])
1526 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1527 bank_st_line_obj.write(cr, uid, [st_line_id], {
1528 'move_ids': [(4, v.move_id.id, False)]
1531 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1532 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1534 def write(self, cr, uid, ids, vals, context=None):
1535 # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1536 # Because the voucher keeps in memory the journal it was created with.
1537 for bk_st in self.browse(cr, uid, ids, context=context):
1538 if vals.get('journal_id') and bk_st.line_ids:
1539 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1540 raise osv.except_osv(_('Unable to change journal !'), _('You can not change the journal as you already reconciled some statement lines!'))
1541 return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1543 account_bank_statement()
1545 class account_bank_statement_line(osv.osv):
1546 _inherit = 'account.bank.statement.line'
1548 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1549 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1550 if 'value' not in res:
1552 res['value'].update({'voucher_id' : False})
1555 def onchange_amount(self, cr, uid, ids, amount, context=None):
1556 return {'value' : {'voucher_id' : False}}
1558 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1562 for line in self.browse(cursor, user, ids, context=context):
1564 res[line.id] = line.voucher_id.amount#
1569 def _check_amount(self, cr, uid, ids, context=None):
1570 for obj in self.browse(cr, uid, ids, context=context):
1572 diff = abs(obj.amount) - obj.voucher_id.amount
1573 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1578 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1582 'amount_reconciled': fields.function(_amount_reconciled,
1583 string='Amount reconciled', type='float'),
1584 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1587 def unlink(self, cr, uid, ids, context=None):
1588 voucher_obj = self.pool.get('account.voucher')
1589 statement_line = self.browse(cr, uid, ids, context=context)
1591 for st_line in statement_line:
1592 if st_line.voucher_id:
1593 unlink_ids.append(st_line.voucher_id.id)
1594 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1595 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1597 account_bank_statement_line()
1599 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1601 for operation in operations:
1603 if not isinstance(operation, (list, tuple)):
1604 result = target_osv.read(cr, uid, operation, fields, context=context)
1605 elif operation[0] == 0:
1606 # may be necessary to check if all the fields are here and get the default values?
1607 result = operation[2]
1608 elif operation[0] == 1:
1609 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1610 if not result: result = {}
1611 result.update(operation[2])
1612 elif operation[0] == 4:
1613 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1615 results.append(result)
1619 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: