1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 from lxml import etree
25 from openerp import netsvc
26 from openerp.osv import fields, osv
27 import openerp.addons.decimal_precision as dp
28 from openerp.tools.translate import _
29 from openerp.tools import float_compare
31 class res_currency(osv.osv):
32 _inherit = "res.currency"
34 def _current_rate(self, cr, uid, ids, name, arg, context=None):
37 res = super(res_currency, self)._current_rate(cr, uid, ids, name, arg, context=context)
38 if context.get('voucher_special_currency') in ids and context.get('voucher_special_currency_rate'):
39 res[context.get('voucher_special_currency')] = context.get('voucher_special_currency_rate')
43 # same definition of rate that in base in order to just overwrite the function
44 'rate': fields.function(_current_rate, string='Current Rate', digits=(12,6),
45 help='The rate of the currency to the currency of rate 1.'),
49 class res_company(osv.osv):
50 _inherit = "res.company"
52 'income_currency_exchange_account_id': fields.many2one(
54 string="Gain Exchange Rate Account",
55 domain="[('type', '=', 'other')]",),
56 'expense_currency_exchange_account_id': fields.many2one(
58 string="Loss Exchange Rate Account",
59 domain="[('type', '=', 'other')]",),
64 class account_config_settings(osv.osv_memory):
65 _inherit = 'account.config.settings'
67 'income_currency_exchange_account_id': fields.related(
68 'company_id', 'income_currency_exchange_account_id',
70 relation='account.account',
71 string="Gain Exchange Rate Account",
72 domain="[('type', '=', 'other')]"),
73 'expense_currency_exchange_account_id': fields.related(
74 'company_id', 'expense_currency_exchange_account_id',
76 relation='account.account',
77 string="Loss Exchange Rate Account",
78 domain="[('type', '=', 'other')]"),
80 def onchange_company_id(self, cr, uid, ids, company_id, context=None):
81 res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id, context=context)
83 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
84 res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False,
85 'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False})
87 res['value'].update({'income_currency_exchange_account_id': False,
88 'expense_currency_exchange_account_id': False})
91 class account_voucher(osv.osv):
92 def _check_paid(self, cr, uid, ids, name, args, context=None):
94 for voucher in self.browse(cr, uid, ids, context=context):
95 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
98 def _get_type(self, cr, uid, context=None):
101 return context.get('type', False)
103 def _get_period(self, cr, uid, context=None):
104 if context is None: context = {}
105 if context.get('period_id', False):
106 return context.get('period_id')
107 ctx = dict(context, account_period_prefer_normal=True)
108 periods = self.pool.get('account.period').find(cr, uid, context=ctx)
109 return periods and periods[0] or False
111 def _make_journal_search(self, cr, uid, ttype, context=None):
112 journal_pool = self.pool.get('account.journal')
113 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
115 def _get_journal(self, cr, uid, context=None):
116 if context is None: context = {}
117 invoice_pool = self.pool.get('account.invoice')
118 journal_pool = self.pool.get('account.journal')
119 if context.get('invoice_id', False):
120 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
121 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
122 return journal_id and journal_id[0] or False
123 if context.get('journal_id', False):
124 return context.get('journal_id')
125 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
126 return context.get('search_default_journal_id')
128 ttype = context.get('type', 'bank')
129 if ttype in ('payment', 'receipt'):
131 res = self._make_journal_search(cr, uid, ttype, context=context)
132 return res and res[0] or False
134 def _get_tax(self, cr, uid, context=None):
135 if context is None: context = {}
136 journal_pool = self.pool.get('account.journal')
137 journal_id = context.get('journal_id', False)
139 ttype = context.get('type', 'bank')
140 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
147 journal = journal_pool.browse(cr, uid, journal_id, context=context)
148 account_id = journal.default_credit_account_id or journal.default_debit_account_id
149 if account_id and account_id.tax_ids:
150 tax_id = account_id.tax_ids[0].id
154 def _get_payment_rate_currency(self, cr, uid, context=None):
156 Return the default value for field payment_rate_currency_id: the currency of the journal
157 if there is one, otherwise the currency of the user's company
159 if context is None: context = {}
160 journal_pool = self.pool.get('account.journal')
161 journal_id = context.get('journal_id', False)
163 journal = journal_pool.browse(cr, uid, journal_id, context=context)
165 return journal.currency.id
166 #no journal given in the context, use company currency as default
167 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
169 def _get_currency(self, cr, uid, context=None):
170 if context is None: context = {}
171 journal_pool = self.pool.get('account.journal')
172 journal_id = context.get('journal_id', False)
174 journal = journal_pool.browse(cr, uid, journal_id, context=context)
176 return journal.currency.id
177 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
179 def _get_partner(self, cr, uid, context=None):
180 if context is None: context = {}
181 return context.get('partner_id', False)
183 def _get_reference(self, cr, uid, context=None):
184 if context is None: context = {}
185 return context.get('reference', False)
187 def _get_narration(self, cr, uid, context=None):
188 if context is None: context = {}
189 return context.get('narration', False)
191 def _get_amount(self, cr, uid, context=None):
194 return context.get('amount', 0.0)
196 def name_get(self, cr, uid, ids, context=None):
199 if context is None: context = {}
200 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
202 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
203 mod_obj = self.pool.get('ir.model.data')
204 if context is None: context = {}
206 if view_type == 'form':
207 if not view_id and context.get('invoice_type'):
208 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
209 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
211 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
212 result = result and result[1] or False
214 if not view_id and context.get('line_type'):
215 if context.get('line_type') == 'customer':
216 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
218 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
219 result = result and result[1] or False
222 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
223 doc = etree.XML(res['arch'])
225 if context.get('type', 'sale') in ('purchase', 'payment'):
226 nodes = doc.xpath("//field[@name='partner_id']")
228 node.set('context', "{'search_default_supplier': 1}")
229 if context.get('invoice_type','') in ('in_invoice', 'in_refund'):
230 node.set('string', _("Supplier"))
231 res['arch'] = etree.tostring(doc)
234 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
236 sign = type == 'payment' and -1 or 1
237 for l in line_dr_ids:
239 for l in line_cr_ids:
240 credit += l['amount']
241 return amount - sign * (credit - debit)
243 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
244 context = context or {}
245 if not line_dr_ids and not line_cr_ids:
246 return {'value':{'writeoff_amount': 0.0}}
247 line_osv = self.pool.get("account.voucher.line")
248 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
249 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
250 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
251 is_multi_currency = False
252 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to see the options
253 for voucher_line in line_dr_ids+line_cr_ids:
254 line_id = voucher_line.get('id') and self.pool.get('account.voucher.line').browse(cr, uid, voucher_line['id'], context=context).move_line_id.id or voucher_line.get('move_line_id')
255 if line_id and self.pool.get('account.move.line').browse(cr, uid, line_id, context=context).currency_id:
256 is_multi_currency = True
258 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
260 def _get_journal_currency(self, cr, uid, ids, name, args, context=None):
262 for voucher in self.browse(cr, uid, ids, context=context):
263 res[voucher.id] = voucher.journal_id.currency and voucher.journal_id.currency.id or voucher.company_id.currency_id.id
266 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
267 if not ids: return {}
268 currency_obj = self.pool.get('res.currency')
271 for voucher in self.browse(cr, uid, ids, context=context):
272 sign = voucher.type == 'payment' and -1 or 1
273 for l in voucher.line_dr_ids:
275 for l in voucher.line_cr_ids:
277 currency = voucher.currency_id or voucher.company_id.currency_id
278 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
281 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
286 for v in self.browse(cr, uid, ids, context=context):
287 ctx.update({'date': v.date})
288 #make a new call to browse in order to have the right date in the context, to get the right currency rate
289 voucher = self.browse(cr, uid, v.id, context=ctx)
291 'voucher_special_currency': voucher.payment_rate_currency_id and voucher.payment_rate_currency_id.id or False,
292 'voucher_special_currency_rate': voucher.currency_id.rate * voucher.payment_rate,})
293 res[voucher.id] = self.pool.get('res.currency').compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, voucher.amount, context=ctx)
296 _name = 'account.voucher'
297 _description = 'Accounting Voucher'
298 _inherit = ['mail.thread']
299 _order = "date desc, id desc"
300 # _rec_name = 'number'
303 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
308 '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."),
309 'type':fields.selection([
311 ('purchase','Purchase'),
312 ('payment','Payment'),
313 ('receipt','Receipt'),
314 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
315 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
316 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
317 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
318 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
319 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
320 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
321 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
322 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
323 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
324 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
325 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
326 'currency_id': fields.function(_get_journal_currency, type='many2one', relation='res.currency', string='Currency', readonly=True, required=True),
327 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
328 'state':fields.selection(
330 ('cancel','Cancelled'),
331 ('proforma','Pro-forma'),
333 ], 'Status', readonly=True, size=32, track_visibility='onchange',
334 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
335 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
336 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
337 \n* The \'Cancelled\' status is used when user cancel voucher.'),
338 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
339 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
340 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
341 'number': fields.char('Number', size=32, readonly=True,),
342 'move_id':fields.many2one('account.move', 'Account Entry'),
343 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
344 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
345 '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'),
346 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
347 'pay_now':fields.selection([
348 ('pay_now','Pay Directly'),
349 ('pay_later','Pay Later or Group Funds'),
350 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
351 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
352 'pre_line':fields.boolean('Previous Payments ?', required=False),
353 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
354 'payment_option':fields.selection([
355 ('without_writeoff', 'Keep Open'),
356 ('with_writeoff', 'Reconcile Payment Balance'),
357 ], '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)"),
358 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
359 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
360 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
361 '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."),
362 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
363 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
364 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
365 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
366 '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'),
370 'period_id': _get_period,
371 'partner_id': _get_partner,
372 'journal_id':_get_journal,
373 'currency_id': _get_currency,
374 'reference': _get_reference,
375 'narration':_get_narration,
376 'amount': _get_amount,
379 'pay_now': 'pay_now',
381 'date': lambda *a: time.strftime('%Y-%m-%d'),
382 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
384 'payment_option': 'without_writeoff',
385 'comment': _('Write-Off'),
387 'payment_rate_currency_id': _get_payment_rate_currency,
390 def compute_tax(self, cr, uid, ids, context=None):
391 tax_pool = self.pool.get('account.tax')
392 partner_pool = self.pool.get('res.partner')
393 position_pool = self.pool.get('account.fiscal.position')
394 voucher_line_pool = self.pool.get('account.voucher.line')
395 voucher_pool = self.pool.get('account.voucher')
396 if context is None: context = {}
398 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
400 for line in voucher.line_ids:
401 voucher_amount += line.untax_amount or line.amount
402 line.amount = line.untax_amount or line.amount
403 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
405 if not voucher.tax_id:
406 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
409 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
410 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
411 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
412 tax = tax_pool.browse(cr, uid, taxes, context=context)
414 total = voucher_amount
417 if not tax[0].price_include:
418 for line in voucher.line_ids:
419 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
420 total_tax += tax_line.get('amount', 0.0)
423 for line in voucher.line_ids:
427 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
428 line_tax += tax_line.get('amount', 0.0)
429 line_total += tax_line.get('price_unit')
430 total_tax += line_tax
431 untax_amount = line.untax_amount or line.amount
432 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
434 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
437 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
438 context = context or {}
439 tax_pool = self.pool.get('account.tax')
440 partner_pool = self.pool.get('res.partner')
441 position_pool = self.pool.get('account.fiscal.position')
442 line_pool = self.pool.get('account.voucher.line')
451 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
454 for line in line_ids:
456 line_amount = line.get('amount',0.0)
459 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
461 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
462 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
463 tax = tax_pool.browse(cr, uid, taxes, context=context)
465 if not tax[0].price_include:
466 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
467 total_tax += tax_line.get('amount')
469 voucher_total += line_amount
470 total = voucher_total + total_tax
473 'amount': total or voucher_total,
474 'tax_amount': total_tax
480 def onchange_term_id(self, cr, uid, ids, term_id, amount):
481 term_pool = self.pool.get('account.payment.term')
484 default = {'date_due':False}
485 if term_id and amount:
486 terms = term_pool.compute(cr, uid, term_id, amount)
488 due_date = terms[-1][0]
492 return {'value':default}
494 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):
496 Returns a dict that contains new values and context
498 @param partner_id: latest value from user input for field partner_id
499 @param args: other arguments
500 @param context: context arguments, like lang, time zone
502 @return: Returns a dict which contains new values, and context
508 if not partner_id or not journal_id:
511 partner_pool = self.pool.get('res.partner')
512 journal_pool = self.pool.get('account.journal')
514 journal = journal_pool.browse(cr, uid, journal_id, context=context)
515 partner = partner_pool.browse(cr, uid, partner_id, context=context)
518 if journal.type in ('sale','sale_refund'):
519 account_id = partner.property_account_receivable.id
521 elif journal.type in ('purchase', 'purchase_refund','expense'):
522 account_id = partner.property_account_payable.id
525 if not journal.default_credit_account_id or not journal.default_debit_account_id:
526 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
527 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
530 default['value']['account_id'] = account_id
531 default['value']['type'] = ttype or tr_type
533 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)
534 default['value'].update(vals.get('value'))
538 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
539 res = {'value': {'paid_amount_in_company_currency': amount}}
540 if rate and amount and currency_id:
541 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
542 #context should contain the date, the payment currency and the payment rate specified on the voucher
543 amount_in_company_currency = self.pool.get('res.currency').compute(cr, uid, currency_id, company_currency.id, amount, context=context)
544 res['value']['paid_amount_in_company_currency'] = amount_in_company_currency
547 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):
551 ctx.update({'date': date})
552 #read the voucher rate with the right date in the context
553 currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
554 voucher_rate = self.pool.get('res.currency').read(cr, uid, currency_id, ['rate'], context=ctx)['rate']
556 'voucher_special_currency': payment_rate_currency_id,
557 'voucher_special_currency_rate': rate * voucher_rate})
558 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=ctx)
559 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
560 for key in vals.keys():
561 res[key].update(vals[key])
564 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
567 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
568 currency_obj = self.pool.get('res.currency')
569 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
570 company_id = journal.company_id.id
572 currency_id = currency_id or journal.company_id.currency_id.id
573 payment_rate_currency_id = currency_id
575 ctx.update({'date': date})
577 if ttype == 'receipt':
578 o2m_to_loop = 'line_cr_ids'
579 elif ttype == 'payment':
580 o2m_to_loop = 'line_dr_ids'
581 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
582 for voucher_line in vals['value'][o2m_to_loop]:
583 if voucher_line['currency_id'] != currency_id:
584 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
585 # is not in the voucher currency
586 payment_rate_currency_id = voucher_line['currency_id']
587 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
588 payment_rate = tmp / currency_obj.browse(cr, uid, currency_id, context=ctx).rate
590 vals['value'].update({
591 'payment_rate': payment_rate,
592 'currency_id': currency_id,
593 'payment_rate_currency_id': payment_rate_currency_id
595 #read the voucher rate with the right date in the context
596 voucher_rate = self.pool.get('res.currency').read(cr, uid, currency_id, ['rate'], context=ctx)['rate']
598 'voucher_special_currency_rate': payment_rate * voucher_rate,
599 'voucher_special_currency': payment_rate_currency_id})
600 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
601 for key in res.keys():
602 vals[key].update(res[key])
605 def basic_onchange_partner(self, cr, uid, ids, partner_id, journal_id, ttype, context=None):
606 partner_pool = self.pool.get('res.partner')
607 journal_pool = self.pool.get('account.journal')
608 res = {'value': {'account_id': False}}
609 if not partner_id or not journal_id:
612 journal = journal_pool.browse(cr, uid, journal_id, context=context)
613 partner = partner_pool.browse(cr, uid, partner_id, context=context)
615 if journal.type in ('sale','sale_refund'):
616 account_id = partner.property_account_receivable.id
617 elif journal.type in ('purchase', 'purchase_refund','expense'):
618 account_id = partner.property_account_payable.id
620 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
622 res['value']['account_id'] = account_id
625 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
630 #TODO: comment me and use me directly in the sales/purchases views
631 res = self.basic_onchange_partner(cr, uid, ids, partner_id, journal_id, ttype, context=context)
632 if ttype in ['sale', 'purchase']:
635 # not passing the payment_rate currency and the payment_rate in the context but it's ok because they are reset in recompute_payment_rate
636 ctx.update({'date': date})
637 vals = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=ctx)
638 vals2 = self.recompute_payment_rate(cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=context)
639 for key in vals.keys():
640 res[key].update(vals[key])
641 for key in vals2.keys():
642 res[key].update(vals2[key])
643 #TODO: can probably be removed now
644 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
645 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
646 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
647 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
648 # onchange returns a value for them
650 del(res['value']['line_dr_ids'])
651 del(res['value']['pre_line'])
652 del(res['value']['payment_rate'])
653 elif ttype == 'purchase':
654 del(res['value']['line_cr_ids'])
655 del(res['value']['pre_line'])
656 del(res['value']['payment_rate'])
659 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
661 Returns a dict that contains new values and context
663 @param partner_id: latest value from user input for field partner_id
664 @param args: other arguments
665 @param context: context arguments, like lang, time zone
667 @return: Returns a dict which contains new values, and context
669 def _remove_noise_in_o2m():
670 """if the line is partially reconciled, then we must pay attention to display it only once and
672 This function returns True if the line is considered as noise and should not be displayed
674 if line.reconcile_partial_id:
675 if currency_id == line.currency_id.id:
676 if line.amount_residual_currency <= 0:
679 if line.amount_residual <= 0:
685 context_multi_currency = context.copy()
687 currency_pool = self.pool.get('res.currency')
688 move_line_pool = self.pool.get('account.move.line')
689 partner_pool = self.pool.get('res.partner')
690 journal_pool = self.pool.get('account.journal')
691 line_pool = self.pool.get('account.voucher.line')
695 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
699 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
701 line_pool.unlink(cr, uid, line_ids)
703 if not partner_id or not journal_id:
706 journal = journal_pool.browse(cr, uid, journal_id, context=context)
707 partner = partner_pool.browse(cr, uid, partner_id, context=context)
708 currency_id = currency_id or journal.company_id.currency_id.id
712 account_type = 'receivable'
713 if ttype == 'payment':
714 account_type = 'payable'
715 total_debit = price or 0.0
717 total_credit = price or 0.0
718 account_type = 'receivable'
720 if not context.get('move_line_ids', False):
721 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
723 ids = context['move_line_ids']
724 invoice_id = context.get('invoice_id', False)
725 company_currency = journal.company_id.currency_id.id
726 move_line_found = False
728 #order the lines by most old first
730 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
732 #compute the total debit/credit and look for a matching open amount or invoice
733 for line in account_move_lines:
734 if _remove_noise_in_o2m():
738 if line.invoice.id == invoice_id:
739 #if the invoice linked to the voucher line is equal to the invoice_id in context
740 #then we assign the amount on that line, whatever the other voucher lines
741 move_line_found = line.id
743 elif currency_id == company_currency:
744 #otherwise treatments is the same but with other field names
745 if line.amount_residual == price:
746 #if the amount residual is equal the amount voucher, we assign it to that voucher
747 #line, whatever the other voucher lines
748 move_line_found = line.id
750 #otherwise we will split the voucher amount on each line (by most old first)
751 total_credit += line.credit or 0.0
752 total_debit += line.debit or 0.0
753 elif currency_id == line.currency_id.id:
754 if line.amount_residual_currency == price:
755 move_line_found = line.id
757 total_credit += line.credit and line.amount_currency or 0.0
758 total_debit += line.debit and line.amount_currency or 0.0
760 #voucher line creation
761 for line in account_move_lines:
763 if _remove_noise_in_o2m():
766 if line.currency_id and currency_id == line.currency_id.id:
767 amount_original = abs(line.amount_currency)
768 amount_unreconciled = abs(line.amount_residual_currency)
770 #always use the amount booked in the company currency as the basis of the conversion into the voucher currency
771 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0, context=context_multi_currency)
772 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual), context=context_multi_currency)
773 line_currency_id = line.currency_id and line.currency_id.id or company_currency
775 'name':line.move_id.name,
776 'type': line.credit and 'dr' or 'cr',
777 'move_line_id':line.id,
778 'account_id':line.account_id.id,
779 'amount_original': amount_original,
780 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
781 'date_original':line.date,
782 'date_due':line.date_maturity,
783 'amount_unreconciled': amount_unreconciled,
784 'currency_id': line_currency_id,
786 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
787 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
788 if not move_line_found:
789 if currency_id == line_currency_id:
791 amount = min(amount_unreconciled, abs(total_debit))
792 rs['amount'] = amount
793 total_debit -= amount
795 amount = min(amount_unreconciled, abs(total_credit))
796 rs['amount'] = amount
797 total_credit -= amount
799 if rs['amount_unreconciled'] == rs['amount']:
800 rs['reconcile'] = True
802 if rs['type'] == 'cr':
803 default['value']['line_cr_ids'].append(rs)
805 default['value']['line_dr_ids'].append(rs)
807 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
808 default['value']['pre_line'] = 1
809 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
810 default['value']['pre_line'] = 1
811 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
814 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
818 if currency_id and currency_id == payment_rate_currency_id:
819 #set the default payment rate of the voucher and compute the paid amount in company currency
821 ctx.update({'date': date})
822 #read the voucher rate with the right date in the context
823 voucher_rate = self.pool.get('res.currency').read(cr, uid, currency_id, ['rate'], context=ctx)['rate']
825 'voucher_special_currency_rate': payment_rate * voucher_rate,
826 'voucher_special_currency': payment_rate_currency_id})
827 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
828 for key in vals.keys():
829 res[key].update(vals[key])
832 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
834 @param date: latest value from user input for field date
835 @param args: other arguments
836 @param context: context arguments, like lang, time zone
837 @return: Returns a dict which contains new values, and context
842 #set the period of the voucher
843 period_pool = self.pool.get('account.period')
844 currency_obj = self.pool.get('res.currency')
846 ctx.update({'company_id': company_id, 'account_period_prefer_normal': True})
847 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
848 pids = period_pool.find(cr, uid, date, context=ctx)
850 res['value'].update({'period_id':pids[0]})
851 if payment_rate_currency_id:
852 ctx.update({'date': date})
854 if payment_rate_currency_id != currency_id:
855 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
856 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
857 vals = self.onchange_payment_rate_currency(cr, uid, ids, voucher_currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
858 vals['value'].update({'payment_rate': payment_rate})
859 for key in vals.keys():
860 res[key].update(vals[key])
863 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
866 journal_pool = self.pool.get('account.journal')
867 journal = journal_pool.browse(cr, uid, journal_id, context=context)
868 account_id = journal.default_credit_account_id or journal.default_debit_account_id
870 if account_id and account_id.tax_ids:
871 tax_id = account_id.tax_ids[0].id
874 if ttype in ('sale', 'purchase'):
875 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
876 vals['value'].update({'tax_id':tax_id,'amount': amount})
879 currency_id = journal.currency.id
881 currency_id = journal.company_id.currency_id.id
882 vals['value'].update({'currency_id': currency_id})
883 #in case we want to register the payment directly from an invoice, it's confusing to allow to switch the journal
884 #without seeing that the amount is expressed in the journal currency, and not in the invoice currency. So to avoid
885 #this common mistake, we simply reset the amount to 0 if the currency is not the invoice currency.
886 if context.get('payment_expected_currency') and currency_id != context.get('payment_expected_currency'):
887 vals['value']['amount'] = 0
889 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
890 for key in res.keys():
891 vals[key].update(res[key])
894 def button_proforma_voucher(self, cr, uid, ids, context=None):
895 context = context or {}
896 wf_service = netsvc.LocalService("workflow")
898 wf_service.trg_validate(uid, 'account.voucher', vid, 'proforma_voucher', cr)
899 return {'type': 'ir.actions.act_window_close'}
901 def proforma_voucher(self, cr, uid, ids, context=None):
902 self.action_move_line_create(cr, uid, ids, context=context)
905 def action_cancel_draft(self, cr, uid, ids, context=None):
906 wf_service = netsvc.LocalService("workflow")
907 for voucher_id in ids:
908 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
909 self.write(cr, uid, ids, {'state':'draft'})
912 def cancel_voucher(self, cr, uid, ids, context=None):
913 reconcile_pool = self.pool.get('account.move.reconcile')
914 move_pool = self.pool.get('account.move')
916 for voucher in self.browse(cr, uid, ids, context=context):
918 for line in voucher.move_ids:
919 if line.reconcile_id:
920 recs += [line.reconcile_id.id]
921 if line.reconcile_partial_id:
922 recs += [line.reconcile_partial_id.id]
924 reconcile_pool.unlink(cr, uid, recs)
927 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
928 move_pool.unlink(cr, uid, [voucher.move_id.id])
933 self.write(cr, uid, ids, res)
936 def unlink(self, cr, uid, ids, context=None):
937 for t in self.read(cr, uid, ids, ['state'], context=context):
938 if t['state'] not in ('draft', 'cancel'):
939 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
940 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
942 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
946 res = {'account_id':False}
947 partner_pool = self.pool.get('res.partner')
948 journal_pool = self.pool.get('account.journal')
949 if pay_now == 'pay_later':
950 partner = partner_pool.browse(cr, uid, partner_id)
951 journal = journal_pool.browse(cr, uid, journal_id)
952 if journal.type in ('sale','sale_refund'):
953 account_id = partner.property_account_receivable.id
954 elif journal.type in ('purchase', 'purchase_refund','expense'):
955 account_id = partner.property_account_payable.id
957 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
958 res['account_id'] = account_id
961 def _sel_context(self, cr, uid, voucher_id, context=None):
963 Select the context to use accordingly if it needs to be multicurrency or not.
965 :param voucher_id: Id of the actual voucher
966 :return: The returned context will be the same as given in parameter if the voucher currency is the same
967 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
968 the date of the voucher.
971 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
972 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
973 if current_currency <> company_currency:
974 context_multi_currency = context.copy()
975 voucher = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
976 context_multi_currency.update({'date': voucher.date})
977 return context_multi_currency
980 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
982 Return a dict to be use to create the first account move line of given voucher.
984 :param voucher_id: Id of voucher what we are creating account_move.
985 :param move_id: Id of account move where this line will be added.
986 :param company_currency: id of currency of the company to which the voucher belong
987 :param current_currency: id of currency of the voucher
988 :return: mapping between fieldname and value of account move line to create
991 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
993 # TODO: is there any other alternative then the voucher type ??
994 # ANSWER: We can have payment and receipt "In Advance".
995 # TODO: Make this logic available.
996 # -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
997 if voucher.type in ('purchase', 'payment'):
998 credit = voucher.paid_amount_in_company_currency
999 elif voucher.type in ('sale', 'receipt'):
1000 debit = voucher.paid_amount_in_company_currency
1001 if debit < 0: credit = -debit; debit = 0.0
1002 if credit < 0: debit = -credit; credit = 0.0
1003 sign = debit - credit < 0 and -1 or 1
1004 #set the first line of the voucher
1006 'name': voucher.name or '/',
1009 'account_id': voucher.account_id.id,
1011 'journal_id': voucher.journal_id.id,
1012 'period_id': voucher.period_id.id,
1013 'partner_id': voucher.partner_id.id,
1014 'currency_id': company_currency <> current_currency and current_currency or False,
1015 'amount_currency': company_currency <> current_currency and sign * voucher.amount or 0.0,
1016 'date': voucher.date,
1017 'date_maturity': voucher.date_due
1021 def account_move_get(self, cr, uid, voucher_id, context=None):
1023 This method prepare the creation of the account move related to the given voucher.
1025 :param voucher_id: Id of voucher for which we are creating account_move.
1026 :return: mapping between fieldname and value of account move to create
1029 seq_obj = self.pool.get('ir.sequence')
1030 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1032 name = voucher.number
1033 elif voucher.journal_id.sequence_id:
1034 if not voucher.journal_id.sequence_id.active:
1035 raise osv.except_osv(_('Configuration Error !'),
1036 _('Please activate the sequence of selected journal !'))
1038 c.update({'fiscalyear_id': voucher.period_id.fiscalyear_id.id})
1039 name = seq_obj.next_by_id(cr, uid, voucher.journal_id.sequence_id.id, context=c)
1041 raise osv.except_osv(_('Error!'),
1042 _('Please define a sequence on the journal.'))
1043 if not voucher.reference:
1044 ref = name.replace('/','')
1046 ref = voucher.reference
1050 'journal_id': voucher.journal_id.id,
1051 'narration': voucher.narration,
1052 'date': voucher.date,
1054 'period_id': voucher.period_id.id,
1058 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
1060 Prepare the two lines in company currency due to currency rate difference.
1062 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
1064 :param move_id: Account move wher the move lines will be.
1065 :param amount_residual: Amount to be posted.
1066 :param company_currency: id of currency of the company to which the voucher belong
1067 :param current_currency: id of currency of the voucher
1068 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
1069 :rtype: tuple of dict
1071 if amount_residual > 0:
1072 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1074 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."))
1076 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1078 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."))
1079 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1080 # the receivable/payable account may have a secondary currency, which render this field mandatory
1081 if line.account_id.currency_id:
1082 account_currency_id = line.account_id.currency_id.id
1084 account_currency_id = company_currency <> current_currency and current_currency or False
1086 'journal_id': line.voucher_id.journal_id.id,
1087 'period_id': line.voucher_id.period_id.id,
1088 'name': _('change')+': '+(line.name or '/'),
1089 'account_id': line.account_id.id,
1091 'partner_id': line.voucher_id.partner_id.id,
1092 'currency_id': account_currency_id,
1093 'amount_currency': 0.0,
1095 'credit': amount_residual > 0 and amount_residual or 0.0,
1096 'debit': amount_residual < 0 and -amount_residual or 0.0,
1097 'date': line.voucher_id.date,
1099 move_line_counterpart = {
1100 'journal_id': line.voucher_id.journal_id.id,
1101 'period_id': line.voucher_id.period_id.id,
1102 'name': _('change')+': '+(line.name or '/'),
1103 'account_id': account_id.id,
1105 'amount_currency': 0.0,
1106 'partner_id': line.voucher_id.partner_id.id,
1107 'currency_id': account_currency_id,
1109 'debit': amount_residual > 0 and amount_residual or 0.0,
1110 'credit': amount_residual < 0 and -amount_residual or 0.0,
1111 'date': line.voucher_id.date,
1113 return (move_line, move_line_counterpart)
1115 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1117 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1118 payment_rate_currency_id is relevant) either the rate encoded in the system.
1120 :param amount: float. The amount to convert
1121 :param voucher: id of the voucher on which we want the conversion
1122 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1123 field in order to select the good rate to use.
1124 :return: the amount in the currency of the voucher's company
1129 currency_obj = self.pool.get('res.currency')
1130 voucher = self.browse(cr, uid, voucher_id, context=context)
1131 return currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1133 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1135 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1136 It returns Tuple with tot_line what is total of difference between debit and credit and
1137 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1139 :param voucher_id: Voucher id what we are working with
1140 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1141 :param move_id: Account move wher those lines will be joined.
1142 :param company_currency: id of currency of the company to which the voucher belong
1143 :param current_currency: id of currency of the voucher
1144 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1145 :rtype: tuple(float, list of int)
1149 move_line_obj = self.pool.get('account.move.line')
1150 currency_obj = self.pool.get('res.currency')
1151 tax_obj = self.pool.get('account.tax')
1152 tot_line = line_total
1155 date = self.read(cr, uid, voucher_id, ['date'], context=context)['date']
1156 ctx = context.copy()
1157 ctx.update({'date': date})
1158 voucher = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context=ctx)
1159 voucher_currency = voucher.journal_id.currency or voucher.company_id.currency_id
1161 'voucher_special_currency_rate': voucher_currency.rate * voucher.payment_rate ,
1162 'voucher_special_currency': voucher.payment_rate_currency_id and voucher.payment_rate_currency_id.id or False,})
1163 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1164 for line in voucher.line_ids:
1165 #create one move line per voucher line where amount is not 0.0
1166 # AND (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
1167 if not line.amount and not (line.move_line_id and not float_compare(line.move_line_id.debit, line.move_line_id.credit, precision_rounding=prec) and not float_compare(line.move_line_id.debit, 0.0, precision_rounding=prec)):
1169 # convert the amount set on the voucher line into the currency of the voucher's company
1170 # this calls res_curreny.compute() with the right context, so that it will take either the rate on the voucher if it is relevant or will use the default behaviour
1171 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher.id, context=ctx)
1172 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1173 # currency rate difference
1174 if line.amount == line.amount_unreconciled:
1175 if not line.move_line_id:
1176 raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1177 sign = voucher.type in ('payment', 'purchase') and -1 or 1
1178 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1180 currency_rate_difference = 0.0
1182 'journal_id': voucher.journal_id.id,
1183 'period_id': voucher.period_id.id,
1184 'name': line.name or '/',
1185 'account_id': line.account_id.id,
1187 'partner_id': voucher.partner_id.id,
1188 '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,
1189 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1193 'date': voucher.date
1197 if line.type == 'dr':
1202 if (line.type=='dr'):
1204 move_line['debit'] = amount
1207 move_line['credit'] = amount
1209 if voucher.tax_id and voucher.type in ('sale', 'purchase'):
1211 'account_tax_id': voucher.tax_id.id,
1214 if move_line.get('account_tax_id', False):
1215 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1216 if not (tax_data.base_code_id and tax_data.tax_code_id):
1217 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))
1219 # compute the amount in foreign currency
1220 foreign_currency_diff = 0.0
1221 amount_currency = False
1222 if line.move_line_id:
1223 # We want to set it on the account move line as soon as the original line had a foreign currency
1224 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1225 # we compute the amount in that foreign currency.
1226 if line.move_line_id.currency_id.id == current_currency:
1227 # if the voucher and the voucher line share the same currency, there is no computation to do
1228 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1229 amount_currency = sign * (line.amount)
1231 # if the rate is specified on the voucher, it will be used thanks to the special keys in the context
1232 # otherwise we use the rates of the system
1233 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1234 if line.amount == line.amount_unreconciled:
1235 sign = voucher.type in ('payment', 'purchase') and -1 or 1
1236 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1238 move_line['amount_currency'] = amount_currency
1239 voucher_line = move_line_obj.create(cr, uid, move_line)
1240 rec_ids = [voucher_line, line.move_line_id.id]
1242 if not currency_obj.is_zero(cr, uid, voucher.company_id.currency_id, currency_rate_difference):
1243 # Change difference entry in company currency
1244 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1245 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1246 move_line_obj.create(cr, uid, exch_lines[1], context)
1247 rec_ids.append(new_id)
1249 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):
1250 # Change difference entry in voucher currency
1251 move_line_foreign_currency = {
1252 'journal_id': line.voucher_id.journal_id.id,
1253 'period_id': line.voucher_id.period_id.id,
1254 'name': _('change')+': '+(line.name or '/'),
1255 'account_id': line.account_id.id,
1257 'partner_id': line.voucher_id.partner_id.id,
1258 'currency_id': line.move_line_id.currency_id.id,
1259 'amount_currency': -1 * foreign_currency_diff,
1263 'date': line.voucher_id.date,
1265 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1266 rec_ids.append(new_id)
1268 if line.move_line_id.id:
1269 rec_lst_ids.append(rec_ids)
1271 return (tot_line, rec_lst_ids)
1273 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1275 Set a dict to be use to create the writeoff move line.
1277 :param voucher_id: Id of voucher what we are creating account_move.
1278 :param line_total: Amount remaining to be allocated on lines.
1279 :param move_id: Id of account move where this line will be added.
1280 :param name: Description of account move line.
1281 :param company_currency: id of currency of the company to which the voucher belong
1282 :param current_currency: id of currency of the voucher
1283 :return: mapping between fieldname and value of account move line to create
1286 currency_obj = self.pool.get('res.currency')
1289 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1290 current_currency_obj = voucher.currency_id or voucher.journal_id.company_id.currency_id
1292 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1296 if voucher.payment_option == 'with_writeoff':
1297 account_id = voucher.writeoff_acc_id.id
1298 write_off_name = voucher.comment
1299 elif voucher.type in ('sale', 'receipt'):
1300 account_id = voucher.partner_id.property_account_receivable.id
1302 account_id = voucher.partner_id.property_account_payable.id
1303 sign = voucher.type == 'payment' and -1 or 1
1305 'name': write_off_name or name,
1306 'account_id': account_id,
1308 'partner_id': voucher.partner_id.id,
1309 'date': voucher.date,
1310 'credit': diff > 0 and diff or 0.0,
1311 'debit': diff < 0 and -diff or 0.0,
1312 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher.writeoff_amount) or False,
1313 'currency_id': company_currency <> current_currency and current_currency or False,
1314 'analytic_account_id': voucher.analytic_id and voucher.analytic_id.id or False,
1319 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1321 Get the currency of the actual company.
1323 :param voucher_id: Id of the voucher what i want to obtain company currency.
1324 :return: currency id of the company of the voucher
1327 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1329 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1331 Get the currency of the voucher.
1333 :param voucher_id: Id of the voucher what i want to obtain current currency.
1334 :return: currency id of the voucher
1337 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1338 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1340 def action_move_line_create(self, cr, uid, ids, context=None):
1342 Confirm the vouchers given in ids and create the journal entries for each of them
1346 move_pool = self.pool.get('account.move')
1347 move_line_pool = self.pool.get('account.move.line')
1348 for voucher in self.browse(cr, uid, ids, context=context):
1351 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1352 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1353 # we select the context to use accordingly if it's a multicurrency case or not
1354 context = self._sel_context(cr, uid, voucher.id, context)
1355 # But for the operations made by _convert_amount, we always need to give the date in the context
1356 ctx = context.copy()
1357 ctx.update({'date': voucher.date})
1358 # Create the account move record.
1359 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1360 # Get the name of the account_move just created
1361 name = move_pool.browse(cr, uid, move_id, context=context).name
1362 # Create the first line of the voucher
1363 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)
1364 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1365 line_total = move_line_brw.debit - move_line_brw.credit
1367 if voucher.type == 'sale':
1368 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1369 elif voucher.type == 'purchase':
1370 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1371 # Create one move line per voucher line where amount is not 0.0
1372 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1374 # Create the writeoff line if needed
1375 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1377 move_line_pool.create(cr, uid, ml_writeoff, context)
1378 # We post the voucher.
1379 self.write(cr, uid, [voucher.id], {
1384 if voucher.journal_id.entry_posted:
1385 move_pool.post(cr, uid, [move_id], context={})
1386 # We automatically reconcile the account move lines.
1388 for rec_ids in rec_list_ids:
1389 if len(rec_ids) >= 2:
1390 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)
1393 def copy(self, cr, uid, id, default=None, context=None):
1400 'line_cr_ids': False,
1401 'line_dr_ids': False,
1404 if 'date' not in default:
1405 default['date'] = time.strftime('%Y-%m-%d')
1406 return super(account_voucher, self).copy(cr, uid, id, default, context)
1409 class account_voucher_line(osv.osv):
1410 _name = 'account.voucher.line'
1411 _description = 'Voucher Lines'
1412 _order = "move_line_id"
1414 # If the payment is in the same currency than the invoice, we keep the same amount
1415 # Otherwise, we compute from invoice currency to payment currency
1416 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1417 currency_pool = self.pool.get('res.currency')
1419 for line in self.browse(cr, uid, ids, context=context):
1420 ctx = context.copy()
1421 ctx.update({'date': line.voucher_id.date})
1422 voucher_rate = self.pool.get('res.currency').read(cr, uid, line.voucher_id.currency_id.id, ['rate'], context=ctx)['rate']
1424 'voucher_special_currency': line.voucher_id.payment_rate_currency_id and line.voucher_id.payment_rate_currency_id.id or False,
1425 'voucher_special_currency_rate': line.voucher_id.payment_rate * voucher_rate})
1427 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1428 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1429 move_line = line.move_line_id or False
1432 res['amount_original'] = 0.0
1433 res['amount_unreconciled'] = 0.0
1434 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1435 res['amount_original'] = abs(move_line.amount_currency)
1436 res['amount_unreconciled'] = abs(move_line.amount_residual_currency)
1438 #always use the amount booked in the company currency as the basis of the conversion into the voucher currency
1439 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit or move_line.debit or 0.0, context=ctx)
1440 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1442 rs_data[line.id] = res
1445 def _currency_id(self, cr, uid, ids, name, args, context=None):
1447 This function returns the currency id of a voucher line. It's either the currency of the
1448 associated move line (if any) or the currency of the voucher or the company currency.
1451 for line in self.browse(cr, uid, ids, context=context):
1452 move_line = line.move_line_id
1454 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1456 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1460 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1461 'name':fields.char('Description', size=256),
1462 'account_id':fields.many2one('account.account','Account', required=True),
1463 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1464 'untax_amount':fields.float('Untax Amount'),
1465 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1466 'reconcile': fields.boolean('Full Reconcile'),
1467 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1468 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1469 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1470 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1471 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1472 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1473 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1474 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1475 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1481 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1482 vals = {'amount': 0.0}
1484 vals = { 'amount': amount_unreconciled}
1485 return {'value': vals}
1487 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1490 vals['reconcile'] = (amount == amount_unreconciled)
1491 return {'value': vals}
1493 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1495 Returns a dict that contains new values and context
1497 @param move_line_id: latest value from user input for field move_line_id
1498 @param args: other arguments
1499 @param context: context arguments, like lang, time zone
1501 @return: Returns a dict which contains new values, and context
1504 move_line_pool = self.pool.get('account.move.line')
1506 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1507 if move_line.credit:
1512 'account_id': move_line.account_id.id,
1514 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1520 def default_get(self, cr, user, fields_list, context=None):
1522 Returns default values for fields
1523 @param fields_list: list of fields, for which default values are required to be read
1524 @param context: context arguments, like lang, time zone
1526 @return: Returns a dict that contains default values for fields
1530 journal_id = context.get('journal_id', False)
1531 partner_id = context.get('partner_id', False)
1532 journal_pool = self.pool.get('account.journal')
1533 partner_pool = self.pool.get('res.partner')
1534 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1535 if (not journal_id) or ('account_id' not in fields_list):
1537 journal = journal_pool.browse(cr, user, journal_id, context=context)
1540 if journal.type in ('sale', 'sale_refund'):
1541 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1543 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1544 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1547 partner = partner_pool.browse(cr, user, partner_id, context=context)
1548 if context.get('type') == 'payment':
1550 account_id = partner.property_account_payable.id
1551 elif context.get('type') == 'receipt':
1552 account_id = partner.property_account_receivable.id
1555 'account_id':account_id,
1559 account_voucher_line()
1561 class account_bank_statement(osv.osv):
1562 _inherit = 'account.bank.statement'
1564 def button_confirm_bank(self, cr, uid, ids, context=None):
1565 voucher_obj = self.pool.get('account.voucher')
1567 for statement in self.browse(cr, uid, ids, context=context):
1568 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1570 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1571 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1573 def button_cancel(self, cr, uid, ids, context=None):
1574 voucher_obj = self.pool.get('account.voucher')
1575 for st in self.browse(cr, uid, ids, context=context):
1577 for line in st.line_ids:
1579 voucher_ids.append(line.voucher_id.id)
1580 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1581 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1583 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1584 voucher_obj = self.pool.get('account.voucher')
1585 wf_service = netsvc.LocalService("workflow")
1586 move_line_obj = self.pool.get('account.move.line')
1587 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1588 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1589 if st_line.voucher_id:
1590 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1591 if st_line.voucher_id.state == 'cancel':
1592 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1593 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1595 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1596 bank_st_line_obj.write(cr, uid, [st_line_id], {
1597 'move_ids': [(4, v.move_id.id, False)]
1600 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1601 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1603 def write(self, cr, uid, ids, vals, context=None):
1604 # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1605 # Because the voucher keeps in memory the journal it was created with.
1606 for bk_st in self.browse(cr, uid, ids, context=context):
1607 if vals.get('journal_id') and bk_st.line_ids:
1608 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1609 raise osv.except_osv(_('Unable to change journal !'), _('You can not change the journal as you already reconciled some statement lines!'))
1610 return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1612 account_bank_statement()
1614 class account_bank_statement_line(osv.osv):
1615 _inherit = 'account.bank.statement.line'
1617 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1618 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1619 if 'value' not in res:
1621 res['value'].update({'voucher_id' : False})
1624 def onchange_amount(self, cr, uid, ids, amount, context=None):
1625 return {'value' : {'voucher_id' : False}}
1627 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1631 for line in self.browse(cursor, user, ids, context=context):
1633 res[line.id] = line.voucher_id.amount#
1638 def _check_amount(self, cr, uid, ids, context=None):
1639 for obj in self.browse(cr, uid, ids, context=context):
1641 diff = abs(obj.amount) - obj.voucher_id.amount
1642 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1647 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1651 'amount_reconciled': fields.function(_amount_reconciled,
1652 string='Amount reconciled', type='float'),
1653 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1656 def unlink(self, cr, uid, ids, context=None):
1657 voucher_obj = self.pool.get('account.voucher')
1658 statement_line = self.browse(cr, uid, ids, context=context)
1660 for st_line in statement_line:
1661 if st_line.voucher_id:
1662 unlink_ids.append(st_line.voucher_id.id)
1663 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1664 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1666 account_bank_statement_line()
1668 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1670 for operation in operations:
1672 if not isinstance(operation, (list, tuple)):
1673 result = target_osv.read(cr, uid, operation, fields, context=context)
1674 elif operation[0] == 0:
1675 # may be necessary to check if all the fields are here and get the default values?
1676 result = operation[2]
1677 elif operation[0] == 1:
1678 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1679 if not result: result = {}
1680 result.update(operation[2])
1681 elif operation[0] == 4:
1682 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1684 results.append(result)
1688 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: