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('context', "{'search_default_supplier': 1}")
197 if context.get('invoice_type','') in ('in_invoice', 'in_refund'):
198 node.set('string', _("Supplier"))
199 res['arch'] = etree.tostring(doc)
202 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
204 sign = type == 'payment' and -1 or 1
205 for l in line_dr_ids:
207 for l in line_cr_ids:
208 credit += l['amount']
209 return amount - sign * (credit - debit)
211 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
212 context = context or {}
213 if not line_dr_ids and not line_cr_ids:
215 line_osv = self.pool.get("account.voucher.line")
216 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
217 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
219 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
220 is_multi_currency = False
222 # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
223 is_multi_currency = True
225 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
226 for voucher_line in line_dr_ids+line_cr_ids:
227 company_currency = False
228 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
229 if voucher_line.get('currency_id', company_currency) != company_currency:
230 is_multi_currency = True
232 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
234 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
235 if not ids: return {}
236 currency_obj = self.pool.get('res.currency')
239 for voucher in self.browse(cr, uid, ids, context=context):
240 sign = voucher.type == 'payment' and -1 or 1
241 for l in voucher.line_dr_ids:
243 for l in voucher.line_cr_ids:
245 currency = voucher.currency_id or voucher.company_id.currency_id
246 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
249 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
250 if not ids: return {}
253 for voucher in self.browse(cr, uid, ids, context=context):
254 if voucher.currency_id:
255 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
256 rate = 1 / voucher.payment_rate
259 ctx.update({'date': voucher.date})
260 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
261 company_currency_rate = voucher.company_id.currency_id.rate
262 rate = voucher_rate * company_currency_rate
263 res[voucher.id] = voucher.amount / rate
266 _name = 'account.voucher'
267 _description = 'Accounting Voucher'
268 _inherit = ['mail.thread']
269 _order = "date desc, id desc"
270 # _rec_name = 'number'
273 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
278 '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."),
279 'type':fields.selection([
281 ('purchase','Purchase'),
282 ('payment','Payment'),
283 ('receipt','Receipt'),
284 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
285 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
286 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
287 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
288 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
289 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
290 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
291 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
292 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
293 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
294 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
295 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
296 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
297 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
298 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
299 'state':fields.selection(
301 ('cancel','Cancelled'),
302 ('proforma','Pro-forma'),
304 ], 'Status', readonly=True, size=32, track_visibility='onchange',
305 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
306 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
307 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
308 \n* The \'Cancelled\' status is used when user cancel voucher.'),
309 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
310 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
311 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
312 'number': fields.char('Number', size=32, readonly=True,),
313 'move_id':fields.many2one('account.move', 'Account Entry'),
314 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
315 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
316 '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'),
317 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
318 'pay_now':fields.selection([
319 ('pay_now','Pay Directly'),
320 ('pay_later','Pay Later or Group Funds'),
321 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
322 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
323 'pre_line':fields.boolean('Previous Payments ?', required=False),
324 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
325 'payment_option':fields.selection([
326 ('without_writeoff', 'Keep Open'),
327 ('with_writeoff', 'Reconcile Payment Balance'),
328 ], '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)"),
329 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
330 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
331 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
332 '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."),
333 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
334 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
335 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
336 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
337 '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'),
341 'period_id': _get_period,
342 'partner_id': _get_partner,
343 'journal_id':_get_journal,
344 'currency_id': _get_currency,
345 'reference': _get_reference,
346 'narration':_get_narration,
347 'amount': _get_amount,
350 'pay_now': 'pay_now',
352 'date': lambda *a: time.strftime('%Y-%m-%d'),
353 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
355 'payment_option': 'without_writeoff',
356 'comment': _('Write-Off'),
358 'payment_rate_currency_id': _get_payment_rate_currency,
361 def compute_tax(self, cr, uid, ids, context=None):
362 tax_pool = self.pool.get('account.tax')
363 partner_pool = self.pool.get('res.partner')
364 position_pool = self.pool.get('account.fiscal.position')
365 voucher_line_pool = self.pool.get('account.voucher.line')
366 voucher_pool = self.pool.get('account.voucher')
367 if context is None: context = {}
369 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
371 for line in voucher.line_ids:
372 voucher_amount += line.untax_amount or line.amount
373 line.amount = line.untax_amount or line.amount
374 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
376 if not voucher.tax_id:
377 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
380 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
381 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
382 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
383 tax = tax_pool.browse(cr, uid, taxes, context=context)
385 total = voucher_amount
388 if not tax[0].price_include:
389 for line in voucher.line_ids:
390 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
391 total_tax += tax_line.get('amount', 0.0)
394 for line in voucher.line_ids:
398 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
399 line_tax += tax_line.get('amount', 0.0)
400 line_total += tax_line.get('price_unit')
401 total_tax += line_tax
402 untax_amount = line.untax_amount or line.amount
403 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
405 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
408 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
409 context = context or {}
410 tax_pool = self.pool.get('account.tax')
411 partner_pool = self.pool.get('res.partner')
412 position_pool = self.pool.get('account.fiscal.position')
413 line_pool = self.pool.get('account.voucher.line')
420 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
423 for line in line_ids:
425 line_amount = line.get('amount',0.0)
428 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
430 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
431 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
432 tax = tax_pool.browse(cr, uid, taxes, context=context)
434 if not tax[0].price_include:
435 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
436 total_tax += tax_line.get('amount')
438 voucher_total += line_amount
439 total = voucher_total + total_tax
442 'amount': total or voucher_total,
443 'tax_amount': total_tax
449 def onchange_term_id(self, cr, uid, ids, term_id, amount):
450 term_pool = self.pool.get('account.payment.term')
453 default = {'date_due':False}
454 if term_id and amount:
455 terms = term_pool.compute(cr, uid, term_id, amount)
457 due_date = terms[-1][0]
461 return {'value':default}
463 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):
465 Returns a dict that contains new values and context
467 @param partner_id: latest value from user input for field partner_id
468 @param args: other arguments
469 @param context: context arguments, like lang, time zone
471 @return: Returns a dict which contains new values, and context
477 if not partner_id or not journal_id:
480 partner_pool = self.pool.get('res.partner')
481 journal_pool = self.pool.get('account.journal')
483 journal = journal_pool.browse(cr, uid, journal_id, context=context)
484 partner = partner_pool.browse(cr, uid, partner_id, context=context)
487 if journal.type in ('sale','sale_refund'):
488 account_id = partner.property_account_receivable.id
490 elif journal.type in ('purchase', 'purchase_refund','expense'):
491 account_id = partner.property_account_payable.id
494 if not journal.default_credit_account_id or not journal.default_debit_account_id:
495 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
496 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
499 default['value']['account_id'] = account_id
500 default['value']['type'] = ttype or tr_type
502 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)
503 default['value'].update(vals.get('value'))
507 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
508 res = {'value': {'paid_amount_in_company_currency': amount}}
509 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
510 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
511 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
512 if company_currency.id == payment_rate_currency_id:
515 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
516 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
519 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):
522 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
524 ctx.update({'date': date})
525 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
526 for key in vals.keys():
527 res[key].update(vals[key])
530 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
533 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
534 currency_obj = self.pool.get('res.currency')
535 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
536 company_id = journal.company_id.id
538 payment_rate_currency_id = currency_id
540 ctx.update({'date': date})
542 if ttype == 'receipt':
543 o2m_to_loop = 'line_cr_ids'
544 elif ttype == 'payment':
545 o2m_to_loop = 'line_dr_ids'
546 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
547 for voucher_line in vals['value'][o2m_to_loop]:
548 if voucher_line['currency_id'] != currency_id:
549 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
550 # is not in the voucher currency
551 payment_rate_currency_id = voucher_line['currency_id']
552 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
553 voucher_currency_id = currency_id or journal.company_id.currency_id.id
554 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
556 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
557 for key in res.keys():
558 vals[key].update(res[key])
559 vals['value'].update({'payment_rate': payment_rate})
560 if payment_rate_currency_id:
561 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
564 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
567 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
568 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
569 for key in vals.keys():
570 res[key].update(vals[key])
571 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
572 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
573 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
574 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
575 # onchange returns a value for them
577 del(res['value']['line_dr_ids'])
578 del(res['value']['pre_line'])
579 del(res['value']['payment_rate'])
580 elif ttype == 'purchase':
581 del(res['value']['line_cr_ids'])
582 del(res['value']['pre_line'])
583 del(res['value']['payment_rate'])
586 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
588 Returns a dict that contains new values and context
590 @param partner_id: latest value from user input for field partner_id
591 @param args: other arguments
592 @param context: context arguments, like lang, time zone
594 @return: Returns a dict which contains new values, and context
596 def _remove_noise_in_o2m():
597 """if the line is partially reconciled, then we must pay attention to display it only once and
599 This function returns True if the line is considered as noise and should not be displayed
601 if line.reconcile_partial_id:
602 sign = 1 if ttype == 'receipt' else -1
603 if currency_id == line.currency_id.id:
604 if line.amount_residual_currency * sign <= 0:
607 if line.amount_residual * sign <= 0:
613 context_multi_currency = context.copy()
615 context_multi_currency.update({'date': date})
617 currency_pool = self.pool.get('res.currency')
618 move_line_pool = self.pool.get('account.move.line')
619 partner_pool = self.pool.get('res.partner')
620 journal_pool = self.pool.get('account.journal')
621 line_pool = self.pool.get('account.voucher.line')
625 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
629 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
631 line_pool.unlink(cr, uid, line_ids)
633 if not partner_id or not journal_id:
636 journal = journal_pool.browse(cr, uid, journal_id, context=context)
637 partner = partner_pool.browse(cr, uid, partner_id, context=context)
638 currency_id = currency_id or journal.company_id.currency_id.id
640 if journal.type in ('sale','sale_refund'):
641 account_id = partner.property_account_receivable.id
642 elif journal.type in ('purchase', 'purchase_refund','expense'):
643 account_id = partner.property_account_payable.id
645 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
647 default['value']['account_id'] = account_id
649 if journal.type not in ('cash', 'bank'):
654 account_type = 'receivable'
655 if ttype == 'payment':
656 account_type = 'payable'
657 total_debit = price or 0.0
659 total_credit = price or 0.0
660 account_type = 'receivable'
662 if not context.get('move_line_ids', False):
663 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
665 ids = context['move_line_ids']
666 invoice_id = context.get('invoice_id', False)
667 company_currency = journal.company_id.currency_id.id
668 move_line_found = False
670 #order the lines by most old first
672 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
674 #compute the total debit/credit and look for a matching open amount or invoice
675 for line in account_move_lines:
676 if _remove_noise_in_o2m():
680 if line.invoice.id == invoice_id:
681 #if the invoice linked to the voucher line is equal to the invoice_id in context
682 #then we assign the amount on that line, whatever the other voucher lines
683 move_line_found = line.id
685 elif currency_id == company_currency:
686 #otherwise treatments is the same but with other field names
687 if line.amount_residual == price:
688 #if the amount residual is equal the amount voucher, we assign it to that voucher
689 #line, whatever the other voucher lines
690 move_line_found = line.id
692 #otherwise we will split the voucher amount on each line (by most old first)
693 total_credit += line.credit or 0.0
694 total_debit += line.debit or 0.0
695 elif currency_id == line.currency_id.id:
696 if line.amount_residual_currency == price:
697 move_line_found = line.id
699 total_credit += line.credit and line.amount_currency or 0.0
700 total_debit += line.debit and line.amount_currency or 0.0
702 #voucher line creation
703 for line in account_move_lines:
705 if _remove_noise_in_o2m():
708 if line.currency_id and currency_id==line.currency_id.id:
709 amount_original = abs(line.amount_currency)
710 amount_unreconciled = abs(line.amount_residual_currency)
712 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
713 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
714 line_currency_id = line.currency_id and line.currency_id.id or company_currency
716 'name':line.move_id.name,
717 'type': line.credit and 'dr' or 'cr',
718 'move_line_id':line.id,
719 'account_id':line.account_id.id,
720 'amount_original': amount_original,
721 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
722 'date_original':line.date,
723 'date_due':line.date_maturity,
724 'amount_unreconciled': amount_unreconciled,
725 'currency_id': line_currency_id,
727 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
728 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
729 if not move_line_found:
730 if currency_id == line_currency_id:
732 amount = min(amount_unreconciled, abs(total_debit))
733 rs['amount'] = amount
734 total_debit -= amount
736 amount = min(amount_unreconciled, abs(total_credit))
737 rs['amount'] = amount
738 total_credit -= amount
740 if rs['amount_unreconciled'] == rs['amount']:
741 rs['reconcile'] = True
743 if rs['type'] == 'cr':
744 default['value']['line_cr_ids'].append(rs)
746 default['value']['line_dr_ids'].append(rs)
748 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
749 default['value']['pre_line'] = 1
750 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
751 default['value']['pre_line'] = 1
752 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
755 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
759 #set the default payment rate of the voucher and compute the paid amount in company currency
760 if currency_id and currency_id == payment_rate_currency_id:
762 ctx.update({'date': date})
763 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
764 for key in vals.keys():
765 res[key].update(vals[key])
768 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
770 @param date: latest value from user input for field date
771 @param args: other arguments
772 @param context: context arguments, like lang, time zone
773 @return: Returns a dict which contains new values, and context
778 #set the period of the voucher
779 period_pool = self.pool.get('account.period')
780 currency_obj = self.pool.get('res.currency')
782 ctx.update({'company_id': company_id})
783 pids = period_pool.find(cr, uid, date, context=ctx)
785 res['value'].update({'period_id':pids[0]})
786 if payment_rate_currency_id:
787 ctx.update({'date': date})
789 if payment_rate_currency_id != currency_id:
790 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
791 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
792 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
793 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
794 vals['value'].update({'payment_rate': payment_rate})
795 for key in vals.keys():
796 res[key].update(vals[key])
799 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
802 journal_pool = self.pool.get('account.journal')
803 journal = journal_pool.browse(cr, uid, journal_id, context=context)
804 account_id = journal.default_credit_account_id or journal.default_debit_account_id
806 if account_id and account_id.tax_ids:
807 tax_id = account_id.tax_ids[0].id
810 if ttype in ('sale', 'purchase'):
811 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
812 vals['value'].update({'tax_id':tax_id,'amount': amount})
815 currency_id = journal.currency.id
816 vals['value'].update({'currency_id': currency_id})
817 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
818 for key in res.keys():
819 vals[key].update(res[key])
822 def button_proforma_voucher(self, cr, uid, ids, context=None):
823 context = context or {}
824 wf_service = netsvc.LocalService("workflow")
826 wf_service.trg_validate(uid, 'account.voucher', vid, 'proforma_voucher', cr)
827 return {'type': 'ir.actions.act_window_close'}
829 def proforma_voucher(self, cr, uid, ids, context=None):
830 self.action_move_line_create(cr, uid, ids, context=context)
833 def action_cancel_draft(self, cr, uid, ids, context=None):
834 wf_service = netsvc.LocalService("workflow")
835 for voucher_id in ids:
836 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
837 self.write(cr, uid, ids, {'state':'draft'})
840 def cancel_voucher(self, cr, uid, ids, context=None):
841 reconcile_pool = self.pool.get('account.move.reconcile')
842 move_pool = self.pool.get('account.move')
844 for voucher in self.browse(cr, uid, ids, context=context):
846 for line in voucher.move_ids:
847 if line.reconcile_id:
848 recs += [line.reconcile_id.id]
849 if line.reconcile_partial_id:
850 recs += [line.reconcile_partial_id.id]
852 reconcile_pool.unlink(cr, uid, recs)
855 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
856 move_pool.unlink(cr, uid, [voucher.move_id.id])
861 self.write(cr, uid, ids, res)
864 def unlink(self, cr, uid, ids, context=None):
865 for t in self.read(cr, uid, ids, ['state'], context=context):
866 if t['state'] not in ('draft', 'cancel'):
867 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
868 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
870 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
874 res = {'account_id':False}
875 partner_pool = self.pool.get('res.partner')
876 journal_pool = self.pool.get('account.journal')
877 if pay_now == 'pay_later':
878 partner = partner_pool.browse(cr, uid, partner_id)
879 journal = journal_pool.browse(cr, uid, journal_id)
880 if journal.type in ('sale','sale_refund'):
881 account_id = partner.property_account_receivable.id
882 elif journal.type in ('purchase', 'purchase_refund','expense'):
883 account_id = partner.property_account_payable.id
885 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
886 res['account_id'] = account_id
889 def _sel_context(self, cr, uid, voucher_id, context=None):
891 Select the context to use accordingly if it needs to be multicurrency or not.
893 :param voucher_id: Id of the actual voucher
894 :return: The returned context will be the same as given in parameter if the voucher currency is the same
895 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
896 the date of the voucher.
899 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
900 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
901 if current_currency <> company_currency:
902 context_multi_currency = context.copy()
903 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
904 context_multi_currency.update({'date': voucher_brw.date})
905 return context_multi_currency
908 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
910 Return a dict to be use to create the first account move line of given voucher.
912 :param voucher_id: Id of voucher what we are creating account_move.
913 :param move_id: Id of account move where this line will be added.
914 :param company_currency: id of currency of the company to which the voucher belong
915 :param current_currency: id of currency of the voucher
916 :return: mapping between fieldname and value of account move line to create
919 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
921 # TODO: is there any other alternative then the voucher type ??
922 # ANSWER: We can have payment and receipt "In Advance".
923 # TODO: Make this logic available.
924 # -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
925 if voucher_brw.type in ('purchase', 'payment'):
926 credit = voucher_brw.paid_amount_in_company_currency
927 elif voucher_brw.type in ('sale', 'receipt'):
928 debit = voucher_brw.paid_amount_in_company_currency
929 if debit < 0: credit = -debit; debit = 0.0
930 if credit < 0: debit = -credit; credit = 0.0
931 sign = debit - credit < 0 and -1 or 1
932 #set the first line of the voucher
934 'name': voucher_brw.name or '/',
937 'account_id': voucher_brw.account_id.id,
939 'journal_id': voucher_brw.journal_id.id,
940 'period_id': voucher_brw.period_id.id,
941 'partner_id': voucher_brw.partner_id.id,
942 'currency_id': company_currency <> current_currency and current_currency or False,
943 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
944 'date': voucher_brw.date,
945 'date_maturity': voucher_brw.date_due
949 def account_move_get(self, cr, uid, voucher_id, context=None):
951 This method prepare the creation of the account move related to the given voucher.
953 :param voucher_id: Id of voucher for which we are creating account_move.
954 :return: mapping between fieldname and value of account move to create
957 seq_obj = self.pool.get('ir.sequence')
958 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
959 if voucher_brw.number:
960 name = voucher_brw.number
961 elif voucher_brw.journal_id.sequence_id:
962 if not voucher_brw.journal_id.sequence_id.active:
963 raise osv.except_osv(_('Configuration Error !'),
964 _('Please activate the sequence of selected journal !'))
965 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
967 raise osv.except_osv(_('Error!'),
968 _('Please define a sequence on the journal.'))
969 if not voucher_brw.reference:
970 ref = name.replace('/','')
972 ref = voucher_brw.reference
976 'journal_id': voucher_brw.journal_id.id,
977 'narration': voucher_brw.narration,
978 'date': voucher_brw.date,
980 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
984 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
986 Prepare the two lines in company currency due to currency rate difference.
988 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
990 :param move_id: Account move wher the move lines will be.
991 :param amount_residual: Amount to be posted.
992 :param company_currency: id of currency of the company to which the voucher belong
993 :param current_currency: id of currency of the voucher
994 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
995 :rtype: tuple of dict
997 if amount_residual > 0:
998 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1000 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."))
1002 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1004 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."))
1005 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1006 # the receivable/payable account may have a secondary currency, which render this field mandatory
1007 account_currency_id = company_currency <> current_currency and current_currency or False
1009 'journal_id': line.voucher_id.journal_id.id,
1010 'period_id': line.voucher_id.period_id.id,
1011 'name': _('change')+': '+(line.name or '/'),
1012 'account_id': line.account_id.id,
1014 'partner_id': line.voucher_id.partner_id.id,
1015 'currency_id': account_currency_id,
1016 'amount_currency': 0.0,
1018 'credit': amount_residual > 0 and amount_residual or 0.0,
1019 'debit': amount_residual < 0 and -amount_residual or 0.0,
1020 'date': line.voucher_id.date,
1022 move_line_counterpart = {
1023 'journal_id': line.voucher_id.journal_id.id,
1024 'period_id': line.voucher_id.period_id.id,
1025 'name': _('change')+': '+(line.name or '/'),
1026 'account_id': account_id.id,
1028 'amount_currency': 0.0,
1029 'partner_id': line.voucher_id.partner_id.id,
1030 'currency_id': account_currency_id,
1032 'debit': amount_residual > 0 and amount_residual or 0.0,
1033 'credit': amount_residual < 0 and -amount_residual or 0.0,
1034 'date': line.voucher_id.date,
1036 return (move_line, move_line_counterpart)
1038 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1040 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1041 payment_rate_currency_id is relevant) either the rate encoded in the system.
1043 :param amount: float. The amount to convert
1044 :param voucher: id of the voucher on which we want the conversion
1045 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1046 field in order to select the good rate to use.
1047 :return: the amount in the currency of the voucher's company
1050 currency_obj = self.pool.get('res.currency')
1051 voucher = self.browse(cr, uid, voucher_id, context=context)
1053 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1054 # the rate specified on the voucher is for the company currency
1055 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1057 # the rate specified on the voucher is not relevant, we use all the rates in the system
1058 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1061 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1063 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1064 It returns Tuple with tot_line what is total of difference between debit and credit and
1065 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1067 :param voucher_id: Voucher id what we are working with
1068 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1069 :param move_id: Account move wher those lines will be joined.
1070 :param company_currency: id of currency of the company to which the voucher belong
1071 :param current_currency: id of currency of the voucher
1072 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1073 :rtype: tuple(float, list of int)
1077 move_line_obj = self.pool.get('account.move.line')
1078 currency_obj = self.pool.get('res.currency')
1079 tax_obj = self.pool.get('account.tax')
1080 tot_line = line_total
1083 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1084 ctx = context.copy()
1085 ctx.update({'date': voucher_brw.date})
1086 for line in voucher_brw.line_ids:
1087 #create one move line per voucher line where amount is not 0.0
1090 # convert the amount set on the voucher line into the currency of the voucher's company
1091 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1092 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1093 # currency rate difference
1094 if line.amount == line.amount_unreconciled:
1095 if not line.move_line_id.amount_residual:
1096 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))
1097 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1098 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1100 currency_rate_difference = 0.0
1102 'journal_id': voucher_brw.journal_id.id,
1103 'period_id': voucher_brw.period_id.id,
1104 'name': line.name or '/',
1105 'account_id': line.account_id.id,
1107 'partner_id': voucher_brw.partner_id.id,
1108 '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,
1109 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1113 'date': voucher_brw.date
1117 if line.type == 'dr':
1122 if (line.type=='dr'):
1124 move_line['debit'] = amount
1127 move_line['credit'] = amount
1129 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1131 'account_tax_id': voucher_brw.tax_id.id,
1134 if move_line.get('account_tax_id', False):
1135 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1136 if not (tax_data.base_code_id and tax_data.tax_code_id):
1137 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))
1139 # compute the amount in foreign currency
1140 foreign_currency_diff = 0.0
1141 amount_currency = False
1142 if line.move_line_id:
1143 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1144 # We want to set it on the account move line as soon as the original line had a foreign currency
1145 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1146 # we compute the amount in that foreign currency.
1147 if line.move_line_id.currency_id.id == current_currency:
1148 # if the voucher and the voucher line share the same currency, there is no computation to do
1149 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1150 amount_currency = sign * (line.amount)
1151 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1152 # if the rate is specified on the voucher, we must use it
1153 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1154 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1156 # otherwise we use the rates of the system (giving the voucher date in the context)
1157 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1158 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1159 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1160 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1162 move_line['amount_currency'] = amount_currency
1163 voucher_line = move_line_obj.create(cr, uid, move_line)
1164 rec_ids = [voucher_line, line.move_line_id.id]
1166 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1167 # Change difference entry in company currency
1168 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1169 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1170 move_line_obj.create(cr, uid, exch_lines[1], context)
1171 rec_ids.append(new_id)
1173 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):
1174 # Change difference entry in voucher currency
1175 move_line_foreign_currency = {
1176 'journal_id': line.voucher_id.journal_id.id,
1177 'period_id': line.voucher_id.period_id.id,
1178 'name': _('change')+': '+(line.name or '/'),
1179 'account_id': line.account_id.id,
1181 'partner_id': line.voucher_id.partner_id.id,
1182 'currency_id': line.move_line_id.currency_id.id,
1183 'amount_currency': -1 * foreign_currency_diff,
1187 'date': line.voucher_id.date,
1189 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1190 rec_ids.append(new_id)
1192 if line.move_line_id.id:
1193 rec_lst_ids.append(rec_ids)
1195 return (tot_line, rec_lst_ids)
1197 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1199 Set a dict to be use to create the writeoff move line.
1201 :param voucher_id: Id of voucher what we are creating account_move.
1202 :param line_total: Amount remaining to be allocated on lines.
1203 :param move_id: Id of account move where this line will be added.
1204 :param name: Description of account move line.
1205 :param company_currency: id of currency of the company to which the voucher belong
1206 :param current_currency: id of currency of the voucher
1207 :return: mapping between fieldname and value of account move line to create
1210 currency_obj = self.pool.get('res.currency')
1213 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1214 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1216 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1220 if voucher_brw.payment_option == 'with_writeoff':
1221 account_id = voucher_brw.writeoff_acc_id.id
1222 write_off_name = voucher_brw.comment
1223 elif voucher_brw.type in ('sale', 'receipt'):
1224 account_id = voucher_brw.partner_id.property_account_receivable.id
1226 account_id = voucher_brw.partner_id.property_account_payable.id
1227 sign = voucher_brw.type == 'payment' and -1 or 1
1229 'name': write_off_name or name,
1230 'account_id': account_id,
1232 'partner_id': voucher_brw.partner_id.id,
1233 'date': voucher_brw.date,
1234 'credit': diff > 0 and diff or 0.0,
1235 'debit': diff < 0 and -diff or 0.0,
1236 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1237 'currency_id': company_currency <> current_currency and current_currency or False,
1238 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1243 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1245 Get the currency of the actual company.
1247 :param voucher_id: Id of the voucher what i want to obtain company currency.
1248 :return: currency id of the company of the voucher
1251 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1253 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1255 Get the currency of the voucher.
1257 :param voucher_id: Id of the voucher what i want to obtain current currency.
1258 :return: currency id of the voucher
1261 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1262 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1264 def action_move_line_create(self, cr, uid, ids, context=None):
1266 Confirm the vouchers given in ids and create the journal entries for each of them
1270 move_pool = self.pool.get('account.move')
1271 move_line_pool = self.pool.get('account.move.line')
1272 for voucher in self.browse(cr, uid, ids, context=context):
1275 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1276 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1277 # we select the context to use accordingly if it's a multicurrency case or not
1278 context = self._sel_context(cr, uid, voucher.id, context)
1279 # But for the operations made by _convert_amount, we always need to give the date in the context
1280 ctx = context.copy()
1281 ctx.update({'date': voucher.date})
1282 # Create the account move record.
1283 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1284 # Get the name of the account_move just created
1285 name = move_pool.browse(cr, uid, move_id, context=context).name
1286 # Create the first line of the voucher
1287 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)
1288 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1289 line_total = move_line_brw.debit - move_line_brw.credit
1291 if voucher.type == 'sale':
1292 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1293 elif voucher.type == 'purchase':
1294 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1295 # Create one move line per voucher line where amount is not 0.0
1296 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1298 # Create the writeoff line if needed
1299 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1301 move_line_pool.create(cr, uid, ml_writeoff, context)
1302 # We post the voucher.
1303 self.write(cr, uid, [voucher.id], {
1308 if voucher.journal_id.entry_posted:
1309 move_pool.post(cr, uid, [move_id], context={})
1310 # We automatically reconcile the account move lines.
1312 for rec_ids in rec_list_ids:
1313 if len(rec_ids) >= 2:
1314 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)
1317 def copy(self, cr, uid, id, default=None, context=None):
1324 'line_cr_ids': False,
1325 'line_dr_ids': False,
1328 if 'date' not in default:
1329 default['date'] = time.strftime('%Y-%m-%d')
1330 return super(account_voucher, self).copy(cr, uid, id, default, context)
1333 class account_voucher_line(osv.osv):
1334 _name = 'account.voucher.line'
1335 _description = 'Voucher Lines'
1336 _order = "move_line_id"
1338 # If the payment is in the same currency than the invoice, we keep the same amount
1339 # Otherwise, we compute from company currency to payment currency
1340 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1341 currency_pool = self.pool.get('res.currency')
1343 for line in self.browse(cr, uid, ids, context=context):
1344 ctx = context.copy()
1345 ctx.update({'date': line.voucher_id.date})
1347 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1348 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1349 move_line = line.move_line_id or False
1352 res['amount_original'] = 0.0
1353 res['amount_unreconciled'] = 0.0
1354 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1355 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1356 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)
1357 elif move_line and move_line.credit > 0:
1358 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1359 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1361 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1362 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1364 rs_data[line.id] = res
1367 def _currency_id(self, cr, uid, ids, name, args, context=None):
1369 This function returns the currency id of a voucher line. It's either the currency of the
1370 associated move line (if any) or the currency of the voucher or the company currency.
1373 for line in self.browse(cr, uid, ids, context=context):
1374 move_line = line.move_line_id
1376 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1378 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1382 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1383 'name':fields.char('Description', size=256),
1384 'account_id':fields.many2one('account.account','Account', required=True),
1385 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1386 'untax_amount':fields.float('Untax Amount'),
1387 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1388 'reconcile': fields.boolean('Full Reconcile'),
1389 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1390 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1391 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1392 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1393 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1394 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1395 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1396 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1397 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1403 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1404 vals = {'amount': 0.0}
1406 vals = { 'amount': amount_unreconciled}
1407 return {'value': vals}
1409 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1412 vals['reconcile'] = (amount == amount_unreconciled)
1413 return {'value': vals}
1415 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1417 Returns a dict that contains new values and context
1419 @param move_line_id: latest value from user input for field move_line_id
1420 @param args: other arguments
1421 @param context: context arguments, like lang, time zone
1423 @return: Returns a dict which contains new values, and context
1426 move_line_pool = self.pool.get('account.move.line')
1428 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1429 if move_line.credit:
1434 'account_id': move_line.account_id.id,
1436 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1442 def default_get(self, cr, user, fields_list, context=None):
1444 Returns default values for fields
1445 @param fields_list: list of fields, for which default values are required to be read
1446 @param context: context arguments, like lang, time zone
1448 @return: Returns a dict that contains default values for fields
1452 journal_id = context.get('journal_id', False)
1453 partner_id = context.get('partner_id', False)
1454 journal_pool = self.pool.get('account.journal')
1455 partner_pool = self.pool.get('res.partner')
1456 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1457 if (not journal_id) or ('account_id' not in fields_list):
1459 journal = journal_pool.browse(cr, user, journal_id, context=context)
1462 if journal.type in ('sale', 'sale_refund'):
1463 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1465 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1466 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1469 partner = partner_pool.browse(cr, user, partner_id, context=context)
1470 if context.get('type') == 'payment':
1472 account_id = partner.property_account_payable.id
1473 elif context.get('type') == 'receipt':
1474 account_id = partner.property_account_receivable.id
1477 'account_id':account_id,
1481 account_voucher_line()
1483 class account_bank_statement(osv.osv):
1484 _inherit = 'account.bank.statement'
1486 def button_confirm_bank(self, cr, uid, ids, context=None):
1487 voucher_obj = self.pool.get('account.voucher')
1489 for statement in self.browse(cr, uid, ids, context=context):
1490 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1492 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1493 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1495 def button_cancel(self, cr, uid, ids, context=None):
1496 voucher_obj = self.pool.get('account.voucher')
1497 for st in self.browse(cr, uid, ids, context=context):
1499 for line in st.line_ids:
1501 voucher_ids.append(line.voucher_id.id)
1502 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1503 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1505 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1506 voucher_obj = self.pool.get('account.voucher')
1507 wf_service = netsvc.LocalService("workflow")
1508 move_line_obj = self.pool.get('account.move.line')
1509 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1510 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1511 if st_line.voucher_id:
1512 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1513 if st_line.voucher_id.state == 'cancel':
1514 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1515 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1517 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1518 bank_st_line_obj.write(cr, uid, [st_line_id], {
1519 'move_ids': [(4, v.move_id.id, False)]
1522 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1523 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1525 def write(self, cr, uid, ids, vals, context=None):
1526 # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1527 # Because the voucher keeps in memory the journal it was created with.
1528 for bk_st in self.browse(cr, uid, ids, context=context):
1529 if vals.get('journal_id') and bk_st.line_ids:
1530 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1531 raise osv.except_osv(_('Unable to change journal !'), _('You can not change the journal as you already reconciled some statement lines!'))
1532 return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1534 account_bank_statement()
1536 class account_bank_statement_line(osv.osv):
1537 _inherit = 'account.bank.statement.line'
1539 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1540 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1541 if 'value' not in res:
1543 res['value'].update({'voucher_id' : False})
1546 def onchange_amount(self, cr, uid, ids, amount, context=None):
1547 return {'value' : {'voucher_id' : False}}
1549 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1553 for line in self.browse(cursor, user, ids, context=context):
1555 res[line.id] = line.voucher_id.amount#
1560 def _check_amount(self, cr, uid, ids, context=None):
1561 for obj in self.browse(cr, uid, ids, context=context):
1563 diff = abs(obj.amount) - obj.voucher_id.amount
1564 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1569 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1573 'amount_reconciled': fields.function(_amount_reconciled,
1574 string='Amount reconciled', type='float'),
1575 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1578 def unlink(self, cr, uid, ids, context=None):
1579 voucher_obj = self.pool.get('account.voucher')
1580 statement_line = self.browse(cr, uid, ids, context=context)
1582 for st_line in statement_line:
1583 if st_line.voucher_id:
1584 unlink_ids.append(st_line.voucher_id.id)
1585 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1586 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1588 account_bank_statement_line()
1590 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1592 for operation in operations:
1594 if not isinstance(operation, (list, tuple)):
1595 result = target_osv.read(cr, uid, operation, fields, context=context)
1596 elif operation[0] == 0:
1597 # may be necessary to check if all the fields are here and get the default values?
1598 result = operation[2]
1599 elif operation[0] == 1:
1600 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1601 if not result: result = {}
1602 result.update(operation[2])
1603 elif operation[0] == 4:
1604 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1606 results.append(result)
1610 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: