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 _
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')]",),
45 class account_config_settings(osv.osv_memory):
46 _inherit = 'account.config.settings'
48 'income_currency_exchange_account_id': fields.related(
49 'company_id', 'income_currency_exchange_account_id',
51 relation='account.account',
52 string="Gain Exchange Rate Account"),
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"),
60 class account_voucher(osv.osv):
61 def _check_paid(self, cr, uid, ids, name, args, context=None):
63 for voucher in self.browse(cr, uid, ids, context=context):
64 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
67 def _get_type(self, cr, uid, context=None):
70 return context.get('type', False)
72 def _get_period(self, cr, uid, context=None):
73 if context is None: context = {}
74 if context.get('period_id', False):
75 return context.get('period_id')
76 periods = self.pool.get('account.period').find(cr, uid)
77 return periods and periods[0] or False
79 def _make_journal_search(self, cr, uid, ttype, context=None):
80 journal_pool = self.pool.get('account.journal')
81 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
83 def _get_journal(self, cr, uid, context=None):
84 if context is None: context = {}
85 invoice_pool = self.pool.get('account.invoice')
86 journal_pool = self.pool.get('account.journal')
87 if context.get('invoice_id', False):
88 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
89 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
90 return journal_id and journal_id[0] or False
91 if context.get('journal_id', False):
92 return context.get('journal_id')
93 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
94 return context.get('search_default_journal_id')
96 ttype = context.get('type', 'bank')
97 if ttype in ('payment', 'receipt'):
99 res = self._make_journal_search(cr, uid, ttype, context=context)
100 return res and res[0] or False
102 def _get_tax(self, cr, uid, context=None):
103 if context is None: context = {}
104 journal_pool = self.pool.get('account.journal')
105 journal_id = context.get('journal_id', False)
107 ttype = context.get('type', 'bank')
108 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
115 journal = journal_pool.browse(cr, uid, journal_id, context=context)
116 account_id = journal.default_credit_account_id or journal.default_debit_account_id
117 if account_id and account_id.tax_ids:
118 tax_id = account_id.tax_ids[0].id
122 def _get_payment_rate_currency(self, cr, uid, context=None):
124 Return the default value for field payment_rate_currency_id: the currency of the journal
125 if there is one, otherwise the currency of the user's company
127 if context is None: context = {}
128 journal_pool = self.pool.get('account.journal')
129 journal_id = context.get('journal_id', False)
131 journal = journal_pool.browse(cr, uid, journal_id, context=context)
133 return journal.currency.id
134 #no journal given in the context, use company currency as default
135 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
137 def _get_currency(self, cr, uid, context=None):
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
147 def _get_partner(self, cr, uid, context=None):
148 if context is None: context = {}
149 return context.get('partner_id', False)
151 def _get_reference(self, cr, uid, context=None):
152 if context is None: context = {}
153 return context.get('reference', False)
155 def _get_narration(self, cr, uid, context=None):
156 if context is None: context = {}
157 return context.get('narration', False)
159 def _get_amount(self, cr, uid, context=None):
162 return context.get('amount', 0.0)
164 def name_get(self, cr, uid, ids, context=None):
167 if context is None: context = {}
168 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
170 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
171 mod_obj = self.pool.get('ir.model.data')
172 if context is None: context = {}
174 if view_type == 'form':
175 if not view_id and context.get('invoice_type'):
176 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
177 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
179 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
180 result = result and result[1] or False
182 if not view_id and context.get('line_type'):
183 if context.get('line_type') == 'customer':
184 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
186 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
187 result = result and result[1] or False
190 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
191 doc = etree.XML(res['arch'])
193 if context.get('type', 'sale') in ('purchase', 'payment'):
194 nodes = doc.xpath("//field[@name='partner_id']")
196 node.set('domain', "[('supplier', '=', True)]")
197 res['arch'] = etree.tostring(doc)
200 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
202 sign = type == 'payment' and -1 or 1
203 for l in line_dr_ids:
205 for l in line_cr_ids:
206 credit += l['amount']
207 return amount - sign * (credit - debit)
209 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
210 context = context or {}
211 if not line_dr_ids and not line_cr_ids:
213 line_osv = self.pool.get("account.voucher.line")
214 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
215 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
217 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
218 is_multi_currency = False
220 # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
221 is_multi_currency = True
223 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
224 for voucher_line in line_dr_ids+line_cr_ids:
225 company_currency = False
226 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
227 if voucher_line.get('currency_id', company_currency) != company_currency:
228 is_multi_currency = True
230 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
232 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
233 if not ids: return {}
234 currency_obj = self.pool.get('res.currency')
237 for voucher in self.browse(cr, uid, ids, context=context):
238 sign = voucher.type == 'payment' and -1 or 1
239 for l in voucher.line_dr_ids:
241 for l in voucher.line_cr_ids:
243 currency = voucher.currency_id or voucher.company_id.currency_id
244 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
247 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
248 if not ids: return {}
251 for voucher in self.browse(cr, uid, ids, context=context):
252 if voucher.currency_id:
253 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
254 rate = 1 / voucher.payment_rate
257 ctx.update({'date': voucher.date})
258 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
259 company_currency_rate = voucher.company_id.currency_id.rate
260 rate = voucher_rate * company_currency_rate
261 res[voucher.id] = voucher.amount / rate
264 _name = 'account.voucher'
265 _description = 'Accounting Voucher'
266 _inherit = ['mail.thread']
267 _order = "date desc, id desc"
268 # _rec_name = 'number'
271 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
276 '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."),
277 'type':fields.selection([
279 ('purchase','Purchase'),
280 ('payment','Payment'),
281 ('receipt','Receipt'),
282 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
283 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
284 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
285 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
286 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
287 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
288 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
289 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
290 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
291 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
292 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
293 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
294 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
295 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
296 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
297 'state':fields.selection(
299 ('cancel','Cancelled'),
300 ('proforma','Pro-forma'),
302 ], 'Status', readonly=True, size=32, track_visibility='onchange',
303 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
304 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
305 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
306 \n* The \'Cancelled\' status is used when user cancel voucher.'),
307 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
308 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
309 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
310 'number': fields.char('Number', size=32, readonly=True,),
311 'move_id':fields.many2one('account.move', 'Account Entry'),
312 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
313 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
314 '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'),
315 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
316 'pay_now':fields.selection([
317 ('pay_now','Pay Directly'),
318 ('pay_later','Pay Later or Group Funds'),
319 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
320 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
321 'pre_line':fields.boolean('Previous Payments ?', required=False),
322 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
323 'payment_option':fields.selection([
324 ('without_writeoff', 'Keep Open'),
325 ('with_writeoff', 'Reconcile Payment Balance'),
326 ], '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)"),
327 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
328 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
329 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
330 '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."),
331 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
332 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
333 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
334 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
335 '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'),
339 'period_id': _get_period,
340 'partner_id': _get_partner,
341 'journal_id':_get_journal,
342 'currency_id': _get_currency,
343 'reference': _get_reference,
344 'narration':_get_narration,
345 'amount': _get_amount,
348 'pay_now': 'pay_now',
350 'date': lambda *a: time.strftime('%Y-%m-%d'),
351 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
353 'payment_option': 'without_writeoff',
354 'comment': _('Write-Off'),
356 'payment_rate_currency_id': _get_payment_rate_currency,
359 def compute_tax(self, cr, uid, ids, context=None):
360 tax_pool = self.pool.get('account.tax')
361 partner_pool = self.pool.get('res.partner')
362 position_pool = self.pool.get('account.fiscal.position')
363 voucher_line_pool = self.pool.get('account.voucher.line')
364 voucher_pool = self.pool.get('account.voucher')
365 if context is None: context = {}
367 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
369 for line in voucher.line_ids:
370 voucher_amount += line.untax_amount or line.amount
371 line.amount = line.untax_amount or line.amount
372 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
374 if not voucher.tax_id:
375 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
378 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
379 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
380 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
381 tax = tax_pool.browse(cr, uid, taxes, context=context)
383 total = voucher_amount
386 if not tax[0].price_include:
387 for line in voucher.line_ids:
388 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
389 total_tax += tax_line.get('amount', 0.0)
392 for line in voucher.line_ids:
396 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
397 line_tax += tax_line.get('amount', 0.0)
398 line_total += tax_line.get('price_unit')
399 total_tax += line_tax
400 untax_amount = line.untax_amount or line.amount
401 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
403 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
406 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
407 context = context or {}
408 tax_pool = self.pool.get('account.tax')
409 partner_pool = self.pool.get('res.partner')
410 position_pool = self.pool.get('account.fiscal.position')
411 line_pool = self.pool.get('account.voucher.line')
418 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
421 for line in line_ids:
423 line_amount = line.get('amount',0.0)
426 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
428 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
429 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
430 tax = tax_pool.browse(cr, uid, taxes, context=context)
432 if not tax[0].price_include:
433 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
434 total_tax += tax_line.get('amount')
436 voucher_total += line_amount
437 total = voucher_total + total_tax
440 'amount': total or voucher_total,
441 'tax_amount': total_tax
447 def onchange_term_id(self, cr, uid, ids, term_id, amount):
448 term_pool = self.pool.get('account.payment.term')
451 default = {'date_due':False}
452 if term_id and amount:
453 terms = term_pool.compute(cr, uid, term_id, amount)
455 due_date = terms[-1][0]
459 return {'value':default}
461 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):
463 Returns a dict that contains new values and context
465 @param partner_id: latest value from user input for field partner_id
466 @param args: other arguments
467 @param context: context arguments, like lang, time zone
469 @return: Returns a dict which contains new values, and context
475 if not partner_id or not journal_id:
478 partner_pool = self.pool.get('res.partner')
479 journal_pool = self.pool.get('account.journal')
481 journal = journal_pool.browse(cr, uid, journal_id, context=context)
482 partner = partner_pool.browse(cr, uid, partner_id, context=context)
485 if journal.type in ('sale','sale_refund'):
486 account_id = partner.property_account_receivable.id
488 elif journal.type in ('purchase', 'purchase_refund','expense'):
489 account_id = partner.property_account_payable.id
492 if not journal.default_credit_account_id or not journal.default_debit_account_id:
493 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
494 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
497 default['value']['account_id'] = account_id
498 default['value']['type'] = ttype or tr_type
500 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)
501 default['value'].update(vals.get('value'))
505 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
506 res = {'value': {'paid_amount_in_company_currency': amount}}
507 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
508 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
509 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
510 if company_currency.id == payment_rate_currency_id:
513 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
514 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
517 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):
520 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
522 ctx.update({'date': date})
523 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
524 for key in vals.keys():
525 res[key].update(vals[key])
528 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
531 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
532 currency_obj = self.pool.get('res.currency')
533 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
534 company_id = journal.company_id.id
536 payment_rate_currency_id = currency_id
538 ctx.update({'date': date})
540 if ttype == 'receipt':
541 o2m_to_loop = 'line_cr_ids'
542 elif ttype == 'payment':
543 o2m_to_loop = 'line_dr_ids'
544 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
545 for voucher_line in vals['value'][o2m_to_loop]:
546 if voucher_line['currency_id'] != currency_id:
547 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
548 # is not in the voucher currency
549 payment_rate_currency_id = voucher_line['currency_id']
550 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
551 voucher_currency_id = currency_id or journal.company_id.currency_id.id
552 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
554 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
555 for key in res.keys():
556 vals[key].update(res[key])
557 vals['value'].update({'payment_rate': payment_rate})
558 if payment_rate_currency_id:
559 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
562 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
565 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
566 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
567 for key in vals.keys():
568 res[key].update(vals[key])
569 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
570 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
571 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
572 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
573 # onchange returns a value for them
575 del(res['value']['line_dr_ids'])
576 del(res['value']['pre_line'])
577 del(res['value']['payment_rate'])
578 elif ttype == 'purchase':
579 del(res['value']['line_cr_ids'])
580 del(res['value']['pre_line'])
581 del(res['value']['payment_rate'])
584 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
586 Returns a dict that contains new values and context
588 @param partner_id: latest value from user input for field partner_id
589 @param args: other arguments
590 @param context: context arguments, like lang, time zone
592 @return: Returns a dict which contains new values, and context
594 def _remove_noise_in_o2m():
595 """if the line is partially reconciled, then we must pay attention to display it only once and
597 This function returns True if the line is considered as noise and should not be displayed
599 if line.reconcile_partial_id:
600 sign = 1 if ttype == 'receipt' else -1
601 if currency_id == line.currency_id.id:
602 if line.amount_residual_currency * sign <= 0:
605 if line.amount_residual * sign <= 0:
611 context_multi_currency = context.copy()
613 context_multi_currency.update({'date': date})
615 currency_pool = self.pool.get('res.currency')
616 move_line_pool = self.pool.get('account.move.line')
617 partner_pool = self.pool.get('res.partner')
618 journal_pool = self.pool.get('account.journal')
619 line_pool = self.pool.get('account.voucher.line')
623 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
627 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
629 line_pool.unlink(cr, uid, line_ids)
631 if not partner_id or not journal_id:
634 journal = journal_pool.browse(cr, uid, journal_id, context=context)
635 partner = partner_pool.browse(cr, uid, partner_id, context=context)
636 currency_id = currency_id or journal.company_id.currency_id.id
638 if journal.type in ('sale','sale_refund'):
639 account_id = partner.property_account_receivable.id
640 elif journal.type in ('purchase', 'purchase_refund','expense'):
641 account_id = partner.property_account_payable.id
643 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
645 default['value']['account_id'] = account_id
647 if journal.type not in ('cash', 'bank'):
652 account_type = 'receivable'
653 if ttype == 'payment':
654 account_type = 'payable'
655 total_debit = price or 0.0
657 total_credit = price or 0.0
658 account_type = 'receivable'
660 if not context.get('move_line_ids', False):
661 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
663 ids = context['move_line_ids']
664 invoice_id = context.get('invoice_id', False)
665 company_currency = journal.company_id.currency_id.id
666 move_line_found = False
668 #order the lines by most old first
670 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
672 #compute the total debit/credit and look for a matching open amount or invoice
673 for line in account_move_lines:
674 if _remove_noise_in_o2m():
678 if line.invoice.id == invoice_id:
679 #if the invoice linked to the voucher line is equal to the invoice_id in context
680 #then we assign the amount on that line, whatever the other voucher lines
681 move_line_found = line.id
683 elif currency_id == company_currency:
684 #otherwise treatments is the same but with other field names
685 if line.amount_residual == price:
686 #if the amount residual is equal the amount voucher, we assign it to that voucher
687 #line, whatever the other voucher lines
688 move_line_found = line.id
690 #otherwise we will split the voucher amount on each line (by most old first)
691 total_credit += line.credit or 0.0
692 total_debit += line.debit or 0.0
693 elif currency_id == line.currency_id.id:
694 if line.amount_residual_currency == price:
695 move_line_found = line.id
697 total_credit += line.credit and line.amount_currency or 0.0
698 total_debit += line.debit and line.amount_currency or 0.0
700 #voucher line creation
701 for line in account_move_lines:
703 if _remove_noise_in_o2m():
706 if line.currency_id and currency_id==line.currency_id.id:
707 amount_original = abs(line.amount_currency)
708 amount_unreconciled = abs(line.amount_residual_currency)
710 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
711 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
712 line_currency_id = line.currency_id and line.currency_id.id or company_currency
714 'name':line.move_id.name,
715 'type': line.credit and 'dr' or 'cr',
716 'move_line_id':line.id,
717 'account_id':line.account_id.id,
718 'amount_original': amount_original,
719 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
720 'date_original':line.date,
721 'date_due':line.date_maturity,
722 'amount_unreconciled': amount_unreconciled,
723 'currency_id': line_currency_id,
725 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
726 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
727 if not move_line_found:
728 if currency_id == line_currency_id:
730 amount = min(amount_unreconciled, abs(total_debit))
731 rs['amount'] = amount
732 total_debit -= amount
734 amount = min(amount_unreconciled, abs(total_credit))
735 rs['amount'] = amount
736 total_credit -= amount
738 if rs['amount_unreconciled'] == rs['amount']:
739 rs['reconcile'] = True
741 if rs['type'] == 'cr':
742 default['value']['line_cr_ids'].append(rs)
744 default['value']['line_dr_ids'].append(rs)
746 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
747 default['value']['pre_line'] = 1
748 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
749 default['value']['pre_line'] = 1
750 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
753 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
757 #set the default payment rate of the voucher and compute the paid amount in company currency
758 if currency_id and currency_id == payment_rate_currency_id:
760 ctx.update({'date': date})
761 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
762 for key in vals.keys():
763 res[key].update(vals[key])
766 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
768 @param date: latest value from user input for field date
769 @param args: other arguments
770 @param context: context arguments, like lang, time zone
771 @return: Returns a dict which contains new values, and context
776 #set the period of the voucher
777 period_pool = self.pool.get('account.period')
778 currency_obj = self.pool.get('res.currency')
780 ctx.update({'company_id': company_id})
781 pids = period_pool.find(cr, uid, date, context=ctx)
783 res['value'].update({'period_id':pids[0]})
784 if payment_rate_currency_id:
785 ctx.update({'date': date})
787 if payment_rate_currency_id != currency_id:
788 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
789 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
790 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
791 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
792 vals['value'].update({'payment_rate': payment_rate})
793 for key in vals.keys():
794 res[key].update(vals[key])
797 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
800 journal_pool = self.pool.get('account.journal')
801 journal = journal_pool.browse(cr, uid, journal_id, context=context)
802 account_id = journal.default_credit_account_id or journal.default_debit_account_id
804 if account_id and account_id.tax_ids:
805 tax_id = account_id.tax_ids[0].id
808 if ttype in ('sale', 'purchase'):
809 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
810 vals['value'].update({'tax_id':tax_id,'amount': amount})
813 currency_id = journal.currency.id
814 vals['value'].update({'currency_id': currency_id})
815 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
816 for key in res.keys():
817 vals[key].update(res[key])
820 def button_proforma_voucher(self, cr, uid, ids, context=None):
821 context = context or {}
822 wf_service = netsvc.LocalService("workflow")
824 wf_service.trg_validate(uid, 'account.voucher', vid, 'proforma_voucher', cr)
825 return {'type': 'ir.actions.act_window_close'}
827 def proforma_voucher(self, cr, uid, ids, context=None):
828 self.action_move_line_create(cr, uid, ids, context=context)
831 def action_cancel_draft(self, cr, uid, ids, context=None):
832 wf_service = netsvc.LocalService("workflow")
833 for voucher_id in ids:
834 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
835 self.write(cr, uid, ids, {'state':'draft'})
838 def cancel_voucher(self, cr, uid, ids, context=None):
839 reconcile_pool = self.pool.get('account.move.reconcile')
840 move_pool = self.pool.get('account.move')
842 for voucher in self.browse(cr, uid, ids, context=context):
844 for line in voucher.move_ids:
845 if line.reconcile_id:
846 recs += [line.reconcile_id.id]
847 if line.reconcile_partial_id:
848 recs += [line.reconcile_partial_id.id]
850 reconcile_pool.unlink(cr, uid, recs)
853 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
854 move_pool.unlink(cr, uid, [voucher.move_id.id])
859 self.write(cr, uid, ids, res)
862 def unlink(self, cr, uid, ids, context=None):
863 for t in self.read(cr, uid, ids, ['state'], context=context):
864 if t['state'] not in ('draft', 'cancel'):
865 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
866 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
868 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
872 res = {'account_id':False}
873 partner_pool = self.pool.get('res.partner')
874 journal_pool = self.pool.get('account.journal')
875 if pay_now == 'pay_later':
876 partner = partner_pool.browse(cr, uid, partner_id)
877 journal = journal_pool.browse(cr, uid, journal_id)
878 if journal.type in ('sale','sale_refund'):
879 account_id = partner.property_account_receivable.id
880 elif journal.type in ('purchase', 'purchase_refund','expense'):
881 account_id = partner.property_account_payable.id
883 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
884 res['account_id'] = account_id
887 def _sel_context(self, cr, uid, voucher_id, context=None):
889 Select the context to use accordingly if it needs to be multicurrency or not.
891 :param voucher_id: Id of the actual voucher
892 :return: The returned context will be the same as given in parameter if the voucher currency is the same
893 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
894 the date of the voucher.
897 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
898 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
899 if current_currency <> company_currency:
900 context_multi_currency = context.copy()
901 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
902 context_multi_currency.update({'date': voucher_brw.date})
903 return context_multi_currency
906 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
908 Return a dict to be use to create the first account move line of given voucher.
910 :param voucher_id: Id of voucher what we are creating account_move.
911 :param move_id: Id of account move where this line will be added.
912 :param company_currency: id of currency of the company to which the voucher belong
913 :param current_currency: id of currency of the voucher
914 :return: mapping between fieldname and value of account move line to create
917 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
919 # TODO: is there any other alternative then the voucher type ??
920 # ANSWER: We can have payment and receipt "In Advance".
921 # TODO: Make this logic available.
922 # -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
923 if voucher_brw.type in ('purchase', 'payment'):
924 credit = voucher_brw.paid_amount_in_company_currency
925 elif voucher_brw.type in ('sale', 'receipt'):
926 debit = voucher_brw.paid_amount_in_company_currency
927 if debit < 0: credit = -debit; debit = 0.0
928 if credit < 0: debit = -credit; credit = 0.0
929 sign = debit - credit < 0 and -1 or 1
930 #set the first line of the voucher
932 'name': voucher_brw.name or '/',
935 'account_id': voucher_brw.account_id.id,
937 'journal_id': voucher_brw.journal_id.id,
938 'period_id': voucher_brw.period_id.id,
939 'partner_id': voucher_brw.partner_id.id,
940 'currency_id': company_currency <> current_currency and current_currency or False,
941 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
942 'date': voucher_brw.date,
943 'date_maturity': voucher_brw.date_due
947 def account_move_get(self, cr, uid, voucher_id, context=None):
949 This method prepare the creation of the account move related to the given voucher.
951 :param voucher_id: Id of voucher for which we are creating account_move.
952 :return: mapping between fieldname and value of account move to create
955 seq_obj = self.pool.get('ir.sequence')
956 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
957 if voucher_brw.number:
958 name = voucher_brw.number
959 elif voucher_brw.journal_id.sequence_id:
960 if not voucher_brw.journal_id.sequence_id.active:
961 raise osv.except_osv(_('Configuration Error !'),
962 _('Please activate the sequence of selected journal !'))
963 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
965 raise osv.except_osv(_('Error!'),
966 _('Please define a sequence on the journal.'))
967 if not voucher_brw.reference:
968 ref = name.replace('/','')
970 ref = voucher_brw.reference
974 'journal_id': voucher_brw.journal_id.id,
975 'narration': voucher_brw.narration,
976 'date': voucher_brw.date,
978 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
982 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
984 Prepare the two lines in company currency due to currency rate difference.
986 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
988 :param move_id: Account move wher the move lines will be.
989 :param amount_residual: Amount to be posted.
990 :param company_currency: id of currency of the company to which the voucher belong
991 :param current_currency: id of currency of the voucher
992 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
993 :rtype: tuple of dict
995 if amount_residual > 0:
996 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
998 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."))
1000 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1002 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."))
1003 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1004 # the receivable/payable account may have a secondary currency, which render this field mandatory
1005 account_currency_id = company_currency <> current_currency and current_currency or False
1007 'journal_id': line.voucher_id.journal_id.id,
1008 'period_id': line.voucher_id.period_id.id,
1009 'name': _('change')+': '+(line.name or '/'),
1010 'account_id': line.account_id.id,
1012 'partner_id': line.voucher_id.partner_id.id,
1013 'currency_id': account_currency_id,
1014 'amount_currency': 0.0,
1016 'credit': amount_residual > 0 and amount_residual or 0.0,
1017 'debit': amount_residual < 0 and -amount_residual or 0.0,
1018 'date': line.voucher_id.date,
1020 move_line_counterpart = {
1021 'journal_id': line.voucher_id.journal_id.id,
1022 'period_id': line.voucher_id.period_id.id,
1023 'name': _('change')+': '+(line.name or '/'),
1024 'account_id': account_id.id,
1026 'amount_currency': 0.0,
1027 'partner_id': line.voucher_id.partner_id.id,
1028 'currency_id': account_currency_id,
1030 'debit': amount_residual > 0 and amount_residual or 0.0,
1031 'credit': amount_residual < 0 and -amount_residual or 0.0,
1032 'date': line.voucher_id.date,
1034 return (move_line, move_line_counterpart)
1036 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1038 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1039 payment_rate_currency_id is relevant) either the rate encoded in the system.
1041 :param amount: float. The amount to convert
1042 :param voucher: id of the voucher on which we want the conversion
1043 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1044 field in order to select the good rate to use.
1045 :return: the amount in the currency of the voucher's company
1048 currency_obj = self.pool.get('res.currency')
1049 voucher = self.browse(cr, uid, voucher_id, context=context)
1051 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1052 # the rate specified on the voucher is for the company currency
1053 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1055 # the rate specified on the voucher is not relevant, we use all the rates in the system
1056 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1059 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1061 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1062 It returns Tuple with tot_line what is total of difference between debit and credit and
1063 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1065 :param voucher_id: Voucher id what we are working with
1066 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1067 :param move_id: Account move wher those lines will be joined.
1068 :param company_currency: id of currency of the company to which the voucher belong
1069 :param current_currency: id of currency of the voucher
1070 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1071 :rtype: tuple(float, list of int)
1075 move_line_obj = self.pool.get('account.move.line')
1076 currency_obj = self.pool.get('res.currency')
1077 tax_obj = self.pool.get('account.tax')
1078 tot_line = line_total
1081 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1082 ctx = context.copy()
1083 ctx.update({'date': voucher_brw.date})
1084 for line in voucher_brw.line_ids:
1085 #create one move line per voucher line where amount is not 0.0
1088 # convert the amount set on the voucher line into the currency of the voucher's company
1089 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1090 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1091 # currency rate difference
1092 if line.amount == line.amount_unreconciled:
1093 if not line.move_line_id.amount_residual:
1094 raise osv.except_osv(_('Wrong bank statement line'),_("You have to delete the bank statement line which the payment was reconciled to manually. Please check the payment of the partner %s by the amount of %s.")%(line.voucher_id.partner_id.name, line.voucher_id.amount))
1095 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1096 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1098 currency_rate_difference = 0.0
1100 'journal_id': voucher_brw.journal_id.id,
1101 'period_id': voucher_brw.period_id.id,
1102 'name': line.name or '/',
1103 'account_id': line.account_id.id,
1105 'partner_id': voucher_brw.partner_id.id,
1106 '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,
1107 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1111 'date': voucher_brw.date
1115 if line.type == 'dr':
1120 if (line.type=='dr'):
1122 move_line['debit'] = amount
1125 move_line['credit'] = amount
1127 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1129 'account_tax_id': voucher_brw.tax_id.id,
1132 if move_line.get('account_tax_id', False):
1133 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1134 if not (tax_data.base_code_id and tax_data.tax_code_id):
1135 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))
1137 # compute the amount in foreign currency
1138 foreign_currency_diff = 0.0
1139 amount_currency = False
1140 if line.move_line_id:
1141 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1142 # We want to set it on the account move line as soon as the original line had a foreign currency
1143 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1144 # we compute the amount in that foreign currency.
1145 if line.move_line_id.currency_id.id == current_currency:
1146 # if the voucher and the voucher line share the same currency, there is no computation to do
1147 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1148 amount_currency = sign * (line.amount)
1149 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1150 # if the rate is specified on the voucher, we must use it
1151 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1152 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1154 # otherwise we use the rates of the system (giving the voucher date in the context)
1155 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1156 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1157 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1158 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1160 move_line['amount_currency'] = amount_currency
1161 voucher_line = move_line_obj.create(cr, uid, move_line)
1162 rec_ids = [voucher_line, line.move_line_id.id]
1164 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1165 # Change difference entry in company currency
1166 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1167 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1168 move_line_obj.create(cr, uid, exch_lines[1], context)
1169 rec_ids.append(new_id)
1171 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):
1172 # Change difference entry in voucher currency
1173 move_line_foreign_currency = {
1174 'journal_id': line.voucher_id.journal_id.id,
1175 'period_id': line.voucher_id.period_id.id,
1176 'name': _('change')+': '+(line.name or '/'),
1177 'account_id': line.account_id.id,
1179 'partner_id': line.voucher_id.partner_id.id,
1180 'currency_id': line.move_line_id.currency_id.id,
1181 'amount_currency': -1 * foreign_currency_diff,
1185 'date': line.voucher_id.date,
1187 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1188 rec_ids.append(new_id)
1190 if line.move_line_id.id:
1191 rec_lst_ids.append(rec_ids)
1193 return (tot_line, rec_lst_ids)
1195 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1197 Set a dict to be use to create the writeoff move line.
1199 :param voucher_id: Id of voucher what we are creating account_move.
1200 :param line_total: Amount remaining to be allocated on lines.
1201 :param move_id: Id of account move where this line will be added.
1202 :param name: Description of account move line.
1203 :param company_currency: id of currency of the company to which the voucher belong
1204 :param current_currency: id of currency of the voucher
1205 :return: mapping between fieldname and value of account move line to create
1208 currency_obj = self.pool.get('res.currency')
1211 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1212 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1214 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1218 if voucher_brw.payment_option == 'with_writeoff':
1219 account_id = voucher_brw.writeoff_acc_id.id
1220 write_off_name = voucher_brw.comment
1221 elif voucher_brw.type in ('sale', 'receipt'):
1222 account_id = voucher_brw.partner_id.property_account_receivable.id
1224 account_id = voucher_brw.partner_id.property_account_payable.id
1225 sign = voucher_brw.type == 'payment' and -1 or 1
1227 'name': write_off_name or name,
1228 'account_id': account_id,
1230 'partner_id': voucher_brw.partner_id.id,
1231 'date': voucher_brw.date,
1232 'credit': diff > 0 and diff or 0.0,
1233 'debit': diff < 0 and -diff or 0.0,
1234 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1235 'currency_id': company_currency <> current_currency and current_currency or False,
1236 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1241 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1243 Get the currency of the actual company.
1245 :param voucher_id: Id of the voucher what i want to obtain company currency.
1246 :return: currency id of the company of the voucher
1249 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1251 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1253 Get the currency of the voucher.
1255 :param voucher_id: Id of the voucher what i want to obtain current currency.
1256 :return: currency id of the voucher
1259 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1260 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1262 def action_move_line_create(self, cr, uid, ids, context=None):
1264 Confirm the vouchers given in ids and create the journal entries for each of them
1268 move_pool = self.pool.get('account.move')
1269 move_line_pool = self.pool.get('account.move.line')
1270 for voucher in self.browse(cr, uid, ids, context=context):
1273 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1274 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1275 # we select the context to use accordingly if it's a multicurrency case or not
1276 context = self._sel_context(cr, uid, voucher.id, context)
1277 # But for the operations made by _convert_amount, we always need to give the date in the context
1278 ctx = context.copy()
1279 ctx.update({'date': voucher.date})
1280 # Create the account move record.
1281 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1282 # Get the name of the account_move just created
1283 name = move_pool.browse(cr, uid, move_id, context=context).name
1284 # Create the first line of the voucher
1285 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)
1286 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1287 line_total = move_line_brw.debit - move_line_brw.credit
1289 if voucher.type == 'sale':
1290 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1291 elif voucher.type == 'purchase':
1292 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1293 # Create one move line per voucher line where amount is not 0.0
1294 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1296 # Create the writeoff line if needed
1297 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1299 move_line_pool.create(cr, uid, ml_writeoff, context)
1300 # We post the voucher.
1301 self.write(cr, uid, [voucher.id], {
1306 if voucher.journal_id.entry_posted:
1307 move_pool.post(cr, uid, [move_id], context={})
1308 # We automatically reconcile the account move lines.
1310 for rec_ids in rec_list_ids:
1311 if len(rec_ids) >= 2:
1312 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)
1315 def copy(self, cr, uid, id, default=None, context=None):
1322 'line_cr_ids': False,
1323 'line_dr_ids': False,
1326 if 'date' not in default:
1327 default['date'] = time.strftime('%Y-%m-%d')
1328 return super(account_voucher, self).copy(cr, uid, id, default, context)
1331 class account_voucher_line(osv.osv):
1332 _name = 'account.voucher.line'
1333 _description = 'Voucher Lines'
1334 _order = "move_line_id"
1336 # If the payment is in the same currency than the invoice, we keep the same amount
1337 # Otherwise, we compute from company currency to payment currency
1338 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1339 currency_pool = self.pool.get('res.currency')
1341 for line in self.browse(cr, uid, ids, context=context):
1342 ctx = context.copy()
1343 ctx.update({'date': line.voucher_id.date})
1345 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1346 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1347 move_line = line.move_line_id or False
1350 res['amount_original'] = 0.0
1351 res['amount_unreconciled'] = 0.0
1352 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1353 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1354 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)
1355 elif move_line and move_line.credit > 0:
1356 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1357 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1359 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1360 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1362 rs_data[line.id] = res
1365 def _currency_id(self, cr, uid, ids, name, args, context=None):
1367 This function returns the currency id of a voucher line. It's either the currency of the
1368 associated move line (if any) or the currency of the voucher or the company currency.
1371 for line in self.browse(cr, uid, ids, context=context):
1372 move_line = line.move_line_id
1374 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1376 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1380 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1381 'name':fields.char('Description', size=256),
1382 'account_id':fields.many2one('account.account','Account', required=True),
1383 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1384 'untax_amount':fields.float('Untax Amount'),
1385 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1386 'reconcile': fields.boolean('Full Reconcile'),
1387 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1388 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1389 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1390 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1391 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1392 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1393 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1394 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1395 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1401 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1402 vals = {'amount': 0.0}
1404 vals = { 'amount': amount_unreconciled}
1405 return {'value': vals}
1407 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1410 vals['reconcile'] = (amount == amount_unreconciled)
1411 return {'value': vals}
1413 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1415 Returns a dict that contains new values and context
1417 @param move_line_id: latest value from user input for field move_line_id
1418 @param args: other arguments
1419 @param context: context arguments, like lang, time zone
1421 @return: Returns a dict which contains new values, and context
1424 move_line_pool = self.pool.get('account.move.line')
1426 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1427 if move_line.credit:
1432 'account_id': move_line.account_id.id,
1434 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1440 def default_get(self, cr, user, fields_list, context=None):
1442 Returns default values for fields
1443 @param fields_list: list of fields, for which default values are required to be read
1444 @param context: context arguments, like lang, time zone
1446 @return: Returns a dict that contains default values for fields
1450 journal_id = context.get('journal_id', False)
1451 partner_id = context.get('partner_id', False)
1452 journal_pool = self.pool.get('account.journal')
1453 partner_pool = self.pool.get('res.partner')
1454 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1455 if (not journal_id) or ('account_id' not in fields_list):
1457 journal = journal_pool.browse(cr, user, journal_id, context=context)
1460 if journal.type in ('sale', 'sale_refund'):
1461 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1463 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1464 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1467 partner = partner_pool.browse(cr, user, partner_id, context=context)
1468 if context.get('type') == 'payment':
1470 account_id = partner.property_account_payable.id
1471 elif context.get('type') == 'receipt':
1472 account_id = partner.property_account_receivable.id
1475 'account_id':account_id,
1479 account_voucher_line()
1481 class account_bank_statement(osv.osv):
1482 _inherit = 'account.bank.statement'
1484 def button_confirm_bank(self, cr, uid, ids, context=None):
1485 voucher_obj = self.pool.get('account.voucher')
1487 for statement in self.browse(cr, uid, ids, context=context):
1488 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1490 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1491 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1493 def button_cancel(self, cr, uid, ids, context=None):
1494 voucher_obj = self.pool.get('account.voucher')
1495 for st in self.browse(cr, uid, ids, context=context):
1497 for line in st.line_ids:
1499 voucher_ids.append(line.voucher_id.id)
1500 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1501 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1503 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1504 voucher_obj = self.pool.get('account.voucher')
1505 wf_service = netsvc.LocalService("workflow")
1506 move_line_obj = self.pool.get('account.move.line')
1507 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1508 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1509 if st_line.voucher_id:
1510 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1511 if st_line.voucher_id.state == 'cancel':
1512 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1513 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1515 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1516 bank_st_line_obj.write(cr, uid, [st_line_id], {
1517 'move_ids': [(4, v.move_id.id, False)]
1520 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1521 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1523 def write(self, cr, uid, ids, vals, context=None):
1524 # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1525 # Because the voucher keeps in memory the journal it was created with.
1526 for bk_st in self.browse(cr, uid, ids, context=context):
1527 if vals.get('journal_id') and bk_st.line_ids:
1528 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1529 raise osv.except_osv(_('Unable to change journal !'), _('You can not change the journal as you already reconciled some statement lines!'))
1530 return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1532 account_bank_statement()
1534 class account_bank_statement_line(osv.osv):
1535 _inherit = 'account.bank.statement.line'
1537 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1538 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1539 if 'value' not in res:
1541 res['value'].update({'voucher_id' : False})
1544 def onchange_amount(self, cr, uid, ids, amount, context=None):
1545 return {'value' : {'voucher_id' : False}}
1547 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1551 for line in self.browse(cursor, user, ids, context=context):
1553 res[line.id] = line.voucher_id.amount#
1558 def _check_amount(self, cr, uid, ids, context=None):
1559 for obj in self.browse(cr, uid, ids, context=context):
1561 diff = abs(obj.amount) - obj.voucher_id.amount
1562 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1567 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1571 'amount_reconciled': fields.function(_amount_reconciled,
1572 string='Amount reconciled', type='float'),
1573 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1576 def unlink(self, cr, uid, ids, context=None):
1577 voucher_obj = self.pool.get('account.voucher')
1578 statement_line = self.browse(cr, uid, ids, context=context)
1580 for st_line in statement_line:
1581 if st_line.voucher_id:
1582 unlink_ids.append(st_line.voucher_id.id)
1583 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1584 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1586 account_bank_statement_line()
1588 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1590 for operation in operations:
1592 if not isinstance(operation, (list, tuple)):
1593 result = target_osv.read(cr, uid, operation, fields, context=context)
1594 elif operation[0] == 0:
1595 # may be necessary to check if all the fields are here and get the default values?
1596 result = operation[2]
1597 elif operation[0] == 1:
1598 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1599 if not result: result = {}
1600 result.update(operation[2])
1601 elif operation[0] == 4:
1602 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1604 results.append(result)
1608 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: