1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 from lxml import etree
25 from openerp import netsvc
26 from openerp.osv import fields, osv
27 import openerp.addons.decimal_precision as dp
28 from openerp.tools.translate import _
30 class res_company(osv.osv):
31 _inherit = "res.company"
33 'income_currency_exchange_account_id': fields.many2one(
35 string="Gain Exchange Rate Account",
36 domain="[('type', '=', 'other')]",),
37 'expense_currency_exchange_account_id': fields.many2one(
39 string="Loss Exchange Rate Account",
40 domain="[('type', '=', 'other')]",),
45 class account_config_settings(osv.osv_memory):
46 _inherit = 'account.config.settings'
48 'income_currency_exchange_account_id': fields.related(
49 'company_id', 'income_currency_exchange_account_id',
51 relation='account.account',
52 string="Gain Exchange Rate Account"),
53 'expense_currency_exchange_account_id': fields.related(
54 'company_id', 'expense_currency_exchange_account_id',
56 relation='account.account',
57 string="Loss Exchange Rate Account"),
59 def onchange_company_id(self, cr, uid, ids, company_id):
60 res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id)
62 company = self.pool.get('res.company').browse(cr, uid, company_id)
63 res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id,
64 'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id})
67 class account_voucher(osv.osv):
68 def _check_paid(self, cr, uid, ids, name, args, context=None):
70 for voucher in self.browse(cr, uid, ids, context=context):
71 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
74 def _get_type(self, cr, uid, context=None):
77 return context.get('type', False)
79 def _get_period(self, cr, uid, context=None):
80 if context is None: context = {}
81 if context.get('period_id', False):
82 return context.get('period_id')
83 periods = self.pool.get('account.period').find(cr, uid)
84 return periods and periods[0] or False
86 def _make_journal_search(self, cr, uid, ttype, context=None):
87 journal_pool = self.pool.get('account.journal')
88 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
90 def _get_journal(self, cr, uid, context=None):
91 if context is None: context = {}
92 invoice_pool = self.pool.get('account.invoice')
93 journal_pool = self.pool.get('account.journal')
94 if context.get('invoice_id', False):
95 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
96 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
97 return journal_id and journal_id[0] or False
98 if context.get('journal_id', False):
99 return context.get('journal_id')
100 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
101 return context.get('search_default_journal_id')
103 ttype = context.get('type', 'bank')
104 if ttype in ('payment', 'receipt'):
106 res = self._make_journal_search(cr, uid, ttype, context=context)
107 return res and res[0] or False
109 def _get_tax(self, cr, uid, context=None):
110 if context is None: context = {}
111 journal_pool = self.pool.get('account.journal')
112 journal_id = context.get('journal_id', False)
114 ttype = context.get('type', 'bank')
115 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
122 journal = journal_pool.browse(cr, uid, journal_id, context=context)
123 account_id = journal.default_credit_account_id or journal.default_debit_account_id
124 if account_id and account_id.tax_ids:
125 tax_id = account_id.tax_ids[0].id
129 def _get_payment_rate_currency(self, cr, uid, context=None):
131 Return the default value for field payment_rate_currency_id: the currency of the journal
132 if there is one, otherwise the currency of the user's company
134 if context is None: context = {}
135 journal_pool = self.pool.get('account.journal')
136 journal_id = context.get('journal_id', False)
138 journal = journal_pool.browse(cr, uid, journal_id, context=context)
140 return journal.currency.id
141 #no journal given in the context, use company currency as default
142 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
144 def _get_currency(self, cr, uid, context=None):
145 if context is None: context = {}
146 journal_pool = self.pool.get('account.journal')
147 journal_id = context.get('journal_id', False)
149 journal = journal_pool.browse(cr, uid, journal_id, context=context)
151 return journal.currency.id
154 def _get_partner(self, cr, uid, context=None):
155 if context is None: context = {}
156 return context.get('partner_id', False)
158 def _get_reference(self, cr, uid, context=None):
159 if context is None: context = {}
160 return context.get('reference', False)
162 def _get_narration(self, cr, uid, context=None):
163 if context is None: context = {}
164 return context.get('narration', False)
166 def _get_amount(self, cr, uid, context=None):
169 return context.get('amount', 0.0)
171 def name_get(self, cr, uid, ids, context=None):
174 if context is None: context = {}
175 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
177 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
178 mod_obj = self.pool.get('ir.model.data')
179 if context is None: context = {}
181 if view_type == 'form':
182 if not view_id and context.get('invoice_type'):
183 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
184 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
186 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
187 result = result and result[1] or False
189 if not view_id and context.get('line_type'):
190 if context.get('line_type') == 'customer':
191 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
193 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
194 result = result and result[1] or False
197 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
198 doc = etree.XML(res['arch'])
200 if context.get('type', 'sale') in ('purchase', 'payment'):
201 nodes = doc.xpath("//field[@name='partner_id']")
203 node.set('domain', "[('supplier', '=', True)]")
204 res['arch'] = etree.tostring(doc)
207 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
209 sign = type == 'payment' and -1 or 1
210 for l in line_dr_ids:
212 for l in line_cr_ids:
213 credit += l['amount']
214 return amount - sign * (credit - debit)
216 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
217 context = context or {}
218 if not line_dr_ids and not line_cr_ids:
220 line_osv = self.pool.get("account.voucher.line")
221 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
222 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
224 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
225 is_multi_currency = False
227 # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
228 is_multi_currency = True
230 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
231 for voucher_line in line_dr_ids+line_cr_ids:
232 company_currency = False
233 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
234 if voucher_line.get('currency_id', company_currency) != company_currency:
235 is_multi_currency = True
237 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
239 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
240 if not ids: return {}
241 currency_obj = self.pool.get('res.currency')
244 for voucher in self.browse(cr, uid, ids, context=context):
245 sign = voucher.type == 'payment' and -1 or 1
246 for l in voucher.line_dr_ids:
248 for l in voucher.line_cr_ids:
250 currency = voucher.currency_id or voucher.company_id.currency_id
251 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
254 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
255 if not ids: return {}
258 for voucher in self.browse(cr, uid, ids, context=context):
259 if voucher.currency_id:
260 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
261 rate = 1 / voucher.payment_rate
264 ctx.update({'date': voucher.date})
265 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
266 company_currency_rate = voucher.company_id.currency_id.rate
267 rate = voucher_rate * company_currency_rate
268 res[voucher.id] = voucher.amount / rate
271 _name = 'account.voucher'
272 _description = 'Accounting Voucher'
273 _inherit = ['mail.thread']
274 _order = "date desc, id desc"
275 # _rec_name = 'number'
277 '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."),
278 'type':fields.selection([
280 ('purchase','Purchase'),
281 ('payment','Payment'),
282 ('receipt','Receipt'),
283 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
284 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
285 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
286 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
287 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
288 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
289 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
290 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
291 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
292 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
293 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
294 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
295 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
296 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
297 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
298 'state':fields.selection(
300 ('cancel','Cancelled'),
301 ('proforma','Pro-forma'),
303 ], 'Status', readonly=True, size=32,
304 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
305 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
306 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
307 \n* The \'Cancelled\' status is used when user cancel voucher.'),
308 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
309 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
310 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
311 'number': fields.char('Number', size=32, readonly=True,),
312 'move_id':fields.many2one('account.move', 'Account Entry'),
313 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
314 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
315 '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'),
316 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
317 'pay_now':fields.selection([
318 ('pay_now','Pay Directly'),
319 ('pay_later','Pay Later or Group Funds'),
320 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
321 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
322 'pre_line':fields.boolean('Previous Payments ?', required=False),
323 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
324 'payment_option':fields.selection([
325 ('without_writeoff', 'Keep Open'),
326 ('with_writeoff', 'Reconcile Payment Balance'),
327 ], '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)"),
328 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
329 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
330 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
331 '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."),
332 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
333 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
334 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
335 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
336 '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'),
340 'period_id': _get_period,
341 'partner_id': _get_partner,
342 'journal_id':_get_journal,
343 'currency_id': _get_currency,
344 'reference': _get_reference,
345 'narration':_get_narration,
346 'amount': _get_amount,
349 'pay_now': 'pay_now',
351 'date': lambda *a: time.strftime('%Y-%m-%d'),
352 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
354 'payment_option': 'without_writeoff',
355 'comment': _('Write-Off'),
357 'payment_rate_currency_id': _get_payment_rate_currency,
360 def create(self, cr, uid, vals, context=None):
361 voucher = super(account_voucher, self).create(cr, uid, vals, context=context)
362 self.create_send_note(cr, uid, [voucher], context=context)
365 def compute_tax(self, cr, uid, ids, context=None):
366 tax_pool = self.pool.get('account.tax')
367 partner_pool = self.pool.get('res.partner')
368 position_pool = self.pool.get('account.fiscal.position')
369 voucher_line_pool = self.pool.get('account.voucher.line')
370 voucher_pool = self.pool.get('account.voucher')
371 if context is None: context = {}
373 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
375 for line in voucher.line_ids:
376 voucher_amount += line.untax_amount or line.amount
377 line.amount = line.untax_amount or line.amount
378 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
380 if not voucher.tax_id:
381 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
384 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
385 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
386 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
387 tax = tax_pool.browse(cr, uid, taxes, context=context)
389 total = voucher_amount
392 if not tax[0].price_include:
393 for line in voucher.line_ids:
394 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
395 total_tax += tax_line.get('amount', 0.0)
398 for line in voucher.line_ids:
402 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
403 line_tax += tax_line.get('amount', 0.0)
404 line_total += tax_line.get('price_unit')
405 total_tax += line_tax
406 untax_amount = line.untax_amount or line.amount
407 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
409 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
412 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
413 context = context or {}
414 tax_pool = self.pool.get('account.tax')
415 partner_pool = self.pool.get('res.partner')
416 position_pool = self.pool.get('account.fiscal.position')
417 line_pool = self.pool.get('account.voucher.line')
424 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
427 for line in line_ids:
429 line_amount = line.get('amount',0.0)
432 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
434 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
435 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
436 tax = tax_pool.browse(cr, uid, taxes, context=context)
438 if not tax[0].price_include:
439 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
440 total_tax += tax_line.get('amount')
442 voucher_total += line_amount
443 total = voucher_total + total_tax
446 'amount': total or voucher_total,
447 'tax_amount': total_tax
453 def onchange_term_id(self, cr, uid, ids, term_id, amount):
454 term_pool = self.pool.get('account.payment.term')
457 default = {'date_due':False}
458 if term_id and amount:
459 terms = term_pool.compute(cr, uid, term_id, amount)
461 due_date = terms[-1][0]
465 return {'value':default}
467 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):
469 Returns a dict that contains new values and context
471 @param partner_id: latest value from user input for field partner_id
472 @param args: other arguments
473 @param context: context arguments, like lang, time zone
475 @return: Returns a dict which contains new values, and context
481 if not partner_id or not journal_id:
484 partner_pool = self.pool.get('res.partner')
485 journal_pool = self.pool.get('account.journal')
487 journal = journal_pool.browse(cr, uid, journal_id, context=context)
488 partner = partner_pool.browse(cr, uid, partner_id, context=context)
491 if journal.type in ('sale','sale_refund'):
492 account_id = partner.property_account_receivable.id
494 elif journal.type in ('purchase', 'purchase_refund','expense'):
495 account_id = partner.property_account_payable.id
498 if not journal.default_credit_account_id or not journal.default_debit_account_id:
499 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
500 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
503 default['value']['account_id'] = account_id
504 default['value']['type'] = ttype or tr_type
506 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)
507 default['value'].update(vals.get('value'))
511 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
512 res = {'value': {'paid_amount_in_company_currency': amount}}
513 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
514 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
515 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
516 if company_currency.id == payment_rate_currency_id:
519 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
520 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
523 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):
526 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
528 ctx.update({'date': date})
529 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
530 for key in vals.keys():
531 res[key].update(vals[key])
534 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
537 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
538 currency_obj = self.pool.get('res.currency')
539 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
540 company_id = journal.company_id.id
542 payment_rate_currency_id = currency_id
544 ctx.update({'date': date})
546 if ttype == 'receipt':
547 o2m_to_loop = 'line_cr_ids'
548 elif ttype == 'payment':
549 o2m_to_loop = 'line_dr_ids'
550 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
551 for voucher_line in vals['value'][o2m_to_loop]:
552 if voucher_line['currency_id'] != currency_id:
553 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
554 # is not in the voucher currency
555 payment_rate_currency_id = voucher_line['currency_id']
556 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
557 voucher_currency_id = currency_id or journal.company_id.currency_id.id
558 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
560 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
561 for key in res.keys():
562 vals[key].update(res[key])
563 vals['value'].update({'payment_rate': payment_rate})
564 if payment_rate_currency_id:
565 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
568 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
571 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
572 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
573 for key in vals.keys():
574 res[key].update(vals[key])
575 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
576 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
577 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
578 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
579 # onchange returns a value for them
581 del(res['value']['line_dr_ids'])
582 del(res['value']['pre_line'])
583 del(res['value']['payment_rate'])
584 elif ttype == 'purchase':
585 del(res['value']['line_cr_ids'])
586 del(res['value']['pre_line'])
587 del(res['value']['payment_rate'])
590 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
592 Returns a dict that contains new values and context
594 @param partner_id: latest value from user input for field partner_id
595 @param args: other arguments
596 @param context: context arguments, like lang, time zone
598 @return: Returns a dict which contains new values, and context
600 def _remove_noise_in_o2m():
601 """if the line is partially reconciled, then we must pay attention to display it only once and
603 This function returns True if the line is considered as noise and should not be displayed
605 if line.reconcile_partial_id:
606 sign = 1 if ttype == 'receipt' else -1
607 if currency_id == line.currency_id.id:
608 if line.amount_residual_currency * sign <= 0:
611 if line.amount_residual * sign <= 0:
617 context_multi_currency = context.copy()
619 context_multi_currency.update({'date': date})
621 currency_pool = self.pool.get('res.currency')
622 move_line_pool = self.pool.get('account.move.line')
623 partner_pool = self.pool.get('res.partner')
624 journal_pool = self.pool.get('account.journal')
625 line_pool = self.pool.get('account.voucher.line')
629 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
633 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
635 line_pool.unlink(cr, uid, line_ids)
637 if not partner_id or not journal_id:
640 journal = journal_pool.browse(cr, uid, journal_id, context=context)
641 partner = partner_pool.browse(cr, uid, partner_id, context=context)
642 currency_id = currency_id or journal.company_id.currency_id.id
644 if journal.type in ('sale','sale_refund'):
645 account_id = partner.property_account_receivable.id
646 elif journal.type in ('purchase', 'purchase_refund','expense'):
647 account_id = partner.property_account_payable.id
649 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
651 default['value']['account_id'] = account_id
653 if journal.type not in ('cash', 'bank'):
658 account_type = 'receivable'
659 if ttype == 'payment':
660 account_type = 'payable'
661 total_debit = price or 0.0
663 total_credit = price or 0.0
664 account_type = 'receivable'
666 if not context.get('move_line_ids', False):
667 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
669 ids = context['move_line_ids']
670 invoice_id = context.get('invoice_id', False)
671 company_currency = journal.company_id.currency_id.id
672 move_line_found = False
674 #order the lines by most old first
676 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
678 #compute the total debit/credit and look for a matching open amount or invoice
679 for line in account_move_lines:
680 if _remove_noise_in_o2m():
684 if line.invoice.id == invoice_id:
685 #if the invoice linked to the voucher line is equal to the invoice_id in context
686 #then we assign the amount on that line, whatever the other voucher lines
687 move_line_found = line.id
689 elif currency_id == company_currency:
690 #otherwise treatments is the same but with other field names
691 if line.amount_residual == price:
692 #if the amount residual is equal the amount voucher, we assign it to that voucher
693 #line, whatever the other voucher lines
694 move_line_found = line.id
696 #otherwise we will split the voucher amount on each line (by most old first)
697 total_credit += line.credit or 0.0
698 total_debit += line.debit or 0.0
699 elif currency_id == line.currency_id.id:
700 if line.amount_residual_currency == price:
701 move_line_found = line.id
703 total_credit += line.credit and line.amount_currency or 0.0
704 total_debit += line.debit and line.amount_currency or 0.0
706 #voucher line creation
707 for line in account_move_lines:
709 if _remove_noise_in_o2m():
712 if line.currency_id and currency_id==line.currency_id.id:
713 amount_original = abs(line.amount_currency)
714 amount_unreconciled = abs(line.amount_residual_currency)
716 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
717 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
718 line_currency_id = line.currency_id and line.currency_id.id or company_currency
720 'name':line.move_id.name,
721 'type': line.credit and 'dr' or 'cr',
722 'move_line_id':line.id,
723 'account_id':line.account_id.id,
724 'amount_original': amount_original,
725 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
726 'date_original':line.date,
727 'date_due':line.date_maturity,
728 'amount_unreconciled': amount_unreconciled,
729 'currency_id': line_currency_id,
731 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
732 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
733 if not move_line_found:
734 if currency_id == line_currency_id:
736 amount = min(amount_unreconciled, abs(total_debit))
737 rs['amount'] = amount
738 total_debit -= amount
740 amount = min(amount_unreconciled, abs(total_credit))
741 rs['amount'] = amount
742 total_credit -= amount
744 if rs['amount_unreconciled'] == rs['amount']:
745 rs['reconcile'] = True
747 if rs['type'] == 'cr':
748 default['value']['line_cr_ids'].append(rs)
750 default['value']['line_dr_ids'].append(rs)
752 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
753 default['value']['pre_line'] = 1
754 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
755 default['value']['pre_line'] = 1
756 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
759 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
763 #set the default payment rate of the voucher and compute the paid amount in company currency
764 if currency_id and currency_id == payment_rate_currency_id:
766 ctx.update({'date': date})
767 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
768 for key in vals.keys():
769 res[key].update(vals[key])
772 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
774 @param date: latest value from user input for field date
775 @param args: other arguments
776 @param context: context arguments, like lang, time zone
777 @return: Returns a dict which contains new values, and context
782 #set the period of the voucher
783 period_pool = self.pool.get('account.period')
784 currency_obj = self.pool.get('res.currency')
786 ctx.update({'company_id': company_id})
787 pids = period_pool.find(cr, uid, date, context=ctx)
789 res['value'].update({'period_id':pids[0]})
790 if payment_rate_currency_id:
791 ctx.update({'date': date})
793 if payment_rate_currency_id != currency_id:
794 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
795 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
796 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
797 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
798 vals['value'].update({'payment_rate': payment_rate})
799 for key in vals.keys():
800 res[key].update(vals[key])
803 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
806 journal_pool = self.pool.get('account.journal')
807 journal = journal_pool.browse(cr, uid, journal_id, context=context)
808 account_id = journal.default_credit_account_id or journal.default_debit_account_id
810 if account_id and account_id.tax_ids:
811 tax_id = account_id.tax_ids[0].id
814 if ttype in ('sale', 'purchase'):
815 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
816 vals['value'].update({'tax_id':tax_id,'amount': amount})
819 currency_id = journal.currency.id
820 vals['value'].update({'currency_id': currency_id})
821 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
822 for key in res.keys():
823 vals[key].update(res[key])
826 def button_proforma_voucher(self, cr, uid, ids, context=None):
827 context = context or {}
828 wf_service = netsvc.LocalService("workflow")
830 wf_service.trg_validate(uid, 'account.voucher', vid, 'proforma_voucher', cr)
831 return {'type': 'ir.actions.act_window_close'}
833 def proforma_voucher(self, cr, uid, ids, context=None):
834 self.action_move_line_create(cr, uid, ids, context=context)
837 def action_cancel_draft(self, cr, uid, ids, context=None):
838 wf_service = netsvc.LocalService("workflow")
839 for voucher_id in ids:
840 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
841 self.write(cr, uid, ids, {'state':'draft'})
844 def cancel_voucher(self, cr, uid, ids, context=None):
845 reconcile_pool = self.pool.get('account.move.reconcile')
846 move_pool = self.pool.get('account.move')
848 for voucher in self.browse(cr, uid, ids, context=context):
850 for line in voucher.move_ids:
851 if line.reconcile_id:
852 recs += [line.reconcile_id.id]
853 if line.reconcile_partial_id:
854 recs += [line.reconcile_partial_id.id]
856 reconcile_pool.unlink(cr, uid, recs)
859 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
860 move_pool.unlink(cr, uid, [voucher.move_id.id])
865 self.write(cr, uid, ids, res)
868 def unlink(self, cr, uid, ids, context=None):
869 for t in self.read(cr, uid, ids, ['state'], context=context):
870 if t['state'] not in ('draft', 'cancel'):
871 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
872 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
874 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
878 res = {'account_id':False}
879 partner_pool = self.pool.get('res.partner')
880 journal_pool = self.pool.get('account.journal')
881 if pay_now == 'pay_later':
882 partner = partner_pool.browse(cr, uid, partner_id)
883 journal = journal_pool.browse(cr, uid, journal_id)
884 if journal.type in ('sale','sale_refund'):
885 account_id = partner.property_account_receivable.id
886 elif journal.type in ('purchase', 'purchase_refund','expense'):
887 account_id = partner.property_account_payable.id
889 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
890 res['account_id'] = account_id
893 def _sel_context(self, cr, uid, voucher_id, context=None):
895 Select the context to use accordingly if it needs to be multicurrency or not.
897 :param voucher_id: Id of the actual voucher
898 :return: The returned context will be the same as given in parameter if the voucher currency is the same
899 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
900 the date of the voucher.
903 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
904 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
905 if current_currency <> company_currency:
906 context_multi_currency = context.copy()
907 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
908 context_multi_currency.update({'date': voucher_brw.date})
909 return context_multi_currency
912 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
914 Return a dict to be use to create the first account move line of given voucher.
916 :param voucher_id: Id of voucher what we are creating account_move.
917 :param move_id: Id of account move where this line will be added.
918 :param company_currency: id of currency of the company to which the voucher belong
919 :param current_currency: id of currency of the voucher
920 :return: mapping between fieldname and value of account move line to create
923 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
925 # TODO: is there any other alternative then the voucher type ??
926 # ANSWER: We can have payment and receipt "In Advance".
927 # TODO: Make this logic available.
928 # -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
929 if voucher_brw.type in ('purchase', 'payment'):
930 credit = voucher_brw.paid_amount_in_company_currency
931 elif voucher_brw.type in ('sale', 'receipt'):
932 debit = voucher_brw.paid_amount_in_company_currency
933 if debit < 0: credit = -debit; debit = 0.0
934 if credit < 0: debit = -credit; credit = 0.0
935 sign = debit - credit < 0 and -1 or 1
936 #set the first line of the voucher
938 'name': voucher_brw.name or '/',
941 'account_id': voucher_brw.account_id.id,
943 'journal_id': voucher_brw.journal_id.id,
944 'period_id': voucher_brw.period_id.id,
945 'partner_id': voucher_brw.partner_id.id,
946 'currency_id': company_currency <> current_currency and current_currency or False,
947 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
948 'date': voucher_brw.date,
949 'date_maturity': voucher_brw.date_due
953 def account_move_get(self, cr, uid, voucher_id, context=None):
955 This method prepare the creation of the account move related to the given voucher.
957 :param voucher_id: Id of voucher for which we are creating account_move.
958 :return: mapping between fieldname and value of account move to create
961 seq_obj = self.pool.get('ir.sequence')
962 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
963 if voucher_brw.number:
964 name = voucher_brw.number
965 elif voucher_brw.journal_id.sequence_id:
966 if not voucher_brw.journal_id.sequence_id.active:
967 raise osv.except_osv(_('Configuration Error !'),
968 _('Please activate the sequence of selected journal !'))
969 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
971 raise osv.except_osv(_('Error!'),
972 _('Please define a sequence on the journal.'))
973 if not voucher_brw.reference:
974 ref = name.replace('/','')
976 ref = voucher_brw.reference
980 'journal_id': voucher_brw.journal_id.id,
981 'narration': voucher_brw.narration,
982 'date': voucher_brw.date,
984 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
988 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
990 Prepare the two lines in company currency due to currency rate difference.
992 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
994 :param move_id: Account move wher the move lines will be.
995 :param amount_residual: Amount to be posted.
996 :param company_currency: id of currency of the company to which the voucher belong
997 :param current_currency: id of currency of the voucher
998 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
999 :rtype: tuple of dict
1001 if amount_residual > 0:
1002 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1004 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."))
1006 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1008 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."))
1009 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1010 # the receivable/payable account may have a secondary currency, which render this field mandatory
1011 account_currency_id = company_currency <> current_currency and current_currency or False
1013 'journal_id': line.voucher_id.journal_id.id,
1014 'period_id': line.voucher_id.period_id.id,
1015 'name': _('change')+': '+(line.name or '/'),
1016 'account_id': line.account_id.id,
1018 'partner_id': line.voucher_id.partner_id.id,
1019 'currency_id': account_currency_id,
1020 'amount_currency': 0.0,
1022 'credit': amount_residual > 0 and amount_residual or 0.0,
1023 'debit': amount_residual < 0 and -amount_residual or 0.0,
1024 'date': line.voucher_id.date,
1026 move_line_counterpart = {
1027 'journal_id': line.voucher_id.journal_id.id,
1028 'period_id': line.voucher_id.period_id.id,
1029 'name': _('change')+': '+(line.name or '/'),
1030 'account_id': account_id.id,
1032 'amount_currency': 0.0,
1033 'partner_id': line.voucher_id.partner_id.id,
1034 'currency_id': account_currency_id,
1036 'debit': amount_residual > 0 and amount_residual or 0.0,
1037 'credit': amount_residual < 0 and -amount_residual or 0.0,
1038 'date': line.voucher_id.date,
1040 return (move_line, move_line_counterpart)
1042 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1044 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1045 payment_rate_currency_id is relevant) either the rate encoded in the system.
1047 :param amount: float. The amount to convert
1048 :param voucher: id of the voucher on which we want the conversion
1049 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1050 field in order to select the good rate to use.
1051 :return: the amount in the currency of the voucher's company
1054 currency_obj = self.pool.get('res.currency')
1055 voucher = self.browse(cr, uid, voucher_id, context=context)
1057 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1058 # the rate specified on the voucher is for the company currency
1059 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1061 # the rate specified on the voucher is not relevant, we use all the rates in the system
1062 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1065 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1067 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1068 It returns Tuple with tot_line what is total of difference between debit and credit and
1069 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1071 :param voucher_id: Voucher id what we are working with
1072 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1073 :param move_id: Account move wher those lines will be joined.
1074 :param company_currency: id of currency of the company to which the voucher belong
1075 :param current_currency: id of currency of the voucher
1076 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1077 :rtype: tuple(float, list of int)
1081 move_line_obj = self.pool.get('account.move.line')
1082 currency_obj = self.pool.get('res.currency')
1083 tax_obj = self.pool.get('account.tax')
1084 tot_line = line_total
1087 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1088 ctx = context.copy()
1089 ctx.update({'date': voucher_brw.date})
1090 for line in voucher_brw.line_ids:
1091 #create one move line per voucher line where amount is not 0.0
1094 # convert the amount set on the voucher line into the currency of the voucher's company
1095 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1096 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1097 # currency rate difference
1098 if line.amount == line.amount_unreconciled:
1099 if not line.move_line_id.amount_residual:
1100 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))
1101 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1102 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1104 currency_rate_difference = 0.0
1106 'journal_id': voucher_brw.journal_id.id,
1107 'period_id': voucher_brw.period_id.id,
1108 'name': line.name or '/',
1109 'account_id': line.account_id.id,
1111 'partner_id': voucher_brw.partner_id.id,
1112 '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,
1113 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1117 'date': voucher_brw.date
1121 if line.type == 'dr':
1126 if (line.type=='dr'):
1128 move_line['debit'] = amount
1131 move_line['credit'] = amount
1133 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1135 'account_tax_id': voucher_brw.tax_id.id,
1138 if move_line.get('account_tax_id', False):
1139 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1140 if not (tax_data.base_code_id and tax_data.tax_code_id):
1141 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))
1143 # compute the amount in foreign currency
1144 foreign_currency_diff = 0.0
1145 amount_currency = False
1146 if line.move_line_id:
1147 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1148 # We want to set it on the account move line as soon as the original line had a foreign currency
1149 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1150 # we compute the amount in that foreign currency.
1151 if line.move_line_id.currency_id.id == current_currency:
1152 # if the voucher and the voucher line share the same currency, there is no computation to do
1153 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1154 amount_currency = sign * (line.amount)
1155 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1156 # if the rate is specified on the voucher, we must use it
1157 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1158 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1160 # otherwise we use the rates of the system (giving the voucher date in the context)
1161 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1162 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1163 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1164 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1166 move_line['amount_currency'] = amount_currency
1167 voucher_line = move_line_obj.create(cr, uid, move_line)
1168 rec_ids = [voucher_line, line.move_line_id.id]
1170 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1171 # Change difference entry in company currency
1172 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1173 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1174 move_line_obj.create(cr, uid, exch_lines[1], context)
1175 rec_ids.append(new_id)
1177 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):
1178 # Change difference entry in voucher currency
1179 move_line_foreign_currency = {
1180 'journal_id': line.voucher_id.journal_id.id,
1181 'period_id': line.voucher_id.period_id.id,
1182 'name': _('change')+': '+(line.name or '/'),
1183 'account_id': line.account_id.id,
1185 'partner_id': line.voucher_id.partner_id.id,
1186 'currency_id': line.move_line_id.currency_id.id,
1187 'amount_currency': -1 * foreign_currency_diff,
1191 'date': line.voucher_id.date,
1193 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1194 rec_ids.append(new_id)
1196 if line.move_line_id.id:
1197 rec_lst_ids.append(rec_ids)
1199 return (tot_line, rec_lst_ids)
1201 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1203 Set a dict to be use to create the writeoff move line.
1205 :param voucher_id: Id of voucher what we are creating account_move.
1206 :param line_total: Amount remaining to be allocated on lines.
1207 :param move_id: Id of account move where this line will be added.
1208 :param name: Description of account move line.
1209 :param company_currency: id of currency of the company to which the voucher belong
1210 :param current_currency: id of currency of the voucher
1211 :return: mapping between fieldname and value of account move line to create
1214 currency_obj = self.pool.get('res.currency')
1217 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1218 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1220 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1224 if voucher_brw.payment_option == 'with_writeoff':
1225 account_id = voucher_brw.writeoff_acc_id.id
1226 write_off_name = voucher_brw.comment
1227 elif voucher_brw.type in ('sale', 'receipt'):
1228 account_id = voucher_brw.partner_id.property_account_receivable.id
1230 account_id = voucher_brw.partner_id.property_account_payable.id
1231 sign = voucher_brw.type == 'payment' and -1 or 1
1233 'name': write_off_name or name,
1234 'account_id': account_id,
1236 'partner_id': voucher_brw.partner_id.id,
1237 'date': voucher_brw.date,
1238 'credit': diff > 0 and diff or 0.0,
1239 'debit': diff < 0 and -diff or 0.0,
1240 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1241 'currency_id': company_currency <> current_currency and current_currency or False,
1242 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1247 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1249 Get the currency of the actual company.
1251 :param voucher_id: Id of the voucher what i want to obtain company currency.
1252 :return: currency id of the company of the voucher
1255 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1257 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1259 Get the currency of the voucher.
1261 :param voucher_id: Id of the voucher what i want to obtain current currency.
1262 :return: currency id of the voucher
1265 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1266 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1268 def action_move_line_create(self, cr, uid, ids, context=None):
1270 Confirm the vouchers given in ids and create the journal entries for each of them
1274 move_pool = self.pool.get('account.move')
1275 move_line_pool = self.pool.get('account.move.line')
1276 for voucher in self.browse(cr, uid, ids, context=context):
1279 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1280 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1281 # we select the context to use accordingly if it's a multicurrency case or not
1282 context = self._sel_context(cr, uid, voucher.id, context)
1283 # But for the operations made by _convert_amount, we always need to give the date in the context
1284 ctx = context.copy()
1285 ctx.update({'date': voucher.date})
1286 # Create the account move record.
1287 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1288 # Get the name of the account_move just created
1289 name = move_pool.browse(cr, uid, move_id, context=context).name
1290 # Create the first line of the voucher
1291 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)
1292 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1293 line_total = move_line_brw.debit - move_line_brw.credit
1295 if voucher.type == 'sale':
1296 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1297 elif voucher.type == 'purchase':
1298 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1299 # Create one move line per voucher line where amount is not 0.0
1300 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1302 # Create the writeoff line if needed
1303 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1305 move_line_pool.create(cr, uid, ml_writeoff, context)
1306 # We post the voucher.
1307 self.write(cr, uid, [voucher.id], {
1312 self.post_send_note(cr, uid, [voucher.id], context=context)
1313 if voucher.journal_id.entry_posted:
1314 move_pool.post(cr, uid, [move_id], context={})
1315 # We automatically reconcile the account move lines.
1317 for rec_ids in rec_list_ids:
1318 if len(rec_ids) >= 2:
1319 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)
1321 self.reconcile_send_note(cr, uid, [voucher.id], context=context)
1324 def copy(self, cr, uid, id, default=None, context=None):
1331 'line_cr_ids': False,
1332 'line_dr_ids': False,
1335 if 'date' not in default:
1336 default['date'] = time.strftime('%Y-%m-%d')
1337 return super(account_voucher, self).copy(cr, uid, id, default, context)
1339 # -----------------------------------------
1340 # OpenChatter notifications and need_action
1341 # -----------------------------------------
1343 'sale': 'Sales Receipt',
1344 'purchase': 'Purchase Receipt',
1345 'payment': 'Supplier Payment',
1346 'receipt': 'Customer Payment',
1350 def create_send_note(self, cr, uid, ids, context=None):
1351 for obj in self.browse(cr, uid, ids, context=context):
1352 message = "%s <b>created</b>." % self._document_type[obj.type or False]
1353 self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
1355 def post_send_note(self, cr, uid, ids, context=None):
1356 for obj in self.browse(cr, uid, ids, context=context):
1357 message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
1358 self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
1360 def reconcile_send_note(self, cr, uid, ids, context=None):
1361 for obj in self.browse(cr, uid, ids, context=context):
1362 message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
1363 self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
1367 class account_voucher_line(osv.osv):
1368 _name = 'account.voucher.line'
1369 _description = 'Voucher Lines'
1370 _order = "move_line_id"
1372 # If the payment is in the same currency than the invoice, we keep the same amount
1373 # Otherwise, we compute from company currency to payment currency
1374 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1375 currency_pool = self.pool.get('res.currency')
1377 for line in self.browse(cr, uid, ids, context=context):
1378 ctx = context.copy()
1379 ctx.update({'date': line.voucher_id.date})
1381 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1382 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1383 move_line = line.move_line_id or False
1386 res['amount_original'] = 0.0
1387 res['amount_unreconciled'] = 0.0
1388 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1389 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1390 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)
1391 elif move_line and move_line.credit > 0:
1392 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1393 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1395 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1396 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1398 rs_data[line.id] = res
1401 def _currency_id(self, cr, uid, ids, name, args, context=None):
1403 This function returns the currency id of a voucher line. It's either the currency of the
1404 associated move line (if any) or the currency of the voucher or the company currency.
1407 for line in self.browse(cr, uid, ids, context=context):
1408 move_line = line.move_line_id
1410 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1412 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1416 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1417 'name':fields.char('Description', size=256),
1418 'account_id':fields.many2one('account.account','Account', required=True),
1419 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1420 'untax_amount':fields.float('Untax Amount'),
1421 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1422 'reconcile': fields.boolean('Full Reconcile'),
1423 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1424 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1425 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1426 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1427 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1428 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1429 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1430 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1431 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1437 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1438 vals = {'amount': 0.0}
1440 vals = { 'amount': amount_unreconciled}
1441 return {'value': vals}
1443 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1446 vals['reconcile'] = (amount == amount_unreconciled)
1447 return {'value': vals}
1449 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1451 Returns a dict that contains new values and context
1453 @param move_line_id: latest value from user input for field move_line_id
1454 @param args: other arguments
1455 @param context: context arguments, like lang, time zone
1457 @return: Returns a dict which contains new values, and context
1460 move_line_pool = self.pool.get('account.move.line')
1462 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1463 if move_line.credit:
1468 'account_id': move_line.account_id.id,
1470 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1476 def default_get(self, cr, user, fields_list, context=None):
1478 Returns default values for fields
1479 @param fields_list: list of fields, for which default values are required to be read
1480 @param context: context arguments, like lang, time zone
1482 @return: Returns a dict that contains default values for fields
1486 journal_id = context.get('journal_id', False)
1487 partner_id = context.get('partner_id', False)
1488 journal_pool = self.pool.get('account.journal')
1489 partner_pool = self.pool.get('res.partner')
1490 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1491 if (not journal_id) or ('account_id' not in fields_list):
1493 journal = journal_pool.browse(cr, user, journal_id, context=context)
1496 if journal.type in ('sale', 'sale_refund'):
1497 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1499 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1500 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1503 partner = partner_pool.browse(cr, user, partner_id, context=context)
1504 if context.get('type') == 'payment':
1506 account_id = partner.property_account_payable.id
1507 elif context.get('type') == 'receipt':
1508 account_id = partner.property_account_receivable.id
1511 'account_id':account_id,
1515 account_voucher_line()
1517 class account_bank_statement(osv.osv):
1518 _inherit = 'account.bank.statement'
1520 def button_confirm_bank(self, cr, uid, ids, context=None):
1521 voucher_obj = self.pool.get('account.voucher')
1523 for statement in self.browse(cr, uid, ids, context=context):
1524 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1526 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1527 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1529 def button_cancel(self, cr, uid, ids, context=None):
1530 voucher_obj = self.pool.get('account.voucher')
1531 for st in self.browse(cr, uid, ids, context=context):
1533 for line in st.line_ids:
1535 voucher_ids.append(line.voucher_id.id)
1536 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1537 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1539 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1540 voucher_obj = self.pool.get('account.voucher')
1541 wf_service = netsvc.LocalService("workflow")
1542 move_line_obj = self.pool.get('account.move.line')
1543 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1544 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1545 if st_line.voucher_id:
1546 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1547 if st_line.voucher_id.state == 'cancel':
1548 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1549 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1551 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1552 bank_st_line_obj.write(cr, uid, [st_line_id], {
1553 'move_ids': [(4, v.move_id.id, False)]
1556 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1557 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1559 def write(self, cr, uid, ids, vals, context=None):
1560 # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1561 # Because the voucher keeps in memory the journal it was created with.
1562 for bk_st in self.browse(cr, uid, ids, context=context):
1563 if vals.get('journal_id') and bk_st.line_ids:
1564 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1565 raise osv.except_osv(_('Unable to change journal !'), _('You can not change the journal as you already reconciled some statement lines!'))
1566 return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1568 account_bank_statement()
1570 class account_bank_statement_line(osv.osv):
1571 _inherit = 'account.bank.statement.line'
1573 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1574 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1575 if 'value' not in res:
1577 res['value'].update({'voucher_id' : False})
1580 def onchange_amount(self, cr, uid, ids, amount, context=None):
1581 return {'value' : {'voucher_id' : False}}
1583 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1587 for line in self.browse(cursor, user, ids, context=context):
1589 res[line.id] = line.voucher_id.amount#
1594 def _check_amount(self, cr, uid, ids, context=None):
1595 for obj in self.browse(cr, uid, ids, context=context):
1597 diff = abs(obj.amount) - obj.voucher_id.amount
1598 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1603 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1607 'amount_reconciled': fields.function(_amount_reconciled,
1608 string='Amount reconciled', type='float'),
1609 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1612 def unlink(self, cr, uid, ids, context=None):
1613 voucher_obj = self.pool.get('account.voucher')
1614 statement_line = self.browse(cr, uid, ids, context=context)
1616 for st_line in statement_line:
1617 if st_line.voucher_id:
1618 unlink_ids.append(st_line.voucher_id.id)
1619 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1620 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1622 account_bank_statement_line()
1624 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1626 for operation in operations:
1628 if not isinstance(operation, (list, tuple)):
1629 result = target_osv.read(cr, uid, operation, fields, context=context)
1630 elif operation[0] == 0:
1631 # may be necessary to check if all the fields are here and get the default values?
1632 result = operation[2]
1633 elif operation[0] == 1:
1634 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1635 if not result: result = {}
1636 result.update(operation[2])
1637 elif operation[0] == 4:
1638 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1640 results.append(result)
1644 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: