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:
228 return {'value':{'writeoff_amount': 0.0, 'is_multi_currency': False}}
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
235 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to see the options
236 for voucher_line in line_dr_ids+line_cr_ids:
237 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
239 is_multi_currency = True
241 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
243 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
244 if not ids: return {}
245 currency_obj = self.pool.get('res.currency')
248 for voucher in self.browse(cr, uid, ids, context=context):
249 sign = voucher.type == 'payment' and -1 or 1
250 for l in voucher.line_dr_ids:
252 for l in voucher.line_cr_ids:
254 currency = voucher.currency_id or voucher.company_id.currency_id
255 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
258 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
259 if not ids: return {}
262 for voucher in self.browse(cr, uid, ids, context=context):
263 if voucher.currency_id:
264 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
265 rate = 1 / voucher.payment_rate
268 ctx.update({'date': voucher.date})
269 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
270 company_currency_rate = voucher.company_id.currency_id.rate
271 rate = voucher_rate * company_currency_rate
272 res[voucher.id] = voucher.amount / rate
275 _name = 'account.voucher'
276 _description = 'Accounting Voucher'
277 _inherit = ['mail.thread']
278 _order = "date desc, id desc"
279 # _rec_name = 'number'
282 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
287 '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."),
288 'type':fields.selection([
290 ('purchase','Purchase'),
291 ('payment','Payment'),
292 ('receipt','Receipt'),
293 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
294 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
295 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
296 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
297 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
298 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
299 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
300 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
301 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
302 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
303 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
304 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
305 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
306 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
307 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
308 'state':fields.selection(
310 ('cancel','Cancelled'),
311 ('proforma','Pro-forma'),
313 ], 'Status', readonly=True, size=32, track_visibility='onchange',
314 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
315 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
316 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
317 \n* The \'Cancelled\' status is used when user cancel voucher.'),
318 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
319 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
320 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
321 'number': fields.char('Number', size=32, readonly=True,),
322 'move_id':fields.many2one('account.move', 'Account Entry'),
323 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
324 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
325 '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'),
326 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
327 'pay_now':fields.selection([
328 ('pay_now','Pay Directly'),
329 ('pay_later','Pay Later or Group Funds'),
330 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
331 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
332 'pre_line':fields.boolean('Previous Payments ?', required=False),
333 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
334 'payment_option':fields.selection([
335 ('without_writeoff', 'Keep Open'),
336 ('with_writeoff', 'Reconcile Payment Balance'),
337 ], '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)"),
338 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
339 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
340 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
341 '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."),
342 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
343 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
344 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
345 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
346 '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'),
350 'period_id': _get_period,
351 'partner_id': _get_partner,
352 'journal_id':_get_journal,
353 'currency_id': _get_currency,
354 'reference': _get_reference,
355 'narration':_get_narration,
356 'amount': _get_amount,
359 'pay_now': 'pay_now',
361 'date': lambda *a: time.strftime('%Y-%m-%d'),
362 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
364 'payment_option': 'without_writeoff',
365 'comment': _('Write-Off'),
367 'payment_rate_currency_id': _get_payment_rate_currency,
370 def compute_tax(self, cr, uid, ids, context=None):
371 tax_pool = self.pool.get('account.tax')
372 partner_pool = self.pool.get('res.partner')
373 position_pool = self.pool.get('account.fiscal.position')
374 voucher_line_pool = self.pool.get('account.voucher.line')
375 voucher_pool = self.pool.get('account.voucher')
376 if context is None: context = {}
378 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
380 for line in voucher.line_ids:
381 voucher_amount += line.untax_amount or line.amount
382 line.amount = line.untax_amount or line.amount
383 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
385 if not voucher.tax_id:
386 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
389 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
390 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
391 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
392 tax = tax_pool.browse(cr, uid, taxes, context=context)
394 total = voucher_amount
397 if not tax[0].price_include:
398 for line in voucher.line_ids:
399 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
400 total_tax += tax_line.get('amount', 0.0)
403 for line in voucher.line_ids:
407 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
408 line_tax += tax_line.get('amount', 0.0)
409 line_total += tax_line.get('price_unit')
410 total_tax += line_tax
411 untax_amount = line.untax_amount or line.amount
412 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
414 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
417 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
418 context = context or {}
419 tax_pool = self.pool.get('account.tax')
420 partner_pool = self.pool.get('res.partner')
421 position_pool = self.pool.get('account.fiscal.position')
422 line_pool = self.pool.get('account.voucher.line')
429 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
432 for line in line_ids:
434 line_amount = line.get('amount',0.0)
437 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
439 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
440 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
441 tax = tax_pool.browse(cr, uid, taxes, context=context)
443 if not tax[0].price_include:
444 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
445 total_tax += tax_line.get('amount')
447 voucher_total += line_amount
448 total = voucher_total + total_tax
451 'amount': total or voucher_total,
452 'tax_amount': total_tax
458 def onchange_term_id(self, cr, uid, ids, term_id, amount):
459 term_pool = self.pool.get('account.payment.term')
462 default = {'date_due':False}
463 if term_id and amount:
464 terms = term_pool.compute(cr, uid, term_id, amount)
466 due_date = terms[-1][0]
470 return {'value':default}
472 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):
474 Returns a dict that contains new values and context
476 @param partner_id: latest value from user input for field partner_id
477 @param args: other arguments
478 @param context: context arguments, like lang, time zone
480 @return: Returns a dict which contains new values, and context
486 if not partner_id or not journal_id:
489 partner_pool = self.pool.get('res.partner')
490 journal_pool = self.pool.get('account.journal')
492 journal = journal_pool.browse(cr, uid, journal_id, context=context)
493 partner = partner_pool.browse(cr, uid, partner_id, context=context)
496 if journal.type in ('sale','sale_refund'):
497 account_id = partner.property_account_receivable.id
499 elif journal.type in ('purchase', 'purchase_refund','expense'):
500 account_id = partner.property_account_payable.id
503 if not journal.default_credit_account_id or not journal.default_debit_account_id:
504 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
505 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
508 default['value']['account_id'] = account_id
509 default['value']['type'] = ttype or tr_type
511 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)
512 default['value'].update(vals.get('value'))
516 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
517 res = {'value': {'paid_amount_in_company_currency': amount}}
518 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
519 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
520 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
521 if company_currency.id == payment_rate_currency_id:
524 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
525 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
528 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):
531 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
533 ctx.update({'date': date})
534 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
535 for key in vals.keys():
536 res[key].update(vals[key])
539 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
542 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
543 currency_obj = self.pool.get('res.currency')
544 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
545 company_id = journal.company_id.id
547 payment_rate_currency_id = currency_id
549 ctx.update({'date': date})
551 if ttype == 'receipt':
552 o2m_to_loop = 'line_cr_ids'
553 elif ttype == 'payment':
554 o2m_to_loop = 'line_dr_ids'
555 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
556 for voucher_line in vals['value'][o2m_to_loop]:
557 if voucher_line['currency_id'] != currency_id:
558 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
559 # is not in the voucher currency
560 payment_rate_currency_id = voucher_line['currency_id']
561 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
562 voucher_currency_id = currency_id or journal.company_id.currency_id.id
563 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
565 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
566 for key in res.keys():
567 vals[key].update(res[key])
568 vals['value'].update({'payment_rate': payment_rate})
569 if payment_rate_currency_id:
570 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
573 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
576 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
577 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
578 for key in vals.keys():
579 res[key].update(vals[key])
580 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
581 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
582 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
583 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
584 # onchange returns a value for them
586 del(res['value']['line_dr_ids'])
587 del(res['value']['pre_line'])
588 del(res['value']['payment_rate'])
589 elif ttype == 'purchase':
590 del(res['value']['line_cr_ids'])
591 del(res['value']['pre_line'])
592 del(res['value']['payment_rate'])
595 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
597 Returns a dict that contains new values and context
599 @param partner_id: latest value from user input for field partner_id
600 @param args: other arguments
601 @param context: context arguments, like lang, time zone
603 @return: Returns a dict which contains new values, and context
605 def _remove_noise_in_o2m():
606 """if the line is partially reconciled, then we must pay attention to display it only once and
608 This function returns True if the line is considered as noise and should not be displayed
610 if line.reconcile_partial_id:
611 if currency_id == line.currency_id.id:
612 if line.amount_residual_currency <= 0:
615 if line.amount_residual <= 0:
621 context_multi_currency = context.copy()
623 context_multi_currency.update({'date': date})
625 currency_pool = self.pool.get('res.currency')
626 move_line_pool = self.pool.get('account.move.line')
627 partner_pool = self.pool.get('res.partner')
628 journal_pool = self.pool.get('account.journal')
629 line_pool = self.pool.get('account.voucher.line')
633 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
637 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
639 line_pool.unlink(cr, uid, line_ids)
641 if not partner_id or not journal_id:
644 journal = journal_pool.browse(cr, uid, journal_id, context=context)
645 partner = partner_pool.browse(cr, uid, partner_id, context=context)
646 currency_id = currency_id or journal.company_id.currency_id.id
648 if journal.type in ('sale','sale_refund'):
649 account_id = partner.property_account_receivable.id
650 elif journal.type in ('purchase', 'purchase_refund','expense'):
651 account_id = partner.property_account_payable.id
653 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
655 default['value']['account_id'] = account_id
657 if journal.type not in ('cash', 'bank'):
662 account_type = 'receivable'
663 if ttype == 'payment':
664 account_type = 'payable'
665 total_debit = price or 0.0
667 total_credit = price or 0.0
668 account_type = 'receivable'
670 if not context.get('move_line_ids', False):
671 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
673 ids = context['move_line_ids']
674 invoice_id = context.get('invoice_id', False)
675 company_currency = journal.company_id.currency_id.id
676 move_line_found = False
678 #order the lines by most old first
680 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
682 #compute the total debit/credit and look for a matching open amount or invoice
683 for line in account_move_lines:
684 if _remove_noise_in_o2m():
688 if line.invoice.id == invoice_id:
689 #if the invoice linked to the voucher line is equal to the invoice_id in context
690 #then we assign the amount on that line, whatever the other voucher lines
691 move_line_found = line.id
693 elif currency_id == company_currency:
694 #otherwise treatments is the same but with other field names
695 if line.amount_residual == price:
696 #if the amount residual is equal the amount voucher, we assign it to that voucher
697 #line, whatever the other voucher lines
698 move_line_found = line.id
700 #otherwise we will split the voucher amount on each line (by most old first)
701 total_credit += line.credit or 0.0
702 total_debit += line.debit or 0.0
703 elif currency_id == line.currency_id.id:
704 if line.amount_residual_currency == price:
705 move_line_found = line.id
707 total_credit += line.credit and line.amount_currency or 0.0
708 total_debit += line.debit and line.amount_currency or 0.0
710 #voucher line creation
711 for line in account_move_lines:
713 if _remove_noise_in_o2m():
716 if line.currency_id and currency_id==line.currency_id.id:
717 amount_original = abs(line.amount_currency)
718 amount_unreconciled = abs(line.amount_residual_currency)
720 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
721 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
722 line_currency_id = line.currency_id and line.currency_id.id or company_currency
724 'name':line.move_id.name,
725 'type': line.credit and 'dr' or 'cr',
726 'move_line_id':line.id,
727 'account_id':line.account_id.id,
728 'amount_original': amount_original,
729 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
730 'date_original':line.date,
731 'date_due':line.date_maturity,
732 'amount_unreconciled': amount_unreconciled,
733 'currency_id': line_currency_id,
735 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
736 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
737 if not move_line_found:
738 if currency_id == line_currency_id:
740 amount = min(amount_unreconciled, abs(total_debit))
741 rs['amount'] = amount
742 total_debit -= amount
744 amount = min(amount_unreconciled, abs(total_credit))
745 rs['amount'] = amount
746 total_credit -= amount
748 if rs['amount_unreconciled'] == rs['amount']:
749 rs['reconcile'] = True
751 if rs['type'] == 'cr':
752 default['value']['line_cr_ids'].append(rs)
754 default['value']['line_dr_ids'].append(rs)
756 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
757 default['value']['pre_line'] = 1
758 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
759 default['value']['pre_line'] = 1
760 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
763 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
767 #set the default payment rate of the voucher and compute the paid amount in company currency
768 if currency_id and currency_id == payment_rate_currency_id:
770 ctx.update({'date': date})
771 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
772 for key in vals.keys():
773 res[key].update(vals[key])
776 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
778 @param date: latest value from user input for field date
779 @param args: other arguments
780 @param context: context arguments, like lang, time zone
781 @return: Returns a dict which contains new values, and context
786 #set the period of the voucher
787 period_pool = self.pool.get('account.period')
788 currency_obj = self.pool.get('res.currency')
790 ctx.update({'company_id': company_id, 'account_period_prefer_normal': True})
791 pids = period_pool.find(cr, uid, date, context=ctx)
793 res['value'].update({'period_id':pids[0]})
794 if payment_rate_currency_id:
795 ctx.update({'date': date})
797 if payment_rate_currency_id != currency_id:
798 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
799 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
800 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
801 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
802 vals['value'].update({'payment_rate': payment_rate})
803 for key in vals.keys():
804 res[key].update(vals[key])
807 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
810 journal_pool = self.pool.get('account.journal')
811 journal = journal_pool.browse(cr, uid, journal_id, context=context)
812 account_id = journal.default_credit_account_id or journal.default_debit_account_id
814 if account_id and account_id.tax_ids:
815 tax_id = account_id.tax_ids[0].id
818 if ttype in ('sale', 'purchase'):
819 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
820 vals['value'].update({'tax_id':tax_id,'amount': amount})
823 currency_id = journal.currency.id
825 currency_id = journal.company_id.currency_id.id
826 vals['value'].update({'currency_id': currency_id})
827 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
828 for key in res.keys():
829 vals[key].update(res[key])
832 def button_proforma_voucher(self, cr, uid, ids, context=None):
833 context = context or {}
834 wf_service = netsvc.LocalService("workflow")
836 wf_service.trg_validate(uid, 'account.voucher', vid, 'proforma_voucher', cr)
837 return {'type': 'ir.actions.act_window_close'}
839 def proforma_voucher(self, cr, uid, ids, context=None):
840 self.action_move_line_create(cr, uid, ids, context=context)
843 def action_cancel_draft(self, cr, uid, ids, context=None):
844 wf_service = netsvc.LocalService("workflow")
845 for voucher_id in ids:
846 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
847 self.write(cr, uid, ids, {'state':'draft'})
850 def cancel_voucher(self, cr, uid, ids, context=None):
851 reconcile_pool = self.pool.get('account.move.reconcile')
852 move_pool = self.pool.get('account.move')
854 for voucher in self.browse(cr, uid, ids, context=context):
856 for line in voucher.move_ids:
857 if line.reconcile_id:
858 recs += [line.reconcile_id.id]
859 if line.reconcile_partial_id:
860 recs += [line.reconcile_partial_id.id]
862 reconcile_pool.unlink(cr, uid, recs)
865 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
866 move_pool.unlink(cr, uid, [voucher.move_id.id])
871 self.write(cr, uid, ids, res)
874 def unlink(self, cr, uid, ids, context=None):
875 for t in self.read(cr, uid, ids, ['state'], context=context):
876 if t['state'] not in ('draft', 'cancel'):
877 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
878 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
880 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
884 res = {'account_id':False}
885 partner_pool = self.pool.get('res.partner')
886 journal_pool = self.pool.get('account.journal')
887 if pay_now == 'pay_later':
888 partner = partner_pool.browse(cr, uid, partner_id)
889 journal = journal_pool.browse(cr, uid, journal_id)
890 if journal.type in ('sale','sale_refund'):
891 account_id = partner.property_account_receivable.id
892 elif journal.type in ('purchase', 'purchase_refund','expense'):
893 account_id = partner.property_account_payable.id
895 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
896 res['account_id'] = account_id
899 def _sel_context(self, cr, uid, voucher_id, context=None):
901 Select the context to use accordingly if it needs to be multicurrency or not.
903 :param voucher_id: Id of the actual voucher
904 :return: The returned context will be the same as given in parameter if the voucher currency is the same
905 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
906 the date of the voucher.
909 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
910 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
911 if current_currency <> company_currency:
912 context_multi_currency = context.copy()
913 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
914 context_multi_currency.update({'date': voucher_brw.date})
915 return context_multi_currency
918 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
920 Return a dict to be use to create the first account move line of given voucher.
922 :param voucher_id: Id of voucher what we are creating account_move.
923 :param move_id: Id of account move where this line will be added.
924 :param company_currency: id of currency of the company to which the voucher belong
925 :param current_currency: id of currency of the voucher
926 :return: mapping between fieldname and value of account move line to create
929 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
931 # TODO: is there any other alternative then the voucher type ??
932 # ANSWER: We can have payment and receipt "In Advance".
933 # TODO: Make this logic available.
934 # -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
935 if voucher_brw.type in ('purchase', 'payment'):
936 credit = voucher_brw.paid_amount_in_company_currency
937 elif voucher_brw.type in ('sale', 'receipt'):
938 debit = voucher_brw.paid_amount_in_company_currency
939 if debit < 0: credit = -debit; debit = 0.0
940 if credit < 0: debit = -credit; credit = 0.0
941 sign = debit - credit < 0 and -1 or 1
942 #set the first line of the voucher
944 'name': voucher_brw.name or '/',
947 'account_id': voucher_brw.account_id.id,
949 'journal_id': voucher_brw.journal_id.id,
950 'period_id': voucher_brw.period_id.id,
951 'partner_id': voucher_brw.partner_id.id,
952 'currency_id': company_currency <> current_currency and current_currency or False,
953 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
954 'date': voucher_brw.date,
955 'date_maturity': voucher_brw.date_due
959 def account_move_get(self, cr, uid, voucher_id, context=None):
961 This method prepare the creation of the account move related to the given voucher.
963 :param voucher_id: Id of voucher for which we are creating account_move.
964 :return: mapping between fieldname and value of account move to create
967 seq_obj = self.pool.get('ir.sequence')
968 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
969 if voucher_brw.number:
970 name = voucher_brw.number
971 elif voucher_brw.journal_id.sequence_id:
972 if not voucher_brw.journal_id.sequence_id.active:
973 raise osv.except_osv(_('Configuration Error !'),
974 _('Please activate the sequence of selected journal !'))
976 c.update({'fiscalyear_id': voucher_brw.period_id.fiscalyear_id.id})
977 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=c)
979 raise osv.except_osv(_('Error!'),
980 _('Please define a sequence on the journal.'))
981 if not voucher_brw.reference:
982 ref = name.replace('/','')
984 ref = voucher_brw.reference
988 'journal_id': voucher_brw.journal_id.id,
989 'narration': voucher_brw.narration,
990 'date': voucher_brw.date,
992 'period_id': voucher_brw.period_id.id,
996 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
998 Prepare the two lines in company currency due to currency rate difference.
1000 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
1002 :param move_id: Account move wher the move lines will be.
1003 :param amount_residual: Amount to be posted.
1004 :param company_currency: id of currency of the company to which the voucher belong
1005 :param current_currency: id of currency of the voucher
1006 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
1007 :rtype: tuple of dict
1009 if amount_residual > 0:
1010 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1012 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."))
1014 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1016 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."))
1017 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1018 # the receivable/payable account may have a secondary currency, which render this field mandatory
1019 account_currency_id = company_currency <> current_currency and current_currency or False
1021 'journal_id': line.voucher_id.journal_id.id,
1022 'period_id': line.voucher_id.period_id.id,
1023 'name': _('change')+': '+(line.name or '/'),
1024 'account_id': line.account_id.id,
1026 'partner_id': line.voucher_id.partner_id.id,
1027 'currency_id': account_currency_id,
1028 'amount_currency': 0.0,
1030 'credit': amount_residual > 0 and amount_residual or 0.0,
1031 'debit': amount_residual < 0 and -amount_residual or 0.0,
1032 'date': line.voucher_id.date,
1034 move_line_counterpart = {
1035 'journal_id': line.voucher_id.journal_id.id,
1036 'period_id': line.voucher_id.period_id.id,
1037 'name': _('change')+': '+(line.name or '/'),
1038 'account_id': account_id.id,
1040 'amount_currency': 0.0,
1041 'partner_id': line.voucher_id.partner_id.id,
1042 'currency_id': account_currency_id,
1044 'debit': amount_residual > 0 and amount_residual or 0.0,
1045 'credit': amount_residual < 0 and -amount_residual or 0.0,
1046 'date': line.voucher_id.date,
1048 return (move_line, move_line_counterpart)
1050 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1052 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1053 payment_rate_currency_id is relevant) either the rate encoded in the system.
1055 :param amount: float. The amount to convert
1056 :param voucher: id of the voucher on which we want the conversion
1057 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1058 field in order to select the good rate to use.
1059 :return: the amount in the currency of the voucher's company
1062 currency_obj = self.pool.get('res.currency')
1063 voucher = self.browse(cr, uid, voucher_id, context=context)
1065 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1066 # the rate specified on the voucher is for the company currency
1067 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1069 # the rate specified on the voucher is not relevant, we use all the rates in the system
1070 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1073 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1075 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1076 It returns Tuple with tot_line what is total of difference between debit and credit and
1077 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1079 :param voucher_id: Voucher id what we are working with
1080 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1081 :param move_id: Account move wher those lines will be joined.
1082 :param company_currency: id of currency of the company to which the voucher belong
1083 :param current_currency: id of currency of the voucher
1084 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1085 :rtype: tuple(float, list of int)
1089 move_line_obj = self.pool.get('account.move.line')
1090 currency_obj = self.pool.get('res.currency')
1091 tax_obj = self.pool.get('account.tax')
1092 tot_line = line_total
1095 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1096 ctx = context.copy()
1097 ctx.update({'date': voucher_brw.date})
1098 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1099 for line in voucher_brw.line_ids:
1100 #create one move line per voucher line where amount is not 0.0
1101 # AND (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
1102 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)):
1104 # convert the amount set on the voucher line into the currency of the voucher's company
1105 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1106 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1107 # currency rate difference
1108 if line.amount == line.amount_unreconciled:
1109 if not line.move_line_id:
1110 raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1111 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1112 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1114 currency_rate_difference = 0.0
1116 'journal_id': voucher_brw.journal_id.id,
1117 'period_id': voucher_brw.period_id.id,
1118 'name': line.name or '/',
1119 'account_id': line.account_id.id,
1121 'partner_id': voucher_brw.partner_id.id,
1122 '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,
1123 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1127 'date': voucher_brw.date
1131 if line.type == 'dr':
1136 if (line.type=='dr'):
1138 move_line['debit'] = amount
1141 move_line['credit'] = amount
1143 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1145 'account_tax_id': voucher_brw.tax_id.id,
1148 if move_line.get('account_tax_id', False):
1149 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1150 if not (tax_data.base_code_id and tax_data.tax_code_id):
1151 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))
1153 # compute the amount in foreign currency
1154 foreign_currency_diff = 0.0
1155 amount_currency = False
1156 if line.move_line_id:
1157 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1158 # We want to set it on the account move line as soon as the original line had a foreign currency
1159 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1160 # we compute the amount in that foreign currency.
1161 if line.move_line_id.currency_id.id == current_currency:
1162 # if the voucher and the voucher line share the same currency, there is no computation to do
1163 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1164 amount_currency = sign * (line.amount)
1165 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1166 # if the rate is specified on the voucher, we must use it
1167 payment_rate = voucher_brw.payment_rate
1168 if voucher_currency != company_currency:
1169 #if the voucher currency is not the company currency, we need to consider the rate of the line's currency
1170 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1171 payment_rate = voucher_rate * payment_rate
1172 amount_currency = (move_line['debit'] - move_line['credit']) * payment_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: