1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 from lxml import etree
25 from openerp.osv import fields, osv
26 import openerp.addons.decimal_precision as dp
27 from openerp.tools.translate import _
28 from openerp.tools import float_compare
30 class res_company(osv.osv):
31 _inherit = "res.company"
33 'income_currency_exchange_account_id': fields.many2one(
35 string="Gain Exchange Rate Account",
36 domain="[('type', '=', 'other')]",),
37 'expense_currency_exchange_account_id': fields.many2one(
39 string="Loss Exchange Rate Account",
40 domain="[('type', '=', 'other')]",),
44 class account_config_settings(osv.osv_memory):
45 _inherit = 'account.config.settings'
47 'income_currency_exchange_account_id': fields.related(
48 'company_id', 'income_currency_exchange_account_id',
50 relation='account.account',
51 string="Gain Exchange Rate Account",
52 domain="[('type', '=', 'other')]"),
53 'expense_currency_exchange_account_id': fields.related(
54 'company_id', 'expense_currency_exchange_account_id',
56 relation='account.account',
57 string="Loss Exchange Rate Account",
58 domain="[('type', '=', 'other')]"),
60 def onchange_company_id(self, cr, uid, ids, company_id, context=None):
61 res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id, context=context)
63 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
64 res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False,
65 'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False})
67 res['value'].update({'income_currency_exchange_account_id': False,
68 'expense_currency_exchange_account_id': False})
71 class account_voucher(osv.osv):
72 def _check_paid(self, cr, uid, ids, name, args, context=None):
74 for voucher in self.browse(cr, uid, ids, context=context):
75 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
78 def _get_type(self, cr, uid, context=None):
81 return context.get('type', False)
83 def _get_period(self, cr, uid, context=None):
84 if context is None: context = {}
85 if context.get('period_id', False):
86 return context.get('period_id')
87 periods = self.pool.get('account.period').find(cr, uid, context=context)
88 return periods and periods[0] or False
90 def _make_journal_search(self, cr, uid, ttype, context=None):
91 journal_pool = self.pool.get('account.journal')
92 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
94 def _get_journal(self, cr, uid, context=None):
95 if context is None: context = {}
96 invoice_pool = self.pool.get('account.invoice')
97 journal_pool = self.pool.get('account.journal')
98 if context.get('invoice_id', False):
99 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
100 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
101 return journal_id and journal_id[0] or False
102 if context.get('journal_id', False):
103 return context.get('journal_id')
104 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
105 return context.get('search_default_journal_id')
107 ttype = context.get('type', 'bank')
108 if ttype in ('payment', 'receipt'):
110 res = self._make_journal_search(cr, uid, ttype, context=context)
111 return res and res[0] or False
113 def _get_tax(self, cr, uid, context=None):
114 if context is None: context = {}
115 journal_pool = self.pool.get('account.journal')
116 journal_id = context.get('journal_id', False)
118 ttype = context.get('type', 'bank')
119 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
126 journal = journal_pool.browse(cr, uid, journal_id, context=context)
127 account_id = journal.default_credit_account_id or journal.default_debit_account_id
128 if account_id and account_id.tax_ids:
129 tax_id = account_id.tax_ids[0].id
133 def _get_payment_rate_currency(self, cr, uid, context=None):
135 Return the default value for field payment_rate_currency_id: the currency of the journal
136 if there is one, otherwise the currency of the user's company
138 if context is None: context = {}
139 journal_pool = self.pool.get('account.journal')
140 journal_id = context.get('journal_id', False)
142 journal = journal_pool.browse(cr, uid, journal_id, context=context)
144 return journal.currency.id
145 #no journal given in the context, use company currency as default
146 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
148 def _get_currency(self, cr, uid, context=None):
149 if context is None: context = {}
150 journal_pool = self.pool.get('account.journal')
151 journal_id = context.get('journal_id', False)
153 journal = journal_pool.browse(cr, uid, journal_id, context=context)
155 return journal.currency.id
158 def _get_partner(self, cr, uid, context=None):
159 if context is None: context = {}
160 return context.get('partner_id', False)
162 def _get_reference(self, cr, uid, context=None):
163 if context is None: context = {}
164 return context.get('reference', False)
166 def _get_narration(self, cr, uid, context=None):
167 if context is None: context = {}
168 return context.get('narration', False)
170 def _get_amount(self, cr, uid, context=None):
173 return context.get('amount', 0.0)
175 def name_get(self, cr, uid, ids, context=None):
178 if context is None: context = {}
179 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
181 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
182 mod_obj = self.pool.get('ir.model.data')
183 if context is None: context = {}
185 if view_type == 'form':
186 if not view_id and context.get('invoice_type'):
187 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
188 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
190 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
191 result = result and result[1] or False
193 if not view_id and context.get('line_type'):
194 if context.get('line_type') == 'customer':
195 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
197 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
198 result = result and result[1] or False
201 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
202 doc = etree.XML(res['arch'])
204 if context.get('type', 'sale') in ('purchase', 'payment'):
205 nodes = doc.xpath("//field[@name='partner_id']")
207 node.set('context', "{'search_default_supplier': 1}")
208 if context.get('invoice_type','') in ('in_invoice', 'in_refund'):
209 node.set('string', _("Supplier"))
210 res['arch'] = etree.tostring(doc)
213 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
215 sign = type == 'payment' and -1 or 1
216 for l in line_dr_ids:
218 for l in line_cr_ids:
219 credit += l['amount']
220 return amount - sign * (credit - debit)
222 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
223 context = context or {}
224 if not line_dr_ids and not line_cr_ids:
226 line_osv = self.pool.get("account.voucher.line")
227 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
228 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
230 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
231 is_multi_currency = False
233 # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
234 is_multi_currency = True
236 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
237 for voucher_line in line_dr_ids+line_cr_ids:
238 company_currency = False
239 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
240 if voucher_line.get('currency_id', company_currency) != company_currency:
241 is_multi_currency = True
243 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
245 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
246 if not ids: return {}
247 currency_obj = self.pool.get('res.currency')
250 for voucher in self.browse(cr, uid, ids, context=context):
251 sign = voucher.type == 'payment' and -1 or 1
252 for l in voucher.line_dr_ids:
254 for l in voucher.line_cr_ids:
256 currency = voucher.currency_id or voucher.company_id.currency_id
257 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
260 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
261 if not ids: return {}
264 for voucher in self.browse(cr, uid, ids, context=context):
265 if voucher.currency_id:
266 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
267 rate = 1 / voucher.payment_rate
270 ctx.update({'date': voucher.date})
271 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
272 company_currency_rate = voucher.company_id.currency_id.rate
273 rate = voucher_rate * company_currency_rate
274 res[voucher.id] = voucher.amount / rate
277 _name = 'account.voucher'
278 _description = 'Accounting Voucher'
279 _inherit = ['mail.thread']
280 _order = "date desc, id desc"
281 # _rec_name = 'number'
284 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
289 '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."),
290 'type':fields.selection([
292 ('purchase','Purchase'),
293 ('payment','Payment'),
294 ('receipt','Receipt'),
295 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
296 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
297 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
298 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
299 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
300 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
301 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
302 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
303 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
304 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
305 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
306 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
307 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
308 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
309 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
310 'state':fields.selection(
312 ('cancel','Cancelled'),
313 ('proforma','Pro-forma'),
315 ], 'Status', readonly=True, size=32, track_visibility='onchange',
316 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
317 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
318 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
319 \n* The \'Cancelled\' status is used when user cancel voucher.'),
320 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
321 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
322 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
323 'number': fields.char('Number', size=32, readonly=True,),
324 'move_id':fields.many2one('account.move', 'Account Entry'),
325 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
326 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
327 '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'),
328 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
329 'pay_now':fields.selection([
330 ('pay_now','Pay Directly'),
331 ('pay_later','Pay Later or Group Funds'),
332 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
333 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
334 'pre_line':fields.boolean('Previous Payments ?', required=False),
335 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
336 'payment_option':fields.selection([
337 ('without_writeoff', 'Keep Open'),
338 ('with_writeoff', 'Reconcile Payment Balance'),
339 ], '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)"),
340 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
341 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
342 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
343 '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."),
344 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
345 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
346 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
347 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
348 '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'),
352 'period_id': _get_period,
353 'partner_id': _get_partner,
354 'journal_id':_get_journal,
355 'currency_id': _get_currency,
356 'reference': _get_reference,
357 'narration':_get_narration,
358 'amount': _get_amount,
361 'pay_now': 'pay_now',
363 'date': lambda *a: time.strftime('%Y-%m-%d'),
364 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
366 'payment_option': 'without_writeoff',
367 'comment': _('Write-Off'),
369 'payment_rate_currency_id': _get_payment_rate_currency,
372 def compute_tax(self, cr, uid, ids, context=None):
373 tax_pool = self.pool.get('account.tax')
374 partner_pool = self.pool.get('res.partner')
375 position_pool = self.pool.get('account.fiscal.position')
376 voucher_line_pool = self.pool.get('account.voucher.line')
377 voucher_pool = self.pool.get('account.voucher')
378 if context is None: context = {}
380 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
382 for line in voucher.line_ids:
383 voucher_amount += line.untax_amount or line.amount
384 line.amount = line.untax_amount or line.amount
385 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
387 if not voucher.tax_id:
388 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
391 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
392 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
393 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
394 tax = tax_pool.browse(cr, uid, taxes, context=context)
396 total = voucher_amount
399 if not tax[0].price_include:
400 for line in voucher.line_ids:
401 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
402 total_tax += tax_line.get('amount', 0.0)
405 for line in voucher.line_ids:
409 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
410 line_tax += tax_line.get('amount', 0.0)
411 line_total += tax_line.get('price_unit')
412 total_tax += line_tax
413 untax_amount = line.untax_amount or line.amount
414 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
416 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
419 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
420 context = context or {}
421 tax_pool = self.pool.get('account.tax')
422 partner_pool = self.pool.get('res.partner')
423 position_pool = self.pool.get('account.fiscal.position')
424 line_pool = self.pool.get('account.voucher.line')
431 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
434 for line in line_ids:
436 line_amount = line.get('amount',0.0)
439 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
441 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
442 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
443 tax = tax_pool.browse(cr, uid, taxes, context=context)
445 if not tax[0].price_include:
446 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
447 total_tax += tax_line.get('amount')
449 voucher_total += line_amount
450 total = voucher_total + total_tax
453 'amount': total or voucher_total,
454 'tax_amount': total_tax
460 def onchange_term_id(self, cr, uid, ids, term_id, amount):
461 term_pool = self.pool.get('account.payment.term')
464 default = {'date_due':False}
465 if term_id and amount:
466 terms = term_pool.compute(cr, uid, term_id, amount)
468 due_date = terms[-1][0]
472 return {'value':default}
474 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):
476 Returns a dict that contains new values and context
478 @param partner_id: latest value from user input for field partner_id
479 @param args: other arguments
480 @param context: context arguments, like lang, time zone
482 @return: Returns a dict which contains new values, and context
488 if not partner_id or not journal_id:
491 partner_pool = self.pool.get('res.partner')
492 journal_pool = self.pool.get('account.journal')
494 journal = journal_pool.browse(cr, uid, journal_id, context=context)
495 partner = partner_pool.browse(cr, uid, partner_id, context=context)
498 if journal.type in ('sale','sale_refund'):
499 account_id = partner.property_account_receivable.id
501 elif journal.type in ('purchase', 'purchase_refund','expense'):
502 account_id = partner.property_account_payable.id
505 if not journal.default_credit_account_id or not journal.default_debit_account_id:
506 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
507 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
510 default['value']['account_id'] = account_id
511 default['value']['type'] = ttype or tr_type
513 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)
514 default['value'].update(vals.get('value'))
518 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
519 res = {'value': {'paid_amount_in_company_currency': amount}}
520 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
521 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
522 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
523 if company_currency.id == payment_rate_currency_id:
526 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
527 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
530 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):
533 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
535 ctx.update({'date': date})
536 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
537 for key in vals.keys():
538 res[key].update(vals[key])
541 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
544 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
545 currency_obj = self.pool.get('res.currency')
546 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
547 company_id = journal.company_id.id
549 payment_rate_currency_id = currency_id
551 ctx.update({'date': date})
553 if ttype == 'receipt':
554 o2m_to_loop = 'line_cr_ids'
555 elif ttype == 'payment':
556 o2m_to_loop = 'line_dr_ids'
557 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
558 for voucher_line in vals['value'][o2m_to_loop]:
559 if voucher_line['currency_id'] != currency_id:
560 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
561 # is not in the voucher currency
562 payment_rate_currency_id = voucher_line['currency_id']
563 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
564 voucher_currency_id = currency_id or journal.company_id.currency_id.id
565 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
567 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
568 for key in res.keys():
569 vals[key].update(res[key])
570 vals['value'].update({'payment_rate': payment_rate})
571 if payment_rate_currency_id:
572 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
575 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
578 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
579 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
580 for key in vals.keys():
581 res[key].update(vals[key])
582 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
583 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
584 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
585 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
586 # onchange returns a value for them
588 del(res['value']['line_dr_ids'])
589 del(res['value']['pre_line'])
590 del(res['value']['payment_rate'])
591 elif ttype == 'purchase':
592 del(res['value']['line_cr_ids'])
593 del(res['value']['pre_line'])
594 del(res['value']['payment_rate'])
597 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
599 Returns a dict that contains new values and context
601 @param partner_id: latest value from user input for field partner_id
602 @param args: other arguments
603 @param context: context arguments, like lang, time zone
605 @return: Returns a dict which contains new values, and context
607 def _remove_noise_in_o2m():
608 """if the line is partially reconciled, then we must pay attention to display it only once and
610 This function returns True if the line is considered as noise and should not be displayed
612 if line.reconcile_partial_id:
613 if currency_id == line.currency_id.id:
614 if line.amount_residual_currency <= 0:
617 if line.amount_residual <= 0:
623 context_multi_currency = context.copy()
625 context_multi_currency.update({'date': date})
627 currency_pool = self.pool.get('res.currency')
628 move_line_pool = self.pool.get('account.move.line')
629 partner_pool = self.pool.get('res.partner')
630 journal_pool = self.pool.get('account.journal')
631 line_pool = self.pool.get('account.voucher.line')
635 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
639 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
641 line_pool.unlink(cr, uid, line_ids)
643 if not partner_id or not journal_id:
646 journal = journal_pool.browse(cr, uid, journal_id, context=context)
647 partner = partner_pool.browse(cr, uid, partner_id, context=context)
648 currency_id = currency_id or journal.company_id.currency_id.id
650 if journal.type in ('sale','sale_refund'):
651 account_id = partner.property_account_receivable.id
652 elif journal.type in ('purchase', 'purchase_refund','expense'):
653 account_id = partner.property_account_payable.id
655 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
657 default['value']['account_id'] = account_id
659 if journal.type not in ('cash', 'bank'):
664 account_type = 'receivable'
665 if ttype == 'payment':
666 account_type = 'payable'
667 total_debit = price or 0.0
669 total_credit = price or 0.0
670 account_type = 'receivable'
672 if not context.get('move_line_ids', False):
673 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
675 ids = context['move_line_ids']
676 invoice_id = context.get('invoice_id', False)
677 company_currency = journal.company_id.currency_id.id
678 move_line_found = False
680 #order the lines by most old first
682 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
684 #compute the total debit/credit and look for a matching open amount or invoice
685 for line in account_move_lines:
686 if _remove_noise_in_o2m():
690 if line.invoice.id == invoice_id:
691 #if the invoice linked to the voucher line is equal to the invoice_id in context
692 #then we assign the amount on that line, whatever the other voucher lines
693 move_line_found = line.id
695 elif currency_id == company_currency:
696 #otherwise treatments is the same but with other field names
697 if line.amount_residual == price:
698 #if the amount residual is equal the amount voucher, we assign it to that voucher
699 #line, whatever the other voucher lines
700 move_line_found = line.id
702 #otherwise we will split the voucher amount on each line (by most old first)
703 total_credit += line.credit or 0.0
704 total_debit += line.debit or 0.0
705 elif currency_id == line.currency_id.id:
706 if line.amount_residual_currency == price:
707 move_line_found = line.id
709 total_credit += line.credit and line.amount_currency or 0.0
710 total_debit += line.debit and line.amount_currency or 0.0
712 #voucher line creation
713 for line in account_move_lines:
715 if _remove_noise_in_o2m():
718 if line.currency_id and currency_id==line.currency_id.id:
719 amount_original = abs(line.amount_currency)
720 amount_unreconciled = abs(line.amount_residual_currency)
722 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
723 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
724 line_currency_id = line.currency_id and line.currency_id.id or company_currency
726 'name':line.move_id.name,
727 'type': line.credit and 'dr' or 'cr',
728 'move_line_id':line.id,
729 'account_id':line.account_id.id,
730 'amount_original': amount_original,
731 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
732 'date_original':line.date,
733 'date_due':line.date_maturity,
734 'amount_unreconciled': amount_unreconciled,
735 'currency_id': line_currency_id,
737 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
738 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
739 if not move_line_found:
740 if currency_id == line_currency_id:
742 amount = min(amount_unreconciled, abs(total_debit))
743 rs['amount'] = amount
744 total_debit -= amount
746 amount = min(amount_unreconciled, abs(total_credit))
747 rs['amount'] = amount
748 total_credit -= amount
750 if rs['amount_unreconciled'] == rs['amount']:
751 rs['reconcile'] = True
753 if rs['type'] == 'cr':
754 default['value']['line_cr_ids'].append(rs)
756 default['value']['line_dr_ids'].append(rs)
758 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
759 default['value']['pre_line'] = 1
760 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
761 default['value']['pre_line'] = 1
762 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
765 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
769 #set the default payment rate of the voucher and compute the paid amount in company currency
770 if currency_id and currency_id == payment_rate_currency_id:
772 ctx.update({'date': date})
773 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
774 for key in vals.keys():
775 res[key].update(vals[key])
778 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
780 @param date: latest value from user input for field date
781 @param args: other arguments
782 @param context: context arguments, like lang, time zone
783 @return: Returns a dict which contains new values, and context
788 #set the period of the voucher
789 period_pool = self.pool.get('account.period')
790 currency_obj = self.pool.get('res.currency')
792 ctx.update({'company_id': company_id})
793 pids = period_pool.find(cr, uid, date, context=ctx)
795 res['value'].update({'period_id':pids[0]})
796 if payment_rate_currency_id:
797 ctx.update({'date': date})
799 if payment_rate_currency_id != currency_id:
800 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
801 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
802 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
803 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
804 vals['value'].update({'payment_rate': payment_rate})
805 for key in vals.keys():
806 res[key].update(vals[key])
809 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
812 journal_pool = self.pool.get('account.journal')
813 journal = journal_pool.browse(cr, uid, journal_id, context=context)
814 account_id = journal.default_credit_account_id or journal.default_debit_account_id
816 if account_id and account_id.tax_ids:
817 tax_id = account_id.tax_ids[0].id
820 if ttype in ('sale', 'purchase'):
821 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
822 vals['value'].update({'tax_id':tax_id,'amount': amount})
825 currency_id = journal.currency.id
826 vals['value'].update({'currency_id': currency_id})
827 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
828 for key in res.keys():
829 vals[key].update(res[key])
832 def button_proforma_voucher(self, cr, uid, ids, context=None):
833 self.signal_proforma_voucher(cr, uid, ids)
834 return {'type': 'ir.actions.act_window_close'}
836 def proforma_voucher(self, cr, uid, ids, context=None):
837 self.action_move_line_create(cr, uid, ids, context=context)
840 def action_cancel_draft(self, cr, uid, ids, context=None):
841 self.create_workflow(cr, uid, ids)
842 self.write(cr, uid, ids, {'state':'draft'})
845 def cancel_voucher(self, cr, uid, ids, context=None):
846 reconcile_pool = self.pool.get('account.move.reconcile')
847 move_pool = self.pool.get('account.move')
849 for voucher in self.browse(cr, uid, ids, context=context):
851 for line in voucher.move_ids:
852 if line.reconcile_id:
853 recs += [line.reconcile_id.id]
854 if line.reconcile_partial_id:
855 recs += [line.reconcile_partial_id.id]
857 reconcile_pool.unlink(cr, uid, recs)
860 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
861 move_pool.unlink(cr, uid, [voucher.move_id.id])
866 self.write(cr, uid, ids, res)
869 def unlink(self, cr, uid, ids, context=None):
870 for t in self.read(cr, uid, ids, ['state'], context=context):
871 if t['state'] not in ('draft', 'cancel'):
872 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
873 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
875 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
879 res = {'account_id':False}
880 partner_pool = self.pool.get('res.partner')
881 journal_pool = self.pool.get('account.journal')
882 if pay_now == 'pay_later':
883 partner = partner_pool.browse(cr, uid, partner_id)
884 journal = journal_pool.browse(cr, uid, journal_id)
885 if journal.type in ('sale','sale_refund'):
886 account_id = partner.property_account_receivable.id
887 elif journal.type in ('purchase', 'purchase_refund','expense'):
888 account_id = partner.property_account_payable.id
890 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
891 res['account_id'] = account_id
894 def _sel_context(self, cr, uid, voucher_id, context=None):
896 Select the context to use accordingly if it needs to be multicurrency or not.
898 :param voucher_id: Id of the actual voucher
899 :return: The returned context will be the same as given in parameter if the voucher currency is the same
900 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
901 the date of the voucher.
904 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
905 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
906 if current_currency <> company_currency:
907 context_multi_currency = context.copy()
908 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
909 context_multi_currency.update({'date': voucher_brw.date})
910 return context_multi_currency
913 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
915 Return a dict to be use to create the first account move line of given voucher.
917 :param voucher_id: Id of voucher what we are creating account_move.
918 :param move_id: Id of account move where this line will be added.
919 :param company_currency: id of currency of the company to which the voucher belong
920 :param current_currency: id of currency of the voucher
921 :return: mapping between fieldname and value of account move line to create
924 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
926 # TODO: is there any other alternative then the voucher type ??
927 # ANSWER: We can have payment and receipt "In Advance".
928 # TODO: Make this logic available.
929 # -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
930 if voucher_brw.type in ('purchase', 'payment'):
931 credit = voucher_brw.paid_amount_in_company_currency
932 elif voucher_brw.type in ('sale', 'receipt'):
933 debit = voucher_brw.paid_amount_in_company_currency
934 if debit < 0: credit = -debit; debit = 0.0
935 if credit < 0: debit = -credit; credit = 0.0
936 sign = debit - credit < 0 and -1 or 1
937 #set the first line of the voucher
939 'name': voucher_brw.name or '/',
942 'account_id': voucher_brw.account_id.id,
944 'journal_id': voucher_brw.journal_id.id,
945 'period_id': voucher_brw.period_id.id,
946 'partner_id': voucher_brw.partner_id.id,
947 'currency_id': company_currency <> current_currency and current_currency or False,
948 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
949 'date': voucher_brw.date,
950 'date_maturity': voucher_brw.date_due
954 def account_move_get(self, cr, uid, voucher_id, context=None):
956 This method prepare the creation of the account move related to the given voucher.
958 :param voucher_id: Id of voucher for which we are creating account_move.
959 :return: mapping between fieldname and value of account move to create
962 seq_obj = self.pool.get('ir.sequence')
963 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
964 if voucher_brw.number:
965 name = voucher_brw.number
966 elif voucher_brw.journal_id.sequence_id:
967 if not voucher_brw.journal_id.sequence_id.active:
968 raise osv.except_osv(_('Configuration Error!'),
969 _('Please activate the sequence of selected journal !'))
971 c.update({'fiscalyear_id': voucher_brw.period_id.fiscalyear_id.id})
972 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=c)
974 raise osv.except_osv(_('Error!'),
975 _('Please define a sequence on the journal.'))
976 if not voucher_brw.reference:
977 ref = name.replace('/','')
979 ref = voucher_brw.reference
983 'journal_id': voucher_brw.journal_id.id,
984 'narration': voucher_brw.narration,
985 'date': voucher_brw.date,
987 'period_id': voucher_brw.period_id.id,
991 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
993 Prepare the two lines in company currency due to currency rate difference.
995 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
997 :param move_id: Account move wher the move lines will be.
998 :param amount_residual: Amount to be posted.
999 :param company_currency: id of currency of the company to which the voucher belong
1000 :param current_currency: id of currency of the voucher
1001 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
1002 :rtype: tuple of dict
1004 if amount_residual > 0:
1005 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1007 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."))
1009 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1011 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."))
1012 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1013 # the receivable/payable account may have a secondary currency, which render this field mandatory
1014 account_currency_id = company_currency <> current_currency and current_currency or False
1016 'journal_id': line.voucher_id.journal_id.id,
1017 'period_id': line.voucher_id.period_id.id,
1018 'name': _('change')+': '+(line.name or '/'),
1019 'account_id': line.account_id.id,
1021 'partner_id': line.voucher_id.partner_id.id,
1022 'currency_id': account_currency_id,
1023 'amount_currency': 0.0,
1025 'credit': amount_residual > 0 and amount_residual or 0.0,
1026 'debit': amount_residual < 0 and -amount_residual or 0.0,
1027 'date': line.voucher_id.date,
1029 move_line_counterpart = {
1030 'journal_id': line.voucher_id.journal_id.id,
1031 'period_id': line.voucher_id.period_id.id,
1032 'name': _('change')+': '+(line.name or '/'),
1033 'account_id': account_id.id,
1035 'amount_currency': 0.0,
1036 'partner_id': line.voucher_id.partner_id.id,
1037 'currency_id': account_currency_id,
1039 'debit': amount_residual > 0 and amount_residual or 0.0,
1040 'credit': amount_residual < 0 and -amount_residual or 0.0,
1041 'date': line.voucher_id.date,
1043 return (move_line, move_line_counterpart)
1045 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1047 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1048 payment_rate_currency_id is relevant) either the rate encoded in the system.
1050 :param amount: float. The amount to convert
1051 :param voucher: id of the voucher on which we want the conversion
1052 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1053 field in order to select the good rate to use.
1054 :return: the amount in the currency of the voucher's company
1057 currency_obj = self.pool.get('res.currency')
1058 voucher = self.browse(cr, uid, voucher_id, context=context)
1060 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1061 # the rate specified on the voucher is for the company currency
1062 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1064 # the rate specified on the voucher is not relevant, we use all the rates in the system
1065 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1068 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1070 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1071 It returns Tuple with tot_line what is total of difference between debit and credit and
1072 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1074 :param voucher_id: Voucher id what we are working with
1075 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1076 :param move_id: Account move wher those lines will be joined.
1077 :param company_currency: id of currency of the company to which the voucher belong
1078 :param current_currency: id of currency of the voucher
1079 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1080 :rtype: tuple(float, list of int)
1084 move_line_obj = self.pool.get('account.move.line')
1085 currency_obj = self.pool.get('res.currency')
1086 tax_obj = self.pool.get('account.tax')
1087 tot_line = line_total
1090 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1091 ctx = context.copy()
1092 ctx.update({'date': voucher_brw.date})
1093 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1094 for line in voucher_brw.line_ids:
1095 #create one move line per voucher line where amount is not 0.0
1096 # AND (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
1097 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)):
1099 # convert the amount set on the voucher line into the currency of the voucher's company
1100 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1101 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1102 # currency rate difference
1103 if line.amount == line.amount_unreconciled:
1104 if not line.move_line_id:
1105 raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1106 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1107 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1109 currency_rate_difference = 0.0
1111 'journal_id': voucher_brw.journal_id.id,
1112 'period_id': voucher_brw.period_id.id,
1113 'name': line.name or '/',
1114 'account_id': line.account_id.id,
1116 'partner_id': voucher_brw.partner_id.id,
1117 '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,
1118 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1122 'date': voucher_brw.date
1126 if line.type == 'dr':
1131 if (line.type=='dr'):
1133 move_line['debit'] = amount
1136 move_line['credit'] = amount
1138 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1140 'account_tax_id': voucher_brw.tax_id.id,
1143 if move_line.get('account_tax_id', False):
1144 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1145 if not (tax_data.base_code_id and tax_data.tax_code_id):
1146 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))
1148 # compute the amount in foreign currency
1149 foreign_currency_diff = 0.0
1150 amount_currency = False
1151 if line.move_line_id:
1152 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1153 # We want to set it on the account move line as soon as the original line had a foreign currency
1154 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1155 # we compute the amount in that foreign currency.
1156 if line.move_line_id.currency_id.id == current_currency:
1157 # if the voucher and the voucher line share the same currency, there is no computation to do
1158 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1159 amount_currency = sign * (line.amount)
1160 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1161 # if the rate is specified on the voucher, we must use it
1162 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1163 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1165 # otherwise we use the rates of the system (giving the voucher date in the context)
1166 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1167 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1168 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1169 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1171 move_line['amount_currency'] = amount_currency
1172 voucher_line = move_line_obj.create(cr, uid, move_line)
1173 rec_ids = [voucher_line, line.move_line_id.id]
1175 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1176 # Change difference entry in company currency
1177 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1178 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1179 move_line_obj.create(cr, uid, exch_lines[1], context)
1180 rec_ids.append(new_id)
1182 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):
1183 # Change difference entry in voucher currency
1184 move_line_foreign_currency = {
1185 'journal_id': line.voucher_id.journal_id.id,
1186 'period_id': line.voucher_id.period_id.id,
1187 'name': _('change')+': '+(line.name or '/'),
1188 'account_id': line.account_id.id,
1190 'partner_id': line.voucher_id.partner_id.id,
1191 'currency_id': line.move_line_id.currency_id.id,
1192 'amount_currency': -1 * foreign_currency_diff,
1196 'date': line.voucher_id.date,
1198 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1199 rec_ids.append(new_id)
1201 if line.move_line_id.id:
1202 rec_lst_ids.append(rec_ids)
1204 return (tot_line, rec_lst_ids)
1206 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1208 Set a dict to be use to create the writeoff move line.
1210 :param voucher_id: Id of voucher what we are creating account_move.
1211 :param line_total: Amount remaining to be allocated on lines.
1212 :param move_id: Id of account move where this line will be added.
1213 :param name: Description of account move line.
1214 :param company_currency: id of currency of the company to which the voucher belong
1215 :param current_currency: id of currency of the voucher
1216 :return: mapping between fieldname and value of account move line to create
1219 currency_obj = self.pool.get('res.currency')
1222 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1223 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1225 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1229 if voucher_brw.payment_option == 'with_writeoff':
1230 account_id = voucher_brw.writeoff_acc_id.id
1231 write_off_name = voucher_brw.comment
1232 elif voucher_brw.type in ('sale', 'receipt'):
1233 account_id = voucher_brw.partner_id.property_account_receivable.id
1235 account_id = voucher_brw.partner_id.property_account_payable.id
1236 sign = voucher_brw.type == 'payment' and -1 or 1
1238 'name': write_off_name or name,
1239 'account_id': account_id,
1241 'partner_id': voucher_brw.partner_id.id,
1242 'date': voucher_brw.date,
1243 'credit': diff > 0 and diff or 0.0,
1244 'debit': diff < 0 and -diff or 0.0,
1245 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1246 'currency_id': company_currency <> current_currency and current_currency or False,
1247 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1252 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1254 Get the currency of the actual company.
1256 :param voucher_id: Id of the voucher what i want to obtain company currency.
1257 :return: currency id of the company of the voucher
1260 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1262 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1264 Get the currency of the voucher.
1266 :param voucher_id: Id of the voucher what i want to obtain current currency.
1267 :return: currency id of the voucher
1270 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1271 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1273 def action_move_line_create(self, cr, uid, ids, context=None):
1275 Confirm the vouchers given in ids and create the journal entries for each of them
1279 move_pool = self.pool.get('account.move')
1280 move_line_pool = self.pool.get('account.move.line')
1281 for voucher in self.browse(cr, uid, ids, context=context):
1284 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1285 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1286 # we select the context to use accordingly if it's a multicurrency case or not
1287 context = self._sel_context(cr, uid, voucher.id, context)
1288 # But for the operations made by _convert_amount, we always need to give the date in the context
1289 ctx = context.copy()
1290 ctx.update({'date': voucher.date})
1291 # Create the account move record.
1292 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1293 # Get the name of the account_move just created
1294 name = move_pool.browse(cr, uid, move_id, context=context).name
1295 # Create the first line of the voucher
1296 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)
1297 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1298 line_total = move_line_brw.debit - move_line_brw.credit
1300 if voucher.type == 'sale':
1301 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1302 elif voucher.type == 'purchase':
1303 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1304 # Create one move line per voucher line where amount is not 0.0
1305 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1307 # Create the writeoff line if needed
1308 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1310 move_line_pool.create(cr, uid, ml_writeoff, context)
1311 # We post the voucher.
1312 self.write(cr, uid, [voucher.id], {
1317 if voucher.journal_id.entry_posted:
1318 move_pool.post(cr, uid, [move_id], context={})
1319 # We automatically reconcile the account move lines.
1321 for rec_ids in rec_list_ids:
1322 if len(rec_ids) >= 2:
1323 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)
1326 def copy(self, cr, uid, id, default=None, context=None):
1333 'line_cr_ids': False,
1334 'line_dr_ids': False,
1337 if 'date' not in default:
1338 default['date'] = time.strftime('%Y-%m-%d')
1339 return super(account_voucher, self).copy(cr, uid, id, default, context)
1342 class account_voucher_line(osv.osv):
1343 _name = 'account.voucher.line'
1344 _description = 'Voucher Lines'
1345 _order = "move_line_id"
1347 # If the payment is in the same currency than the invoice, we keep the same amount
1348 # Otherwise, we compute from company currency to payment currency
1349 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1350 currency_pool = self.pool.get('res.currency')
1352 for line in self.browse(cr, uid, ids, context=context):
1353 ctx = context.copy()
1354 ctx.update({'date': line.voucher_id.date})
1356 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1357 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1358 move_line = line.move_line_id or False
1361 res['amount_original'] = 0.0
1362 res['amount_unreconciled'] = 0.0
1363 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1364 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1365 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)
1366 elif move_line and move_line.credit > 0:
1367 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1368 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1370 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1371 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1373 rs_data[line.id] = res
1376 def _currency_id(self, cr, uid, ids, name, args, context=None):
1378 This function returns the currency id of a voucher line. It's either the currency of the
1379 associated move line (if any) or the currency of the voucher or the company currency.
1382 for line in self.browse(cr, uid, ids, context=context):
1383 move_line = line.move_line_id
1385 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1387 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1391 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1392 'name':fields.char('Description', size=256),
1393 'account_id':fields.many2one('account.account','Account', required=True),
1394 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1395 'untax_amount':fields.float('Untax Amount'),
1396 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1397 'reconcile': fields.boolean('Full Reconcile'),
1398 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1399 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1400 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1401 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1402 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1403 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1404 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1405 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1406 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1412 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1413 vals = {'amount': 0.0}
1415 vals = { 'amount': amount_unreconciled}
1416 return {'value': vals}
1418 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1421 vals['reconcile'] = (amount == amount_unreconciled)
1422 return {'value': vals}
1424 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1426 Returns a dict that contains new values and context
1428 @param move_line_id: latest value from user input for field move_line_id
1429 @param args: other arguments
1430 @param context: context arguments, like lang, time zone
1432 @return: Returns a dict which contains new values, and context
1435 move_line_pool = self.pool.get('account.move.line')
1437 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1438 if move_line.credit:
1443 'account_id': move_line.account_id.id,
1445 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1451 def default_get(self, cr, user, fields_list, context=None):
1453 Returns default values for fields
1454 @param fields_list: list of fields, for which default values are required to be read
1455 @param context: context arguments, like lang, time zone
1457 @return: Returns a dict that contains default values for fields
1461 journal_id = context.get('journal_id', False)
1462 partner_id = context.get('partner_id', False)
1463 journal_pool = self.pool.get('account.journal')
1464 partner_pool = self.pool.get('res.partner')
1465 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1466 if (not journal_id) or ('account_id' not in fields_list):
1468 journal = journal_pool.browse(cr, user, journal_id, context=context)
1471 if journal.type in ('sale', 'sale_refund'):
1472 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1474 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1475 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1478 partner = partner_pool.browse(cr, user, partner_id, context=context)
1479 if context.get('type') == 'payment':
1481 account_id = partner.property_account_payable.id
1482 elif context.get('type') == 'receipt':
1483 account_id = partner.property_account_receivable.id
1486 'account_id':account_id,
1491 class account_bank_statement(osv.osv):
1492 _inherit = 'account.bank.statement'
1494 def button_confirm_bank(self, cr, uid, ids, context=None):
1495 voucher_obj = self.pool.get('account.voucher')
1497 for statement in self.browse(cr, uid, ids, context=context):
1498 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1500 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1501 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1503 def button_cancel(self, cr, uid, ids, context=None):
1504 voucher_obj = self.pool.get('account.voucher')
1505 for st in self.browse(cr, uid, ids, context=context):
1507 for line in st.line_ids:
1509 voucher_ids.append(line.voucher_id.id)
1510 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1511 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1513 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1514 voucher_obj = self.pool.get('account.voucher')
1515 move_line_obj = self.pool.get('account.move.line')
1516 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1517 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1518 if st_line.voucher_id:
1519 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1520 if st_line.voucher_id.state == 'cancel':
1521 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1522 voucher_obj.signal_proforma_voucher(cr, uid, [st_line.voucher_id.id])
1524 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1525 bank_st_line_obj.write(cr, uid, [st_line_id], {
1526 'move_ids': [(4, v.move_id.id, False)]
1529 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1530 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1532 def write(self, cr, uid, ids, vals, context=None):
1533 # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1534 # Because the voucher keeps in memory the journal it was created with.
1535 for bk_st in self.browse(cr, uid, ids, context=context):
1536 if vals.get('journal_id') and bk_st.line_ids:
1537 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1538 raise osv.except_osv(_('Unable to Change Journal!'), _('You can not change the journal as you already reconciled some statement lines!'))
1539 return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1542 class account_bank_statement_line(osv.osv):
1543 _inherit = 'account.bank.statement.line'
1545 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1546 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1547 if 'value' not in res:
1549 res['value'].update({'voucher_id' : False})
1552 def onchange_amount(self, cr, uid, ids, amount, context=None):
1553 return {'value' : {'voucher_id' : False}}
1555 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1559 for line in self.browse(cursor, user, ids, context=context):
1561 res[line.id] = line.voucher_id.amount#
1566 def _check_amount(self, cr, uid, ids, context=None):
1567 for obj in self.browse(cr, uid, ids, context=context):
1569 diff = abs(obj.amount) - obj.voucher_id.amount
1570 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1575 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1579 'amount_reconciled': fields.function(_amount_reconciled,
1580 string='Amount reconciled', type='float'),
1581 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1584 def unlink(self, cr, uid, ids, context=None):
1585 voucher_obj = self.pool.get('account.voucher')
1586 statement_line = self.browse(cr, uid, ids, context=context)
1588 for st_line in statement_line:
1589 if st_line.voucher_id:
1590 unlink_ids.append(st_line.voucher_id.id)
1591 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1592 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1595 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1597 for operation in operations:
1599 if not isinstance(operation, (list, tuple)):
1600 result = target_osv.read(cr, uid, operation, fields, context=context)
1601 elif operation[0] == 0:
1602 # may be necessary to check if all the fields are here and get the default values?
1603 result = operation[2]
1604 elif operation[0] == 1:
1605 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1606 if not result: result = {}
1607 result.update(operation[2])
1608 elif operation[0] == 4:
1609 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1611 results.append(result)
1615 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: