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')]",),
44 class account_config_settings(osv.osv_memory):
45 _inherit = 'account.config.settings'
47 'income_currency_exchange_account_id': fields.related(
48 'company_id', 'income_currency_exchange_account_id',
50 relation='account.account',
51 string="Gain Exchange Rate Account",
52 domain="[('type', '=', 'other')]"),
53 'expense_currency_exchange_account_id': fields.related(
54 'company_id', 'expense_currency_exchange_account_id',
56 relation='account.account',
57 string="Loss Exchange Rate Account",
58 domain="[('type', '=', 'other')]"),
60 def onchange_company_id(self, cr, uid, ids, company_id, context=None):
61 res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id, context=context)
63 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
64 res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False,
65 'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False})
67 res['value'].update({'income_currency_exchange_account_id': False,
68 'expense_currency_exchange_account_id': False})
71 class account_voucher(osv.osv):
72 def _check_paid(self, cr, uid, ids, name, args, context=None):
74 for voucher in self.browse(cr, uid, ids, context=context):
75 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
78 def _get_type(self, cr, uid, context=None):
81 return context.get('type', False)
83 def _get_period(self, cr, uid, context=None):
84 if context is None: context = {}
85 if context.get('period_id', False):
86 return context.get('period_id')
87 periods = self.pool.get('account.period').find(cr, uid, context=context)
88 return periods and periods[0] or False
90 def _make_journal_search(self, cr, uid, ttype, context=None):
91 journal_pool = self.pool.get('account.journal')
92 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
94 def _get_journal(self, cr, uid, context=None):
95 if context is None: context = {}
96 invoice_pool = self.pool.get('account.invoice')
97 journal_pool = self.pool.get('account.journal')
98 if context.get('invoice_id', False):
99 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
100 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
101 return journal_id and journal_id[0] or False
102 if context.get('journal_id', False):
103 return context.get('journal_id')
104 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
105 return context.get('search_default_journal_id')
107 ttype = context.get('type', 'bank')
108 if ttype in ('payment', 'receipt'):
110 res = self._make_journal_search(cr, uid, ttype, context=context)
111 return res and res[0] or False
113 def _get_tax(self, cr, uid, context=None):
114 if context is None: context = {}
115 journal_pool = self.pool.get('account.journal')
116 journal_id = context.get('journal_id', False)
118 ttype = context.get('type', 'bank')
119 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
126 journal = journal_pool.browse(cr, uid, journal_id, context=context)
127 account_id = journal.default_credit_account_id or journal.default_debit_account_id
128 if account_id and account_id.tax_ids:
129 tax_id = account_id.tax_ids[0].id
133 def _get_payment_rate_currency(self, cr, uid, context=None):
135 Return the default value for field payment_rate_currency_id: the currency of the journal
136 if there is one, otherwise the currency of the user's company
138 if context is None: context = {}
139 journal_pool = self.pool.get('account.journal')
140 journal_id = context.get('journal_id', False)
142 journal = journal_pool.browse(cr, uid, journal_id, context=context)
144 return journal.currency.id
145 #no journal given in the context, use company currency as default
146 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
148 def _get_currency(self, cr, uid, context=None):
149 if context is None: context = {}
150 journal_pool = self.pool.get('account.journal')
151 journal_id = context.get('journal_id', False)
153 journal = journal_pool.browse(cr, uid, journal_id, context=context)
155 return journal.currency.id
158 def _get_partner(self, cr, uid, context=None):
159 if context is None: context = {}
160 return context.get('partner_id', False)
162 def _get_reference(self, cr, uid, context=None):
163 if context is None: context = {}
164 return context.get('reference', False)
166 def _get_narration(self, cr, uid, context=None):
167 if context is None: context = {}
168 return context.get('narration', False)
170 def _get_amount(self, cr, uid, context=None):
173 return context.get('amount', 0.0)
175 def name_get(self, cr, uid, ids, context=None):
178 if context is None: context = {}
179 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
181 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
182 mod_obj = self.pool.get('ir.model.data')
183 if context is None: context = {}
185 if view_type == 'form':
186 if not view_id and context.get('invoice_type'):
187 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
188 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
190 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
191 result = result and result[1] or False
193 if not view_id and context.get('line_type'):
194 if context.get('line_type') == 'customer':
195 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
197 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
198 result = result and result[1] or False
201 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
202 doc = etree.XML(res['arch'])
204 if context.get('type', 'sale') in ('purchase', 'payment'):
205 nodes = doc.xpath("//field[@name='partner_id']")
207 node.set('context', "{'search_default_supplier': 1}")
208 if context.get('invoice_type','') in ('in_invoice', 'in_refund'):
209 node.set('string', _("Supplier"))
210 res['arch'] = etree.tostring(doc)
213 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
215 sign = type == 'payment' and -1 or 1
216 for l in line_dr_ids:
218 for l in line_cr_ids:
219 credit += l['amount']
220 return amount - sign * (credit - debit)
222 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
223 context = context or {}
224 if not line_dr_ids and not line_cr_ids:
225 return {'value':{'writeoff_amount': 0.0, 'is_multi_currency': False}}
226 line_osv = self.pool.get("account.voucher.line")
227 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
228 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
230 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
231 is_multi_currency = False
232 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to see the options
233 for voucher_line in line_dr_ids+line_cr_ids:
234 line_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).currency_id
236 is_multi_currency = True
238 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
240 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
241 if not ids: return {}
242 currency_obj = self.pool.get('res.currency')
245 for voucher in self.browse(cr, uid, ids, context=context):
246 sign = voucher.type == 'payment' and -1 or 1
247 for l in voucher.line_dr_ids:
249 for l in voucher.line_cr_ids:
251 currency = voucher.currency_id or voucher.company_id.currency_id
252 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
255 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
256 if not ids: return {}
259 for voucher in self.browse(cr, uid, ids, context=context):
260 if voucher.currency_id:
261 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
262 rate = 1 / voucher.payment_rate
265 ctx.update({'date': voucher.date})
266 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
267 company_currency_rate = voucher.company_id.currency_id.rate
268 rate = voucher_rate * company_currency_rate
269 res[voucher.id] = voucher.amount / rate
272 _name = 'account.voucher'
273 _description = 'Accounting Voucher'
274 _inherit = ['mail.thread']
275 _order = "date desc, id desc"
276 # _rec_name = 'number'
279 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
284 '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."),
285 'type':fields.selection([
287 ('purchase','Purchase'),
288 ('payment','Payment'),
289 ('receipt','Receipt'),
290 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
291 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
292 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
293 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
294 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
295 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
296 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
297 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
298 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
299 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
300 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
301 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
302 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
303 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
304 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
305 'state':fields.selection(
307 ('cancel','Cancelled'),
308 ('proforma','Pro-forma'),
310 ], 'Status', readonly=True, size=32, track_visibility='onchange',
311 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
312 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
313 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
314 \n* The \'Cancelled\' status is used when user cancel voucher.'),
315 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
316 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
317 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
318 'number': fields.char('Number', size=32, readonly=True,),
319 'move_id':fields.many2one('account.move', 'Account Entry'),
320 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
321 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
322 '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'),
323 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
324 'pay_now':fields.selection([
325 ('pay_now','Pay Directly'),
326 ('pay_later','Pay Later or Group Funds'),
327 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
328 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
329 'pre_line':fields.boolean('Previous Payments ?', required=False),
330 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
331 'payment_option':fields.selection([
332 ('without_writeoff', 'Keep Open'),
333 ('with_writeoff', 'Reconcile Payment Balance'),
334 ], '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)"),
335 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
336 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
337 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
338 '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."),
339 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
340 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
341 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
342 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
343 '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'),
347 'period_id': _get_period,
348 'partner_id': _get_partner,
349 'journal_id':_get_journal,
350 'currency_id': _get_currency,
351 'reference': _get_reference,
352 'narration':_get_narration,
353 'amount': _get_amount,
356 'pay_now': 'pay_now',
358 'date': lambda *a: time.strftime('%Y-%m-%d'),
359 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
361 'payment_option': 'without_writeoff',
362 'comment': _('Write-Off'),
364 'payment_rate_currency_id': _get_payment_rate_currency,
367 def compute_tax(self, cr, uid, ids, context=None):
368 tax_pool = self.pool.get('account.tax')
369 partner_pool = self.pool.get('res.partner')
370 position_pool = self.pool.get('account.fiscal.position')
371 voucher_line_pool = self.pool.get('account.voucher.line')
372 voucher_pool = self.pool.get('account.voucher')
373 if context is None: context = {}
375 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
377 for line in voucher.line_ids:
378 voucher_amount += line.untax_amount or line.amount
379 line.amount = line.untax_amount or line.amount
380 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
382 if not voucher.tax_id:
383 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
386 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
387 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
388 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
389 tax = tax_pool.browse(cr, uid, taxes, context=context)
391 total = voucher_amount
394 if not tax[0].price_include:
395 for line in voucher.line_ids:
396 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
397 total_tax += tax_line.get('amount', 0.0)
400 for line in voucher.line_ids:
404 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
405 line_tax += tax_line.get('amount', 0.0)
406 line_total += tax_line.get('price_unit')
407 total_tax += line_tax
408 untax_amount = line.untax_amount or line.amount
409 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
411 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
414 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
415 context = context or {}
416 tax_pool = self.pool.get('account.tax')
417 partner_pool = self.pool.get('res.partner')
418 position_pool = self.pool.get('account.fiscal.position')
419 line_pool = self.pool.get('account.voucher.line')
426 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
429 for line in line_ids:
431 line_amount = line.get('amount',0.0)
434 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
436 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
437 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
438 tax = tax_pool.browse(cr, uid, taxes, context=context)
440 if not tax[0].price_include:
441 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
442 total_tax += tax_line.get('amount')
444 voucher_total += line_amount
445 total = voucher_total + total_tax
448 'amount': total or voucher_total,
449 'tax_amount': total_tax
455 def onchange_term_id(self, cr, uid, ids, term_id, amount):
456 term_pool = self.pool.get('account.payment.term')
459 default = {'date_due':False}
460 if term_id and amount:
461 terms = term_pool.compute(cr, uid, term_id, amount)
463 due_date = terms[-1][0]
467 return {'value':default}
469 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):
471 Returns a dict that contains new values and context
473 @param partner_id: latest value from user input for field partner_id
474 @param args: other arguments
475 @param context: context arguments, like lang, time zone
477 @return: Returns a dict which contains new values, and context
483 if not partner_id or not journal_id:
486 partner_pool = self.pool.get('res.partner')
487 journal_pool = self.pool.get('account.journal')
489 journal = journal_pool.browse(cr, uid, journal_id, context=context)
490 partner = partner_pool.browse(cr, uid, partner_id, context=context)
493 if journal.type in ('sale','sale_refund'):
494 account_id = partner.property_account_receivable.id
496 elif journal.type in ('purchase', 'purchase_refund','expense'):
497 account_id = partner.property_account_payable.id
500 if not journal.default_credit_account_id or not journal.default_debit_account_id:
501 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
502 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
505 default['value']['account_id'] = account_id
506 default['value']['type'] = ttype or tr_type
508 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)
509 default['value'].update(vals.get('value'))
513 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
514 res = {'value': {'paid_amount_in_company_currency': amount}}
515 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
516 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
517 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
518 if company_currency.id == payment_rate_currency_id:
521 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
522 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
525 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):
528 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
530 ctx.update({'date': date})
531 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
532 for key in vals.keys():
533 res[key].update(vals[key])
536 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
539 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
540 currency_obj = self.pool.get('res.currency')
541 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
542 company_id = journal.company_id.id
544 payment_rate_currency_id = currency_id
546 ctx.update({'date': date})
548 if ttype == 'receipt':
549 o2m_to_loop = 'line_cr_ids'
550 elif ttype == 'payment':
551 o2m_to_loop = 'line_dr_ids'
552 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
553 for voucher_line in vals['value'][o2m_to_loop]:
554 if voucher_line['currency_id'] != currency_id:
555 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
556 # is not in the voucher currency
557 payment_rate_currency_id = voucher_line['currency_id']
558 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
559 voucher_currency_id = currency_id or journal.company_id.currency_id.id
560 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
562 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
563 for key in res.keys():
564 vals[key].update(res[key])
565 vals['value'].update({'payment_rate': payment_rate})
566 if payment_rate_currency_id:
567 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
570 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
573 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
574 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
575 for key in vals.keys():
576 res[key].update(vals[key])
577 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
578 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
579 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
580 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
581 # onchange returns a value for them
583 del(res['value']['line_dr_ids'])
584 del(res['value']['pre_line'])
585 del(res['value']['payment_rate'])
586 elif ttype == 'purchase':
587 del(res['value']['line_cr_ids'])
588 del(res['value']['pre_line'])
589 del(res['value']['payment_rate'])
592 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
594 Returns a dict that contains new values and context
596 @param partner_id: latest value from user input for field partner_id
597 @param args: other arguments
598 @param context: context arguments, like lang, time zone
600 @return: Returns a dict which contains new values, and context
602 def _remove_noise_in_o2m():
603 """if the line is partially reconciled, then we must pay attention to display it only once and
605 This function returns True if the line is considered as noise and should not be displayed
607 if line.reconcile_partial_id:
608 if currency_id == line.currency_id.id:
609 if line.amount_residual_currency <= 0:
612 if line.amount_residual <= 0:
618 context_multi_currency = context.copy()
620 context_multi_currency.update({'date': date})
622 currency_pool = self.pool.get('res.currency')
623 move_line_pool = self.pool.get('account.move.line')
624 partner_pool = self.pool.get('res.partner')
625 journal_pool = self.pool.get('account.journal')
626 line_pool = self.pool.get('account.voucher.line')
630 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
634 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
636 line_pool.unlink(cr, uid, line_ids)
638 if not partner_id or not journal_id:
641 journal = journal_pool.browse(cr, uid, journal_id, context=context)
642 partner = partner_pool.browse(cr, uid, partner_id, context=context)
643 currency_id = currency_id or journal.company_id.currency_id.id
645 if journal.type in ('sale','sale_refund'):
646 account_id = partner.property_account_receivable.id
647 elif journal.type in ('purchase', 'purchase_refund','expense'):
648 account_id = partner.property_account_payable.id
650 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
652 default['value']['account_id'] = account_id
654 if journal.type not in ('cash', 'bank'):
659 account_type = 'receivable'
660 if ttype == 'payment':
661 account_type = 'payable'
662 total_debit = price or 0.0
664 total_credit = price or 0.0
665 account_type = 'receivable'
667 if not context.get('move_line_ids', False):
668 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
670 ids = context['move_line_ids']
671 invoice_id = context.get('invoice_id', False)
672 company_currency = journal.company_id.currency_id.id
673 move_line_found = False
675 #order the lines by most old first
677 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
679 #compute the total debit/credit and look for a matching open amount or invoice
680 for line in account_move_lines:
681 if _remove_noise_in_o2m():
685 if line.invoice.id == invoice_id:
686 #if the invoice linked to the voucher line is equal to the invoice_id in context
687 #then we assign the amount on that line, whatever the other voucher lines
688 move_line_found = line.id
690 elif currency_id == company_currency:
691 #otherwise treatments is the same but with other field names
692 if line.amount_residual == price:
693 #if the amount residual is equal the amount voucher, we assign it to that voucher
694 #line, whatever the other voucher lines
695 move_line_found = line.id
697 #otherwise we will split the voucher amount on each line (by most old first)
698 total_credit += line.credit or 0.0
699 total_debit += line.debit or 0.0
700 elif currency_id == line.currency_id.id:
701 if line.amount_residual_currency == price:
702 move_line_found = line.id
704 total_credit += line.credit and line.amount_currency or 0.0
705 total_debit += line.debit and line.amount_currency or 0.0
707 #voucher line creation
708 for line in account_move_lines:
710 if _remove_noise_in_o2m():
713 if line.currency_id and currency_id==line.currency_id.id:
714 amount_original = abs(line.amount_currency)
715 amount_unreconciled = abs(line.amount_residual_currency)
717 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
718 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
719 line_currency_id = line.currency_id and line.currency_id.id or company_currency
721 'name':line.move_id.name,
722 'type': line.credit and 'dr' or 'cr',
723 'move_line_id':line.id,
724 'account_id':line.account_id.id,
725 'amount_original': amount_original,
726 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
727 'date_original':line.date,
728 'date_due':line.date_maturity,
729 'amount_unreconciled': amount_unreconciled,
730 'currency_id': line_currency_id,
732 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
733 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
734 if not move_line_found:
735 if currency_id == line_currency_id:
737 amount = min(amount_unreconciled, abs(total_debit))
738 rs['amount'] = amount
739 total_debit -= amount
741 amount = min(amount_unreconciled, abs(total_credit))
742 rs['amount'] = amount
743 total_credit -= amount
745 if rs['amount_unreconciled'] == rs['amount']:
746 rs['reconcile'] = True
748 if rs['type'] == 'cr':
749 default['value']['line_cr_ids'].append(rs)
751 default['value']['line_dr_ids'].append(rs)
753 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
754 default['value']['pre_line'] = 1
755 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
756 default['value']['pre_line'] = 1
757 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
760 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
764 #set the default payment rate of the voucher and compute the paid amount in company currency
765 if currency_id and currency_id == payment_rate_currency_id:
767 ctx.update({'date': date})
768 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
769 for key in vals.keys():
770 res[key].update(vals[key])
773 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
775 @param date: latest value from user input for field date
776 @param args: other arguments
777 @param context: context arguments, like lang, time zone
778 @return: Returns a dict which contains new values, and context
783 #set the period of the voucher
784 period_pool = self.pool.get('account.period')
785 currency_obj = self.pool.get('res.currency')
787 ctx.update({'company_id': company_id})
788 pids = period_pool.find(cr, uid, date, context=ctx)
790 res['value'].update({'period_id':pids[0]})
791 if payment_rate_currency_id:
792 ctx.update({'date': date})
794 if payment_rate_currency_id != currency_id:
795 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
796 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
797 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
798 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
799 vals['value'].update({'payment_rate': payment_rate})
800 for key in vals.keys():
801 res[key].update(vals[key])
804 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
807 journal_pool = self.pool.get('account.journal')
808 journal = journal_pool.browse(cr, uid, journal_id, context=context)
809 account_id = journal.default_credit_account_id or journal.default_debit_account_id
811 if account_id and account_id.tax_ids:
812 tax_id = account_id.tax_ids[0].id
815 if ttype in ('sale', 'purchase'):
816 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
817 vals['value'].update({'tax_id':tax_id,'amount': amount})
820 currency_id = journal.currency.id
822 currency_id = journal.company_id.currency_id.id
823 vals['value'].update({'currency_id': currency_id})
824 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
825 for key in res.keys():
826 vals[key].update(res[key])
829 def button_proforma_voucher(self, cr, uid, ids, context=None):
830 self.signal_proforma_voucher(cr, uid, ids)
831 return {'type': 'ir.actions.act_window_close'}
833 def proforma_voucher(self, cr, uid, ids, context=None):
834 self.action_move_line_create(cr, uid, ids, context=context)
837 def action_cancel_draft(self, cr, uid, ids, context=None):
838 self.create_workflow(cr, uid, ids)
839 self.write(cr, uid, ids, {'state':'draft'})
842 def cancel_voucher(self, cr, uid, ids, context=None):
843 reconcile_pool = self.pool.get('account.move.reconcile')
844 move_pool = self.pool.get('account.move')
846 for voucher in self.browse(cr, uid, ids, context=context):
848 for line in voucher.move_ids:
849 if line.reconcile_id:
850 recs += [line.reconcile_id.id]
851 if line.reconcile_partial_id:
852 recs += [line.reconcile_partial_id.id]
854 reconcile_pool.unlink(cr, uid, recs)
857 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
858 move_pool.unlink(cr, uid, [voucher.move_id.id])
863 self.write(cr, uid, ids, res)
866 def unlink(self, cr, uid, ids, context=None):
867 for t in self.read(cr, uid, ids, ['state'], context=context):
868 if t['state'] not in ('draft', 'cancel'):
869 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
870 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
872 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
876 res = {'account_id':False}
877 partner_pool = self.pool.get('res.partner')
878 journal_pool = self.pool.get('account.journal')
879 if pay_now == 'pay_later':
880 partner = partner_pool.browse(cr, uid, partner_id)
881 journal = journal_pool.browse(cr, uid, journal_id)
882 if journal.type in ('sale','sale_refund'):
883 account_id = partner.property_account_receivable.id
884 elif journal.type in ('purchase', 'purchase_refund','expense'):
885 account_id = partner.property_account_payable.id
887 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
888 res['account_id'] = account_id
891 def _sel_context(self, cr, uid, voucher_id, context=None):
893 Select the context to use accordingly if it needs to be multicurrency or not.
895 :param voucher_id: Id of the actual voucher
896 :return: The returned context will be the same as given in parameter if the voucher currency is the same
897 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
898 the date of the voucher.
901 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
902 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
903 if current_currency <> company_currency:
904 context_multi_currency = context.copy()
905 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
906 context_multi_currency.update({'date': voucher_brw.date})
907 return context_multi_currency
910 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
912 Return a dict to be use to create the first account move line of given voucher.
914 :param voucher_id: Id of voucher what we are creating account_move.
915 :param move_id: Id of account move where this line will be added.
916 :param company_currency: id of currency of the company to which the voucher belong
917 :param current_currency: id of currency of the voucher
918 :return: mapping between fieldname and value of account move line to create
921 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
923 # TODO: is there any other alternative then the voucher type ??
924 # ANSWER: We can have payment and receipt "In Advance".
925 # TODO: Make this logic available.
926 # -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
927 if voucher_brw.type in ('purchase', 'payment'):
928 credit = voucher_brw.paid_amount_in_company_currency
929 elif voucher_brw.type in ('sale', 'receipt'):
930 debit = voucher_brw.paid_amount_in_company_currency
931 if debit < 0: credit = -debit; debit = 0.0
932 if credit < 0: debit = -credit; credit = 0.0
933 sign = debit - credit < 0 and -1 or 1
934 #set the first line of the voucher
936 'name': voucher_brw.name or '/',
939 'account_id': voucher_brw.account_id.id,
941 'journal_id': voucher_brw.journal_id.id,
942 'period_id': voucher_brw.period_id.id,
943 'partner_id': voucher_brw.partner_id.id,
944 'currency_id': company_currency <> current_currency and current_currency or False,
945 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
946 'date': voucher_brw.date,
947 'date_maturity': voucher_brw.date_due
951 def account_move_get(self, cr, uid, voucher_id, context=None):
953 This method prepare the creation of the account move related to the given voucher.
955 :param voucher_id: Id of voucher for which we are creating account_move.
956 :return: mapping between fieldname and value of account move to create
959 seq_obj = self.pool.get('ir.sequence')
960 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
961 if voucher_brw.number:
962 name = voucher_brw.number
963 elif voucher_brw.journal_id.sequence_id:
964 if not voucher_brw.journal_id.sequence_id.active:
965 raise osv.except_osv(_('Configuration Error !'),
966 _('Please activate the sequence of selected journal !'))
968 c.update({'fiscalyear_id': voucher_brw.period_id.fiscalyear_id.id})
969 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=c)
971 raise osv.except_osv(_('Error!'),
972 _('Please define a sequence on the journal.'))
973 if not voucher_brw.reference:
974 ref = name.replace('/','')
976 ref = voucher_brw.reference
980 'journal_id': voucher_brw.journal_id.id,
981 'narration': voucher_brw.narration,
982 'date': voucher_brw.date,
984 'period_id': voucher_brw.period_id.id,
988 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
990 Prepare the two lines in company currency due to currency rate difference.
992 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
994 :param move_id: Account move wher the move lines will be.
995 :param amount_residual: Amount to be posted.
996 :param company_currency: id of currency of the company to which the voucher belong
997 :param current_currency: id of currency of the voucher
998 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
999 :rtype: tuple of dict
1001 if amount_residual > 0:
1002 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1004 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."))
1006 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1008 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."))
1009 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1010 # the receivable/payable account may have a secondary currency, which render this field mandatory
1011 account_currency_id = company_currency <> current_currency and current_currency or False
1013 'journal_id': line.voucher_id.journal_id.id,
1014 'period_id': line.voucher_id.period_id.id,
1015 'name': _('change')+': '+(line.name or '/'),
1016 'account_id': line.account_id.id,
1018 'partner_id': line.voucher_id.partner_id.id,
1019 'currency_id': account_currency_id,
1020 'amount_currency': 0.0,
1022 'credit': amount_residual > 0 and amount_residual or 0.0,
1023 'debit': amount_residual < 0 and -amount_residual or 0.0,
1024 'date': line.voucher_id.date,
1026 move_line_counterpart = {
1027 'journal_id': line.voucher_id.journal_id.id,
1028 'period_id': line.voucher_id.period_id.id,
1029 'name': _('change')+': '+(line.name or '/'),
1030 'account_id': account_id.id,
1032 'amount_currency': 0.0,
1033 'partner_id': line.voucher_id.partner_id.id,
1034 'currency_id': account_currency_id,
1036 'debit': amount_residual > 0 and amount_residual or 0.0,
1037 'credit': amount_residual < 0 and -amount_residual or 0.0,
1038 'date': line.voucher_id.date,
1040 return (move_line, move_line_counterpart)
1042 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1044 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1045 payment_rate_currency_id is relevant) either the rate encoded in the system.
1047 :param amount: float. The amount to convert
1048 :param voucher: id of the voucher on which we want the conversion
1049 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1050 field in order to select the good rate to use.
1051 :return: the amount in the currency of the voucher's company
1054 currency_obj = self.pool.get('res.currency')
1055 voucher = self.browse(cr, uid, voucher_id, context=context)
1057 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1058 # the rate specified on the voucher is for the company currency
1059 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1061 # the rate specified on the voucher is not relevant, we use all the rates in the system
1062 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1065 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1067 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1068 It returns Tuple with tot_line what is total of difference between debit and credit and
1069 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1071 :param voucher_id: Voucher id what we are working with
1072 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1073 :param move_id: Account move wher those lines will be joined.
1074 :param company_currency: id of currency of the company to which the voucher belong
1075 :param current_currency: id of currency of the voucher
1076 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1077 :rtype: tuple(float, list of int)
1081 move_line_obj = self.pool.get('account.move.line')
1082 currency_obj = self.pool.get('res.currency')
1083 tax_obj = self.pool.get('account.tax')
1084 tot_line = line_total
1087 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1088 ctx = context.copy()
1089 ctx.update({'date': voucher_brw.date})
1090 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1091 for line in voucher_brw.line_ids:
1092 #create one move line per voucher line where amount is not 0.0
1093 # AND (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
1094 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)):
1096 # convert the amount set on the voucher line into the currency of the voucher's company
1097 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1098 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1099 # currency rate difference
1100 if line.amount == line.amount_unreconciled:
1101 if not line.move_line_id:
1102 raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1103 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1104 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1106 currency_rate_difference = 0.0
1108 'journal_id': voucher_brw.journal_id.id,
1109 'period_id': voucher_brw.period_id.id,
1110 'name': line.name or '/',
1111 'account_id': line.account_id.id,
1113 'partner_id': voucher_brw.partner_id.id,
1114 '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,
1115 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1119 'date': voucher_brw.date
1123 if line.type == 'dr':
1128 if (line.type=='dr'):
1130 move_line['debit'] = amount
1133 move_line['credit'] = amount
1135 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1137 'account_tax_id': voucher_brw.tax_id.id,
1140 if move_line.get('account_tax_id', False):
1141 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1142 if not (tax_data.base_code_id and tax_data.tax_code_id):
1143 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))
1145 # compute the amount in foreign currency
1146 foreign_currency_diff = 0.0
1147 amount_currency = False
1148 if line.move_line_id:
1149 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1150 # We want to set it on the account move line as soon as the original line had a foreign currency
1151 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1152 # we compute the amount in that foreign currency.
1153 if line.move_line_id.currency_id.id == current_currency:
1154 # if the voucher and the voucher line share the same currency, there is no computation to do
1155 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1156 amount_currency = sign * (line.amount)
1157 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1158 # if the rate is specified on the voucher, we must use it
1159 payment_rate = voucher_brw.payment_rate
1160 if voucher_currency != company_currency:
1161 #if the voucher currency is not the company currency, we need to consider the rate of the line's currency
1162 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1163 payment_rate = voucher_rate * payment_rate
1164 amount_currency = (move_line['debit'] - move_line['credit']) * payment_rate
1167 # otherwise we use the rates of the system (giving the voucher date in the context)
1168 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1169 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1170 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1171 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1173 move_line['amount_currency'] = amount_currency
1174 voucher_line = move_line_obj.create(cr, uid, move_line)
1175 rec_ids = [voucher_line, line.move_line_id.id]
1177 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1178 # Change difference entry in company currency
1179 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1180 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1181 move_line_obj.create(cr, uid, exch_lines[1], context)
1182 rec_ids.append(new_id)
1184 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):
1185 # Change difference entry in voucher currency
1186 move_line_foreign_currency = {
1187 'journal_id': line.voucher_id.journal_id.id,
1188 'period_id': line.voucher_id.period_id.id,
1189 'name': _('change')+': '+(line.name or '/'),
1190 'account_id': line.account_id.id,
1192 'partner_id': line.voucher_id.partner_id.id,
1193 'currency_id': line.move_line_id.currency_id.id,
1194 'amount_currency': -1 * foreign_currency_diff,
1198 'date': line.voucher_id.date,
1200 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1201 rec_ids.append(new_id)
1203 if line.move_line_id.id:
1204 rec_lst_ids.append(rec_ids)
1206 return (tot_line, rec_lst_ids)
1208 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1210 Set a dict to be use to create the writeoff move line.
1212 :param voucher_id: Id of voucher what we are creating account_move.
1213 :param line_total: Amount remaining to be allocated on lines.
1214 :param move_id: Id of account move where this line will be added.
1215 :param name: Description of account move line.
1216 :param company_currency: id of currency of the company to which the voucher belong
1217 :param current_currency: id of currency of the voucher
1218 :return: mapping between fieldname and value of account move line to create
1221 currency_obj = self.pool.get('res.currency')
1224 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1225 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1227 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1231 if voucher_brw.payment_option == 'with_writeoff':
1232 account_id = voucher_brw.writeoff_acc_id.id
1233 write_off_name = voucher_brw.comment
1234 elif voucher_brw.type in ('sale', 'receipt'):
1235 account_id = voucher_brw.partner_id.property_account_receivable.id
1237 account_id = voucher_brw.partner_id.property_account_payable.id
1238 sign = voucher_brw.type == 'payment' and -1 or 1
1240 'name': write_off_name or name,
1241 'account_id': account_id,
1243 'partner_id': voucher_brw.partner_id.id,
1244 'date': voucher_brw.date,
1245 'credit': diff > 0 and diff or 0.0,
1246 'debit': diff < 0 and -diff or 0.0,
1247 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1248 'currency_id': company_currency <> current_currency and current_currency or False,
1249 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1254 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1256 Get the currency of the actual company.
1258 :param voucher_id: Id of the voucher what i want to obtain company currency.
1259 :return: currency id of the company of the voucher
1262 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1264 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1266 Get the currency of the voucher.
1268 :param voucher_id: Id of the voucher what i want to obtain current currency.
1269 :return: currency id of the voucher
1272 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1273 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1275 def action_move_line_create(self, cr, uid, ids, context=None):
1277 Confirm the vouchers given in ids and create the journal entries for each of them
1281 move_pool = self.pool.get('account.move')
1282 move_line_pool = self.pool.get('account.move.line')
1283 for voucher in self.browse(cr, uid, ids, context=context):
1286 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1287 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1288 # we select the context to use accordingly if it's a multicurrency case or not
1289 context = self._sel_context(cr, uid, voucher.id, context)
1290 # But for the operations made by _convert_amount, we always need to give the date in the context
1291 ctx = context.copy()
1292 ctx.update({'date': voucher.date})
1293 # Create the account move record.
1294 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1295 # Get the name of the account_move just created
1296 name = move_pool.browse(cr, uid, move_id, context=context).name
1297 # Create the first line of the voucher
1298 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)
1299 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1300 line_total = move_line_brw.debit - move_line_brw.credit
1302 if voucher.type == 'sale':
1303 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1304 elif voucher.type == 'purchase':
1305 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1306 # Create one move line per voucher line where amount is not 0.0
1307 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1309 # Create the writeoff line if needed
1310 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1312 move_line_pool.create(cr, uid, ml_writeoff, context)
1313 # We post the voucher.
1314 self.write(cr, uid, [voucher.id], {
1319 if voucher.journal_id.entry_posted:
1320 move_pool.post(cr, uid, [move_id], context={})
1321 # We automatically reconcile the account move lines.
1323 for rec_ids in rec_list_ids:
1324 if len(rec_ids) >= 2:
1325 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)
1328 def copy(self, cr, uid, id, default=None, context=None):
1335 'line_cr_ids': False,
1336 'line_dr_ids': False,
1339 if 'date' not in default:
1340 default['date'] = time.strftime('%Y-%m-%d')
1341 return super(account_voucher, self).copy(cr, uid, id, default, context)
1344 class account_voucher_line(osv.osv):
1345 _name = 'account.voucher.line'
1346 _description = 'Voucher Lines'
1347 _order = "move_line_id"
1349 # If the payment is in the same currency than the invoice, we keep the same amount
1350 # Otherwise, we compute from company currency to payment currency
1351 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1352 currency_pool = self.pool.get('res.currency')
1354 for line in self.browse(cr, uid, ids, context=context):
1355 ctx = context.copy()
1356 ctx.update({'date': line.voucher_id.date})
1358 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1359 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1360 move_line = line.move_line_id or False
1363 res['amount_original'] = 0.0
1364 res['amount_unreconciled'] = 0.0
1365 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1366 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1367 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)
1368 elif move_line and move_line.credit > 0:
1369 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1370 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1372 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1373 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1375 rs_data[line.id] = res
1378 def _currency_id(self, cr, uid, ids, name, args, context=None):
1380 This function returns the currency id of a voucher line. It's either the currency of the
1381 associated move line (if any) or the currency of the voucher or the company currency.
1384 for line in self.browse(cr, uid, ids, context=context):
1385 move_line = line.move_line_id
1387 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1389 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1393 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1394 'name':fields.char('Description', size=256),
1395 'account_id':fields.many2one('account.account','Account', required=True),
1396 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1397 'untax_amount':fields.float('Untax Amount'),
1398 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1399 'reconcile': fields.boolean('Full Reconcile'),
1400 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1401 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1402 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1403 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1404 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1405 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1406 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1407 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1408 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1414 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1415 vals = {'amount': 0.0}
1417 vals = { 'amount': amount_unreconciled}
1418 return {'value': vals}
1420 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1423 vals['reconcile'] = (amount == amount_unreconciled)
1424 return {'value': vals}
1426 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1428 Returns a dict that contains new values and context
1430 @param move_line_id: latest value from user input for field move_line_id
1431 @param args: other arguments
1432 @param context: context arguments, like lang, time zone
1434 @return: Returns a dict which contains new values, and context
1437 move_line_pool = self.pool.get('account.move.line')
1439 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1440 if move_line.credit:
1445 'account_id': move_line.account_id.id,
1447 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1453 def default_get(self, cr, user, fields_list, context=None):
1455 Returns default values for fields
1456 @param fields_list: list of fields, for which default values are required to be read
1457 @param context: context arguments, like lang, time zone
1459 @return: Returns a dict that contains default values for fields
1463 journal_id = context.get('journal_id', False)
1464 partner_id = context.get('partner_id', False)
1465 journal_pool = self.pool.get('account.journal')
1466 partner_pool = self.pool.get('res.partner')
1467 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1468 if (not journal_id) or ('account_id' not in fields_list):
1470 journal = journal_pool.browse(cr, user, journal_id, context=context)
1473 if journal.type in ('sale', 'sale_refund'):
1474 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1476 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1477 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1480 partner = partner_pool.browse(cr, user, partner_id, context=context)
1481 if context.get('type') == 'payment':
1483 account_id = partner.property_account_payable.id
1484 elif context.get('type') == 'receipt':
1485 account_id = partner.property_account_receivable.id
1488 'account_id':account_id,
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)
1544 class account_bank_statement_line(osv.osv):
1545 _inherit = 'account.bank.statement.line'
1547 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1548 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1549 if 'value' not in res:
1551 res['value'].update({'voucher_id' : False})
1554 def onchange_amount(self, cr, uid, ids, amount, context=None):
1555 return {'value' : {'voucher_id' : False}}
1557 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1561 for line in self.browse(cursor, user, ids, context=context):
1563 res[line.id] = line.voucher_id.amount#
1568 def _check_amount(self, cr, uid, ids, context=None):
1569 for obj in self.browse(cr, uid, ids, context=context):
1571 diff = abs(obj.amount) - obj.voucher_id.amount
1572 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1577 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1581 'amount_reconciled': fields.function(_amount_reconciled,
1582 string='Amount reconciled', type='float'),
1583 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1586 def unlink(self, cr, uid, ids, context=None):
1587 voucher_obj = self.pool.get('account.voucher')
1588 statement_line = self.browse(cr, uid, ids, context=context)
1590 for st_line in statement_line:
1591 if st_line.voucher_id:
1592 unlink_ids.append(st_line.voucher_id.id)
1593 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1594 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1597 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1599 for operation in operations:
1601 if not isinstance(operation, (list, tuple)):
1602 result = target_osv.read(cr, uid, operation, fields, context=context)
1603 elif operation[0] == 0:
1604 # may be necessary to check if all the fields are here and get the default values?
1605 result = operation[2]
1606 elif operation[0] == 1:
1607 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1608 if not result: result = {}
1609 result.update(operation[2])
1610 elif operation[0] == 4:
1611 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1613 results.append(result)
1617 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: