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 for line in voucher_brw.line_ids:
1086 #create one move line per voucher line where amount is not 0.0
1087 if not line.amount and not float_compare(line.move_line_id.invoice.amount_total,0.0,precision_rounding=0.0):
1089 # convert the amount set on the voucher line into the currency of the voucher's company
1090 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1091 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1092 # currency rate difference
1093 if line.amount == line.amount_unreconciled:
1094 if not line.move_line_id:
1095 raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1096 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1097 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1099 currency_rate_difference = 0.0
1101 'journal_id': voucher_brw.journal_id.id,
1102 'period_id': voucher_brw.period_id.id,
1103 'name': line.name or '/',
1104 'account_id': line.account_id.id,
1106 'partner_id': voucher_brw.partner_id.id,
1107 '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,
1108 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1112 'date': voucher_brw.date
1116 if line.type == 'dr':
1121 if (line.type=='dr'):
1123 move_line['debit'] = amount
1126 move_line['credit'] = amount
1128 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1130 'account_tax_id': voucher_brw.tax_id.id,
1133 if move_line.get('account_tax_id', False):
1134 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1135 if not (tax_data.base_code_id and tax_data.tax_code_id):
1136 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))
1138 # compute the amount in foreign currency
1139 foreign_currency_diff = 0.0
1140 amount_currency = False
1141 if line.move_line_id:
1142 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1143 # We want to set it on the account move line as soon as the original line had a foreign currency
1144 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1145 # we compute the amount in that foreign currency.
1146 if line.move_line_id.currency_id.id == current_currency:
1147 # if the voucher and the voucher line share the same currency, there is no computation to do
1148 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1149 amount_currency = sign * (line.amount)
1150 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1151 # if the rate is specified on the voucher, we must use it
1152 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1153 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1155 # otherwise we use the rates of the system (giving the voucher date in the context)
1156 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1157 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1158 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1159 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1161 move_line['amount_currency'] = amount_currency
1162 voucher_line = move_line_obj.create(cr, uid, move_line)
1163 rec_ids = [voucher_line, line.move_line_id.id]
1165 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1166 # Change difference entry in company currency
1167 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1168 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1169 move_line_obj.create(cr, uid, exch_lines[1], context)
1170 rec_ids.append(new_id)
1172 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):
1173 # Change difference entry in voucher currency
1174 move_line_foreign_currency = {
1175 'journal_id': line.voucher_id.journal_id.id,
1176 'period_id': line.voucher_id.period_id.id,
1177 'name': _('change')+': '+(line.name or '/'),
1178 'account_id': line.account_id.id,
1180 'partner_id': line.voucher_id.partner_id.id,
1181 'currency_id': line.move_line_id.currency_id.id,
1182 'amount_currency': -1 * foreign_currency_diff,
1186 'date': line.voucher_id.date,
1188 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1189 rec_ids.append(new_id)
1191 if line.move_line_id.id:
1192 rec_lst_ids.append(rec_ids)
1194 return (tot_line, rec_lst_ids)
1196 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1198 Set a dict to be use to create the writeoff move line.
1200 :param voucher_id: Id of voucher what we are creating account_move.
1201 :param line_total: Amount remaining to be allocated on lines.
1202 :param move_id: Id of account move where this line will be added.
1203 :param name: Description of account move line.
1204 :param company_currency: id of currency of the company to which the voucher belong
1205 :param current_currency: id of currency of the voucher
1206 :return: mapping between fieldname and value of account move line to create
1209 currency_obj = self.pool.get('res.currency')
1212 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1213 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1215 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1219 if voucher_brw.payment_option == 'with_writeoff':
1220 account_id = voucher_brw.writeoff_acc_id.id
1221 write_off_name = voucher_brw.comment
1222 elif voucher_brw.type in ('sale', 'receipt'):
1223 account_id = voucher_brw.partner_id.property_account_receivable.id
1225 account_id = voucher_brw.partner_id.property_account_payable.id
1226 sign = voucher_brw.type == 'payment' and -1 or 1
1228 'name': write_off_name or name,
1229 'account_id': account_id,
1231 'partner_id': voucher_brw.partner_id.id,
1232 'date': voucher_brw.date,
1233 'credit': diff > 0 and diff or 0.0,
1234 'debit': diff < 0 and -diff or 0.0,
1235 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1236 'currency_id': company_currency <> current_currency and current_currency or False,
1237 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1242 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1244 Get the currency of the actual company.
1246 :param voucher_id: Id of the voucher what i want to obtain company currency.
1247 :return: currency id of the company of the voucher
1250 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1252 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1254 Get the currency of the voucher.
1256 :param voucher_id: Id of the voucher what i want to obtain current currency.
1257 :return: currency id of the voucher
1260 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1261 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1263 def action_move_line_create(self, cr, uid, ids, context=None):
1265 Confirm the vouchers given in ids and create the journal entries for each of them
1269 move_pool = self.pool.get('account.move')
1270 move_line_pool = self.pool.get('account.move.line')
1271 for voucher in self.browse(cr, uid, ids, context=context):
1274 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1275 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1276 # we select the context to use accordingly if it's a multicurrency case or not
1277 context = self._sel_context(cr, uid, voucher.id, context)
1278 # But for the operations made by _convert_amount, we always need to give the date in the context
1279 ctx = context.copy()
1280 ctx.update({'date': voucher.date})
1281 # Create the account move record.
1282 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1283 # Get the name of the account_move just created
1284 name = move_pool.browse(cr, uid, move_id, context=context).name
1285 # Create the first line of the voucher
1286 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)
1287 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1288 line_total = move_line_brw.debit - move_line_brw.credit
1290 if voucher.type == 'sale':
1291 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1292 elif voucher.type == 'purchase':
1293 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1294 # Create one move line per voucher line where amount is not 0.0
1295 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1297 # Create the writeoff line if needed
1298 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1300 move_line_pool.create(cr, uid, ml_writeoff, context)
1301 # We post the voucher.
1302 self.write(cr, uid, [voucher.id], {
1307 if voucher.journal_id.entry_posted:
1308 move_pool.post(cr, uid, [move_id], context={})
1309 # We automatically reconcile the account move lines.
1311 for rec_ids in rec_list_ids:
1312 if len(rec_ids) >= 2:
1313 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)
1316 def copy(self, cr, uid, id, default=None, context=None):
1323 'line_cr_ids': False,
1324 'line_dr_ids': False,
1327 if 'date' not in default:
1328 default['date'] = time.strftime('%Y-%m-%d')
1329 return super(account_voucher, self).copy(cr, uid, id, default, context)
1332 class account_voucher_line(osv.osv):
1333 _name = 'account.voucher.line'
1334 _description = 'Voucher Lines'
1335 _order = "move_line_id"
1337 # If the payment is in the same currency than the invoice, we keep the same amount
1338 # Otherwise, we compute from company currency to payment currency
1339 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1340 currency_pool = self.pool.get('res.currency')
1342 for line in self.browse(cr, uid, ids, context=context):
1343 ctx = context.copy()
1344 ctx.update({'date': line.voucher_id.date})
1346 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1347 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1348 move_line = line.move_line_id or False
1351 res['amount_original'] = 0.0
1352 res['amount_unreconciled'] = 0.0
1353 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1354 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1355 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)
1356 elif move_line and move_line.credit > 0:
1357 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1358 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1360 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1361 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1363 rs_data[line.id] = res
1366 def _currency_id(self, cr, uid, ids, name, args, context=None):
1368 This function returns the currency id of a voucher line. It's either the currency of the
1369 associated move line (if any) or the currency of the voucher or the company currency.
1372 for line in self.browse(cr, uid, ids, context=context):
1373 move_line = line.move_line_id
1375 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1377 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1381 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1382 'name':fields.char('Description', size=256),
1383 'account_id':fields.many2one('account.account','Account', required=True),
1384 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1385 'untax_amount':fields.float('Untax Amount'),
1386 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1387 'reconcile': fields.boolean('Full Reconcile'),
1388 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1389 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1390 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1391 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1392 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1393 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1394 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1395 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1396 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1402 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1403 vals = {'amount': 0.0}
1405 vals = { 'amount': amount_unreconciled}
1406 return {'value': vals}
1408 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1411 vals['reconcile'] = (amount == amount_unreconciled)
1412 return {'value': vals}
1414 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1416 Returns a dict that contains new values and context
1418 @param move_line_id: latest value from user input for field move_line_id
1419 @param args: other arguments
1420 @param context: context arguments, like lang, time zone
1422 @return: Returns a dict which contains new values, and context
1425 move_line_pool = self.pool.get('account.move.line')
1427 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1428 if move_line.credit:
1433 'account_id': move_line.account_id.id,
1435 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1441 def default_get(self, cr, user, fields_list, context=None):
1443 Returns default values for fields
1444 @param fields_list: list of fields, for which default values are required to be read
1445 @param context: context arguments, like lang, time zone
1447 @return: Returns a dict that contains default values for fields
1451 journal_id = context.get('journal_id', False)
1452 partner_id = context.get('partner_id', False)
1453 journal_pool = self.pool.get('account.journal')
1454 partner_pool = self.pool.get('res.partner')
1455 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1456 if (not journal_id) or ('account_id' not in fields_list):
1458 journal = journal_pool.browse(cr, user, journal_id, context=context)
1461 if journal.type in ('sale', 'sale_refund'):
1462 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1464 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1465 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1468 partner = partner_pool.browse(cr, user, partner_id, context=context)
1469 if context.get('type') == 'payment':
1471 account_id = partner.property_account_payable.id
1472 elif context.get('type') == 'receipt':
1473 account_id = partner.property_account_receivable.id
1476 'account_id':account_id,
1480 account_voucher_line()
1482 class account_bank_statement(osv.osv):
1483 _inherit = 'account.bank.statement'
1485 def button_confirm_bank(self, cr, uid, ids, context=None):
1486 voucher_obj = self.pool.get('account.voucher')
1488 for statement in self.browse(cr, uid, ids, context=context):
1489 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1491 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1492 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1494 def button_cancel(self, cr, uid, ids, context=None):
1495 voucher_obj = self.pool.get('account.voucher')
1496 for st in self.browse(cr, uid, ids, context=context):
1498 for line in st.line_ids:
1500 voucher_ids.append(line.voucher_id.id)
1501 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1502 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1504 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1505 voucher_obj = self.pool.get('account.voucher')
1506 wf_service = netsvc.LocalService("workflow")
1507 move_line_obj = self.pool.get('account.move.line')
1508 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1509 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1510 if st_line.voucher_id:
1511 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1512 if st_line.voucher_id.state == 'cancel':
1513 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1514 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1516 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1517 bank_st_line_obj.write(cr, uid, [st_line_id], {
1518 'move_ids': [(4, v.move_id.id, False)]
1521 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1522 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1524 def write(self, cr, uid, ids, vals, context=None):
1525 # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1526 # Because the voucher keeps in memory the journal it was created with.
1527 for bk_st in self.browse(cr, uid, ids, context=context):
1528 if vals.get('journal_id') and bk_st.line_ids:
1529 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1530 raise osv.except_osv(_('Unable to change journal !'), _('You can not change the journal as you already reconciled some statement lines!'))
1531 return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1533 account_bank_statement()
1535 class account_bank_statement_line(osv.osv):
1536 _inherit = 'account.bank.statement.line'
1538 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1539 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1540 if 'value' not in res:
1542 res['value'].update({'voucher_id' : False})
1545 def onchange_amount(self, cr, uid, ids, amount, context=None):
1546 return {'value' : {'voucher_id' : False}}
1548 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1552 for line in self.browse(cursor, user, ids, context=context):
1554 res[line.id] = line.voucher_id.amount#
1559 def _check_amount(self, cr, uid, ids, context=None):
1560 for obj in self.browse(cr, uid, ids, context=context):
1562 diff = abs(obj.amount) - obj.voucher_id.amount
1563 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1568 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1572 'amount_reconciled': fields.function(_amount_reconciled,
1573 string='Amount reconciled', type='float'),
1574 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1577 def unlink(self, cr, uid, ids, context=None):
1578 voucher_obj = self.pool.get('account.voucher')
1579 statement_line = self.browse(cr, uid, ids, context=context)
1581 for st_line in statement_line:
1582 if st_line.voucher_id:
1583 unlink_ids.append(st_line.voucher_id.id)
1584 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1585 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1587 account_bank_statement_line()
1589 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1591 for operation in operations:
1593 if not isinstance(operation, (list, tuple)):
1594 result = target_osv.read(cr, uid, operation, fields, context=context)
1595 elif operation[0] == 0:
1596 # may be necessary to check if all the fields are here and get the default values?
1597 result = operation[2]
1598 elif operation[0] == 1:
1599 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1600 if not result: result = {}
1601 result.update(operation[2])
1602 elif operation[0] == 4:
1603 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1605 results.append(result)
1609 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: