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 import netsvc
26 from openerp.osv import fields, osv
27 import openerp.addons.decimal_precision as dp
28 from openerp.tools.translate import _
29 from openerp.tools import float_compare
31 class res_company(osv.osv):
32 _inherit = "res.company"
34 'income_currency_exchange_account_id': fields.many2one(
36 string="Gain Exchange Rate Account",
37 domain="[('type', '=', 'other')]",),
38 'expense_currency_exchange_account_id': fields.many2one(
40 string="Loss Exchange Rate Account",
41 domain="[('type', '=', 'other')]",),
46 class account_config_settings(osv.osv_memory):
47 _inherit = 'account.config.settings'
49 'income_currency_exchange_account_id': fields.related(
50 'company_id', 'income_currency_exchange_account_id',
52 relation='account.account',
53 string="Gain Exchange Rate Account",
54 domain="[('type', '=', 'other')]"),
55 'expense_currency_exchange_account_id': fields.related(
56 'company_id', 'expense_currency_exchange_account_id',
58 relation='account.account',
59 string="Loss Exchange Rate Account",
60 domain="[('type', '=', 'other')]"),
62 def onchange_company_id(self, cr, uid, ids, company_id, context=None):
63 res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id, context=context)
65 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
66 res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False,
67 'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False})
69 res['value'].update({'income_currency_exchange_account_id': False,
70 'expense_currency_exchange_account_id': False})
73 class account_voucher(osv.osv):
74 def _check_paid(self, cr, uid, ids, name, args, context=None):
76 for voucher in self.browse(cr, uid, ids, context=context):
77 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
80 def _get_type(self, cr, uid, context=None):
83 return context.get('type', False)
85 def _get_period(self, cr, uid, context=None):
86 if context is None: context = {}
87 if context.get('period_id', False):
88 return context.get('period_id')
89 ctx = dict(context, account_period_prefer_normal=True)
90 periods = self.pool.get('account.period').find(cr, uid, context=ctx)
91 return periods and periods[0] or False
93 def _make_journal_search(self, cr, uid, ttype, context=None):
94 journal_pool = self.pool.get('account.journal')
95 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
97 def _get_journal(self, cr, uid, context=None):
98 if context is None: context = {}
99 invoice_pool = self.pool.get('account.invoice')
100 journal_pool = self.pool.get('account.journal')
101 if context.get('invoice_id', False):
102 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
103 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
104 return journal_id and journal_id[0] or False
105 if context.get('journal_id', False):
106 return context.get('journal_id')
107 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
108 return context.get('search_default_journal_id')
110 ttype = context.get('type', 'bank')
111 if ttype in ('payment', 'receipt'):
113 res = self._make_journal_search(cr, uid, ttype, context=context)
114 return res and res[0] or False
116 def _get_tax(self, cr, uid, context=None):
117 if context is None: context = {}
118 journal_pool = self.pool.get('account.journal')
119 journal_id = context.get('journal_id', False)
121 ttype = context.get('type', 'bank')
122 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
129 journal = journal_pool.browse(cr, uid, journal_id, context=context)
130 account_id = journal.default_credit_account_id or journal.default_debit_account_id
131 if account_id and account_id.tax_ids:
132 tax_id = account_id.tax_ids[0].id
136 def _get_payment_rate_currency(self, cr, uid, context=None):
138 Return the default value for field payment_rate_currency_id: the currency of the journal
139 if there is one, otherwise the currency of the user's company
141 if context is None: context = {}
142 journal_pool = self.pool.get('account.journal')
143 journal_id = context.get('journal_id', False)
145 journal = journal_pool.browse(cr, uid, journal_id, context=context)
147 return journal.currency.id
148 #no journal given in the context, use company currency as default
149 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
151 def _get_currency(self, cr, uid, context=None):
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
161 def _get_partner(self, cr, uid, context=None):
162 if context is None: context = {}
163 return context.get('partner_id', False)
165 def _get_reference(self, cr, uid, context=None):
166 if context is None: context = {}
167 return context.get('reference', False)
169 def _get_narration(self, cr, uid, context=None):
170 if context is None: context = {}
171 return context.get('narration', False)
173 def _get_amount(self, cr, uid, context=None):
176 return context.get('amount', 0.0)
178 def name_get(self, cr, uid, ids, context=None):
181 if context is None: context = {}
182 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
184 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
185 mod_obj = self.pool.get('ir.model.data')
186 if context is None: context = {}
188 if view_type == 'form':
189 if not view_id and context.get('invoice_type'):
190 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
191 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
193 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
194 result = result and result[1] or False
196 if not view_id and context.get('line_type'):
197 if context.get('line_type') == 'customer':
198 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
200 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
201 result = result and result[1] or False
204 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
205 doc = etree.XML(res['arch'])
207 if context.get('type', 'sale') in ('purchase', 'payment'):
208 nodes = doc.xpath("//field[@name='partner_id']")
210 node.set('context', "{'search_default_supplier': 1}")
211 if context.get('invoice_type','') in ('in_invoice', 'in_refund'):
212 node.set('string', _("Supplier"))
213 res['arch'] = etree.tostring(doc)
216 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
218 sign = type == 'payment' and -1 or 1
219 for l in line_dr_ids:
221 for l in line_cr_ids:
222 credit += l['amount']
223 return amount - sign * (credit - debit)
225 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
226 context = context or {}
227 if not line_dr_ids and not line_cr_ids:
229 line_osv = self.pool.get("account.voucher.line")
230 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
231 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
233 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
234 is_multi_currency = False
236 # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
237 is_multi_currency = True
239 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
240 for voucher_line in line_dr_ids+line_cr_ids:
241 company_currency = False
242 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
243 if voucher_line.get('currency_id', company_currency) != company_currency:
244 is_multi_currency = True
246 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
248 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
249 if not ids: return {}
250 currency_obj = self.pool.get('res.currency')
253 for voucher in self.browse(cr, uid, ids, context=context):
254 sign = voucher.type == 'payment' and -1 or 1
255 for l in voucher.line_dr_ids:
257 for l in voucher.line_cr_ids:
259 currency = voucher.currency_id or voucher.company_id.currency_id
260 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
263 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
264 if not ids: return {}
267 for voucher in self.browse(cr, uid, ids, context=context):
268 if voucher.currency_id:
269 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
270 rate = 1 / voucher.payment_rate
273 ctx.update({'date': voucher.date})
274 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
275 company_currency_rate = voucher.company_id.currency_id.rate
276 rate = voucher_rate * company_currency_rate
277 res[voucher.id] = voucher.amount / rate
280 _name = 'account.voucher'
281 _description = 'Accounting Voucher'
282 _inherit = ['mail.thread']
283 _order = "date desc, id desc"
284 # _rec_name = 'number'
287 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
292 '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."),
293 'type':fields.selection([
295 ('purchase','Purchase'),
296 ('payment','Payment'),
297 ('receipt','Receipt'),
298 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
299 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
300 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
301 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
302 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
303 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
304 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
305 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
306 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
307 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
308 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
309 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
310 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
311 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
312 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
313 'state':fields.selection(
315 ('cancel','Cancelled'),
316 ('proforma','Pro-forma'),
318 ], 'Status', readonly=True, size=32, track_visibility='onchange',
319 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
320 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
321 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
322 \n* The \'Cancelled\' status is used when user cancel voucher.'),
323 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
324 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
325 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
326 'number': fields.char('Number', size=32, readonly=True,),
327 'move_id':fields.many2one('account.move', 'Account Entry'),
328 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
329 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
330 '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'),
331 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
332 'pay_now':fields.selection([
333 ('pay_now','Pay Directly'),
334 ('pay_later','Pay Later or Group Funds'),
335 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
336 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
337 'pre_line':fields.boolean('Previous Payments ?', required=False),
338 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
339 'payment_option':fields.selection([
340 ('without_writeoff', 'Keep Open'),
341 ('with_writeoff', 'Reconcile Payment Balance'),
342 ], '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)"),
343 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
344 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
345 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
346 '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."),
347 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
348 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
349 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
350 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
351 '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'),
355 'period_id': _get_period,
356 'partner_id': _get_partner,
357 'journal_id':_get_journal,
358 'currency_id': _get_currency,
359 'reference': _get_reference,
360 'narration':_get_narration,
361 'amount': _get_amount,
364 'pay_now': 'pay_now',
366 'date': lambda *a: time.strftime('%Y-%m-%d'),
367 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
369 'payment_option': 'without_writeoff',
370 'comment': _('Write-Off'),
372 'payment_rate_currency_id': _get_payment_rate_currency,
375 def compute_tax(self, cr, uid, ids, context=None):
376 tax_pool = self.pool.get('account.tax')
377 partner_pool = self.pool.get('res.partner')
378 position_pool = self.pool.get('account.fiscal.position')
379 voucher_line_pool = self.pool.get('account.voucher.line')
380 voucher_pool = self.pool.get('account.voucher')
381 if context is None: context = {}
383 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
385 for line in voucher.line_ids:
386 voucher_amount += line.untax_amount or line.amount
387 line.amount = line.untax_amount or line.amount
388 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
390 if not voucher.tax_id:
391 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
394 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
395 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
396 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
397 tax = tax_pool.browse(cr, uid, taxes, context=context)
399 total = voucher_amount
402 if not tax[0].price_include:
403 for line in voucher.line_ids:
404 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
405 total_tax += tax_line.get('amount', 0.0)
408 for line in voucher.line_ids:
412 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
413 line_tax += tax_line.get('amount', 0.0)
414 line_total += tax_line.get('price_unit')
415 total_tax += line_tax
416 untax_amount = line.untax_amount or line.amount
417 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
419 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
422 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
423 context = context or {}
424 tax_pool = self.pool.get('account.tax')
425 partner_pool = self.pool.get('res.partner')
426 position_pool = self.pool.get('account.fiscal.position')
427 line_pool = self.pool.get('account.voucher.line')
434 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
437 for line in line_ids:
439 line_amount = line.get('amount',0.0)
442 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
444 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
445 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
446 tax = tax_pool.browse(cr, uid, taxes, context=context)
448 if not tax[0].price_include:
449 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
450 total_tax += tax_line.get('amount')
452 voucher_total += line_amount
453 total = voucher_total + total_tax
456 'amount': total or voucher_total,
457 'tax_amount': total_tax
463 def onchange_term_id(self, cr, uid, ids, term_id, amount):
464 term_pool = self.pool.get('account.payment.term')
467 default = {'date_due':False}
468 if term_id and amount:
469 terms = term_pool.compute(cr, uid, term_id, amount)
471 due_date = terms[-1][0]
475 return {'value':default}
477 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):
479 Returns a dict that contains new values and context
481 @param partner_id: latest value from user input for field partner_id
482 @param args: other arguments
483 @param context: context arguments, like lang, time zone
485 @return: Returns a dict which contains new values, and context
491 if not partner_id or not journal_id:
494 partner_pool = self.pool.get('res.partner')
495 journal_pool = self.pool.get('account.journal')
497 journal = journal_pool.browse(cr, uid, journal_id, context=context)
498 partner = partner_pool.browse(cr, uid, partner_id, context=context)
501 if journal.type in ('sale','sale_refund'):
502 account_id = partner.property_account_receivable.id
504 elif journal.type in ('purchase', 'purchase_refund','expense'):
505 account_id = partner.property_account_payable.id
508 if not journal.default_credit_account_id or not journal.default_debit_account_id:
509 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
510 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
513 default['value']['account_id'] = account_id
514 default['value']['type'] = ttype or tr_type
516 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)
517 default['value'].update(vals.get('value'))
521 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
522 res = {'value': {'paid_amount_in_company_currency': amount}}
523 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
524 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
525 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
526 if company_currency.id == payment_rate_currency_id:
529 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
530 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
533 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):
536 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
538 ctx.update({'date': date})
539 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
540 for key in vals.keys():
541 res[key].update(vals[key])
544 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
547 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
548 currency_obj = self.pool.get('res.currency')
549 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
550 company_id = journal.company_id.id
552 payment_rate_currency_id = currency_id
554 ctx.update({'date': date})
556 if ttype == 'receipt':
557 o2m_to_loop = 'line_cr_ids'
558 elif ttype == 'payment':
559 o2m_to_loop = 'line_dr_ids'
560 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
561 for voucher_line in vals['value'][o2m_to_loop]:
562 if voucher_line['currency_id'] != currency_id:
563 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
564 # is not in the voucher currency
565 payment_rate_currency_id = voucher_line['currency_id']
566 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
567 voucher_currency_id = currency_id or journal.company_id.currency_id.id
568 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
570 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
571 for key in res.keys():
572 vals[key].update(res[key])
573 vals['value'].update({'payment_rate': payment_rate})
574 if payment_rate_currency_id:
575 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
578 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
581 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
582 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
583 for key in vals.keys():
584 res[key].update(vals[key])
585 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
586 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
587 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
588 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
589 # onchange returns a value for them
591 del(res['value']['line_dr_ids'])
592 del(res['value']['pre_line'])
593 del(res['value']['payment_rate'])
594 elif ttype == 'purchase':
595 del(res['value']['line_cr_ids'])
596 del(res['value']['pre_line'])
597 del(res['value']['payment_rate'])
600 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
602 Returns a dict that contains new values and context
604 @param partner_id: latest value from user input for field partner_id
605 @param args: other arguments
606 @param context: context arguments, like lang, time zone
608 @return: Returns a dict which contains new values, and context
610 def _remove_noise_in_o2m():
611 """if the line is partially reconciled, then we must pay attention to display it only once and
613 This function returns True if the line is considered as noise and should not be displayed
615 if line.reconcile_partial_id:
616 if currency_id == line.currency_id.id:
617 if line.amount_residual_currency <= 0:
620 if line.amount_residual <= 0:
626 context_multi_currency = context.copy()
628 context_multi_currency.update({'date': date})
630 currency_pool = self.pool.get('res.currency')
631 move_line_pool = self.pool.get('account.move.line')
632 partner_pool = self.pool.get('res.partner')
633 journal_pool = self.pool.get('account.journal')
634 line_pool = self.pool.get('account.voucher.line')
638 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
642 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
644 line_pool.unlink(cr, uid, line_ids)
646 if not partner_id or not journal_id:
649 journal = journal_pool.browse(cr, uid, journal_id, context=context)
650 partner = partner_pool.browse(cr, uid, partner_id, context=context)
651 currency_id = currency_id or journal.company_id.currency_id.id
653 if journal.type in ('sale','sale_refund'):
654 account_id = partner.property_account_receivable.id
655 elif journal.type in ('purchase', 'purchase_refund','expense'):
656 account_id = partner.property_account_payable.id
658 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
660 default['value']['account_id'] = account_id
662 if journal.type not in ('cash', 'bank'):
667 account_type = 'receivable'
668 if ttype == 'payment':
669 account_type = 'payable'
670 total_debit = price or 0.0
672 total_credit = price or 0.0
673 account_type = 'receivable'
675 if not context.get('move_line_ids', False):
676 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
678 ids = context['move_line_ids']
679 invoice_id = context.get('invoice_id', False)
680 company_currency = journal.company_id.currency_id.id
681 move_line_found = False
683 #order the lines by most old first
685 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
687 #compute the total debit/credit and look for a matching open amount or invoice
688 for line in account_move_lines:
689 if _remove_noise_in_o2m():
693 if line.invoice.id == invoice_id:
694 #if the invoice linked to the voucher line is equal to the invoice_id in context
695 #then we assign the amount on that line, whatever the other voucher lines
696 move_line_found = line.id
698 elif currency_id == company_currency:
699 #otherwise treatments is the same but with other field names
700 if line.amount_residual == price:
701 #if the amount residual is equal the amount voucher, we assign it to that voucher
702 #line, whatever the other voucher lines
703 move_line_found = line.id
705 #otherwise we will split the voucher amount on each line (by most old first)
706 total_credit += line.credit or 0.0
707 total_debit += line.debit or 0.0
708 elif currency_id == line.currency_id.id:
709 if line.amount_residual_currency == price:
710 move_line_found = line.id
712 total_credit += line.credit and line.amount_currency or 0.0
713 total_debit += line.debit and line.amount_currency or 0.0
715 #voucher line creation
716 for line in account_move_lines:
718 if _remove_noise_in_o2m():
721 if line.currency_id and currency_id==line.currency_id.id:
722 amount_original = abs(line.amount_currency)
723 amount_unreconciled = abs(line.amount_residual_currency)
725 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
726 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
727 line_currency_id = line.currency_id and line.currency_id.id or company_currency
729 'name':line.move_id.name,
730 'type': line.credit and 'dr' or 'cr',
731 'move_line_id':line.id,
732 'account_id':line.account_id.id,
733 'amount_original': amount_original,
734 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
735 'date_original':line.date,
736 'date_due':line.date_maturity,
737 'amount_unreconciled': amount_unreconciled,
738 'currency_id': line_currency_id,
740 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
741 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
742 if not move_line_found:
743 if currency_id == line_currency_id:
745 amount = min(amount_unreconciled, abs(total_debit))
746 rs['amount'] = amount
747 total_debit -= amount
749 amount = min(amount_unreconciled, abs(total_credit))
750 rs['amount'] = amount
751 total_credit -= amount
753 if rs['amount_unreconciled'] == rs['amount']:
754 rs['reconcile'] = True
756 if rs['type'] == 'cr':
757 default['value']['line_cr_ids'].append(rs)
759 default['value']['line_dr_ids'].append(rs)
761 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
762 default['value']['pre_line'] = 1
763 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
764 default['value']['pre_line'] = 1
765 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
768 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
772 #set the default payment rate of the voucher and compute the paid amount in company currency
773 if currency_id and currency_id == payment_rate_currency_id:
775 ctx.update({'date': date})
776 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
777 for key in vals.keys():
778 res[key].update(vals[key])
781 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
783 @param date: latest value from user input for field date
784 @param args: other arguments
785 @param context: context arguments, like lang, time zone
786 @return: Returns a dict which contains new values, and context
791 #set the period of the voucher
792 period_pool = self.pool.get('account.period')
793 currency_obj = self.pool.get('res.currency')
795 ctx.update({'company_id': company_id, 'account_period_prefer_normal': True})
796 pids = period_pool.find(cr, uid, date, context=ctx)
798 res['value'].update({'period_id':pids[0]})
799 if payment_rate_currency_id:
800 ctx.update({'date': date})
802 if payment_rate_currency_id != currency_id:
803 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
804 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
805 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
806 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
807 vals['value'].update({'payment_rate': payment_rate})
808 for key in vals.keys():
809 res[key].update(vals[key])
812 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
815 journal_pool = self.pool.get('account.journal')
816 journal = journal_pool.browse(cr, uid, journal_id, context=context)
817 account_id = journal.default_credit_account_id or journal.default_debit_account_id
819 if account_id and account_id.tax_ids:
820 tax_id = account_id.tax_ids[0].id
823 if ttype in ('sale', 'purchase'):
824 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
825 vals['value'].update({'tax_id':tax_id,'amount': amount})
828 currency_id = journal.currency.id
830 currency_id = journal.company_id.currency_id.id
831 vals['value'].update({'currency_id': currency_id})
832 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
833 for key in res.keys():
834 vals[key].update(res[key])
837 def button_proforma_voucher(self, cr, uid, ids, context=None):
838 context = context or {}
839 wf_service = netsvc.LocalService("workflow")
841 wf_service.trg_validate(uid, 'account.voucher', vid, 'proforma_voucher', cr)
842 return {'type': 'ir.actions.act_window_close'}
844 def proforma_voucher(self, cr, uid, ids, context=None):
845 self.action_move_line_create(cr, uid, ids, context=context)
848 def action_cancel_draft(self, cr, uid, ids, context=None):
849 wf_service = netsvc.LocalService("workflow")
850 for voucher_id in ids:
851 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
852 self.write(cr, uid, ids, {'state':'draft'})
855 def cancel_voucher(self, cr, uid, ids, context=None):
856 reconcile_pool = self.pool.get('account.move.reconcile')
857 move_pool = self.pool.get('account.move')
859 for voucher in self.browse(cr, uid, ids, context=context):
861 for line in voucher.move_ids:
862 if line.reconcile_id:
863 recs += [line.reconcile_id.id]
864 if line.reconcile_partial_id:
865 recs += [line.reconcile_partial_id.id]
867 reconcile_pool.unlink(cr, uid, recs)
870 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
871 move_pool.unlink(cr, uid, [voucher.move_id.id])
876 self.write(cr, uid, ids, res)
879 def unlink(self, cr, uid, ids, context=None):
880 for t in self.read(cr, uid, ids, ['state'], context=context):
881 if t['state'] not in ('draft', 'cancel'):
882 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
883 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
885 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
889 res = {'account_id':False}
890 partner_pool = self.pool.get('res.partner')
891 journal_pool = self.pool.get('account.journal')
892 if pay_now == 'pay_later':
893 partner = partner_pool.browse(cr, uid, partner_id)
894 journal = journal_pool.browse(cr, uid, journal_id)
895 if journal.type in ('sale','sale_refund'):
896 account_id = partner.property_account_receivable.id
897 elif journal.type in ('purchase', 'purchase_refund','expense'):
898 account_id = partner.property_account_payable.id
900 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
901 res['account_id'] = account_id
904 def _sel_context(self, cr, uid, voucher_id, context=None):
906 Select the context to use accordingly if it needs to be multicurrency or not.
908 :param voucher_id: Id of the actual voucher
909 :return: The returned context will be the same as given in parameter if the voucher currency is the same
910 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
911 the date of the voucher.
914 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
915 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
916 if current_currency <> company_currency:
917 context_multi_currency = context.copy()
918 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
919 context_multi_currency.update({'date': voucher_brw.date})
920 return context_multi_currency
923 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
925 Return a dict to be use to create the first account move line of given voucher.
927 :param voucher_id: Id of voucher what we are creating account_move.
928 :param move_id: Id of account move where this line will be added.
929 :param company_currency: id of currency of the company to which the voucher belong
930 :param current_currency: id of currency of the voucher
931 :return: mapping between fieldname and value of account move line to create
934 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
936 # TODO: is there any other alternative then the voucher type ??
937 # ANSWER: We can have payment and receipt "In Advance".
938 # TODO: Make this logic available.
939 # -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
940 if voucher_brw.type in ('purchase', 'payment'):
941 credit = voucher_brw.paid_amount_in_company_currency
942 elif voucher_brw.type in ('sale', 'receipt'):
943 debit = voucher_brw.paid_amount_in_company_currency
944 if debit < 0: credit = -debit; debit = 0.0
945 if credit < 0: debit = -credit; credit = 0.0
946 sign = debit - credit < 0 and -1 or 1
947 #set the first line of the voucher
949 'name': voucher_brw.name or '/',
952 'account_id': voucher_brw.account_id.id,
954 'journal_id': voucher_brw.journal_id.id,
955 'period_id': voucher_brw.period_id.id,
956 'partner_id': voucher_brw.partner_id.id,
957 'currency_id': company_currency <> current_currency and current_currency or False,
958 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
959 'date': voucher_brw.date,
960 'date_maturity': voucher_brw.date_due
964 def account_move_get(self, cr, uid, voucher_id, context=None):
966 This method prepare the creation of the account move related to the given voucher.
968 :param voucher_id: Id of voucher for which we are creating account_move.
969 :return: mapping between fieldname and value of account move to create
972 seq_obj = self.pool.get('ir.sequence')
973 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
974 if voucher_brw.number:
975 name = voucher_brw.number
976 elif voucher_brw.journal_id.sequence_id:
977 if not voucher_brw.journal_id.sequence_id.active:
978 raise osv.except_osv(_('Configuration Error !'),
979 _('Please activate the sequence of selected journal !'))
981 c.update({'fiscalyear_id': voucher_brw.period_id.fiscalyear_id.id})
982 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=c)
984 raise osv.except_osv(_('Error!'),
985 _('Please define a sequence on the journal.'))
986 if not voucher_brw.reference:
987 ref = name.replace('/','')
989 ref = voucher_brw.reference
993 'journal_id': voucher_brw.journal_id.id,
994 'narration': voucher_brw.narration,
995 'date': voucher_brw.date,
997 'period_id': voucher_brw.period_id.id,
1001 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
1003 Prepare the two lines in company currency due to currency rate difference.
1005 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
1007 :param move_id: Account move wher the move lines will be.
1008 :param amount_residual: Amount to be posted.
1009 :param company_currency: id of currency of the company to which the voucher belong
1010 :param current_currency: id of currency of the voucher
1011 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
1012 :rtype: tuple of dict
1014 if amount_residual > 0:
1015 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1017 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."))
1019 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1021 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."))
1022 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1023 # the receivable/payable account may have a secondary currency, which render this field mandatory
1024 account_currency_id = company_currency <> current_currency and current_currency or False
1026 'journal_id': line.voucher_id.journal_id.id,
1027 'period_id': line.voucher_id.period_id.id,
1028 'name': _('change')+': '+(line.name or '/'),
1029 'account_id': line.account_id.id,
1031 'partner_id': line.voucher_id.partner_id.id,
1032 'currency_id': account_currency_id,
1033 'amount_currency': 0.0,
1035 'credit': amount_residual > 0 and amount_residual or 0.0,
1036 'debit': amount_residual < 0 and -amount_residual or 0.0,
1037 'date': line.voucher_id.date,
1039 move_line_counterpart = {
1040 'journal_id': line.voucher_id.journal_id.id,
1041 'period_id': line.voucher_id.period_id.id,
1042 'name': _('change')+': '+(line.name or '/'),
1043 'account_id': account_id.id,
1045 'amount_currency': 0.0,
1046 'partner_id': line.voucher_id.partner_id.id,
1047 'currency_id': account_currency_id,
1049 'debit': amount_residual > 0 and amount_residual or 0.0,
1050 'credit': amount_residual < 0 and -amount_residual or 0.0,
1051 'date': line.voucher_id.date,
1053 return (move_line, move_line_counterpart)
1055 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1057 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1058 payment_rate_currency_id is relevant) either the rate encoded in the system.
1060 :param amount: float. The amount to convert
1061 :param voucher: id of the voucher on which we want the conversion
1062 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1063 field in order to select the good rate to use.
1064 :return: the amount in the currency of the voucher's company
1067 currency_obj = self.pool.get('res.currency')
1068 voucher = self.browse(cr, uid, voucher_id, context=context)
1070 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1071 # the rate specified on the voucher is for the company currency
1072 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1074 # the rate specified on the voucher is not relevant, we use all the rates in the system
1075 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1078 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1080 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1081 It returns Tuple with tot_line what is total of difference between debit and credit and
1082 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1084 :param voucher_id: Voucher id what we are working with
1085 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1086 :param move_id: Account move wher those lines will be joined.
1087 :param company_currency: id of currency of the company to which the voucher belong
1088 :param current_currency: id of currency of the voucher
1089 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1090 :rtype: tuple(float, list of int)
1094 move_line_obj = self.pool.get('account.move.line')
1095 currency_obj = self.pool.get('res.currency')
1096 tax_obj = self.pool.get('account.tax')
1097 tot_line = line_total
1100 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1101 ctx = context.copy()
1102 ctx.update({'date': voucher_brw.date})
1103 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1104 for line in voucher_brw.line_ids:
1105 #create one move line per voucher line where amount is not 0.0
1106 # AND (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
1107 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)):
1109 # convert the amount set on the voucher line into the currency of the voucher's company
1110 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1111 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1112 # currency rate difference
1113 if line.amount == line.amount_unreconciled:
1114 if not line.move_line_id:
1115 raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1116 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1117 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1119 currency_rate_difference = 0.0
1121 'journal_id': voucher_brw.journal_id.id,
1122 'period_id': voucher_brw.period_id.id,
1123 'name': line.name or '/',
1124 'account_id': line.account_id.id,
1126 'partner_id': voucher_brw.partner_id.id,
1127 '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,
1128 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1132 'date': voucher_brw.date
1136 if line.type == 'dr':
1141 if (line.type=='dr'):
1143 move_line['debit'] = amount
1146 move_line['credit'] = amount
1148 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1150 'account_tax_id': voucher_brw.tax_id.id,
1153 if move_line.get('account_tax_id', False):
1154 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1155 if not (tax_data.base_code_id and tax_data.tax_code_id):
1156 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))
1158 # compute the amount in foreign currency
1159 foreign_currency_diff = 0.0
1160 amount_currency = False
1161 if line.move_line_id:
1162 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1163 # We want to set it on the account move line as soon as the original line had a foreign currency
1164 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1165 # we compute the amount in that foreign currency.
1166 if line.move_line_id.currency_id.id == current_currency:
1167 # if the voucher and the voucher line share the same currency, there is no computation to do
1168 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1169 amount_currency = sign * (line.amount)
1170 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1171 # if the rate is specified on the voucher, we must use it
1172 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1173 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1175 # otherwise we use the rates of the system (giving the voucher date in the context)
1176 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1177 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1178 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1179 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1181 move_line['amount_currency'] = amount_currency
1182 voucher_line = move_line_obj.create(cr, uid, move_line)
1183 rec_ids = [voucher_line, line.move_line_id.id]
1185 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1186 # Change difference entry in company currency
1187 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1188 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1189 move_line_obj.create(cr, uid, exch_lines[1], context)
1190 rec_ids.append(new_id)
1192 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):
1193 # Change difference entry in voucher currency
1194 move_line_foreign_currency = {
1195 'journal_id': line.voucher_id.journal_id.id,
1196 'period_id': line.voucher_id.period_id.id,
1197 'name': _('change')+': '+(line.name or '/'),
1198 'account_id': line.account_id.id,
1200 'partner_id': line.voucher_id.partner_id.id,
1201 'currency_id': line.move_line_id.currency_id.id,
1202 'amount_currency': -1 * foreign_currency_diff,
1206 'date': line.voucher_id.date,
1208 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1209 rec_ids.append(new_id)
1211 if line.move_line_id.id:
1212 rec_lst_ids.append(rec_ids)
1214 return (tot_line, rec_lst_ids)
1216 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1218 Set a dict to be use to create the writeoff move line.
1220 :param voucher_id: Id of voucher what we are creating account_move.
1221 :param line_total: Amount remaining to be allocated on lines.
1222 :param move_id: Id of account move where this line will be added.
1223 :param name: Description of account move line.
1224 :param company_currency: id of currency of the company to which the voucher belong
1225 :param current_currency: id of currency of the voucher
1226 :return: mapping between fieldname and value of account move line to create
1229 currency_obj = self.pool.get('res.currency')
1232 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1233 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1235 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1239 if voucher_brw.payment_option == 'with_writeoff':
1240 account_id = voucher_brw.writeoff_acc_id.id
1241 write_off_name = voucher_brw.comment
1242 elif voucher_brw.type in ('sale', 'receipt'):
1243 account_id = voucher_brw.partner_id.property_account_receivable.id
1245 account_id = voucher_brw.partner_id.property_account_payable.id
1246 sign = voucher_brw.type == 'payment' and -1 or 1
1248 'name': write_off_name or name,
1249 'account_id': account_id,
1251 'partner_id': voucher_brw.partner_id.id,
1252 'date': voucher_brw.date,
1253 'credit': diff > 0 and diff or 0.0,
1254 'debit': diff < 0 and -diff or 0.0,
1255 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1256 'currency_id': company_currency <> current_currency and current_currency or False,
1257 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1262 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1264 Get the currency of the actual company.
1266 :param voucher_id: Id of the voucher what i want to obtain company currency.
1267 :return: currency id of the company of the voucher
1270 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1272 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1274 Get the currency of the voucher.
1276 :param voucher_id: Id of the voucher what i want to obtain current currency.
1277 :return: currency id of the voucher
1280 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1281 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1283 def action_move_line_create(self, cr, uid, ids, context=None):
1285 Confirm the vouchers given in ids and create the journal entries for each of them
1289 move_pool = self.pool.get('account.move')
1290 move_line_pool = self.pool.get('account.move.line')
1291 for voucher in self.browse(cr, uid, ids, context=context):
1294 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1295 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1296 # we select the context to use accordingly if it's a multicurrency case or not
1297 context = self._sel_context(cr, uid, voucher.id, context)
1298 # But for the operations made by _convert_amount, we always need to give the date in the context
1299 ctx = context.copy()
1300 ctx.update({'date': voucher.date})
1301 # Create the account move record.
1302 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1303 # Get the name of the account_move just created
1304 name = move_pool.browse(cr, uid, move_id, context=context).name
1305 # Create the first line of the voucher
1306 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)
1307 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1308 line_total = move_line_brw.debit - move_line_brw.credit
1310 if voucher.type == 'sale':
1311 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1312 elif voucher.type == 'purchase':
1313 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1314 # Create one move line per voucher line where amount is not 0.0
1315 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1317 # Create the writeoff line if needed
1318 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1320 move_line_pool.create(cr, uid, ml_writeoff, context)
1321 # We post the voucher.
1322 self.write(cr, uid, [voucher.id], {
1327 if voucher.journal_id.entry_posted:
1328 move_pool.post(cr, uid, [move_id], context={})
1329 # We automatically reconcile the account move lines.
1331 for rec_ids in rec_list_ids:
1332 if len(rec_ids) >= 2:
1333 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)
1336 def copy(self, cr, uid, id, default=None, context=None):
1343 'line_cr_ids': False,
1344 'line_dr_ids': False,
1347 if 'date' not in default:
1348 default['date'] = time.strftime('%Y-%m-%d')
1349 return super(account_voucher, self).copy(cr, uid, id, default, context)
1352 class account_voucher_line(osv.osv):
1353 _name = 'account.voucher.line'
1354 _description = 'Voucher Lines'
1355 _order = "move_line_id"
1357 # If the payment is in the same currency than the invoice, we keep the same amount
1358 # Otherwise, we compute from company currency to payment currency
1359 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1360 currency_pool = self.pool.get('res.currency')
1362 for line in self.browse(cr, uid, ids, context=context):
1363 ctx = context.copy()
1364 ctx.update({'date': line.voucher_id.date})
1366 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1367 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1368 move_line = line.move_line_id or False
1371 res['amount_original'] = 0.0
1372 res['amount_unreconciled'] = 0.0
1373 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1374 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1375 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)
1376 elif move_line and move_line.credit > 0:
1377 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1378 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1380 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1381 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1383 rs_data[line.id] = res
1386 def _currency_id(self, cr, uid, ids, name, args, context=None):
1388 This function returns the currency id of a voucher line. It's either the currency of the
1389 associated move line (if any) or the currency of the voucher or the company currency.
1392 for line in self.browse(cr, uid, ids, context=context):
1393 move_line = line.move_line_id
1395 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1397 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1401 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1402 'name':fields.char('Description', size=256),
1403 'account_id':fields.many2one('account.account','Account', required=True),
1404 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1405 'untax_amount':fields.float('Untax Amount'),
1406 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1407 'reconcile': fields.boolean('Full Reconcile'),
1408 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1409 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1410 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1411 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1412 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1413 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1414 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1415 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1416 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1422 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1423 vals = {'amount': 0.0}
1425 vals = { 'amount': amount_unreconciled}
1426 return {'value': vals}
1428 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1431 vals['reconcile'] = (amount == amount_unreconciled)
1432 return {'value': vals}
1434 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1436 Returns a dict that contains new values and context
1438 @param move_line_id: latest value from user input for field move_line_id
1439 @param args: other arguments
1440 @param context: context arguments, like lang, time zone
1442 @return: Returns a dict which contains new values, and context
1445 move_line_pool = self.pool.get('account.move.line')
1447 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1448 if move_line.credit:
1453 'account_id': move_line.account_id.id,
1455 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1461 def default_get(self, cr, user, fields_list, context=None):
1463 Returns default values for fields
1464 @param fields_list: list of fields, for which default values are required to be read
1465 @param context: context arguments, like lang, time zone
1467 @return: Returns a dict that contains default values for fields
1471 journal_id = context.get('journal_id', False)
1472 partner_id = context.get('partner_id', False)
1473 journal_pool = self.pool.get('account.journal')
1474 partner_pool = self.pool.get('res.partner')
1475 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1476 if (not journal_id) or ('account_id' not in fields_list):
1478 journal = journal_pool.browse(cr, user, journal_id, context=context)
1481 if journal.type in ('sale', 'sale_refund'):
1482 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1484 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1485 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1488 partner = partner_pool.browse(cr, user, partner_id, context=context)
1489 if context.get('type') == 'payment':
1491 account_id = partner.property_account_payable.id
1492 elif context.get('type') == 'receipt':
1493 account_id = partner.property_account_receivable.id
1496 'account_id':account_id,
1500 account_voucher_line()
1502 class account_bank_statement(osv.osv):
1503 _inherit = 'account.bank.statement'
1505 def button_confirm_bank(self, cr, uid, ids, context=None):
1506 voucher_obj = self.pool.get('account.voucher')
1508 for statement in self.browse(cr, uid, ids, context=context):
1509 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1511 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1512 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1514 def button_cancel(self, cr, uid, ids, context=None):
1515 voucher_obj = self.pool.get('account.voucher')
1516 for st in self.browse(cr, uid, ids, context=context):
1518 for line in st.line_ids:
1520 voucher_ids.append(line.voucher_id.id)
1521 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1522 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1524 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1525 voucher_obj = self.pool.get('account.voucher')
1526 wf_service = netsvc.LocalService("workflow")
1527 move_line_obj = self.pool.get('account.move.line')
1528 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1529 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1530 if st_line.voucher_id:
1531 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1532 if st_line.voucher_id.state == 'cancel':
1533 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1534 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1536 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1537 bank_st_line_obj.write(cr, uid, [st_line_id], {
1538 'move_ids': [(4, v.move_id.id, False)]
1541 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1542 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1544 def write(self, cr, uid, ids, vals, context=None):
1545 # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1546 # Because the voucher keeps in memory the journal it was created with.
1547 for bk_st in self.browse(cr, uid, ids, context=context):
1548 if vals.get('journal_id') and bk_st.line_ids:
1549 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1550 raise osv.except_osv(_('Unable to change journal !'), _('You can not change the journal as you already reconciled some statement lines!'))
1551 return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1553 account_bank_statement()
1555 class account_bank_statement_line(osv.osv):
1556 _inherit = 'account.bank.statement.line'
1558 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1559 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1560 if 'value' not in res:
1562 res['value'].update({'voucher_id' : False})
1565 def onchange_amount(self, cr, uid, ids, amount, context=None):
1566 return {'value' : {'voucher_id' : False}}
1568 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1572 for line in self.browse(cursor, user, ids, context=context):
1574 res[line.id] = line.voucher_id.amount#
1579 def _check_amount(self, cr, uid, ids, context=None):
1580 for obj in self.browse(cr, uid, ids, context=context):
1582 diff = abs(obj.amount) - obj.voucher_id.amount
1583 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1588 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1592 'amount_reconciled': fields.function(_amount_reconciled,
1593 string='Amount reconciled', type='float'),
1594 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1597 def unlink(self, cr, uid, ids, context=None):
1598 voucher_obj = self.pool.get('account.voucher')
1599 statement_line = self.browse(cr, uid, ids, context=context)
1601 for st_line in statement_line:
1602 if st_line.voucher_id:
1603 unlink_ids.append(st_line.voucher_id.id)
1604 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1605 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1607 account_bank_statement_line()
1609 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1611 for operation in operations:
1613 if not isinstance(operation, (list, tuple)):
1614 result = target_osv.read(cr, uid, operation, fields, context=context)
1615 elif operation[0] == 0:
1616 # may be necessary to check if all the fields are here and get the default values?
1617 result = operation[2]
1618 elif operation[0] == 1:
1619 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1620 if not result: result = {}
1621 result.update(operation[2])
1622 elif operation[0] == 4:
1623 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1625 results.append(result)
1629 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: