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 'expense_currency_exchange_account_id': fields.related(
55 'company_id', 'expense_currency_exchange_account_id',
57 relation='account.account',
58 string="Loss Exchange Rate Account"),
61 class account_voucher(osv.osv):
62 def _check_paid(self, cr, uid, ids, name, args, context=None):
64 for voucher in self.browse(cr, uid, ids, context=context):
65 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
68 def _get_type(self, cr, uid, context=None):
71 return context.get('type', False)
73 def _get_period(self, cr, uid, context=None):
74 if context is None: context = {}
75 if context.get('period_id', False):
76 return context.get('period_id')
77 periods = self.pool.get('account.period').find(cr, uid)
78 return periods and periods[0] or False
80 def _make_journal_search(self, cr, uid, ttype, context=None):
81 journal_pool = self.pool.get('account.journal')
82 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
84 def _get_journal(self, cr, uid, context=None):
85 if context is None: context = {}
86 invoice_pool = self.pool.get('account.invoice')
87 journal_pool = self.pool.get('account.journal')
88 if context.get('invoice_id', False):
89 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
90 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
91 return journal_id and journal_id[0] or False
92 if context.get('journal_id', False):
93 return context.get('journal_id')
94 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
95 return context.get('search_default_journal_id')
97 ttype = context.get('type', 'bank')
98 if ttype in ('payment', 'receipt'):
100 res = self._make_journal_search(cr, uid, ttype, context=context)
101 return res and res[0] or False
103 def _get_tax(self, cr, uid, context=None):
104 if context is None: context = {}
105 journal_pool = self.pool.get('account.journal')
106 journal_id = context.get('journal_id', False)
108 ttype = context.get('type', 'bank')
109 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
116 journal = journal_pool.browse(cr, uid, journal_id, context=context)
117 account_id = journal.default_credit_account_id or journal.default_debit_account_id
118 if account_id and account_id.tax_ids:
119 tax_id = account_id.tax_ids[0].id
123 def _get_payment_rate_currency(self, cr, uid, context=None):
125 Return the default value for field payment_rate_currency_id: the currency of the journal
126 if there is one, otherwise the currency of the user's company
128 if context is None: context = {}
129 journal_pool = self.pool.get('account.journal')
130 journal_id = context.get('journal_id', False)
132 journal = journal_pool.browse(cr, uid, journal_id, context=context)
134 return journal.currency.id
135 #no journal given in the context, use company currency as default
136 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
138 def _get_currency(self, cr, uid, context=None):
139 if context is None: context = {}
140 journal_pool = self.pool.get('account.journal')
141 journal_id = context.get('journal_id', False)
143 journal = journal_pool.browse(cr, uid, journal_id, context=context)
145 return journal.currency.id
148 def _get_partner(self, cr, uid, context=None):
149 if context is None: context = {}
150 return context.get('partner_id', False)
152 def _get_reference(self, cr, uid, context=None):
153 if context is None: context = {}
154 return context.get('reference', False)
156 def _get_narration(self, cr, uid, context=None):
157 if context is None: context = {}
158 return context.get('narration', False)
160 def _get_amount(self, cr, uid, context=None):
163 return context.get('amount', 0.0)
165 def name_get(self, cr, uid, ids, context=None):
168 if context is None: context = {}
169 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
171 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
172 mod_obj = self.pool.get('ir.model.data')
173 if context is None: context = {}
175 if view_type == 'form':
176 if not view_id and context.get('invoice_type'):
177 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
178 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
180 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
181 result = result and result[1] or False
183 if not view_id and context.get('line_type'):
184 if context.get('line_type') == 'customer':
185 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
187 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
188 result = result and result[1] or False
191 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
192 doc = etree.XML(res['arch'])
194 if context.get('type', 'sale') in ('purchase', 'payment'):
195 nodes = doc.xpath("//field[@name='partner_id']")
197 node.set('domain', "[('supplier', '=', True)]")
198 res['arch'] = etree.tostring(doc)
201 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
203 sign = type == 'payment' and -1 or 1
204 for l in line_dr_ids:
206 for l in line_cr_ids:
207 credit += l['amount']
208 return amount - sign * (credit - debit)
210 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
211 context = context or {}
212 if not line_dr_ids and not line_cr_ids:
214 line_osv = self.pool.get("account.voucher.line")
215 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
216 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
218 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
219 is_multi_currency = False
221 # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
222 is_multi_currency = True
224 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
225 for voucher_line in line_dr_ids+line_cr_ids:
226 company_currency = False
227 company_currency = voucher_line.get('move_line_id', False) and self.pool.get('account.move.line').browse(cr, uid, voucher_line.get('move_line_id'), context=context).company_id.currency_id.id
228 if voucher_line.get('currency_id', company_currency) != company_currency:
229 is_multi_currency = True
231 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
233 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
234 if not ids: return {}
235 currency_obj = self.pool.get('res.currency')
238 for voucher in self.browse(cr, uid, ids, context=context):
239 sign = voucher.type == 'payment' and -1 or 1
240 for l in voucher.line_dr_ids:
242 for l in voucher.line_cr_ids:
244 currency = voucher.currency_id or voucher.company_id.currency_id
245 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
248 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
249 if not ids: return {}
252 for voucher in self.browse(cr, uid, ids, context=context):
253 if voucher.currency_id:
254 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
255 rate = 1 / voucher.payment_rate
258 ctx.update({'date': voucher.date})
259 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
260 company_currency_rate = voucher.company_id.currency_id.rate
261 rate = voucher_rate * company_currency_rate
262 res[voucher.id] = voucher.amount / rate
265 _name = 'account.voucher'
266 _description = 'Accounting Voucher'
267 _inherit = ['mail.thread']
268 _order = "date desc, id desc"
269 # _rec_name = 'number'
272 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
277 '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."),
278 'type':fields.selection([
280 ('purchase','Purchase'),
281 ('payment','Payment'),
282 ('receipt','Receipt'),
283 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
284 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
285 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
286 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
287 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
288 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
289 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
290 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
291 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
292 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
293 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
294 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
295 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
296 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
297 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
298 'state':fields.selection(
300 ('cancel','Cancelled'),
301 ('proforma','Pro-forma'),
303 ], 'Status', readonly=True, size=32, track_visibility='onchange',
304 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
305 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
306 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
307 \n* The \'Cancelled\' status is used when user cancel voucher.'),
308 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
309 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
310 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
311 'number': fields.char('Number', size=32, readonly=True,),
312 'move_id':fields.many2one('account.move', 'Account Entry'),
313 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
314 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
315 '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'),
316 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
317 'pay_now':fields.selection([
318 ('pay_now','Pay Directly'),
319 ('pay_later','Pay Later or Group Funds'),
320 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
321 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
322 'pre_line':fields.boolean('Previous Payments ?', required=False),
323 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
324 'payment_option':fields.selection([
325 ('without_writeoff', 'Keep Open'),
326 ('with_writeoff', 'Reconcile Payment Balance'),
327 ], '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)"),
328 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
329 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
330 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
331 '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."),
332 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
333 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
334 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
335 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
336 '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'),
340 'period_id': _get_period,
341 'partner_id': _get_partner,
342 'journal_id':_get_journal,
343 'currency_id': _get_currency,
344 'reference': _get_reference,
345 'narration':_get_narration,
346 'amount': _get_amount,
349 'pay_now': 'pay_now',
351 'date': lambda *a: time.strftime('%Y-%m-%d'),
352 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
354 'payment_option': 'without_writeoff',
355 'comment': _('Write-Off'),
357 'payment_rate_currency_id': _get_payment_rate_currency,
360 def compute_tax(self, cr, uid, ids, context=None):
361 tax_pool = self.pool.get('account.tax')
362 partner_pool = self.pool.get('res.partner')
363 position_pool = self.pool.get('account.fiscal.position')
364 voucher_line_pool = self.pool.get('account.voucher.line')
365 voucher_pool = self.pool.get('account.voucher')
366 if context is None: context = {}
368 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
370 for line in voucher.line_ids:
371 voucher_amount += line.untax_amount or line.amount
372 line.amount = line.untax_amount or line.amount
373 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
375 if not voucher.tax_id:
376 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
379 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
380 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
381 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
382 tax = tax_pool.browse(cr, uid, taxes, context=context)
384 total = voucher_amount
387 if not tax[0].price_include:
388 for line in voucher.line_ids:
389 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
390 total_tax += tax_line.get('amount', 0.0)
393 for line in voucher.line_ids:
397 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
398 line_tax += tax_line.get('amount', 0.0)
399 line_total += tax_line.get('price_unit')
400 total_tax += line_tax
401 untax_amount = line.untax_amount or line.amount
402 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
404 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
407 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
408 context = context or {}
409 tax_pool = self.pool.get('account.tax')
410 partner_pool = self.pool.get('res.partner')
411 position_pool = self.pool.get('account.fiscal.position')
412 line_pool = self.pool.get('account.voucher.line')
419 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
422 for line in line_ids:
424 line_amount = line.get('amount',0.0)
427 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
429 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
430 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
431 tax = tax_pool.browse(cr, uid, taxes, context=context)
433 if not tax[0].price_include:
434 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
435 total_tax += tax_line.get('amount')
437 voucher_total += line_amount
438 total = voucher_total + total_tax
441 'amount': total or voucher_total,
442 'tax_amount': total_tax
448 def onchange_term_id(self, cr, uid, ids, term_id, amount):
449 term_pool = self.pool.get('account.payment.term')
452 default = {'date_due':False}
453 if term_id and amount:
454 terms = term_pool.compute(cr, uid, term_id, amount)
456 due_date = terms[-1][0]
460 return {'value':default}
462 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):
464 Returns a dict that contains new values and context
466 @param partner_id: latest value from user input for field partner_id
467 @param args: other arguments
468 @param context: context arguments, like lang, time zone
470 @return: Returns a dict which contains new values, and context
476 if not partner_id or not journal_id:
479 partner_pool = self.pool.get('res.partner')
480 journal_pool = self.pool.get('account.journal')
482 journal = journal_pool.browse(cr, uid, journal_id, context=context)
483 partner = partner_pool.browse(cr, uid, partner_id, context=context)
486 if journal.type in ('sale','sale_refund'):
487 account_id = partner.property_account_receivable.id
489 elif journal.type in ('purchase', 'purchase_refund','expense'):
490 account_id = partner.property_account_payable.id
493 if not journal.default_credit_account_id or not journal.default_debit_account_id:
494 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
495 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
498 default['value']['account_id'] = account_id
499 default['value']['type'] = ttype or tr_type
501 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)
502 default['value'].update(vals.get('value'))
506 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
507 res = {'value': {'paid_amount_in_company_currency': amount}}
508 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
509 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
510 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
511 if company_currency.id == payment_rate_currency_id:
514 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
515 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
518 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):
521 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
523 ctx.update({'date': date})
524 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
525 for key in vals.keys():
526 res[key].update(vals[key])
529 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
532 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
533 currency_obj = self.pool.get('res.currency')
534 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
535 company_id = journal.company_id.id
537 payment_rate_currency_id = currency_id
539 ctx.update({'date': date})
541 if ttype == 'receipt':
542 o2m_to_loop = 'line_cr_ids'
543 elif ttype == 'payment':
544 o2m_to_loop = 'line_dr_ids'
545 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
546 for voucher_line in vals['value'][o2m_to_loop]:
547 if voucher_line['currency_id'] != currency_id:
548 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
549 # is not in the voucher currency
550 payment_rate_currency_id = voucher_line['currency_id']
551 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
552 voucher_currency_id = currency_id or journal.company_id.currency_id.id
553 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
555 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
556 for key in res.keys():
557 vals[key].update(res[key])
558 vals['value'].update({'payment_rate': payment_rate})
559 if payment_rate_currency_id:
560 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
563 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
566 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
567 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
568 for key in vals.keys():
569 res[key].update(vals[key])
570 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
571 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
572 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
573 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
574 # onchange returns a value for them
576 del(res['value']['line_dr_ids'])
577 del(res['value']['pre_line'])
578 del(res['value']['payment_rate'])
579 elif ttype == 'purchase':
580 del(res['value']['line_cr_ids'])
581 del(res['value']['pre_line'])
582 del(res['value']['payment_rate'])
585 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
587 Returns a dict that contains new values and context
589 @param partner_id: latest value from user input for field partner_id
590 @param args: other arguments
591 @param context: context arguments, like lang, time zone
593 @return: Returns a dict which contains new values, and context
595 def _remove_noise_in_o2m():
596 """if the line is partially reconciled, then we must pay attention to display it only once and
598 This function returns True if the line is considered as noise and should not be displayed
600 if line.reconcile_partial_id:
601 sign = 1 if ttype in ['payment', 'receipt'] else -1
602 if currency_id == line.currency_id.id:
603 if line.amount_residual_currency * sign <= 0:
606 if line.amount_residual * sign <= 0:
612 context_multi_currency = context.copy()
614 context_multi_currency.update({'date': date})
616 currency_pool = self.pool.get('res.currency')
617 move_line_pool = self.pool.get('account.move.line')
618 partner_pool = self.pool.get('res.partner')
619 journal_pool = self.pool.get('account.journal')
620 line_pool = self.pool.get('account.voucher.line')
624 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
628 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
630 line_pool.unlink(cr, uid, line_ids)
632 if not partner_id or not journal_id:
635 journal = journal_pool.browse(cr, uid, journal_id, context=context)
636 partner = partner_pool.browse(cr, uid, partner_id, context=context)
637 currency_id = currency_id or journal.company_id.currency_id.id
639 if journal.type in ('sale','sale_refund'):
640 account_id = partner.property_account_receivable.id
641 elif journal.type in ('purchase', 'purchase_refund','expense'):
642 account_id = partner.property_account_payable.id
644 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
646 default['value']['account_id'] = account_id
648 if journal.type not in ('cash', 'bank'):
653 account_type = 'receivable'
654 if ttype == 'payment':
655 account_type = 'payable'
656 total_debit = price or 0.0
658 total_credit = price or 0.0
659 account_type = 'receivable'
661 if not context.get('move_line_ids', False):
662 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
664 ids = context['move_line_ids']
665 invoice_id = context.get('invoice_id', False)
666 company_currency = journal.company_id.currency_id.id
667 move_line_found = False
669 #order the lines by most old first
671 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
673 #compute the total debit/credit and look for a matching open amount or invoice
674 for line in account_move_lines:
675 if _remove_noise_in_o2m():
679 if line.invoice.id == invoice_id:
680 #if the invoice linked to the voucher line is equal to the invoice_id in context
681 #then we assign the amount on that line, whatever the other voucher lines
682 move_line_found = line.id
684 elif currency_id == company_currency:
685 #otherwise treatments is the same but with other field names
686 if line.amount_residual == price:
687 #if the amount residual is equal the amount voucher, we assign it to that voucher
688 #line, whatever the other voucher lines
689 move_line_found = line.id
691 #otherwise we will split the voucher amount on each line (by most old first)
692 total_credit += line.credit or 0.0
693 total_debit += line.debit or 0.0
694 elif currency_id == line.currency_id.id:
695 if line.amount_residual_currency == price:
696 move_line_found = line.id
698 total_credit += line.credit and line.amount_currency or 0.0
699 total_debit += line.debit and line.amount_currency or 0.0
701 #voucher line creation
702 for line in account_move_lines:
704 if _remove_noise_in_o2m():
707 if line.currency_id and currency_id==line.currency_id.id:
708 amount_original = abs(line.amount_currency)
709 amount_unreconciled = abs(line.amount_residual_currency)
711 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
712 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
713 line_currency_id = line.currency_id and line.currency_id.id or company_currency
715 'name':line.move_id.name,
716 'type': line.credit and 'dr' or 'cr',
717 'move_line_id':line.id,
718 'account_id':line.account_id.id,
719 'amount_original': amount_original,
720 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
721 'date_original':line.date,
722 'date_due':line.date_maturity,
723 'amount_unreconciled': amount_unreconciled,
724 'currency_id': line_currency_id,
726 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
727 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
728 if not move_line_found:
729 if currency_id == line_currency_id:
731 amount = min(amount_unreconciled, abs(total_debit))
732 rs['amount'] = amount
733 total_debit -= amount
735 amount = min(amount_unreconciled, abs(total_credit))
736 rs['amount'] = amount
737 total_credit -= amount
739 if rs['amount_unreconciled'] == rs['amount']:
740 rs['reconcile'] = True
742 if rs['type'] == 'cr':
743 default['value']['line_cr_ids'].append(rs)
745 default['value']['line_dr_ids'].append(rs)
747 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
748 default['value']['pre_line'] = 1
749 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
750 default['value']['pre_line'] = 1
751 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
754 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
758 #set the default payment rate of the voucher and compute the paid amount in company currency
759 if currency_id and currency_id == payment_rate_currency_id:
761 ctx.update({'date': date})
762 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
763 for key in vals.keys():
764 res[key].update(vals[key])
767 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
769 @param date: latest value from user input for field date
770 @param args: other arguments
771 @param context: context arguments, like lang, time zone
772 @return: Returns a dict which contains new values, and context
777 #set the period of the voucher
778 period_pool = self.pool.get('account.period')
779 currency_obj = self.pool.get('res.currency')
781 ctx.update({'company_id': company_id})
782 pids = period_pool.find(cr, uid, date, context=ctx)
784 res['value'].update({'period_id':pids[0]})
785 if payment_rate_currency_id:
786 ctx.update({'date': date})
788 if payment_rate_currency_id != currency_id:
789 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
790 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
791 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
792 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
793 vals['value'].update({'payment_rate': payment_rate})
794 for key in vals.keys():
795 res[key].update(vals[key])
798 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
801 journal_pool = self.pool.get('account.journal')
802 journal = journal_pool.browse(cr, uid, journal_id, context=context)
803 account_id = journal.default_credit_account_id or journal.default_debit_account_id
805 if account_id and account_id.tax_ids:
806 tax_id = account_id.tax_ids[0].id
809 if ttype in ('sale', 'purchase'):
810 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
811 vals['value'].update({'tax_id':tax_id,'amount': amount})
814 currency_id = journal.currency.id
815 vals['value'].update({'currency_id': currency_id})
816 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
817 for key in res.keys():
818 vals[key].update(res[key])
821 def button_proforma_voucher(self, cr, uid, ids, context=None):
822 context = context or {}
823 wf_service = netsvc.LocalService("workflow")
825 wf_service.trg_validate(uid, 'account.voucher', vid, 'proforma_voucher', cr)
826 return {'type': 'ir.actions.act_window_close'}
828 def proforma_voucher(self, cr, uid, ids, context=None):
829 self.action_move_line_create(cr, uid, ids, context=context)
832 def action_cancel_draft(self, cr, uid, ids, context=None):
833 wf_service = netsvc.LocalService("workflow")
834 for voucher_id in ids:
835 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
836 self.write(cr, uid, ids, {'state':'draft'})
839 def cancel_voucher(self, cr, uid, ids, context=None):
840 reconcile_pool = self.pool.get('account.move.reconcile')
841 move_pool = self.pool.get('account.move')
843 for voucher in self.browse(cr, uid, ids, context=context):
845 for line in voucher.move_ids:
846 if line.reconcile_id:
847 recs += [line.reconcile_id.id]
848 if line.reconcile_partial_id:
849 recs += [line.reconcile_partial_id.id]
851 reconcile_pool.unlink(cr, uid, recs)
854 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
855 move_pool.unlink(cr, uid, [voucher.move_id.id])
860 self.write(cr, uid, ids, res)
863 def unlink(self, cr, uid, ids, context=None):
864 for t in self.read(cr, uid, ids, ['state'], context=context):
865 if t['state'] not in ('draft', 'cancel'):
866 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
867 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
869 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
873 res = {'account_id':False}
874 partner_pool = self.pool.get('res.partner')
875 journal_pool = self.pool.get('account.journal')
876 if pay_now == 'pay_later':
877 partner = partner_pool.browse(cr, uid, partner_id)
878 journal = journal_pool.browse(cr, uid, journal_id)
879 if journal.type in ('sale','sale_refund'):
880 account_id = partner.property_account_receivable.id
881 elif journal.type in ('purchase', 'purchase_refund','expense'):
882 account_id = partner.property_account_payable.id
884 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
885 res['account_id'] = account_id
888 def _sel_context(self, cr, uid, voucher_id, context=None):
890 Select the context to use accordingly if it needs to be multicurrency or not.
892 :param voucher_id: Id of the actual voucher
893 :return: The returned context will be the same as given in parameter if the voucher currency is the same
894 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
895 the date of the voucher.
898 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
899 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
900 if current_currency <> company_currency:
901 context_multi_currency = context.copy()
902 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
903 context_multi_currency.update({'date': voucher_brw.date})
904 return context_multi_currency
907 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
909 Return a dict to be use to create the first account move line of given voucher.
911 :param voucher_id: Id of voucher what we are creating account_move.
912 :param move_id: Id of account move where this line will be added.
913 :param company_currency: id of currency of the company to which the voucher belong
914 :param current_currency: id of currency of the voucher
915 :return: mapping between fieldname and value of account move line to create
918 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
920 # TODO: is there any other alternative then the voucher type ??
921 # ANSWER: We can have payment and receipt "In Advance".
922 # TODO: Make this logic available.
923 # -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
924 if voucher_brw.type in ('purchase', 'payment'):
925 credit = voucher_brw.paid_amount_in_company_currency
926 elif voucher_brw.type in ('sale', 'receipt'):
927 debit = voucher_brw.paid_amount_in_company_currency
928 if debit < 0: credit = -debit; debit = 0.0
929 if credit < 0: debit = -credit; credit = 0.0
930 sign = debit - credit < 0 and -1 or 1
931 #set the first line of the voucher
933 'name': voucher_brw.name or '/',
936 'account_id': voucher_brw.account_id.id,
938 'journal_id': voucher_brw.journal_id.id,
939 'period_id': voucher_brw.period_id.id,
940 'partner_id': voucher_brw.partner_id.id,
941 'currency_id': company_currency <> current_currency and current_currency or False,
942 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
943 'date': voucher_brw.date,
944 'date_maturity': voucher_brw.date_due
948 def account_move_get(self, cr, uid, voucher_id, context=None):
950 This method prepare the creation of the account move related to the given voucher.
952 :param voucher_id: Id of voucher for which we are creating account_move.
953 :return: mapping between fieldname and value of account move to create
956 seq_obj = self.pool.get('ir.sequence')
957 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
958 if voucher_brw.number:
959 name = voucher_brw.number
960 elif voucher_brw.journal_id.sequence_id:
961 if not voucher_brw.journal_id.sequence_id.active:
962 raise osv.except_osv(_('Configuration Error !'),
963 _('Please activate the sequence of selected journal !'))
964 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
966 raise osv.except_osv(_('Error!'),
967 _('Please define a sequence on the journal.'))
968 if not voucher_brw.reference:
969 ref = name.replace('/','')
971 ref = voucher_brw.reference
975 'journal_id': voucher_brw.journal_id.id,
976 'narration': voucher_brw.narration,
977 'date': voucher_brw.date,
979 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
983 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
985 Prepare the two lines in company currency due to currency rate difference.
987 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
989 :param move_id: Account move wher the move lines will be.
990 :param amount_residual: Amount to be posted.
991 :param company_currency: id of currency of the company to which the voucher belong
992 :param current_currency: id of currency of the voucher
993 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
994 :rtype: tuple of dict
996 if amount_residual > 0:
997 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
999 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."))
1001 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1003 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."))
1004 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1005 # the receivable/payable account may have a secondary currency, which render this field mandatory
1006 account_currency_id = company_currency <> current_currency and current_currency or False
1008 'journal_id': line.voucher_id.journal_id.id,
1009 'period_id': line.voucher_id.period_id.id,
1010 'name': _('change')+': '+(line.name or '/'),
1011 'account_id': line.account_id.id,
1013 'partner_id': line.voucher_id.partner_id.id,
1014 'currency_id': account_currency_id,
1015 'amount_currency': 0.0,
1017 'credit': amount_residual > 0 and amount_residual or 0.0,
1018 'debit': amount_residual < 0 and -amount_residual or 0.0,
1019 'date': line.voucher_id.date,
1021 move_line_counterpart = {
1022 'journal_id': line.voucher_id.journal_id.id,
1023 'period_id': line.voucher_id.period_id.id,
1024 'name': _('change')+': '+(line.name or '/'),
1025 'account_id': account_id.id,
1027 'amount_currency': 0.0,
1028 'partner_id': line.voucher_id.partner_id.id,
1029 'currency_id': account_currency_id,
1031 'debit': amount_residual > 0 and amount_residual or 0.0,
1032 'credit': amount_residual < 0 and -amount_residual or 0.0,
1033 'date': line.voucher_id.date,
1035 return (move_line, move_line_counterpart)
1037 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1039 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1040 payment_rate_currency_id is relevant) either the rate encoded in the system.
1042 :param amount: float. The amount to convert
1043 :param voucher: id of the voucher on which we want the conversion
1044 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1045 field in order to select the good rate to use.
1046 :return: the amount in the currency of the voucher's company
1049 currency_obj = self.pool.get('res.currency')
1050 voucher = self.browse(cr, uid, voucher_id, context=context)
1052 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1053 # the rate specified on the voucher is for the company currency
1054 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1056 # the rate specified on the voucher is not relevant, we use all the rates in the system
1057 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1060 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1062 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1063 It returns Tuple with tot_line what is total of difference between debit and credit and
1064 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1066 :param voucher_id: Voucher id what we are working with
1067 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1068 :param move_id: Account move wher those lines will be joined.
1069 :param company_currency: id of currency of the company to which the voucher belong
1070 :param current_currency: id of currency of the voucher
1071 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1072 :rtype: tuple(float, list of int)
1076 move_line_obj = self.pool.get('account.move.line')
1077 currency_obj = self.pool.get('res.currency')
1078 tax_obj = self.pool.get('account.tax')
1079 tot_line = line_total
1082 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1083 ctx = context.copy()
1084 ctx.update({'date': voucher_brw.date})
1085 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1086 for line in voucher_brw.line_ids:
1087 #create one move line per voucher line where amount is not 0.0
1088 #The second part of the condition handle cases were payment/receipt has an amount of 0
1089 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)):
1091 # convert the amount set on the voucher line into the currency of the voucher's company
1092 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1093 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1094 # currency rate difference
1095 if line.amount == line.amount_unreconciled:
1096 if not line.move_line_id:
1097 raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1098 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1099 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1101 currency_rate_difference = 0.0
1103 'journal_id': voucher_brw.journal_id.id,
1104 'period_id': voucher_brw.period_id.id,
1105 'name': line.name or '/',
1106 'account_id': line.account_id.id,
1108 'partner_id': voucher_brw.partner_id.id,
1109 '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,
1110 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1114 'date': voucher_brw.date
1118 if line.type == 'dr':
1123 if (line.type=='dr'):
1125 move_line['debit'] = amount
1128 move_line['credit'] = amount
1130 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1132 'account_tax_id': voucher_brw.tax_id.id,
1135 if move_line.get('account_tax_id', False):
1136 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1137 if not (tax_data.base_code_id and tax_data.tax_code_id):
1138 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))
1140 # compute the amount in foreign currency
1141 foreign_currency_diff = 0.0
1142 amount_currency = False
1143 if line.move_line_id:
1144 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1145 # We want to set it on the account move line as soon as the original line had a foreign currency
1146 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1147 # we compute the amount in that foreign currency.
1148 if line.move_line_id.currency_id.id == current_currency:
1149 # if the voucher and the voucher line share the same currency, there is no computation to do
1150 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1151 amount_currency = sign * (line.amount)
1152 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1153 # if the rate is specified on the voucher, we must use it
1154 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1155 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1157 # otherwise we use the rates of the system (giving the voucher date in the context)
1158 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1159 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1160 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1161 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1163 move_line['amount_currency'] = amount_currency
1164 voucher_line = move_line_obj.create(cr, uid, move_line)
1165 rec_ids = [voucher_line, line.move_line_id.id]
1167 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1168 # Change difference entry in company currency
1169 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1170 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1171 move_line_obj.create(cr, uid, exch_lines[1], context)
1172 rec_ids.append(new_id)
1174 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):
1175 # Change difference entry in voucher currency
1176 move_line_foreign_currency = {
1177 'journal_id': line.voucher_id.journal_id.id,
1178 'period_id': line.voucher_id.period_id.id,
1179 'name': _('change')+': '+(line.name or '/'),
1180 'account_id': line.account_id.id,
1182 'partner_id': line.voucher_id.partner_id.id,
1183 'currency_id': line.move_line_id.currency_id.id,
1184 'amount_currency': -1 * foreign_currency_diff,
1188 'date': line.voucher_id.date,
1190 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1191 rec_ids.append(new_id)
1193 if line.move_line_id.id:
1194 rec_lst_ids.append(rec_ids)
1196 return (tot_line, rec_lst_ids)
1198 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1200 Set a dict to be use to create the writeoff move line.
1202 :param voucher_id: Id of voucher what we are creating account_move.
1203 :param line_total: Amount remaining to be allocated on lines.
1204 :param move_id: Id of account move where this line will be added.
1205 :param name: Description of account move line.
1206 :param company_currency: id of currency of the company to which the voucher belong
1207 :param current_currency: id of currency of the voucher
1208 :return: mapping between fieldname and value of account move line to create
1211 currency_obj = self.pool.get('res.currency')
1214 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1215 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1217 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1221 if voucher_brw.payment_option == 'with_writeoff':
1222 account_id = voucher_brw.writeoff_acc_id.id
1223 write_off_name = voucher_brw.comment
1224 elif voucher_brw.type in ('sale', 'receipt'):
1225 account_id = voucher_brw.partner_id.property_account_receivable.id
1227 account_id = voucher_brw.partner_id.property_account_payable.id
1228 sign = voucher_brw.type == 'payment' and -1 or 1
1230 'name': write_off_name or name,
1231 'account_id': account_id,
1233 'partner_id': voucher_brw.partner_id.id,
1234 'date': voucher_brw.date,
1235 'credit': diff > 0 and diff or 0.0,
1236 'debit': diff < 0 and -diff or 0.0,
1237 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1238 'currency_id': company_currency <> current_currency and current_currency or False,
1239 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1244 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1246 Get the currency of the actual company.
1248 :param voucher_id: Id of the voucher what i want to obtain company currency.
1249 :return: currency id of the company of the voucher
1252 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1254 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1256 Get the currency of the voucher.
1258 :param voucher_id: Id of the voucher what i want to obtain current currency.
1259 :return: currency id of the voucher
1262 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1263 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1265 def action_move_line_create(self, cr, uid, ids, context=None):
1267 Confirm the vouchers given in ids and create the journal entries for each of them
1271 move_pool = self.pool.get('account.move')
1272 move_line_pool = self.pool.get('account.move.line')
1273 for voucher in self.browse(cr, uid, ids, context=context):
1276 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1277 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1278 # we select the context to use accordingly if it's a multicurrency case or not
1279 context = self._sel_context(cr, uid, voucher.id, context)
1280 # But for the operations made by _convert_amount, we always need to give the date in the context
1281 ctx = context.copy()
1282 ctx.update({'date': voucher.date})
1283 # Create the account move record.
1284 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1285 # Get the name of the account_move just created
1286 name = move_pool.browse(cr, uid, move_id, context=context).name
1287 # Create the first line of the voucher
1288 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)
1289 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1290 line_total = move_line_brw.debit - move_line_brw.credit
1292 if voucher.type == 'sale':
1293 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1294 elif voucher.type == 'purchase':
1295 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1296 # Create one move line per voucher line where amount is not 0.0
1297 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1299 # Create the writeoff line if needed
1300 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1302 move_line_pool.create(cr, uid, ml_writeoff, context)
1303 # We post the voucher.
1304 self.write(cr, uid, [voucher.id], {
1309 if voucher.journal_id.entry_posted:
1310 move_pool.post(cr, uid, [move_id], context={})
1311 # We automatically reconcile the account move lines.
1313 for rec_ids in rec_list_ids:
1314 if len(rec_ids) >= 2:
1315 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)
1318 def copy(self, cr, uid, id, default=None, context=None):
1325 'line_cr_ids': False,
1326 'line_dr_ids': False,
1329 if 'date' not in default:
1330 default['date'] = time.strftime('%Y-%m-%d')
1331 return super(account_voucher, self).copy(cr, uid, id, default, context)
1334 class account_voucher_line(osv.osv):
1335 _name = 'account.voucher.line'
1336 _description = 'Voucher Lines'
1337 _order = "move_line_id"
1339 # If the payment is in the same currency than the invoice, we keep the same amount
1340 # Otherwise, we compute from company currency to payment currency
1341 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1342 currency_pool = self.pool.get('res.currency')
1344 for line in self.browse(cr, uid, ids, context=context):
1345 ctx = context.copy()
1346 ctx.update({'date': line.voucher_id.date})
1348 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1349 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1350 move_line = line.move_line_id or False
1353 res['amount_original'] = 0.0
1354 res['amount_unreconciled'] = 0.0
1355 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1356 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1357 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)
1358 elif move_line and move_line.credit > 0:
1359 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1360 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1362 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1363 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1365 rs_data[line.id] = res
1368 def _currency_id(self, cr, uid, ids, name, args, context=None):
1370 This function returns the currency id of a voucher line. It's either the currency of the
1371 associated move line (if any) or the currency of the voucher or the company currency.
1374 for line in self.browse(cr, uid, ids, context=context):
1375 move_line = line.move_line_id
1377 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1379 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1383 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1384 'name':fields.char('Description', size=256),
1385 'account_id':fields.many2one('account.account','Account', required=True),
1386 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1387 'untax_amount':fields.float('Untax Amount'),
1388 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1389 'reconcile': fields.boolean('Full Reconcile'),
1390 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1391 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1392 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1393 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1394 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1395 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1396 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1397 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1398 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1404 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1405 vals = {'amount': 0.0}
1407 vals = { 'amount': amount_unreconciled}
1408 return {'value': vals}
1410 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1413 vals['reconcile'] = (amount == amount_unreconciled)
1414 return {'value': vals}
1416 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1418 Returns a dict that contains new values and context
1420 @param move_line_id: latest value from user input for field move_line_id
1421 @param args: other arguments
1422 @param context: context arguments, like lang, time zone
1424 @return: Returns a dict which contains new values, and context
1427 move_line_pool = self.pool.get('account.move.line')
1429 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1430 if move_line.credit:
1435 'account_id': move_line.account_id.id,
1437 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1443 def default_get(self, cr, user, fields_list, context=None):
1445 Returns default values for fields
1446 @param fields_list: list of fields, for which default values are required to be read
1447 @param context: context arguments, like lang, time zone
1449 @return: Returns a dict that contains default values for fields
1453 journal_id = context.get('journal_id', False)
1454 partner_id = context.get('partner_id', False)
1455 journal_pool = self.pool.get('account.journal')
1456 partner_pool = self.pool.get('res.partner')
1457 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1458 if (not journal_id) or ('account_id' not in fields_list):
1460 journal = journal_pool.browse(cr, user, journal_id, context=context)
1463 if journal.type in ('sale', 'sale_refund'):
1464 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1466 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1467 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1470 partner = partner_pool.browse(cr, user, partner_id, context=context)
1471 if context.get('type') == 'payment':
1473 account_id = partner.property_account_payable.id
1474 elif context.get('type') == 'receipt':
1475 account_id = partner.property_account_receivable.id
1478 'account_id':account_id,
1482 account_voucher_line()
1484 class account_bank_statement(osv.osv):
1485 _inherit = 'account.bank.statement'
1487 def button_confirm_bank(self, cr, uid, ids, context=None):
1488 voucher_obj = self.pool.get('account.voucher')
1490 for statement in self.browse(cr, uid, ids, context=context):
1491 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1493 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1494 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1496 def button_cancel(self, cr, uid, ids, context=None):
1497 voucher_obj = self.pool.get('account.voucher')
1498 for st in self.browse(cr, uid, ids, context=context):
1500 for line in st.line_ids:
1502 voucher_ids.append(line.voucher_id.id)
1503 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1504 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1506 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1507 voucher_obj = self.pool.get('account.voucher')
1508 wf_service = netsvc.LocalService("workflow")
1509 move_line_obj = self.pool.get('account.move.line')
1510 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1511 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1512 if st_line.voucher_id:
1513 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1514 if st_line.voucher_id.state == 'cancel':
1515 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1516 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1518 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1519 bank_st_line_obj.write(cr, uid, [st_line_id], {
1520 'move_ids': [(4, v.move_id.id, False)]
1523 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1524 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1526 def write(self, cr, uid, ids, vals, context=None):
1527 # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1528 # Because the voucher keeps in memory the journal it was created with.
1529 for bk_st in self.browse(cr, uid, ids, context=context):
1530 if vals.get('journal_id') and bk_st.line_ids:
1531 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1532 raise osv.except_osv(_('Unable to change journal !'), _('You can not change the journal as you already reconciled some statement lines!'))
1533 return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1535 account_bank_statement()
1537 class account_bank_statement_line(osv.osv):
1538 _inherit = 'account.bank.statement.line'
1540 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1541 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1542 if 'value' not in res:
1544 res['value'].update({'voucher_id' : False})
1547 def onchange_amount(self, cr, uid, ids, amount, context=None):
1548 return {'value' : {'voucher_id' : False}}
1550 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1554 for line in self.browse(cursor, user, ids, context=context):
1556 res[line.id] = line.voucher_id.amount#
1561 def _check_amount(self, cr, uid, ids, context=None):
1562 for obj in self.browse(cr, uid, ids, context=context):
1564 diff = abs(obj.amount) - obj.voucher_id.amount
1565 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1570 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1574 'amount_reconciled': fields.function(_amount_reconciled,
1575 string='Amount reconciled', type='float'),
1576 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1579 def unlink(self, cr, uid, ids, context=None):
1580 voucher_obj = self.pool.get('account.voucher')
1581 statement_line = self.browse(cr, uid, ids, context=context)
1583 for st_line in statement_line:
1584 if st_line.voucher_id:
1585 unlink_ids.append(st_line.voucher_id.id)
1586 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1587 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1589 account_bank_statement_line()
1591 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1593 for operation in operations:
1595 if not isinstance(operation, (list, tuple)):
1596 result = target_osv.read(cr, uid, operation, fields, context=context)
1597 elif operation[0] == 0:
1598 # may be necessary to check if all the fields are here and get the default values?
1599 result = operation[2]
1600 elif operation[0] == 1:
1601 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1602 if not result: result = {}
1603 result.update(operation[2])
1604 elif operation[0] == 4:
1605 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1607 results.append(result)
1611 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: