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
26 from osv import osv, fields
27 import decimal_precision as dp
28 from tools.translate import _
30 class res_company(osv.osv):
31 _inherit = "res.company"
33 'income_currency_exchange_account_id': fields.many2one(
35 string="Income Currency Rate",
36 domain="[('type', '=', 'other')]",),
37 'expense_currency_exchange_account_id': fields.many2one(
39 string="Expense Currency Rate",
40 domain="[('type', '=', 'other')]",),
45 class account_voucher(osv.osv):
46 def _check_paid(self, cr, uid, ids, name, args, context=None):
48 for voucher in self.browse(cr, uid, ids, context=context):
50 for line in voucher.move_ids:
51 if (line.account_id.type, 'in', ('receivable', 'payable')) and not line.reconcile_id:
56 def _get_type(self, cr, uid, context=None):
59 return context.get('type', False)
61 def _get_period(self, cr, uid, context=None):
62 if context is None: context = {}
63 if context.get('period_id', False):
64 return context.get('period_id')
65 periods = self.pool.get('account.period').find(cr, uid)
66 return periods and periods[0] or False
68 def _make_journal_search(self, cr, uid, ttype, context=None):
69 journal_pool = self.pool.get('account.journal')
70 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
72 def _get_journal(self, cr, uid, context=None):
73 if context is None: context = {}
74 invoice_pool = self.pool.get('account.invoice')
75 journal_pool = self.pool.get('account.journal')
76 if context.get('invoice_id', False):
77 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
78 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
79 return journal_id and journal_id[0] or False
80 if context.get('journal_id', False):
81 return context.get('journal_id')
82 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
83 return context.get('search_default_journal_id')
85 ttype = context.get('type', 'bank')
86 if ttype in ('payment', 'receipt'):
88 res = self._make_journal_search(cr, uid, ttype, context=context)
89 return res and res[0] or False
91 def _get_tax(self, cr, uid, context=None):
92 if context is None: context = {}
93 journal_pool = self.pool.get('account.journal')
94 journal_id = context.get('journal_id', False)
96 ttype = context.get('type', 'bank')
97 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
104 journal = journal_pool.browse(cr, uid, journal_id, context=context)
105 account_id = journal.default_credit_account_id or journal.default_debit_account_id
106 if account_id and account_id.tax_ids:
107 tax_id = account_id.tax_ids[0].id
111 def _get_payment_rate_currency(self, cr, uid, context=None):
113 Return the default value for field payment_rate_currency_id: the currency of the journal
114 if there is one, otherwise the currency of the user's company
116 if context is None: context = {}
117 journal_pool = self.pool.get('account.journal')
118 journal_id = context.get('journal_id', False)
120 journal = journal_pool.browse(cr, uid, journal_id, context=context)
122 return journal.currency.id
123 #no journal given in the context, use company currency as default
124 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
126 def _get_currency(self, cr, uid, context=None):
127 if context is None: context = {}
128 journal_pool = self.pool.get('account.journal')
129 journal_id = context.get('journal_id', False)
131 journal = journal_pool.browse(cr, uid, journal_id, context=context)
133 return journal.currency.id
136 def _get_partner(self, cr, uid, context=None):
137 if context is None: context = {}
138 return context.get('partner_id', False)
140 def _get_reference(self, cr, uid, context=None):
141 if context is None: context = {}
142 return context.get('reference', False)
144 def _get_narration(self, cr, uid, context=None):
145 if context is None: context = {}
146 return context.get('narration', False)
148 def _get_amount(self, cr, uid, context=None):
151 return context.get('amount', 0.0)
153 def name_get(self, cr, uid, ids, context=None):
156 if context is None: context = {}
157 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
159 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
160 mod_obj = self.pool.get('ir.model.data')
161 if context is None: context = {}
163 if view_type == 'form':
164 if not view_id and context.get('invoice_type'):
165 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
166 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
168 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
169 result = result and result[1] or False
171 if not view_id and context.get('line_type'):
172 if context.get('line_type') == 'customer':
173 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
175 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
176 result = result and result[1] or False
179 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
180 doc = etree.XML(res['arch'])
182 if context.get('type', 'sale') in ('purchase', 'payment'):
183 nodes = doc.xpath("//field[@name='partner_id']")
185 node.set('domain', "[('supplier', '=', True)]")
186 res['arch'] = etree.tostring(doc)
189 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount):
191 for l in line_dr_ids:
193 for l in line_cr_ids:
194 credit += l['amount']
195 return abs(amount - abs(credit - debit))
197 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, context=None):
198 context = context or {}
199 if not line_dr_ids and not line_cr_ids:
201 line_osv = self.pool.get("account.voucher.line")
202 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
203 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
205 #loop into the lines to see if there is an amount allocated on a voucher line with a currency different than the voucher currency
206 is_multi_currency = False
207 for voucher_line in line_dr_ids+line_cr_ids:
208 if voucher_line.get('currency_id',False) != voucher_currency:
209 is_multi_currency = True
211 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount), 'is_multi_currency': is_multi_currency}}
213 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
214 if not ids: return {}
215 currency_obj = self.pool.get('res.currency')
218 for voucher in self.browse(cr, uid, ids, context=context):
219 for l in voucher.line_dr_ids:
221 for l in voucher.line_cr_ids:
223 currency = voucher.currency_id or voucher.company_id.currency_id
224 res[voucher.id] = currency_obj.round(cr, uid, currency, abs(voucher.amount - abs(credit - debit)))
227 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
228 if not ids: return {}
231 for voucher in self.browse(cr, uid, ids, context=context):
232 if voucher.currency_id:
233 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
234 rate = 1 / voucher.payment_rate
237 ctx.update({'date': voucher.date})
238 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
239 company_currency_rate = voucher.company_id.currency_id.rate
240 rate = voucher_rate * company_currency_rate
241 res[voucher.id] = self.pool.get('res.currency').round(cr, uid, voucher.company_id.currency_id, (voucher.amount / rate))
244 _name = 'account.voucher'
245 _description = 'Accounting Voucher'
246 _order = "date desc, id desc"
247 # _rec_name = 'number'
249 'type':fields.selection([
251 ('purchase','Purchase'),
252 ('payment','Payment'),
253 ('receipt','Receipt'),
254 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
255 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
256 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
257 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
258 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
259 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
260 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
261 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
262 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
263 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
264 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
265 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
266 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
267 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
268 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
269 'state':fields.selection(
271 ('proforma','Pro-forma'),
273 ('cancel','Cancelled')
274 ], 'State', readonly=True, size=32,
275 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Voucher. \
276 \n* The \'Pro-forma\' when voucher is in Pro-forma state,voucher does not have an voucher number. \
277 \n* The \'Posted\' state is used when user create voucher,a voucher number is generated and voucher entries are created in account \
278 \n* The \'Cancelled\' state is used when user cancel voucher.'),
279 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
280 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
281 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
282 'number': fields.char('Number', size=32, readonly=True,),
283 'move_id':fields.many2one('account.move', 'Account Entry'),
284 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
285 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
286 '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'),
287 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
288 'pay_now':fields.selection([
289 ('pay_now','Pay Directly'),
290 ('pay_later','Pay Later or Group Funds'),
291 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
292 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
293 'pre_line':fields.boolean('Previous Payments ?', required=False),
294 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
295 'payment_option':fields.selection([
296 ('without_writeoff', 'Keep Open'),
297 ('with_writeoff', 'Reconcile Payment Balance'),
298 ], '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)"),
299 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
300 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
301 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
302 '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."),
303 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
304 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
305 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
306 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
307 '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'),
310 'period_id': _get_period,
311 'partner_id': _get_partner,
312 'journal_id':_get_journal,
313 'currency_id': _get_currency,
314 'reference': _get_reference,
315 'narration':_get_narration,
316 'amount': _get_amount,
319 'pay_now': 'pay_later',
321 'date': lambda *a: time.strftime('%Y-%m-%d'),
322 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
324 'payment_option': 'without_writeoff',
325 'comment': _('Write-Off'),
327 'payment_rate_currency_id': _get_payment_rate_currency,
330 def compute_tax(self, cr, uid, ids, context=None):
331 tax_pool = self.pool.get('account.tax')
332 partner_pool = self.pool.get('res.partner')
333 position_pool = self.pool.get('account.fiscal.position')
334 voucher_line_pool = self.pool.get('account.voucher.line')
335 voucher_pool = self.pool.get('account.voucher')
336 if context is None: context = {}
338 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
340 for line in voucher.line_ids:
341 voucher_amount += line.untax_amount or line.amount
342 line.amount = line.untax_amount or line.amount
343 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
345 if not voucher.tax_id:
346 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
349 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
350 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
351 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
352 tax = tax_pool.browse(cr, uid, taxes, context=context)
354 total = voucher_amount
357 if not tax[0].price_include:
358 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_amount, 1).get('taxes', []):
359 total_tax += tax_line.get('amount', 0.0)
362 for line in voucher.line_ids:
366 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
367 line_tax += tax_line.get('amount', 0.0)
368 line_total += tax_line.get('price_unit')
369 total_tax += line_tax
370 untax_amount = line.untax_amount or line.amount
371 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
373 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
376 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
377 context = context or {}
378 tax_pool = self.pool.get('account.tax')
379 partner_pool = self.pool.get('res.partner')
380 position_pool = self.pool.get('account.fiscal.position')
381 line_pool = self.pool.get('account.voucher.line')
388 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
390 for line in line_ids:
392 line_amount = line.get('amount',0.0)
393 voucher_total += line_amount
395 total = voucher_total
398 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
400 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
401 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
402 tax = tax_pool.browse(cr, uid, taxes, context=context)
404 if not tax[0].price_include:
405 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_total, 1).get('taxes', []):
406 total_tax += tax_line.get('amount')
410 'amount':total or voucher_total,
411 'tax_amount':total_tax
417 def onchange_term_id(self, cr, uid, ids, term_id, amount):
418 term_pool = self.pool.get('account.payment.term')
421 default = {'date_due':False}
422 if term_id and amount:
423 terms = term_pool.compute(cr, uid, term_id, amount)
425 due_date = terms[-1][0]
429 return {'value':default}
431 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):
433 Returns a dict that contains new values and context
435 @param partner_id: latest value from user input for field partner_id
436 @param args: other arguments
437 @param context: context arguments, like lang, time zone
439 @return: Returns a dict which contains new values, and context
445 if not partner_id or not journal_id:
448 partner_pool = self.pool.get('res.partner')
449 journal_pool = self.pool.get('account.journal')
451 journal = journal_pool.browse(cr, uid, journal_id, context=context)
452 partner = partner_pool.browse(cr, uid, partner_id, context=context)
455 if journal.type in ('sale','sale_refund'):
456 account_id = partner.property_account_receivable.id
458 elif journal.type in ('purchase', 'purchase_refund','expense'):
459 account_id = partner.property_account_payable.id
462 if not journal.default_credit_account_id or not journal.default_debit_account_id:
463 raise osv.except_osv(_('Error !'), _('Please define default credit/debit accounts on the journal "%s" !') % (journal.name))
464 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
467 default['value']['account_id'] = account_id
468 default['value']['type'] = ttype or tr_type
470 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)
471 default['value'].update(vals.get('value'))
475 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
476 res = {'value': {'paid_amount_in_company_currency': amount}}
477 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
478 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
479 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
480 if company_currency.id == payment_rate_currency_id:
483 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
484 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
487 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):
490 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
492 ctx.update({'date': date})
493 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
494 for key in vals.keys():
495 res[key].update(vals[key])
498 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
501 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
502 currency_obj = self.pool.get('res.currency')
503 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
504 company_id = journal.company_id.id
506 payment_rate_currency_id = currency_id
508 ctx.update({'date': date})
510 if ttype == 'receipt':
511 o2m_to_loop = 'line_cr_ids'
512 elif ttype == 'payment':
513 o2m_to_loop = 'line_dr_ids'
514 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
515 for voucher_line in vals['value'][o2m_to_loop]:
516 if voucher_line['currency_id'] != currency_id:
517 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
518 # is not in the voucher currency
519 payment_rate_currency_id = voucher_line['currency_id']
520 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
521 voucher_currency_id = currency_id or journal.company_id.currency_id.id
522 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
524 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
525 for key in res.keys():
526 vals[key].update(res[key])
527 vals['value'].update({'payment_rate': payment_rate})
528 if payment_rate_currency_id:
529 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
532 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
535 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
536 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
537 for key in vals.keys():
538 res[key].update(vals[key])
541 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
543 Returns a dict that contains new values and context
545 @param partner_id: latest value from user input for field partner_id
546 @param args: other arguments
547 @param context: context arguments, like lang, time zone
549 @return: Returns a dict which contains new values, and context
553 context_multi_currency = context.copy()
555 context_multi_currency.update({'date': date})
557 currency_pool = self.pool.get('res.currency')
558 move_line_pool = self.pool.get('account.move.line')
559 partner_pool = self.pool.get('res.partner')
560 journal_pool = self.pool.get('account.journal')
561 line_pool = self.pool.get('account.voucher.line')
565 'value': {'line_ids': [] ,'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
569 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
571 line_pool.unlink(cr, uid, line_ids)
573 if not partner_id or not journal_id:
576 journal = journal_pool.browse(cr, uid, journal_id, context=context)
577 partner = partner_pool.browse(cr, uid, partner_id, context=context)
578 currency_id = currency_id or journal.company_id.currency_id.id
580 if journal.type in ('sale','sale_refund'):
581 account_id = partner.property_account_receivable.id
582 elif journal.type in ('purchase', 'purchase_refund','expense'):
583 account_id = partner.property_account_payable.id
585 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
587 default['value']['account_id'] = account_id
589 if journal.type not in ('cash', 'bank'):
594 account_type = 'receivable'
595 if ttype == 'payment':
596 account_type = 'payable'
597 total_debit = price or 0.0
599 total_credit = price or 0.0
600 account_type = 'receivable'
602 if not context.get('move_line_ids', False):
603 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id), ('journal_id.type', 'not in', ('bank', 'cash'))], context=context)
605 ids = context['move_line_ids']
606 invoice_id = context.get('invoice_id', False)
607 company_currency = journal.company_id.currency_id.id
608 move_line_found = False
610 #order the lines by most old first
612 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
614 for line in account_move_lines:
616 if line.invoice.id == invoice_id:
617 #if the invoice linked to the voucher line is equal to the invoice_id in context
618 #then we assign the amount on that line, whatever the other voucher lines
619 move_line_found = line.id
621 elif currency_id == company_currency:
622 #otherwise treatments is the same but with other field names
623 if line.amount_residual == price:
624 #if the amount residual is equal the amount voucher, we assign it to that voucher
625 #line, whatever the other voucher lines
626 move_line_found = line.id
628 #otherwise we will split the voucher amount on each line (by most old first)
629 total_credit += line.credit or 0.0
630 total_debit += line.debit or 0.0
631 elif currency_id == line.currency_id.id:
632 if line.amount_residual_currency == price:
633 move_line_found = line.id
635 total_credit += line.credit and line.amount_currency or 0.0
636 total_debit += line.debit and line.amount_currency or 0.0
638 #voucher line creation
639 for line in account_move_lines:
640 if line.currency_id and currency_id==line.currency_id.id:
641 amount_original = abs(line.amount_currency)
642 amount_unreconciled = abs(line.amount_residual_currency)
644 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
645 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
646 line_currency_id = line.currency_id and line.currency_id.id or company_currency
648 'name':line.move_id.name,
649 'type': line.credit and 'dr' or 'cr',
650 'move_line_id':line.id,
651 'account_id':line.account_id.id,
652 'amount_original': amount_original,
653 'amount': (move_line_found == line.id) and min(price, amount_unreconciled) or 0.0,
654 'date_original':line.date,
655 'date_due':line.date_maturity,
656 'amount_unreconciled': amount_unreconciled,
657 'currency_id': line_currency_id,
660 #split voucher amount by most old first, but only for lines in the same currency
661 if not move_line_found:
662 if currency_id == line_currency_id:
664 amount = min(amount_unreconciled, abs(total_debit))
665 rs['amount'] = amount
666 total_debit -= amount
668 amount = min(amount_unreconciled, abs(total_credit))
669 rs['amount'] = amount
670 total_credit -= amount
672 if rs['amount_unreconciled'] == rs['amount']:
673 rs['reconcile'] = True
675 if rs['type'] == 'cr':
676 default['value']['line_cr_ids'].append(rs)
678 default['value']['line_dr_ids'].append(rs)
680 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
681 default['value']['pre_line'] = 1
682 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
683 default['value']['pre_line'] = 1
684 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price)
687 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
691 #set the default payment rate of the voucher and compute the paid amount in company currency
692 if currency_id and currency_id == payment_rate_currency_id:
694 ctx.update({'date': date})
695 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
696 for key in vals.keys():
697 res[key].update(vals[key])
700 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
702 @param date: latest value from user input for field date
703 @param args: other arguments
704 @param context: context arguments, like lang, time zone
705 @return: Returns a dict which contains new values, and context
710 #set the period of the voucher
711 period_pool = self.pool.get('account.period')
712 currency_obj = self.pool.get('res.currency')
714 ctx.update({'company_id': company_id})
715 pids = period_pool.find(cr, uid, date, context=ctx)
717 res['value'].update({'period_id':pids[0]})
718 if payment_rate_currency_id:
719 ctx.update({'date': date})
721 if payment_rate_currency_id != currency_id:
722 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
723 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
724 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
725 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
726 vals['value'].update({'payment_rate': payment_rate})
727 for key in vals.keys():
728 res[key].update(vals[key])
731 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
734 journal_pool = self.pool.get('account.journal')
735 journal = journal_pool.browse(cr, uid, journal_id, context=context)
736 account_id = journal.default_credit_account_id or journal.default_debit_account_id
738 if account_id and account_id.tax_ids:
739 tax_id = account_id.tax_ids[0].id
741 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
742 vals['value'].update({'tax_id':tax_id,'amount': amount})
745 currency_id = journal.currency.id
746 vals['value'].update({'currency_id': currency_id})
747 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
748 for key in res.keys():
749 vals[key].update(res[key])
752 def proforma_voucher(self, cr, uid, ids, context=None):
753 self.action_move_line_create(cr, uid, ids, context=context)
756 def action_cancel_draft(self, cr, uid, ids, context=None):
757 wf_service = netsvc.LocalService("workflow")
758 for voucher_id in ids:
759 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
760 self.write(cr, uid, ids, {'state':'draft'})
763 def cancel_voucher(self, cr, uid, ids, context=None):
764 reconcile_pool = self.pool.get('account.move.reconcile')
765 move_pool = self.pool.get('account.move')
767 for voucher in self.browse(cr, uid, ids, context=context):
769 for line in voucher.move_ids:
770 if line.reconcile_id:
771 recs += [line.reconcile_id.id]
772 if line.reconcile_partial_id:
773 recs += [line.reconcile_partial_id.id]
775 reconcile_pool.unlink(cr, uid, recs)
778 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
779 move_pool.unlink(cr, uid, [voucher.move_id.id])
784 self.write(cr, uid, ids, res)
787 def unlink(self, cr, uid, ids, context=None):
788 for t in self.read(cr, uid, ids, ['state'], context=context):
789 if t['state'] not in ('draft', 'cancel'):
790 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Voucher(s) which are already opened or paid !'))
791 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
793 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
797 res = {'account_id':False}
798 partner_pool = self.pool.get('res.partner')
799 journal_pool = self.pool.get('account.journal')
800 if pay_now == 'pay_later':
801 partner = partner_pool.browse(cr, uid, partner_id)
802 journal = journal_pool.browse(cr, uid, journal_id)
803 if journal.type in ('sale','sale_refund'):
804 account_id = partner.property_account_receivable.id
805 elif journal.type in ('purchase', 'purchase_refund','expense'):
806 account_id = partner.property_account_payable.id
808 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
809 res['account_id'] = account_id
812 def _sel_context(self, cr, uid, voucher_id,context=None):
814 Select the context to use accordingly if it needs to be multicurrency or not.
816 :param voucher_id: Id of the actual voucher
817 :return: The returned context will be the same as given in parameter if the voucher currency is the same
818 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
819 the date of the voucher.
822 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
823 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
824 if current_currency <> company_currency:
825 context_multi_currency = context.copy()
826 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
827 context_multi_currency.update({'date': voucher_brw.date})
828 return context_multi_currency
831 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
833 Return a dict to be use to create the first account move line of given voucher.
835 :param voucher_id: Id of voucher what we are creating account_move.
836 :param move_id: Id of account move where this line will be added.
837 :param company_currency: id of currency of the company to which the voucher belong
838 :param current_currency: id of currency of the voucher
839 :return: mapping between fieldname and value of account move line to create
842 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
844 # TODO: is there any other alternative then the voucher type ??
845 # ANSWER: We can have payment and receipt "In Advance".
846 # TODO: Make this logic available.
847 # -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
848 if voucher_brw.type in ('purchase', 'payment'):
849 credit = voucher_brw.paid_amount_in_company_currency
850 elif voucher_brw.type in ('sale', 'receipt'):
851 debit = voucher_brw.paid_amount_in_company_currency
852 if debit < 0: credit = -debit; debit = 0.0
853 if credit < 0: debit = -credit; credit = 0.0
854 sign = debit - credit < 0 and -1 or 1
855 #set the first line of the voucher
857 'name': voucher_brw.name or '/',
860 'account_id': voucher_brw.account_id.id,
862 'journal_id': voucher_brw.journal_id.id,
863 'period_id': voucher_brw.period_id.id,
864 'partner_id': voucher_brw.partner_id.id,
865 'currency_id': company_currency <> current_currency and current_currency or False,
866 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
867 'date': voucher_brw.date,
868 'date_maturity': voucher_brw.date_due
872 def account_move_get(self, cr, uid, voucher_id, context=None):
874 This method prepare the creation of the account move related to the given voucher.
876 :param voucher_id: Id of voucher for which we are creating account_move.
877 :return: mapping between fieldname and value of account move to create
880 seq_obj = self.pool.get('ir.sequence')
881 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
882 if voucher_brw.number:
883 name = voucher_brw.number
884 elif voucher_brw.journal_id.sequence_id:
885 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
887 raise osv.except_osv(_('Error !'),
888 _('Please define a sequence on the journal !'))
889 if not voucher_brw.reference:
890 ref = name.replace('/','')
892 ref = voucher_brw.reference
896 'journal_id': voucher_brw.journal_id.id,
897 'narration': voucher_brw.narration,
898 'date': voucher_brw.date,
900 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
904 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
906 Prepare the two lines in company currency due to currency rate difference.
908 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
910 :param move_id: Account move wher the move lines will be.
911 :param amount_residual: Amount to be posted.
912 :param company_currency: id of currency of the company to which the voucher belong
913 :param current_currency: id of currency of the voucher
914 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value.
915 The first element returned is the one that gonna be reconciled (with the receivalble or payable account)
916 :rtype: tuple of dict
918 if amount_residual > 0:
919 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
921 raise osv.except_osv(_('Warning'),_("Unable to create accounting entry for currency rate difference. You have to configure the field 'Income Currency Rate' on the company! "))
923 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
925 raise osv.except_osv(_('Warning'),_("Unable to create accounting entry for currency rate difference. You have to configure the field 'Expense Currency Rate' on the company! "))
926 # Even if the amount_currency is never filled, we need to pass the foreign currency because
927 # the receivable/payable account may have a secondary currency, which render this field mandatory
928 account_currency_id = company_currency <> current_currency and current_currency or False
930 'journal_id': line.voucher_id.journal_id.id,
931 'period_id': line.voucher_id.period_id.id,
932 'name': _('change')+': '+(line.name or '/'),
933 'account_id': line.voucher_id.type in ('sale', 'receipt') and line.account_id.id or account_id.id,
935 'partner_id': line.voucher_id.partner_id.id,
936 'currency_id': account_currency_id,
937 'amount_currency': 0.0,
939 'credit': amount_residual > 0 and amount_residual or 0.0,
940 'debit': amount_residual < 0 and -amount_residual or 0.0,
941 'date': line.voucher_id.date,
943 move_line_counterpart = {
944 'journal_id': line.voucher_id.journal_id.id,
945 'period_id': line.voucher_id.period_id.id,
946 'name': _('change')+': '+(line.name or '/'),
947 'account_id': line.voucher_id.type in ('sale', 'receipt') and account_id.id or line.account_id.id,
949 'amount_currency': 0.0,
950 'partner_id': line.voucher_id.partner_id.id,
951 'currency_id': account_currency_id,
953 'debit': amount_residual > 0 and amount_residual or 0.0,
954 'credit': amount_residual < 0 and -amount_residual or 0.0,
955 'date': line.voucher_id.date,
957 if line.voucher_id.type not in ('sale', 'receipt'):
958 # in case of supplier vouchers, the line with the payable account is 'move_line_counterpart',
959 # and thus it must be returned as first element
960 return (move_line_counterpart, move_line)
961 return (move_line, move_line_counterpart)
963 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
965 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
966 payment_rate_currency_id is relevant) either the rate encoded in the system.
968 :param amount: float. The amount to convert
969 :param voucher: id of the voucher on which we want the conversion
970 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
971 field in order to select the good rate to use.
972 :return: the amount in the currency of the voucher's company
975 currency_obj = self.pool.get('res.currency')
976 voucher = self.browse(cr, uid, voucher_id, context=context)
978 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
979 # the rate specified on the voucher is for the company currency
980 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
982 # the rate specified on the voucher is not relevant, we use all the rates in the system
983 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
986 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
988 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
989 It returns Tuple with tot_line what is total of difference between debit and credit and
990 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
992 :param voucher_id: Voucher id what we are working with
993 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
994 :param move_id: Account move wher those lines will be joined.
995 :param company_currency: id of currency of the company to which the voucher belong
996 :param current_currency: id of currency of the voucher
997 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
998 :rtype: tuple(float, list of int)
1002 move_line_obj = self.pool.get('account.move.line')
1003 currency_obj = self.pool.get('res.currency')
1004 tax_obj = self.pool.get('account.tax')
1005 tot_line = line_total
1008 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1009 ctx = context.copy()
1010 ctx.update({'date': voucher_brw.date})
1011 for line in voucher_brw.line_ids:
1012 #create one move line per voucher line where amount is not 0.0
1015 # convert the amount set on the voucher line into the currency of the voucher's company
1016 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1017 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1018 # currency rate difference
1019 if line.amount == line.amount_unreconciled:
1020 currency_rate_difference = line.move_line_id.amount_residual - amount
1022 currency_rate_difference = 0.0
1024 'journal_id': voucher_brw.journal_id.id,
1025 'period_id': voucher_brw.period_id.id,
1026 'name': line.name or '/',
1027 'account_id': line.account_id.id,
1029 'partner_id': voucher_brw.partner_id.id,
1030 '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,
1031 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1035 'date': voucher_brw.date
1039 if line.type == 'dr':
1044 if (line.type=='dr'):
1046 move_line['debit'] = amount
1049 move_line['credit'] = amount
1051 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1053 'account_tax_id': voucher_brw.tax_id.id,
1056 if move_line.get('account_tax_id', False):
1057 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1058 if not (tax_data.base_code_id and tax_data.tax_code_id):
1059 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))
1061 # compute the amount in foreign currency
1062 foreign_currency_diff = 0.0
1063 amount_currency = False
1064 if line.move_line_id:
1065 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1066 # We want to set it on the account move line as soon as the original line had a foreign currency
1067 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1068 # we compute the amount in that foreign currency.
1069 if line.move_line_id.currency_id.id == current_currency:
1070 # if the voucher and the voucher line share the same currency, there is no computation to do
1071 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1072 amount_currency = sign * (line.amount)
1073 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1074 # if the rate is specified on the voucher, we must use it
1075 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1076 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1078 # otherwise we use the rates of the system (giving the voucher date in the context)
1079 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1080 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1081 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1082 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1084 move_line['amount_currency'] = amount_currency
1085 voucher_line = move_line_obj.create(cr, uid, move_line)
1086 rec_ids = [voucher_line, line.move_line_id.id]
1088 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1089 # Change difference entry in company currency
1090 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1091 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1092 move_line_obj.create(cr, uid, exch_lines[1], context)
1093 rec_ids.append(new_id)
1095 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):
1096 # Change difference entry in voucher currency
1097 move_line_foreign_currency = {
1098 'journal_id': line.voucher_id.journal_id.id,
1099 'period_id': line.voucher_id.period_id.id,
1100 'name': _('change')+': '+(line.name or '/'),
1101 'account_id': line.account_id.id,
1103 'partner_id': line.voucher_id.partner_id.id,
1104 'currency_id': line.move_line_id.currency_id.id,
1105 'amount_currency': -1 * foreign_currency_diff,
1109 'date': line.voucher_id.date,
1111 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1112 rec_ids.append(new_id)
1114 if line.move_line_id.id:
1115 rec_lst_ids.append(rec_ids)
1117 return (tot_line, rec_lst_ids)
1119 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1121 Set a dict to be use to create the writeoff move line.
1123 :param voucher_id: Id of voucher what we are creating account_move.
1124 :param line_total: Amount remaining to be allocated on lines.
1125 :param move_id: Id of account move where this line will be added.
1126 :param name: Description of account move line.
1127 :param company_currency: id of currency of the company to which the voucher belong
1128 :param current_currency: id of currency of the voucher
1129 :return: mapping between fieldname and value of account move line to create
1132 currency_obj = self.pool.get('res.currency')
1135 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1136 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1138 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1142 if voucher_brw.payment_option == 'with_writeoff':
1143 account_id = voucher_brw.writeoff_acc_id.id
1144 write_off_name = voucher_brw.comment
1145 elif voucher_brw.type in ('sale', 'receipt'):
1146 account_id = voucher_brw.partner_id.property_account_receivable.id
1148 account_id = voucher_brw.partner_id.property_account_payable.id
1150 'name': write_off_name or name,
1151 'account_id': account_id,
1153 'partner_id': voucher_brw.partner_id.id,
1154 'date': voucher_brw.date,
1155 'credit': diff > 0 and diff or 0.0,
1156 'debit': diff < 0 and -diff or 0.0,
1157 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
1158 'currency_id': company_currency <> current_currency and current_currency or False,
1159 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1164 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1166 Get the currency of the actual company.
1168 :param voucher_id: Id of the voucher what i want to obtain company currency.
1169 :return: currency id of the company of the voucher
1172 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1174 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1176 Get the currency of the voucher.
1178 :param voucher_id: Id of the voucher what i want to obtain current currency.
1179 :return: currency id of the voucher
1182 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1183 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1185 def action_move_line_create(self, cr, uid, ids, context=None):
1187 Confirm the vouchers given in ids and create the journal entries for each of them
1191 move_pool = self.pool.get('account.move')
1192 move_line_pool = self.pool.get('account.move.line')
1193 for voucher in self.browse(cr, uid, ids, context=context):
1196 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1197 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1198 # we select the context to use accordingly if it's a multicurrency case or not
1199 context = self._sel_context(cr, uid, voucher.id, context)
1200 # But for the operations made by _convert_amount, we always need to give the date in the context
1201 ctx = context.copy()
1202 ctx.update({'date': voucher.date})
1203 # Create the account move record.
1204 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1205 # Get the name of the account_move just created
1206 name = move_pool.browse(cr, uid, move_id, context=context).name
1207 # Create the first line of the voucher
1208 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)
1209 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1210 line_total = move_line_brw.debit - move_line_brw.credit
1212 if voucher.type == 'sale':
1213 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1214 elif voucher.type == 'purchase':
1215 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1216 # Create one move line per voucher line where amount is not 0.0
1217 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1219 # Create the writeoff line if needed
1220 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1222 move_line_pool.create(cr, uid, ml_writeoff, context)
1223 # We post the voucher.
1224 self.write(cr, uid, [voucher.id], {
1229 if voucher.journal_id.entry_posted:
1230 move_pool.post(cr, uid, [move_id], context={})
1231 # We automatically reconcile the account move lines.
1232 for rec_ids in rec_list_ids:
1233 if len(rec_ids) >= 2:
1234 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)
1237 def copy(self, cr, uid, id, default={}, context=None):
1242 'line_cr_ids': False,
1243 'line_dr_ids': False,
1246 if 'date' not in default:
1247 default['date'] = time.strftime('%Y-%m-%d')
1248 return super(account_voucher, self).copy(cr, uid, id, default, context)
1252 class account_voucher_line(osv.osv):
1253 _name = 'account.voucher.line'
1254 _description = 'Voucher Lines'
1255 _order = "move_line_id"
1257 # If the payment is in the same currency than the invoice, we keep the same amount
1258 # Otherwise, we compute from company currency to payment currency
1259 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1260 currency_pool = self.pool.get('res.currency')
1262 for line in self.browse(cr, uid, ids, context=context):
1263 ctx = context.copy()
1264 ctx.update({'date': line.voucher_id.date})
1266 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1267 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1268 move_line = line.move_line_id or False
1271 res['amount_original'] = 0.0
1272 res['amount_unreconciled'] = 0.0
1273 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1274 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1275 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)
1276 elif move_line and move_line.credit > 0:
1277 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1278 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1280 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1281 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1283 rs_data[line.id] = res
1286 def _currency_id(self, cr, uid, ids, name, args, context=None):
1288 This function returns the currency id of a voucher line. It's either the currency of the
1289 associated move line (if any) or the currency of the voucher or the company currency.
1292 for line in self.browse(cr, uid, ids, context=context):
1293 move_line = line.move_line_id
1295 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1297 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1301 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1302 'name':fields.char('Description', size=256),
1303 'account_id':fields.many2one('account.account','Account', required=True),
1304 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1305 'untax_amount':fields.float('Untax Amount'),
1306 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1307 'reconcile': fields.boolean('Full Reconcile'),
1308 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1309 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1310 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1311 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1312 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1313 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1314 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1315 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1316 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1322 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1323 vals = { 'amount': 0.0}
1325 vals = { 'amount': amount_unreconciled}
1326 return {'value': vals}
1328 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1331 vals['reconcile'] = (amount == amount_unreconciled)
1332 return {'value': vals}
1334 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1336 Returns a dict that contains new values and context
1338 @param move_line_id: latest value from user input for field move_line_id
1339 @param args: other arguments
1340 @param context: context arguments, like lang, time zone
1342 @return: Returns a dict which contains new values, and context
1345 move_line_pool = self.pool.get('account.move.line')
1347 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1348 if move_line.credit:
1353 'account_id': move_line.account_id.id,
1355 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1361 def default_get(self, cr, user, fields_list, context=None):
1363 Returns default values for fields
1364 @param fields_list: list of fields, for which default values are required to be read
1365 @param context: context arguments, like lang, time zone
1367 @return: Returns a dict that contains default values for fields
1371 journal_id = context.get('journal_id', False)
1372 partner_id = context.get('partner_id', False)
1373 journal_pool = self.pool.get('account.journal')
1374 partner_pool = self.pool.get('res.partner')
1375 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1376 if (not journal_id) or ('account_id' not in fields_list):
1378 journal = journal_pool.browse(cr, user, journal_id, context=context)
1381 if journal.type in ('sale', 'sale_refund'):
1382 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1384 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1385 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1388 partner = partner_pool.browse(cr, user, partner_id, context=context)
1389 if context.get('type') == 'payment':
1391 account_id = partner.property_account_payable.id
1392 elif context.get('type') == 'receipt':
1393 account_id = partner.property_account_receivable.id
1396 'account_id':account_id,
1400 account_voucher_line()
1402 class account_bank_statement(osv.osv):
1403 _inherit = 'account.bank.statement'
1405 def button_cancel(self, cr, uid, ids, context=None):
1406 voucher_obj = self.pool.get('account.voucher')
1407 for st in self.browse(cr, uid, ids, context=context):
1409 for line in st.line_ids:
1411 voucher_ids.append(line.voucher_id.id)
1412 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1413 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1415 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1416 voucher_obj = self.pool.get('account.voucher')
1417 wf_service = netsvc.LocalService("workflow")
1418 move_line_obj = self.pool.get('account.move.line')
1419 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1420 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1421 if st_line.voucher_id:
1422 voucher_obj.write(cr, uid, [st_line.voucher_id.id],
1423 {'number': next_number,
1424 'date': st_line.date,
1425 'period_id': st_line.statement_id.period_id.id},
1427 if st_line.voucher_id.state == 'cancel':
1428 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1429 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1431 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1432 bank_st_line_obj.write(cr, uid, [st_line_id], {
1433 'move_ids': [(4, v.move_id.id, False)]
1436 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1437 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1439 account_bank_statement()
1441 class account_bank_statement_line(osv.osv):
1442 _inherit = 'account.bank.statement.line'
1444 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1448 for line in self.browse(cursor, user, ids, context=context):
1450 res[line.id] = line.voucher_id.amount#
1455 def _check_amount(self, cr, uid, ids, context=None):
1456 for obj in self.browse(cr, uid, ids, context=context):
1458 diff = abs(obj.amount) - obj.voucher_id.amount
1459 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1464 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1468 'amount_reconciled': fields.function(_amount_reconciled,
1469 string='Amount reconciled', type='float'),
1470 'voucher_id': fields.many2one('account.voucher', 'Payment'),
1473 def unlink(self, cr, uid, ids, context=None):
1474 voucher_obj = self.pool.get('account.voucher')
1475 statement_line = self.browse(cr, uid, ids, context=context)
1477 for st_line in statement_line:
1478 if st_line.voucher_id:
1479 unlink_ids.append(st_line.voucher_id.id)
1480 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1481 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1483 account_bank_statement_line()
1485 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1487 for operation in operations:
1489 if not isinstance(operation, (list, tuple)):
1490 result = target_osv.read(cr, uid, operation, fields, context=context)
1491 elif operation[0] == 0:
1492 # may be necessary to check if all the fields are here and get the default values?
1493 result = operation[2]
1494 elif operation[0] == 1:
1495 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1496 if not result: result = {}
1497 result.update(operation[2])
1498 elif operation[0] == 4:
1499 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1501 results.append(result)
1505 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: