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 domain="[('type', '=', 'other')]"),
54 'expense_currency_exchange_account_id': fields.related(
55 'company_id', 'expense_currency_exchange_account_id',
57 relation='account.account',
58 string="Loss Exchange Rate Account",
59 domain="[('type', '=', 'other')]"),
61 def onchange_company_id(self, cr, uid, ids, company_id):
62 res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id)
64 company = self.pool.get('res.company').browse(cr, uid, company_id)
65 res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False,
66 'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False})
68 res['value'].update({'income_currency_exchange_account_id': False,
69 'expense_currency_exchange_account_id': False})
72 class account_voucher(osv.osv):
73 def _check_paid(self, cr, uid, ids, name, args, context=None):
75 for voucher in self.browse(cr, uid, ids, context=context):
76 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
79 def _get_type(self, cr, uid, context=None):
82 return context.get('type', False)
84 def _get_period(self, cr, uid, context=None):
85 if context is None: context = {}
86 if context.get('period_id', False):
87 return context.get('period_id')
88 periods = self.pool.get('account.period').find(cr, uid)
89 return periods and periods[0] or False
91 def _make_journal_search(self, cr, uid, ttype, context=None):
92 journal_pool = self.pool.get('account.journal')
93 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
95 def _get_journal(self, cr, uid, context=None):
96 if context is None: context = {}
97 invoice_pool = self.pool.get('account.invoice')
98 journal_pool = self.pool.get('account.journal')
99 if context.get('invoice_id', False):
100 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
101 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
102 return journal_id and journal_id[0] or False
103 if context.get('journal_id', False):
104 return context.get('journal_id')
105 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
106 return context.get('search_default_journal_id')
108 ttype = context.get('type', 'bank')
109 if ttype in ('payment', 'receipt'):
111 res = self._make_journal_search(cr, uid, ttype, context=context)
112 return res and res[0] or False
114 def _get_tax(self, cr, uid, context=None):
115 if context is None: context = {}
116 journal_pool = self.pool.get('account.journal')
117 journal_id = context.get('journal_id', False)
119 ttype = context.get('type', 'bank')
120 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
127 journal = journal_pool.browse(cr, uid, journal_id, context=context)
128 account_id = journal.default_credit_account_id or journal.default_debit_account_id
129 if account_id and account_id.tax_ids:
130 tax_id = account_id.tax_ids[0].id
134 def _get_payment_rate_currency(self, cr, uid, context=None):
136 Return the default value for field payment_rate_currency_id: the currency of the journal
137 if there is one, otherwise the currency of the user's company
139 if context is None: context = {}
140 journal_pool = self.pool.get('account.journal')
141 journal_id = context.get('journal_id', False)
143 journal = journal_pool.browse(cr, uid, journal_id, context=context)
145 return journal.currency.id
146 #no journal given in the context, use company currency as default
147 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
149 def _get_currency(self, cr, uid, context=None):
150 if context is None: context = {}
151 journal_pool = self.pool.get('account.journal')
152 journal_id = context.get('journal_id', False)
154 journal = journal_pool.browse(cr, uid, journal_id, context=context)
156 return journal.currency.id
159 def _get_partner(self, cr, uid, context=None):
160 if context is None: context = {}
161 return context.get('partner_id', False)
163 def _get_reference(self, cr, uid, context=None):
164 if context is None: context = {}
165 return context.get('reference', False)
167 def _get_narration(self, cr, uid, context=None):
168 if context is None: context = {}
169 return context.get('narration', False)
171 def _get_amount(self, cr, uid, context=None):
174 return context.get('amount', 0.0)
176 def name_get(self, cr, uid, ids, context=None):
179 if context is None: context = {}
180 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
182 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
183 mod_obj = self.pool.get('ir.model.data')
184 if context is None: context = {}
186 if view_type == 'form':
187 if not view_id and context.get('invoice_type'):
188 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
189 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
191 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
192 result = result and result[1] or False
194 if not view_id and context.get('line_type'):
195 if context.get('line_type') == 'customer':
196 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
198 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
199 result = result and result[1] or False
202 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
203 doc = etree.XML(res['arch'])
205 if context.get('type', 'sale') in ('purchase', 'payment'):
206 nodes = doc.xpath("//field[@name='partner_id']")
208 node.set('domain', "[('supplier', '=', True)]")
209 res['arch'] = etree.tostring(doc)
212 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
214 sign = type == 'payment' and -1 or 1
215 for l in line_dr_ids:
217 for l in line_cr_ids:
218 credit += l['amount']
219 return amount - sign * (credit - debit)
221 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
222 context = context or {}
223 if not line_dr_ids and not line_cr_ids:
225 line_osv = self.pool.get("account.voucher.line")
226 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
227 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
229 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
230 is_multi_currency = False
232 # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
233 is_multi_currency = True
235 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
236 for voucher_line in line_dr_ids+line_cr_ids:
237 company_currency = False
238 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
239 if voucher_line.get('currency_id', company_currency) != company_currency:
240 is_multi_currency = True
242 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
244 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
245 if not ids: return {}
246 currency_obj = self.pool.get('res.currency')
249 for voucher in self.browse(cr, uid, ids, context=context):
250 sign = voucher.type == 'payment' and -1 or 1
251 for l in voucher.line_dr_ids:
253 for l in voucher.line_cr_ids:
255 currency = voucher.currency_id or voucher.company_id.currency_id
256 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
259 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
260 if not ids: return {}
263 for voucher in self.browse(cr, uid, ids, context=context):
264 if voucher.currency_id:
265 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
266 rate = 1 / voucher.payment_rate
269 ctx.update({'date': voucher.date})
270 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
271 company_currency_rate = voucher.company_id.currency_id.rate
272 rate = voucher_rate * company_currency_rate
273 res[voucher.id] = voucher.amount / rate
276 _name = 'account.voucher'
277 _description = 'Accounting Voucher'
278 _inherit = ['mail.thread']
279 _order = "date desc, id desc"
280 # _rec_name = 'number'
282 '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."),
283 'type':fields.selection([
285 ('purchase','Purchase'),
286 ('payment','Payment'),
287 ('receipt','Receipt'),
288 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
289 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
290 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
291 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
292 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
293 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
294 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
295 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
296 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
297 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
298 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
299 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
300 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
301 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
302 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
303 'state':fields.selection(
305 ('cancel','Cancelled'),
306 ('proforma','Pro-forma'),
308 ], 'Status', readonly=True, size=32,
309 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
310 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
311 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
312 \n* The \'Cancelled\' status is used when user cancel voucher.'),
313 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
314 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
315 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
316 'number': fields.char('Number', size=32, readonly=True,),
317 'move_id':fields.many2one('account.move', 'Account Entry'),
318 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
319 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
320 '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'),
321 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
322 'pay_now':fields.selection([
323 ('pay_now','Pay Directly'),
324 ('pay_later','Pay Later or Group Funds'),
325 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
326 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
327 'pre_line':fields.boolean('Previous Payments ?', required=False),
328 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
329 'payment_option':fields.selection([
330 ('without_writeoff', 'Keep Open'),
331 ('with_writeoff', 'Reconcile Payment Balance'),
332 ], '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)"),
333 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
334 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
335 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
336 '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."),
337 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
338 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
339 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
340 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
341 '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'),
345 'period_id': _get_period,
346 'partner_id': _get_partner,
347 'journal_id':_get_journal,
348 'currency_id': _get_currency,
349 'reference': _get_reference,
350 'narration':_get_narration,
351 'amount': _get_amount,
354 'pay_now': 'pay_now',
356 'date': lambda *a: time.strftime('%Y-%m-%d'),
357 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
359 'payment_option': 'without_writeoff',
360 'comment': _('Write-Off'),
362 'payment_rate_currency_id': _get_payment_rate_currency,
365 def create(self, cr, uid, vals, context=None):
366 voucher = super(account_voucher, self).create(cr, uid, vals, context=context)
367 self.create_send_note(cr, uid, [voucher], context=context)
370 def compute_tax(self, cr, uid, ids, context=None):
371 tax_pool = self.pool.get('account.tax')
372 partner_pool = self.pool.get('res.partner')
373 position_pool = self.pool.get('account.fiscal.position')
374 voucher_line_pool = self.pool.get('account.voucher.line')
375 voucher_pool = self.pool.get('account.voucher')
376 if context is None: context = {}
378 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
380 for line in voucher.line_ids:
381 voucher_amount += line.untax_amount or line.amount
382 line.amount = line.untax_amount or line.amount
383 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
385 if not voucher.tax_id:
386 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
389 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
390 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
391 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
392 tax = tax_pool.browse(cr, uid, taxes, context=context)
394 total = voucher_amount
397 if not tax[0].price_include:
398 for line in voucher.line_ids:
399 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
400 total_tax += tax_line.get('amount', 0.0)
403 for line in voucher.line_ids:
407 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
408 line_tax += tax_line.get('amount', 0.0)
409 line_total += tax_line.get('price_unit')
410 total_tax += line_tax
411 untax_amount = line.untax_amount or line.amount
412 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
414 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
417 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
418 context = context or {}
419 tax_pool = self.pool.get('account.tax')
420 partner_pool = self.pool.get('res.partner')
421 position_pool = self.pool.get('account.fiscal.position')
422 line_pool = self.pool.get('account.voucher.line')
429 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
432 for line in line_ids:
434 line_amount = line.get('amount',0.0)
437 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
439 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
440 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
441 tax = tax_pool.browse(cr, uid, taxes, context=context)
443 if not tax[0].price_include:
444 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
445 total_tax += tax_line.get('amount')
447 voucher_total += line_amount
448 total = voucher_total + total_tax
451 'amount': total or voucher_total,
452 'tax_amount': total_tax
458 def onchange_term_id(self, cr, uid, ids, term_id, amount):
459 term_pool = self.pool.get('account.payment.term')
462 default = {'date_due':False}
463 if term_id and amount:
464 terms = term_pool.compute(cr, uid, term_id, amount)
466 due_date = terms[-1][0]
470 return {'value':default}
472 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):
474 Returns a dict that contains new values and context
476 @param partner_id: latest value from user input for field partner_id
477 @param args: other arguments
478 @param context: context arguments, like lang, time zone
480 @return: Returns a dict which contains new values, and context
486 if not partner_id or not journal_id:
489 partner_pool = self.pool.get('res.partner')
490 journal_pool = self.pool.get('account.journal')
492 journal = journal_pool.browse(cr, uid, journal_id, context=context)
493 partner = partner_pool.browse(cr, uid, partner_id, context=context)
496 if journal.type in ('sale','sale_refund'):
497 account_id = partner.property_account_receivable.id
499 elif journal.type in ('purchase', 'purchase_refund','expense'):
500 account_id = partner.property_account_payable.id
503 if not journal.default_credit_account_id or not journal.default_debit_account_id:
504 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
505 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
508 default['value']['account_id'] = account_id
509 default['value']['type'] = ttype or tr_type
511 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)
512 default['value'].update(vals.get('value'))
516 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
517 res = {'value': {'paid_amount_in_company_currency': amount}}
518 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
519 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
520 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
521 if company_currency.id == payment_rate_currency_id:
524 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
525 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
528 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):
531 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
533 ctx.update({'date': date})
534 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
535 for key in vals.keys():
536 res[key].update(vals[key])
539 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
542 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
543 currency_obj = self.pool.get('res.currency')
544 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
545 company_id = journal.company_id.id
547 payment_rate_currency_id = currency_id
549 ctx.update({'date': date})
551 if ttype == 'receipt':
552 o2m_to_loop = 'line_cr_ids'
553 elif ttype == 'payment':
554 o2m_to_loop = 'line_dr_ids'
555 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
556 for voucher_line in vals['value'][o2m_to_loop]:
557 if voucher_line['currency_id'] != currency_id:
558 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
559 # is not in the voucher currency
560 payment_rate_currency_id = voucher_line['currency_id']
561 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
562 voucher_currency_id = currency_id or journal.company_id.currency_id.id
563 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
565 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
566 for key in res.keys():
567 vals[key].update(res[key])
568 vals['value'].update({'payment_rate': payment_rate})
569 if payment_rate_currency_id:
570 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
573 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
576 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
577 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
578 for key in vals.keys():
579 res[key].update(vals[key])
580 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
581 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
582 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
583 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
584 # onchange returns a value for them
586 del(res['value']['line_dr_ids'])
587 del(res['value']['pre_line'])
588 del(res['value']['payment_rate'])
589 elif ttype == 'purchase':
590 del(res['value']['line_cr_ids'])
591 del(res['value']['pre_line'])
592 del(res['value']['payment_rate'])
595 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
597 Returns a dict that contains new values and context
599 @param partner_id: latest value from user input for field partner_id
600 @param args: other arguments
601 @param context: context arguments, like lang, time zone
603 @return: Returns a dict which contains new values, and context
605 def _remove_noise_in_o2m():
606 """if the line is partially reconciled, then we must pay attention to display it only once and
608 This function returns True if the line is considered as noise and should not be displayed
610 if line.reconcile_partial_id:
611 sign = 1 if ttype == 'receipt' else -1
612 if currency_id == line.currency_id.id:
613 if line.amount_residual_currency * sign <= 0:
616 if line.amount_residual * sign <= 0:
622 context_multi_currency = context.copy()
624 context_multi_currency.update({'date': date})
626 currency_pool = self.pool.get('res.currency')
627 move_line_pool = self.pool.get('account.move.line')
628 partner_pool = self.pool.get('res.partner')
629 journal_pool = self.pool.get('account.journal')
630 line_pool = self.pool.get('account.voucher.line')
634 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
638 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
640 line_pool.unlink(cr, uid, line_ids)
642 if not partner_id or not journal_id:
645 journal = journal_pool.browse(cr, uid, journal_id, context=context)
646 partner = partner_pool.browse(cr, uid, partner_id, context=context)
647 currency_id = currency_id or journal.company_id.currency_id.id
649 if journal.type in ('sale','sale_refund'):
650 account_id = partner.property_account_receivable.id
651 elif journal.type in ('purchase', 'purchase_refund','expense'):
652 account_id = partner.property_account_payable.id
654 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
656 default['value']['account_id'] = account_id
658 if journal.type not in ('cash', 'bank'):
663 account_type = 'receivable'
664 if ttype == 'payment':
665 account_type = 'payable'
666 total_debit = price or 0.0
668 total_credit = price or 0.0
669 account_type = 'receivable'
671 if not context.get('move_line_ids', False):
672 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
674 ids = context['move_line_ids']
675 invoice_id = context.get('invoice_id', False)
676 company_currency = journal.company_id.currency_id.id
677 move_line_found = False
679 #order the lines by most old first
681 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
683 #compute the total debit/credit and look for a matching open amount or invoice
684 for line in account_move_lines:
685 if _remove_noise_in_o2m():
689 if line.invoice.id == invoice_id:
690 #if the invoice linked to the voucher line is equal to the invoice_id in context
691 #then we assign the amount on that line, whatever the other voucher lines
692 move_line_found = line.id
694 elif currency_id == company_currency:
695 #otherwise treatments is the same but with other field names
696 if line.amount_residual == price:
697 #if the amount residual is equal the amount voucher, we assign it to that voucher
698 #line, whatever the other voucher lines
699 move_line_found = line.id
701 #otherwise we will split the voucher amount on each line (by most old first)
702 total_credit += line.credit or 0.0
703 total_debit += line.debit or 0.0
704 elif currency_id == line.currency_id.id:
705 if line.amount_residual_currency == price:
706 move_line_found = line.id
708 total_credit += line.credit and line.amount_currency or 0.0
709 total_debit += line.debit and line.amount_currency or 0.0
711 #voucher line creation
712 for line in account_move_lines:
714 if _remove_noise_in_o2m():
717 if line.currency_id and currency_id==line.currency_id.id:
718 amount_original = abs(line.amount_currency)
719 amount_unreconciled = abs(line.amount_residual_currency)
721 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
722 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
723 line_currency_id = line.currency_id and line.currency_id.id or company_currency
725 'name':line.move_id.name,
726 'type': line.credit and 'dr' or 'cr',
727 'move_line_id':line.id,
728 'account_id':line.account_id.id,
729 'amount_original': amount_original,
730 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
731 'date_original':line.date,
732 'date_due':line.date_maturity,
733 'amount_unreconciled': amount_unreconciled,
734 'currency_id': line_currency_id,
736 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
737 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
738 if not move_line_found:
739 if currency_id == line_currency_id:
741 amount = min(amount_unreconciled, abs(total_debit))
742 rs['amount'] = amount
743 total_debit -= amount
745 amount = min(amount_unreconciled, abs(total_credit))
746 rs['amount'] = amount
747 total_credit -= amount
749 if rs['amount_unreconciled'] == rs['amount']:
750 rs['reconcile'] = True
752 if rs['type'] == 'cr':
753 default['value']['line_cr_ids'].append(rs)
755 default['value']['line_dr_ids'].append(rs)
757 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
758 default['value']['pre_line'] = 1
759 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
760 default['value']['pre_line'] = 1
761 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
764 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
768 #set the default payment rate of the voucher and compute the paid amount in company currency
769 if currency_id and currency_id == payment_rate_currency_id:
771 ctx.update({'date': date})
772 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
773 for key in vals.keys():
774 res[key].update(vals[key])
777 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
779 @param date: latest value from user input for field date
780 @param args: other arguments
781 @param context: context arguments, like lang, time zone
782 @return: Returns a dict which contains new values, and context
787 #set the period of the voucher
788 period_pool = self.pool.get('account.period')
789 currency_obj = self.pool.get('res.currency')
791 ctx.update({'company_id': company_id})
792 pids = period_pool.find(cr, uid, date, context=ctx)
794 res['value'].update({'period_id':pids[0]})
795 if payment_rate_currency_id:
796 ctx.update({'date': date})
798 if payment_rate_currency_id != currency_id:
799 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
800 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
801 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
802 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
803 vals['value'].update({'payment_rate': payment_rate})
804 for key in vals.keys():
805 res[key].update(vals[key])
808 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
811 journal_pool = self.pool.get('account.journal')
812 journal = journal_pool.browse(cr, uid, journal_id, context=context)
813 account_id = journal.default_credit_account_id or journal.default_debit_account_id
815 if account_id and account_id.tax_ids:
816 tax_id = account_id.tax_ids[0].id
819 if ttype in ('sale', 'purchase'):
820 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
821 vals['value'].update({'tax_id':tax_id,'amount': amount})
824 currency_id = journal.currency.id
825 vals['value'].update({'currency_id': currency_id})
826 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
827 for key in res.keys():
828 vals[key].update(res[key])
831 def button_proforma_voucher(self, cr, uid, ids, context=None):
832 context = context or {}
833 wf_service = netsvc.LocalService("workflow")
835 wf_service.trg_validate(uid, 'account.voucher', vid, 'proforma_voucher', cr)
836 return {'type': 'ir.actions.act_window_close'}
838 def proforma_voucher(self, cr, uid, ids, context=None):
839 self.action_move_line_create(cr, uid, ids, context=context)
842 def action_cancel_draft(self, cr, uid, ids, context=None):
843 wf_service = netsvc.LocalService("workflow")
844 for voucher_id in ids:
845 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
846 self.write(cr, uid, ids, {'state':'draft'})
849 def cancel_voucher(self, cr, uid, ids, context=None):
850 reconcile_pool = self.pool.get('account.move.reconcile')
851 move_pool = self.pool.get('account.move')
853 for voucher in self.browse(cr, uid, ids, context=context):
855 for line in voucher.move_ids:
856 if line.reconcile_id:
857 recs += [line.reconcile_id.id]
858 if line.reconcile_partial_id:
859 recs += [line.reconcile_partial_id.id]
861 reconcile_pool.unlink(cr, uid, recs)
864 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
865 move_pool.unlink(cr, uid, [voucher.move_id.id])
870 self.write(cr, uid, ids, res)
873 def unlink(self, cr, uid, ids, context=None):
874 for t in self.read(cr, uid, ids, ['state'], context=context):
875 if t['state'] not in ('draft', 'cancel'):
876 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
877 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
879 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
883 res = {'account_id':False}
884 partner_pool = self.pool.get('res.partner')
885 journal_pool = self.pool.get('account.journal')
886 if pay_now == 'pay_later':
887 partner = partner_pool.browse(cr, uid, partner_id)
888 journal = journal_pool.browse(cr, uid, journal_id)
889 if journal.type in ('sale','sale_refund'):
890 account_id = partner.property_account_receivable.id
891 elif journal.type in ('purchase', 'purchase_refund','expense'):
892 account_id = partner.property_account_payable.id
894 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
895 res['account_id'] = account_id
898 def _sel_context(self, cr, uid, voucher_id, context=None):
900 Select the context to use accordingly if it needs to be multicurrency or not.
902 :param voucher_id: Id of the actual voucher
903 :return: The returned context will be the same as given in parameter if the voucher currency is the same
904 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
905 the date of the voucher.
908 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
909 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
910 if current_currency <> company_currency:
911 context_multi_currency = context.copy()
912 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
913 context_multi_currency.update({'date': voucher_brw.date})
914 return context_multi_currency
917 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
919 Return a dict to be use to create the first account move line of given voucher.
921 :param voucher_id: Id of voucher what we are creating account_move.
922 :param move_id: Id of account move where this line will be added.
923 :param company_currency: id of currency of the company to which the voucher belong
924 :param current_currency: id of currency of the voucher
925 :return: mapping between fieldname and value of account move line to create
928 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
930 # TODO: is there any other alternative then the voucher type ??
931 # ANSWER: We can have payment and receipt "In Advance".
932 # TODO: Make this logic available.
933 # -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
934 if voucher_brw.type in ('purchase', 'payment'):
935 credit = voucher_brw.paid_amount_in_company_currency
936 elif voucher_brw.type in ('sale', 'receipt'):
937 debit = voucher_brw.paid_amount_in_company_currency
938 if debit < 0: credit = -debit; debit = 0.0
939 if credit < 0: debit = -credit; credit = 0.0
940 sign = debit - credit < 0 and -1 or 1
941 #set the first line of the voucher
943 'name': voucher_brw.name or '/',
946 'account_id': voucher_brw.account_id.id,
948 'journal_id': voucher_brw.journal_id.id,
949 'period_id': voucher_brw.period_id.id,
950 'partner_id': voucher_brw.partner_id.id,
951 'currency_id': company_currency <> current_currency and current_currency or False,
952 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
953 'date': voucher_brw.date,
954 'date_maturity': voucher_brw.date_due
958 def account_move_get(self, cr, uid, voucher_id, context=None):
960 This method prepare the creation of the account move related to the given voucher.
962 :param voucher_id: Id of voucher for which we are creating account_move.
963 :return: mapping between fieldname and value of account move to create
966 seq_obj = self.pool.get('ir.sequence')
967 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
968 if voucher_brw.number:
969 name = voucher_brw.number
970 elif voucher_brw.journal_id.sequence_id:
971 if not voucher_brw.journal_id.sequence_id.active:
972 raise osv.except_osv(_('Configuration Error !'),
973 _('Please activate the sequence of selected journal !'))
974 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
976 raise osv.except_osv(_('Error!'),
977 _('Please define a sequence on the journal.'))
978 if not voucher_brw.reference:
979 ref = name.replace('/','')
981 ref = voucher_brw.reference
985 'journal_id': voucher_brw.journal_id.id,
986 'narration': voucher_brw.narration,
987 'date': voucher_brw.date,
989 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
993 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
995 Prepare the two lines in company currency due to currency rate difference.
997 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
999 :param move_id: Account move wher the move lines will be.
1000 :param amount_residual: Amount to be posted.
1001 :param company_currency: id of currency of the company to which the voucher belong
1002 :param current_currency: id of currency of the voucher
1003 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
1004 :rtype: tuple of dict
1006 if amount_residual > 0:
1007 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1009 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."))
1011 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1013 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."))
1014 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1015 # the receivable/payable account may have a secondary currency, which render this field mandatory
1016 account_currency_id = company_currency <> current_currency and current_currency or False
1018 'journal_id': line.voucher_id.journal_id.id,
1019 'period_id': line.voucher_id.period_id.id,
1020 'name': _('change')+': '+(line.name or '/'),
1021 'account_id': line.account_id.id,
1023 'partner_id': line.voucher_id.partner_id.id,
1024 'currency_id': account_currency_id,
1025 'amount_currency': 0.0,
1027 'credit': amount_residual > 0 and amount_residual or 0.0,
1028 'debit': amount_residual < 0 and -amount_residual or 0.0,
1029 'date': line.voucher_id.date,
1031 move_line_counterpart = {
1032 'journal_id': line.voucher_id.journal_id.id,
1033 'period_id': line.voucher_id.period_id.id,
1034 'name': _('change')+': '+(line.name or '/'),
1035 'account_id': account_id.id,
1037 'amount_currency': 0.0,
1038 'partner_id': line.voucher_id.partner_id.id,
1039 'currency_id': account_currency_id,
1041 'debit': amount_residual > 0 and amount_residual or 0.0,
1042 'credit': amount_residual < 0 and -amount_residual or 0.0,
1043 'date': line.voucher_id.date,
1045 return (move_line, move_line_counterpart)
1047 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1049 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1050 payment_rate_currency_id is relevant) either the rate encoded in the system.
1052 :param amount: float. The amount to convert
1053 :param voucher: id of the voucher on which we want the conversion
1054 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1055 field in order to select the good rate to use.
1056 :return: the amount in the currency of the voucher's company
1059 currency_obj = self.pool.get('res.currency')
1060 voucher = self.browse(cr, uid, voucher_id, context=context)
1062 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1063 # the rate specified on the voucher is for the company currency
1064 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1066 # the rate specified on the voucher is not relevant, we use all the rates in the system
1067 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1070 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1072 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1073 It returns Tuple with tot_line what is total of difference between debit and credit and
1074 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1076 :param voucher_id: Voucher id what we are working with
1077 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1078 :param move_id: Account move wher those lines will be joined.
1079 :param company_currency: id of currency of the company to which the voucher belong
1080 :param current_currency: id of currency of the voucher
1081 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1082 :rtype: tuple(float, list of int)
1086 move_line_obj = self.pool.get('account.move.line')
1087 currency_obj = self.pool.get('res.currency')
1088 tax_obj = self.pool.get('account.tax')
1089 tot_line = line_total
1092 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1093 ctx = context.copy()
1094 ctx.update({'date': voucher_brw.date})
1095 for line in voucher_brw.line_ids:
1096 #create one move line per voucher line where amount is not 0.0
1099 # convert the amount set on the voucher line into the currency of the voucher's company
1100 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1101 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1102 # currency rate difference
1103 if line.amount == line.amount_unreconciled:
1104 if not line.move_line_id.amount_residual:
1105 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))
1106 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1107 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1109 currency_rate_difference = 0.0
1111 'journal_id': voucher_brw.journal_id.id,
1112 'period_id': voucher_brw.period_id.id,
1113 'name': line.name or '/',
1114 'account_id': line.account_id.id,
1116 'partner_id': voucher_brw.partner_id.id,
1117 'currency_id': line.move_line_id and (company_currency <> line.move_line_id.currency_id.id and line.move_line_id.currency_id.id) or False,
1118 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1122 'date': voucher_brw.date
1126 if line.type == 'dr':
1131 if (line.type=='dr'):
1133 move_line['debit'] = amount
1136 move_line['credit'] = amount
1138 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1140 'account_tax_id': voucher_brw.tax_id.id,
1143 if move_line.get('account_tax_id', False):
1144 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1145 if not (tax_data.base_code_id and tax_data.tax_code_id):
1146 raise osv.except_osv(_('No Account Base Code and Account Tax Code!'),_("You have to configure account base code and account tax code on the '%s' tax!") % (tax_data.name))
1148 # compute the amount in foreign currency
1149 foreign_currency_diff = 0.0
1150 amount_currency = False
1151 if line.move_line_id:
1152 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1153 # We want to set it on the account move line as soon as the original line had a foreign currency
1154 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1155 # we compute the amount in that foreign currency.
1156 if line.move_line_id.currency_id.id == current_currency:
1157 # if the voucher and the voucher line share the same currency, there is no computation to do
1158 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1159 amount_currency = sign * (line.amount)
1160 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1161 # if the rate is specified on the voucher, we must use it
1162 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1163 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1165 # otherwise we use the rates of the system (giving the voucher date in the context)
1166 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1167 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1168 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1169 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1171 move_line['amount_currency'] = amount_currency
1172 voucher_line = move_line_obj.create(cr, uid, move_line)
1173 rec_ids = [voucher_line, line.move_line_id.id]
1175 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1176 # Change difference entry in company currency
1177 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1178 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1179 move_line_obj.create(cr, uid, exch_lines[1], context)
1180 rec_ids.append(new_id)
1182 if line.move_line_id and line.move_line_id.currency_id and not currency_obj.is_zero(cr, uid, line.move_line_id.currency_id, foreign_currency_diff):
1183 # Change difference entry in voucher currency
1184 move_line_foreign_currency = {
1185 'journal_id': line.voucher_id.journal_id.id,
1186 'period_id': line.voucher_id.period_id.id,
1187 'name': _('change')+': '+(line.name or '/'),
1188 'account_id': line.account_id.id,
1190 'partner_id': line.voucher_id.partner_id.id,
1191 'currency_id': line.move_line_id.currency_id.id,
1192 'amount_currency': -1 * foreign_currency_diff,
1196 'date': line.voucher_id.date,
1198 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1199 rec_ids.append(new_id)
1201 if line.move_line_id.id:
1202 rec_lst_ids.append(rec_ids)
1204 return (tot_line, rec_lst_ids)
1206 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1208 Set a dict to be use to create the writeoff move line.
1210 :param voucher_id: Id of voucher what we are creating account_move.
1211 :param line_total: Amount remaining to be allocated on lines.
1212 :param move_id: Id of account move where this line will be added.
1213 :param name: Description of account move line.
1214 :param company_currency: id of currency of the company to which the voucher belong
1215 :param current_currency: id of currency of the voucher
1216 :return: mapping between fieldname and value of account move line to create
1219 currency_obj = self.pool.get('res.currency')
1222 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1223 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1225 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1229 if voucher_brw.payment_option == 'with_writeoff':
1230 account_id = voucher_brw.writeoff_acc_id.id
1231 write_off_name = voucher_brw.comment
1232 elif voucher_brw.type in ('sale', 'receipt'):
1233 account_id = voucher_brw.partner_id.property_account_receivable.id
1235 account_id = voucher_brw.partner_id.property_account_payable.id
1236 sign = voucher_brw.type == 'payment' and -1 or 1
1238 'name': write_off_name or name,
1239 'account_id': account_id,
1241 'partner_id': voucher_brw.partner_id.id,
1242 'date': voucher_brw.date,
1243 'credit': diff > 0 and diff or 0.0,
1244 'debit': diff < 0 and -diff or 0.0,
1245 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1246 'currency_id': company_currency <> current_currency and current_currency or False,
1247 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1252 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1254 Get the currency of the actual company.
1256 :param voucher_id: Id of the voucher what i want to obtain company currency.
1257 :return: currency id of the company of the voucher
1260 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1262 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1264 Get the currency of the voucher.
1266 :param voucher_id: Id of the voucher what i want to obtain current currency.
1267 :return: currency id of the voucher
1270 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1271 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1273 def action_move_line_create(self, cr, uid, ids, context=None):
1275 Confirm the vouchers given in ids and create the journal entries for each of them
1279 move_pool = self.pool.get('account.move')
1280 move_line_pool = self.pool.get('account.move.line')
1281 for voucher in self.browse(cr, uid, ids, context=context):
1284 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1285 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1286 # we select the context to use accordingly if it's a multicurrency case or not
1287 context = self._sel_context(cr, uid, voucher.id, context)
1288 # But for the operations made by _convert_amount, we always need to give the date in the context
1289 ctx = context.copy()
1290 ctx.update({'date': voucher.date})
1291 # Create the account move record.
1292 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1293 # Get the name of the account_move just created
1294 name = move_pool.browse(cr, uid, move_id, context=context).name
1295 # Create the first line of the voucher
1296 move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, company_currency, current_currency, context), context)
1297 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1298 line_total = move_line_brw.debit - move_line_brw.credit
1300 if voucher.type == 'sale':
1301 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1302 elif voucher.type == 'purchase':
1303 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1304 # Create one move line per voucher line where amount is not 0.0
1305 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1307 # Create the writeoff line if needed
1308 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1310 move_line_pool.create(cr, uid, ml_writeoff, context)
1311 # We post the voucher.
1312 self.write(cr, uid, [voucher.id], {
1317 self.post_send_note(cr, uid, [voucher.id], context=context)
1318 if voucher.journal_id.entry_posted:
1319 move_pool.post(cr, uid, [move_id], context={})
1320 # We automatically reconcile the account move lines.
1322 for rec_ids in rec_list_ids:
1323 if len(rec_ids) >= 2:
1324 reconcile = move_line_pool.reconcile_partial(cr, uid, rec_ids, writeoff_acc_id=voucher.writeoff_acc_id.id, writeoff_period_id=voucher.period_id.id, writeoff_journal_id=voucher.journal_id.id)
1326 self.reconcile_send_note(cr, uid, [voucher.id], context=context)
1329 def copy(self, cr, uid, id, default=None, context=None):
1336 'line_cr_ids': False,
1337 'line_dr_ids': False,
1340 if 'date' not in default:
1341 default['date'] = time.strftime('%Y-%m-%d')
1342 return super(account_voucher, self).copy(cr, uid, id, default, context)
1344 # -----------------------------------------
1345 # OpenChatter notifications and need_action
1346 # -----------------------------------------
1348 'sale': 'Sales Receipt',
1349 'purchase': 'Purchase Receipt',
1350 'payment': 'Supplier Payment',
1351 'receipt': 'Customer Payment',
1355 def create_send_note(self, cr, uid, ids, context=None):
1356 for obj in self.browse(cr, uid, ids, context=context):
1357 message = "%s <b>created</b>." % self._document_type[obj.type or False]
1358 self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
1360 def post_send_note(self, cr, uid, ids, context=None):
1361 for obj in self.browse(cr, uid, ids, context=context):
1362 message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
1363 self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
1365 def reconcile_send_note(self, cr, uid, ids, context=None):
1366 for obj in self.browse(cr, uid, ids, context=context):
1367 message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
1368 self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
1372 class account_voucher_line(osv.osv):
1373 _name = 'account.voucher.line'
1374 _description = 'Voucher Lines'
1375 _order = "move_line_id"
1377 # If the payment is in the same currency than the invoice, we keep the same amount
1378 # Otherwise, we compute from company currency to payment currency
1379 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1380 currency_pool = self.pool.get('res.currency')
1382 for line in self.browse(cr, uid, ids, context=context):
1383 ctx = context.copy()
1384 ctx.update({'date': line.voucher_id.date})
1386 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1387 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1388 move_line = line.move_line_id or False
1391 res['amount_original'] = 0.0
1392 res['amount_unreconciled'] = 0.0
1393 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1394 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1395 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)
1396 elif move_line and move_line.credit > 0:
1397 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1398 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1400 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1401 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1403 rs_data[line.id] = res
1406 def _currency_id(self, cr, uid, ids, name, args, context=None):
1408 This function returns the currency id of a voucher line. It's either the currency of the
1409 associated move line (if any) or the currency of the voucher or the company currency.
1412 for line in self.browse(cr, uid, ids, context=context):
1413 move_line = line.move_line_id
1415 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1417 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1421 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1422 'name':fields.char('Description', size=256),
1423 'account_id':fields.many2one('account.account','Account', required=True),
1424 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1425 'untax_amount':fields.float('Untax Amount'),
1426 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1427 'reconcile': fields.boolean('Full Reconcile'),
1428 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1429 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1430 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1431 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1432 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1433 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1434 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1435 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1436 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1442 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1443 vals = {'amount': 0.0}
1445 vals = { 'amount': amount_unreconciled}
1446 return {'value': vals}
1448 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1451 vals['reconcile'] = (amount == amount_unreconciled)
1452 return {'value': vals}
1454 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1456 Returns a dict that contains new values and context
1458 @param move_line_id: latest value from user input for field move_line_id
1459 @param args: other arguments
1460 @param context: context arguments, like lang, time zone
1462 @return: Returns a dict which contains new values, and context
1465 move_line_pool = self.pool.get('account.move.line')
1467 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1468 if move_line.credit:
1473 'account_id': move_line.account_id.id,
1475 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1481 def default_get(self, cr, user, fields_list, context=None):
1483 Returns default values for fields
1484 @param fields_list: list of fields, for which default values are required to be read
1485 @param context: context arguments, like lang, time zone
1487 @return: Returns a dict that contains default values for fields
1491 journal_id = context.get('journal_id', False)
1492 partner_id = context.get('partner_id', False)
1493 journal_pool = self.pool.get('account.journal')
1494 partner_pool = self.pool.get('res.partner')
1495 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1496 if (not journal_id) or ('account_id' not in fields_list):
1498 journal = journal_pool.browse(cr, user, journal_id, context=context)
1501 if journal.type in ('sale', 'sale_refund'):
1502 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1504 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1505 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1508 partner = partner_pool.browse(cr, user, partner_id, context=context)
1509 if context.get('type') == 'payment':
1511 account_id = partner.property_account_payable.id
1512 elif context.get('type') == 'receipt':
1513 account_id = partner.property_account_receivable.id
1516 'account_id':account_id,
1520 account_voucher_line()
1522 class account_bank_statement(osv.osv):
1523 _inherit = 'account.bank.statement'
1525 def button_confirm_bank(self, cr, uid, ids, context=None):
1526 voucher_obj = self.pool.get('account.voucher')
1528 for statement in self.browse(cr, uid, ids, context=context):
1529 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1531 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1532 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1534 def button_cancel(self, cr, uid, ids, context=None):
1535 voucher_obj = self.pool.get('account.voucher')
1536 for st in self.browse(cr, uid, ids, context=context):
1538 for line in st.line_ids:
1540 voucher_ids.append(line.voucher_id.id)
1541 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1542 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1544 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1545 voucher_obj = self.pool.get('account.voucher')
1546 wf_service = netsvc.LocalService("workflow")
1547 move_line_obj = self.pool.get('account.move.line')
1548 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1549 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1550 if st_line.voucher_id:
1551 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1552 if st_line.voucher_id.state == 'cancel':
1553 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1554 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1556 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1557 bank_st_line_obj.write(cr, uid, [st_line_id], {
1558 'move_ids': [(4, v.move_id.id, False)]
1561 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1562 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1564 def write(self, cr, uid, ids, vals, context=None):
1565 # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1566 # Because the voucher keeps in memory the journal it was created with.
1567 for bk_st in self.browse(cr, uid, ids, context=context):
1568 if vals.get('journal_id') and bk_st.line_ids:
1569 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1570 raise osv.except_osv(_('Unable to change journal !'), _('You can not change the journal as you already reconciled some statement lines!'))
1571 return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1573 account_bank_statement()
1575 class account_bank_statement_line(osv.osv):
1576 _inherit = 'account.bank.statement.line'
1578 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1579 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1580 if 'value' not in res:
1582 res['value'].update({'voucher_id' : False})
1585 def onchange_amount(self, cr, uid, ids, amount, context=None):
1586 return {'value' : {'voucher_id' : False}}
1588 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1592 for line in self.browse(cursor, user, ids, context=context):
1594 res[line.id] = line.voucher_id.amount#
1599 def _check_amount(self, cr, uid, ids, context=None):
1600 for obj in self.browse(cr, uid, ids, context=context):
1602 diff = abs(obj.amount) - obj.voucher_id.amount
1603 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1608 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1612 'amount_reconciled': fields.function(_amount_reconciled,
1613 string='Amount reconciled', type='float'),
1614 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1617 def unlink(self, cr, uid, ids, context=None):
1618 voucher_obj = self.pool.get('account.voucher')
1619 statement_line = self.browse(cr, uid, ids, context=context)
1621 for st_line in statement_line:
1622 if st_line.voucher_id:
1623 unlink_ids.append(st_line.voucher_id.id)
1624 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1625 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1627 account_bank_statement_line()
1629 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1631 for operation in operations:
1633 if not isinstance(operation, (list, tuple)):
1634 result = target_osv.read(cr, uid, operation, fields, context=context)
1635 elif operation[0] == 0:
1636 # may be necessary to check if all the fields are here and get the default values?
1637 result = operation[2]
1638 elif operation[0] == 1:
1639 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1640 if not result: result = {}
1641 result.update(operation[2])
1642 elif operation[0] == 4:
1643 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1645 results.append(result)
1649 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: