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 line.reconcile_id:
53 res[voucher.id] = paid
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 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
206 is_multi_currency = False
208 # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
209 is_multi_currency = True
211 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
212 for voucher_line in line_dr_ids+line_cr_ids:
213 company_currency = False
214 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
215 if voucher_line.get('currency_id', company_currency) != company_currency:
216 is_multi_currency = True
218 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount), 'is_multi_currency': is_multi_currency}}
220 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
221 if not ids: return {}
222 currency_obj = self.pool.get('res.currency')
225 for voucher in self.browse(cr, uid, ids, context=context):
226 for l in voucher.line_dr_ids:
228 for l in voucher.line_cr_ids:
230 currency = voucher.currency_id or voucher.company_id.currency_id
231 res[voucher.id] = currency_obj.round(cr, uid, currency, abs(voucher.amount - abs(credit - debit)))
234 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
235 if not ids: return {}
238 for voucher in self.browse(cr, uid, ids, context=context):
239 if voucher.currency_id:
240 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
241 rate = 1 / voucher.payment_rate
244 ctx.update({'date': voucher.date})
245 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
246 company_currency_rate = voucher.company_id.currency_id.rate
247 rate = voucher_rate * company_currency_rate
248 res[voucher.id] = voucher.amount / rate
251 _name = 'account.voucher'
252 _description = 'Accounting Voucher'
253 _inherit = ['mail.thread']
254 _order = "date desc, id desc"
255 # _rec_name = 'number'
257 'type':fields.selection([
259 ('purchase','Purchase'),
260 ('payment','Payment'),
261 ('receipt','Receipt'),
262 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
263 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
264 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
265 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
266 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
267 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
268 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
269 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
270 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
271 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
272 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
273 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
274 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
275 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
276 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
277 'state':fields.selection(
279 ('cancel','Cancelled'),
280 ('proforma','Pro-forma'),
282 ], 'Status', readonly=True, size=32,
283 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Voucher. \
284 \n* The \'Pro-forma\' when voucher is in Pro-forma state,voucher does not have an voucher number. \
285 \n* The \'Posted\' state is used when user create voucher,a voucher number is generated and voucher entries are created in account \
286 \n* The \'Cancelled\' state is used when user cancel voucher.'),
287 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
288 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
289 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
290 'number': fields.char('Number', size=32, readonly=True,),
291 'move_id':fields.many2one('account.move', 'Account Entry'),
292 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
293 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
294 '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'),
295 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
296 'pay_now':fields.selection([
297 ('pay_now','Pay Directly'),
298 ('pay_later','Pay Later or Group Funds'),
299 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
300 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
301 'pre_line':fields.boolean('Previous Payments ?', required=False),
302 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
303 'payment_option':fields.selection([
304 ('without_writeoff', 'Keep Open'),
305 ('with_writeoff', 'Reconcile Payment Balance'),
306 ], '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)"),
307 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
308 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
309 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
310 '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."),
311 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
312 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
313 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
314 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
315 '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'),
318 'period_id': _get_period,
319 'partner_id': _get_partner,
320 'journal_id':_get_journal,
321 'currency_id': _get_currency,
322 'reference': _get_reference,
323 'narration':_get_narration,
324 'amount': _get_amount,
327 'pay_now': 'pay_now',
329 'date': lambda *a: time.strftime('%Y-%m-%d'),
330 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
332 'payment_option': 'without_writeoff',
333 'comment': _('Write-Off'),
335 'payment_rate_currency_id': _get_payment_rate_currency,
338 def create(self, cr, uid, vals, context=None):
339 voucher = super(account_voucher, self).create(cr, uid, vals, context=context)
340 self.create_send_note(cr, uid, [voucher], context=context)
343 def compute_tax(self, cr, uid, ids, context=None):
344 tax_pool = self.pool.get('account.tax')
345 partner_pool = self.pool.get('res.partner')
346 position_pool = self.pool.get('account.fiscal.position')
347 voucher_line_pool = self.pool.get('account.voucher.line')
348 voucher_pool = self.pool.get('account.voucher')
349 if context is None: context = {}
351 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
353 for line in voucher.line_ids:
354 voucher_amount += line.untax_amount or line.amount
355 line.amount = line.untax_amount or line.amount
356 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
358 if not voucher.tax_id:
359 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
362 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
363 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
364 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
365 tax = tax_pool.browse(cr, uid, taxes, context=context)
367 total = voucher_amount
370 if not tax[0].price_include:
371 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_amount, 1).get('taxes', []):
372 total_tax += tax_line.get('amount', 0.0)
375 for line in voucher.line_ids:
379 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
380 line_tax += tax_line.get('amount', 0.0)
381 line_total += tax_line.get('price_unit')
382 total_tax += line_tax
383 untax_amount = line.untax_amount or line.amount
384 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
386 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
389 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
390 context = context or {}
391 tax_pool = self.pool.get('account.tax')
392 partner_pool = self.pool.get('res.partner')
393 position_pool = self.pool.get('account.fiscal.position')
394 line_pool = self.pool.get('account.voucher.line')
401 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
403 for line in line_ids:
405 line_amount = line.get('amount',0.0)
406 voucher_total += line_amount
408 total = voucher_total
411 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
413 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
414 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
415 tax = tax_pool.browse(cr, uid, taxes, context=context)
417 if not tax[0].price_include:
418 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_total, 1).get('taxes', []):
419 total_tax += tax_line.get('amount')
423 'amount':total or voucher_total,
424 'tax_amount':total_tax
430 def onchange_term_id(self, cr, uid, ids, term_id, amount):
431 term_pool = self.pool.get('account.payment.term')
434 default = {'date_due':False}
435 if term_id and amount:
436 terms = term_pool.compute(cr, uid, term_id, amount)
438 due_date = terms[-1][0]
442 return {'value':default}
444 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):
446 Returns a dict that contains new values and context
448 @param partner_id: latest value from user input for field partner_id
449 @param args: other arguments
450 @param context: context arguments, like lang, time zone
452 @return: Returns a dict which contains new values, and context
458 if not partner_id or not journal_id:
461 partner_pool = self.pool.get('res.partner')
462 journal_pool = self.pool.get('account.journal')
464 journal = journal_pool.browse(cr, uid, journal_id, context=context)
465 partner = partner_pool.browse(cr, uid, partner_id, context=context)
468 if journal.type in ('sale','sale_refund'):
469 account_id = partner.property_account_receivable.id
471 elif journal.type in ('purchase', 'purchase_refund','expense'):
472 account_id = partner.property_account_payable.id
475 if not journal.default_credit_account_id or not journal.default_debit_account_id:
476 raise osv.except_osv(_('Error !'), _('Please define default credit/debit accounts on the journal "%s" !') % (journal.name))
477 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
480 default['value']['account_id'] = account_id
481 default['value']['type'] = ttype or tr_type
483 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)
484 default['value'].update(vals.get('value'))
488 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
489 res = {'value': {'paid_amount_in_company_currency': amount}}
490 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
491 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
492 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
493 if company_currency.id == payment_rate_currency_id:
496 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
497 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
500 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):
503 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
505 ctx.update({'date': date})
506 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
507 for key in vals.keys():
508 res[key].update(vals[key])
511 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
514 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
515 currency_obj = self.pool.get('res.currency')
516 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
517 company_id = journal.company_id.id
519 payment_rate_currency_id = currency_id
521 ctx.update({'date': date})
523 if ttype == 'receipt':
524 o2m_to_loop = 'line_cr_ids'
525 elif ttype == 'payment':
526 o2m_to_loop = 'line_dr_ids'
527 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
528 for voucher_line in vals['value'][o2m_to_loop]:
529 if voucher_line['currency_id'] != currency_id:
530 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
531 # is not in the voucher currency
532 payment_rate_currency_id = voucher_line['currency_id']
533 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
534 voucher_currency_id = currency_id or journal.company_id.currency_id.id
535 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
537 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
538 for key in res.keys():
539 vals[key].update(res[key])
540 vals['value'].update({'payment_rate': payment_rate})
541 if payment_rate_currency_id:
542 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
545 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
548 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
549 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
550 for key in vals.keys():
551 res[key].update(vals[key])
554 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
556 Returns a dict that contains new values and context
558 @param partner_id: latest value from user input for field partner_id
559 @param args: other arguments
560 @param context: context arguments, like lang, time zone
562 @return: Returns a dict which contains new values, and context
564 def _remove_noise_in_o2m():
565 """if the line is partially reconciled, then we must pay attention to display it only once and
567 This function returns True if the line is considered as noise and should not be displayed
569 if line.reconcile_partial_id:
570 sign = 1 if ttype == 'receipt' else -1
571 if currency_id == line.currency_id.id:
572 if line.amount_residual_currency * sign <= 0:
575 if line.amount_residual * sign <= 0:
581 context_multi_currency = context.copy()
583 context_multi_currency.update({'date': date})
585 currency_pool = self.pool.get('res.currency')
586 move_line_pool = self.pool.get('account.move.line')
587 partner_pool = self.pool.get('res.partner')
588 journal_pool = self.pool.get('account.journal')
589 line_pool = self.pool.get('account.voucher.line')
593 'value': {'line_ids': [] ,'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
597 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
599 line_pool.unlink(cr, uid, line_ids)
601 if not partner_id or not journal_id:
604 journal = journal_pool.browse(cr, uid, journal_id, context=context)
605 partner = partner_pool.browse(cr, uid, partner_id, context=context)
606 currency_id = currency_id or journal.company_id.currency_id.id
608 if journal.type in ('sale','sale_refund'):
609 account_id = partner.property_account_receivable.id
610 elif journal.type in ('purchase', 'purchase_refund','expense'):
611 account_id = partner.property_account_payable.id
613 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
615 default['value']['account_id'] = account_id
617 if journal.type not in ('cash', 'bank'):
622 account_type = 'receivable'
623 if ttype == 'payment':
624 account_type = 'payable'
625 total_debit = price or 0.0
627 total_credit = price or 0.0
628 account_type = 'receivable'
630 if not context.get('move_line_ids', False):
631 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
633 ids = context['move_line_ids']
634 invoice_id = context.get('invoice_id', False)
635 company_currency = journal.company_id.currency_id.id
636 move_line_found = False
638 #order the lines by most old first
640 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
642 #compute the total debit/credit and look for a matching open amount or invoice
643 for line in account_move_lines:
644 if _remove_noise_in_o2m():
648 if line.invoice.id == invoice_id:
649 #if the invoice linked to the voucher line is equal to the invoice_id in context
650 #then we assign the amount on that line, whatever the other voucher lines
651 move_line_found = line.id
653 elif currency_id == company_currency:
654 #otherwise treatments is the same but with other field names
655 if line.amount_residual == price:
656 #if the amount residual is equal the amount voucher, we assign it to that voucher
657 #line, whatever the other voucher lines
658 move_line_found = line.id
660 #otherwise we will split the voucher amount on each line (by most old first)
661 total_credit += line.credit or 0.0
662 total_debit += line.debit or 0.0
663 elif currency_id == line.currency_id.id:
664 if line.amount_residual_currency == price:
665 move_line_found = line.id
667 total_credit += line.credit and line.amount_currency or 0.0
668 total_debit += line.debit and line.amount_currency or 0.0
670 #voucher line creation
671 for line in account_move_lines:
672 if _remove_noise_in_o2m():
675 if line.currency_id and currency_id==line.currency_id.id:
676 amount_original = abs(line.amount_currency)
677 amount_unreconciled = abs(line.amount_residual_currency)
679 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
680 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
681 line_currency_id = line.currency_id and line.currency_id.id or company_currency
683 'name':line.move_id.name,
684 'type': line.credit and 'dr' or 'cr',
685 'move_line_id':line.id,
686 'account_id':line.account_id.id,
687 'amount_original': amount_original,
688 'amount': (move_line_found == line.id) and min(price, amount_unreconciled) or 0.0,
689 'date_original':line.date,
690 'date_due':line.date_maturity,
691 'amount_unreconciled': amount_unreconciled,
692 'currency_id': line_currency_id,
695 #split voucher amount by most old first, but only for lines in the same currency
696 if not move_line_found:
697 if currency_id == line_currency_id:
699 amount = min(amount_unreconciled, abs(total_debit))
700 rs['amount'] = amount
701 total_debit -= amount
703 amount = min(amount_unreconciled, abs(total_credit))
704 rs['amount'] = amount
705 total_credit -= amount
707 if rs['amount_unreconciled'] == rs['amount']:
708 rs['reconcile'] = True
710 if rs['type'] == 'cr':
711 default['value']['line_cr_ids'].append(rs)
713 default['value']['line_dr_ids'].append(rs)
715 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
716 default['value']['pre_line'] = 1
717 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
718 default['value']['pre_line'] = 1
719 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price)
722 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
726 #set the default payment rate of the voucher and compute the paid amount in company currency
727 if currency_id and currency_id == payment_rate_currency_id:
729 ctx.update({'date': date})
730 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
731 for key in vals.keys():
732 res[key].update(vals[key])
735 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
737 @param date: latest value from user input for field date
738 @param args: other arguments
739 @param context: context arguments, like lang, time zone
740 @return: Returns a dict which contains new values, and context
745 #set the period of the voucher
746 period_pool = self.pool.get('account.period')
747 currency_obj = self.pool.get('res.currency')
749 ctx.update({'company_id': company_id})
750 pids = period_pool.find(cr, uid, date, context=ctx)
752 res['value'].update({'period_id':pids[0]})
753 if payment_rate_currency_id:
754 ctx.update({'date': date})
756 if payment_rate_currency_id != currency_id:
757 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
758 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
759 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
760 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
761 vals['value'].update({'payment_rate': payment_rate})
762 for key in vals.keys():
763 res[key].update(vals[key])
766 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
769 journal_pool = self.pool.get('account.journal')
770 journal = journal_pool.browse(cr, uid, journal_id, context=context)
771 account_id = journal.default_credit_account_id or journal.default_debit_account_id
773 if account_id and account_id.tax_ids:
774 tax_id = account_id.tax_ids[0].id
776 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
777 vals['value'].update({'tax_id':tax_id,'amount': amount})
780 currency_id = journal.currency.id
781 vals['value'].update({'currency_id': currency_id})
782 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
783 for key in res.keys():
784 vals[key].update(res[key])
787 def proforma_voucher(self, cr, uid, ids, context=None):
788 self.action_move_line_create(cr, uid, ids, context=context)
789 return {'type': 'ir.actions.act_window_close'}
791 def action_cancel_draft(self, cr, uid, ids, context=None):
792 wf_service = netsvc.LocalService("workflow")
793 for voucher_id in ids:
794 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
795 self.write(cr, uid, ids, {'state':'draft'})
798 def cancel_voucher(self, cr, uid, ids, context=None):
799 reconcile_pool = self.pool.get('account.move.reconcile')
800 move_pool = self.pool.get('account.move')
802 for voucher in self.browse(cr, uid, ids, context=context):
804 for line in voucher.move_ids:
805 if line.reconcile_id:
806 recs += [line.reconcile_id.id]
807 if line.reconcile_partial_id:
808 recs += [line.reconcile_partial_id.id]
810 reconcile_pool.unlink(cr, uid, recs)
813 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
814 move_pool.unlink(cr, uid, [voucher.move_id.id])
819 self.write(cr, uid, ids, res)
822 def unlink(self, cr, uid, ids, context=None):
823 for t in self.read(cr, uid, ids, ['state'], context=context):
824 if t['state'] not in ('draft', 'cancel'):
825 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Voucher(s) which are already opened or paid !'))
826 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
828 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
832 res = {'account_id':False}
833 partner_pool = self.pool.get('res.partner')
834 journal_pool = self.pool.get('account.journal')
835 if pay_now == 'pay_later':
836 partner = partner_pool.browse(cr, uid, partner_id)
837 journal = journal_pool.browse(cr, uid, journal_id)
838 if journal.type in ('sale','sale_refund'):
839 account_id = partner.property_account_receivable.id
840 elif journal.type in ('purchase', 'purchase_refund','expense'):
841 account_id = partner.property_account_payable.id
843 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
844 res['account_id'] = account_id
847 def _sel_context(self, cr, uid, voucher_id,context=None):
849 Select the context to use accordingly if it needs to be multicurrency or not.
851 :param voucher_id: Id of the actual voucher
852 :return: The returned context will be the same as given in parameter if the voucher currency is the same
853 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
854 the date of the voucher.
857 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
858 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
859 if current_currency <> company_currency:
860 context_multi_currency = context.copy()
861 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
862 context_multi_currency.update({'date': voucher_brw.date})
863 return context_multi_currency
866 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
868 Return a dict to be use to create the first account move line of given voucher.
870 :param voucher_id: Id of voucher what we are creating account_move.
871 :param move_id: Id of account move where this line will be added.
872 :param company_currency: id of currency of the company to which the voucher belong
873 :param current_currency: id of currency of the voucher
874 :return: mapping between fieldname and value of account move line to create
877 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
879 # TODO: is there any other alternative then the voucher type ??
880 # ANSWER: We can have payment and receipt "In Advance".
881 # TODO: Make this logic available.
882 # -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
883 if voucher_brw.type in ('purchase', 'payment'):
884 credit = voucher_brw.paid_amount_in_company_currency
885 elif voucher_brw.type in ('sale', 'receipt'):
886 debit = voucher_brw.paid_amount_in_company_currency
887 if debit < 0: credit = -debit; debit = 0.0
888 if credit < 0: debit = -credit; credit = 0.0
889 sign = debit - credit < 0 and -1 or 1
890 #set the first line of the voucher
892 'name': voucher_brw.name or '/',
895 'account_id': voucher_brw.account_id.id,
897 'journal_id': voucher_brw.journal_id.id,
898 'period_id': voucher_brw.period_id.id,
899 'partner_id': voucher_brw.partner_id.id,
900 'currency_id': company_currency <> current_currency and current_currency or False,
901 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
902 'date': voucher_brw.date,
903 'date_maturity': voucher_brw.date_due
907 def account_move_get(self, cr, uid, voucher_id, context=None):
909 This method prepare the creation of the account move related to the given voucher.
911 :param voucher_id: Id of voucher for which we are creating account_move.
912 :return: mapping between fieldname and value of account move to create
915 seq_obj = self.pool.get('ir.sequence')
916 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
917 if voucher_brw.number:
918 name = voucher_brw.number
919 elif voucher_brw.journal_id.sequence_id:
920 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
922 raise osv.except_osv(_('Error !'),
923 _('Please define a sequence on the journal !'))
924 if not voucher_brw.reference:
925 ref = name.replace('/','')
927 ref = voucher_brw.reference
931 'journal_id': voucher_brw.journal_id.id,
932 'narration': voucher_brw.narration,
933 'date': voucher_brw.date,
935 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
939 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
941 Prepare the two lines in company currency due to currency rate difference.
943 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
945 :param move_id: Account move wher the move lines will be.
946 :param amount_residual: Amount to be posted.
947 :param company_currency: id of currency of the company to which the voucher belong
948 :param current_currency: id of currency of the voucher
949 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
950 :rtype: tuple of dict
952 if amount_residual > 0:
953 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
955 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! "))
957 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
959 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! "))
960 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
961 # the receivable/payable account may have a secondary currency, which render this field mandatory
962 account_currency_id = company_currency <> current_currency and current_currency or False
964 'journal_id': line.voucher_id.journal_id.id,
965 'period_id': line.voucher_id.period_id.id,
966 'name': _('change')+': '+(line.name or '/'),
967 'account_id': line.account_id.id,
969 'partner_id': line.voucher_id.partner_id.id,
970 'currency_id': account_currency_id,
971 'amount_currency': 0.0,
973 'credit': amount_residual > 0 and amount_residual or 0.0,
974 'debit': amount_residual < 0 and -amount_residual or 0.0,
975 'date': line.voucher_id.date,
977 move_line_counterpart = {
978 'journal_id': line.voucher_id.journal_id.id,
979 'period_id': line.voucher_id.period_id.id,
980 'name': _('change')+': '+(line.name or '/'),
981 'account_id': account_id.id,
983 'amount_currency': 0.0,
984 'partner_id': line.voucher_id.partner_id.id,
985 'currency_id': account_currency_id,
987 'debit': amount_residual > 0 and amount_residual or 0.0,
988 'credit': amount_residual < 0 and -amount_residual or 0.0,
989 'date': line.voucher_id.date,
991 return (move_line, move_line_counterpart)
993 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
995 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
996 payment_rate_currency_id is relevant) either the rate encoded in the system.
998 :param amount: float. The amount to convert
999 :param voucher: id of the voucher on which we want the conversion
1000 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1001 field in order to select the good rate to use.
1002 :return: the amount in the currency of the voucher's company
1005 currency_obj = self.pool.get('res.currency')
1006 voucher = self.browse(cr, uid, voucher_id, context=context)
1008 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1009 # the rate specified on the voucher is for the company currency
1010 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1012 # the rate specified on the voucher is not relevant, we use all the rates in the system
1013 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1016 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1018 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1019 It returns Tuple with tot_line what is total of difference between debit and credit and
1020 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1022 :param voucher_id: Voucher id what we are working with
1023 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1024 :param move_id: Account move wher those lines will be joined.
1025 :param company_currency: id of currency of the company to which the voucher belong
1026 :param current_currency: id of currency of the voucher
1027 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1028 :rtype: tuple(float, list of int)
1032 move_line_obj = self.pool.get('account.move.line')
1033 currency_obj = self.pool.get('res.currency')
1034 tax_obj = self.pool.get('account.tax')
1035 tot_line = line_total
1038 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1039 ctx = context.copy()
1040 ctx.update({'date': voucher_brw.date})
1041 for line in voucher_brw.line_ids:
1042 #create one move line per voucher line where amount is not 0.0
1045 # convert the amount set on the voucher line into the currency of the voucher's company
1046 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1047 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1048 # currency rate difference
1049 if line.amount == line.amount_unreconciled:
1050 currency_rate_difference = line.move_line_id.amount_residual - amount
1052 currency_rate_difference = 0.0
1054 'journal_id': voucher_brw.journal_id.id,
1055 'period_id': voucher_brw.period_id.id,
1056 'name': line.name or '/',
1057 'account_id': line.account_id.id,
1059 'partner_id': voucher_brw.partner_id.id,
1060 '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,
1061 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1065 'date': voucher_brw.date
1069 if line.type == 'dr':
1074 if (line.type=='dr'):
1076 move_line['debit'] = amount
1079 move_line['credit'] = amount
1081 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1083 'account_tax_id': voucher_brw.tax_id.id,
1086 if move_line.get('account_tax_id', False):
1087 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1088 if not (tax_data.base_code_id and tax_data.tax_code_id):
1089 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))
1091 # compute the amount in foreign currency
1092 foreign_currency_diff = 0.0
1093 amount_currency = False
1094 if line.move_line_id:
1095 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1096 # We want to set it on the account move line as soon as the original line had a foreign currency
1097 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1098 # we compute the amount in that foreign currency.
1099 if line.move_line_id.currency_id.id == current_currency:
1100 # if the voucher and the voucher line share the same currency, there is no computation to do
1101 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1102 amount_currency = sign * (line.amount)
1103 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1104 # if the rate is specified on the voucher, we must use it
1105 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1106 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1108 # otherwise we use the rates of the system (giving the voucher date in the context)
1109 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1110 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1111 foreign_currency_diff = line.move_line_id.amount_residual_currency + amount_currency
1113 move_line['amount_currency'] = amount_currency
1114 voucher_line = move_line_obj.create(cr, uid, move_line)
1115 rec_ids = [voucher_line, line.move_line_id.id]
1117 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1118 # Change difference entry in company currency
1119 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1120 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1121 move_line_obj.create(cr, uid, exch_lines[1], context)
1122 rec_ids.append(new_id)
1124 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):
1125 # Change difference entry in voucher currency
1126 move_line_foreign_currency = {
1127 'journal_id': line.voucher_id.journal_id.id,
1128 'period_id': line.voucher_id.period_id.id,
1129 'name': _('change')+': '+(line.name or '/'),
1130 'account_id': line.account_id.id,
1132 'partner_id': line.voucher_id.partner_id.id,
1133 'currency_id': line.move_line_id.currency_id.id,
1134 'amount_currency': -1 * foreign_currency_diff,
1138 'date': line.voucher_id.date,
1140 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1141 rec_ids.append(new_id)
1143 if line.move_line_id.id:
1144 rec_lst_ids.append(rec_ids)
1146 return (tot_line, rec_lst_ids)
1148 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1150 Set a dict to be use to create the writeoff move line.
1152 :param voucher_id: Id of voucher what we are creating account_move.
1153 :param line_total: Amount remaining to be allocated on lines.
1154 :param move_id: Id of account move where this line will be added.
1155 :param name: Description of account move line.
1156 :param company_currency: id of currency of the company to which the voucher belong
1157 :param current_currency: id of currency of the voucher
1158 :return: mapping between fieldname and value of account move line to create
1161 currency_obj = self.pool.get('res.currency')
1164 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1165 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1167 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1171 if voucher_brw.payment_option == 'with_writeoff':
1172 account_id = voucher_brw.writeoff_acc_id.id
1173 write_off_name = voucher_brw.comment
1174 elif voucher_brw.type in ('sale', 'receipt'):
1175 account_id = voucher_brw.partner_id.property_account_receivable.id
1177 account_id = voucher_brw.partner_id.property_account_payable.id
1179 'name': write_off_name or name,
1180 'account_id': account_id,
1182 'partner_id': voucher_brw.partner_id.id,
1183 'date': voucher_brw.date,
1184 'credit': diff > 0 and diff or 0.0,
1185 'debit': diff < 0 and -diff or 0.0,
1186 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
1187 'currency_id': company_currency <> current_currency and current_currency or False,
1188 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1193 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1195 Get the currency of the actual company.
1197 :param voucher_id: Id of the voucher what i want to obtain company currency.
1198 :return: currency id of the company of the voucher
1201 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1203 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1205 Get the currency of the voucher.
1207 :param voucher_id: Id of the voucher what i want to obtain current currency.
1208 :return: currency id of the voucher
1211 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1212 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1214 def action_move_line_create(self, cr, uid, ids, context=None):
1216 Confirm the vouchers given in ids and create the journal entries for each of them
1220 move_pool = self.pool.get('account.move')
1221 move_line_pool = self.pool.get('account.move.line')
1222 for voucher in self.browse(cr, uid, ids, context=context):
1225 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1226 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1227 # we select the context to use accordingly if it's a multicurrency case or not
1228 context = self._sel_context(cr, uid, voucher.id, context)
1229 # But for the operations made by _convert_amount, we always need to give the date in the context
1230 ctx = context.copy()
1231 ctx.update({'date': voucher.date})
1232 # Create the account move record.
1233 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1234 # Get the name of the account_move just created
1235 name = move_pool.browse(cr, uid, move_id, context=context).name
1236 # Create the first line of the voucher
1237 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)
1238 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1239 line_total = move_line_brw.debit - move_line_brw.credit
1241 if voucher.type == 'sale':
1242 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1243 elif voucher.type == 'purchase':
1244 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1245 # Create one move line per voucher line where amount is not 0.0
1246 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1248 # Create the writeoff line if needed
1249 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1251 move_line_pool.create(cr, uid, ml_writeoff, context)
1252 # We post the voucher.
1253 self.write(cr, uid, [voucher.id], {
1258 self.post_send_note(cr, uid, [voucher.id], context=context)
1259 if voucher.journal_id.entry_posted:
1260 move_pool.post(cr, uid, [move_id], context={})
1261 # We automatically reconcile the account move lines.
1263 for rec_ids in rec_list_ids:
1264 if len(rec_ids) >= 2:
1265 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)
1267 self.reconcile_send_note(cr, uid, [voucher.id], context=context)
1270 def copy(self, cr, uid, id, default={}, context=None):
1275 'line_cr_ids': False,
1276 'line_dr_ids': False,
1279 if 'date' not in default:
1280 default['date'] = time.strftime('%Y-%m-%d')
1281 return super(account_voucher, self).copy(cr, uid, id, default, context)
1283 # -----------------------------------------
1284 # OpenChatter notifications and need_action
1285 # -----------------------------------------
1287 'sale': 'Sales Receipt',
1288 'purchase': 'Purchase Receipt',
1289 'payment': 'Supplier Payment',
1290 'receipt': 'Customer Payment',
1294 def create_send_note(self, cr, uid, ids, context=None):
1295 for obj in self.browse(cr, uid, ids, context=context):
1296 message = "%s <b>created</b>." % self._document_type[obj.type or False]
1297 self.message_append_note(cr, uid, [obj.id], body=message, context=context)
1299 def post_send_note(self, cr, uid, ids, context=None):
1300 for obj in self.browse(cr, uid, ids, context=context):
1301 message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
1302 self.message_append_note(cr, uid, [obj.id], body=message, context=context)
1304 def reconcile_send_note(self, cr, uid, ids, context=None):
1305 for obj in self.browse(cr, uid, ids, context=context):
1306 message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
1307 self.message_append_note(cr, uid, [obj.id], body=message, context=context)
1311 class account_voucher_line(osv.osv):
1312 _name = 'account.voucher.line'
1313 _description = 'Voucher Lines'
1314 _order = "move_line_id"
1316 # If the payment is in the same currency than the invoice, we keep the same amount
1317 # Otherwise, we compute from company currency to payment currency
1318 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1319 currency_pool = self.pool.get('res.currency')
1321 for line in self.browse(cr, uid, ids, context=context):
1322 ctx = context.copy()
1323 ctx.update({'date': line.voucher_id.date})
1325 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1326 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1327 move_line = line.move_line_id or False
1330 res['amount_original'] = 0.0
1331 res['amount_unreconciled'] = 0.0
1332 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1333 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1334 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)
1335 elif move_line and move_line.credit > 0:
1336 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1337 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1339 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1340 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1342 rs_data[line.id] = res
1345 def _currency_id(self, cr, uid, ids, name, args, context=None):
1347 This function returns the currency id of a voucher line. It's either the currency of the
1348 associated move line (if any) or the currency of the voucher or the company currency.
1351 for line in self.browse(cr, uid, ids, context=context):
1352 move_line = line.move_line_id
1354 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1356 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1360 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1361 'name':fields.char('Description', size=256),
1362 'account_id':fields.many2one('account.account','Account', required=True),
1363 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1364 'untax_amount':fields.float('Untax Amount'),
1365 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1366 'reconcile': fields.boolean('Full Reconcile'),
1367 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1368 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1369 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1370 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1371 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1372 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1373 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1374 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1375 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1381 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1382 vals = { 'amount': 0.0}
1384 vals = { 'amount': amount_unreconciled}
1385 return {'value': vals}
1387 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1390 vals['reconcile'] = (amount == amount_unreconciled)
1391 return {'value': vals}
1393 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1395 Returns a dict that contains new values and context
1397 @param move_line_id: latest value from user input for field move_line_id
1398 @param args: other arguments
1399 @param context: context arguments, like lang, time zone
1401 @return: Returns a dict which contains new values, and context
1404 move_line_pool = self.pool.get('account.move.line')
1406 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1407 if move_line.credit:
1412 'account_id': move_line.account_id.id,
1414 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1420 def default_get(self, cr, user, fields_list, context=None):
1422 Returns default values for fields
1423 @param fields_list: list of fields, for which default values are required to be read
1424 @param context: context arguments, like lang, time zone
1426 @return: Returns a dict that contains default values for fields
1430 journal_id = context.get('journal_id', False)
1431 partner_id = context.get('partner_id', False)
1432 journal_pool = self.pool.get('account.journal')
1433 partner_pool = self.pool.get('res.partner')
1434 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1435 if (not journal_id) or ('account_id' not in fields_list):
1437 journal = journal_pool.browse(cr, user, journal_id, context=context)
1440 if journal.type in ('sale', 'sale_refund'):
1441 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1443 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1444 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1447 partner = partner_pool.browse(cr, user, partner_id, context=context)
1448 if context.get('type') == 'payment':
1450 account_id = partner.property_account_payable.id
1451 elif context.get('type') == 'receipt':
1452 account_id = partner.property_account_receivable.id
1455 'account_id':account_id,
1459 account_voucher_line()
1461 class account_bank_statement(osv.osv):
1462 _inherit = 'account.bank.statement'
1464 def button_cancel(self, cr, uid, ids, context=None):
1465 voucher_obj = self.pool.get('account.voucher')
1466 for st in self.browse(cr, uid, ids, context=context):
1468 for line in st.line_ids:
1470 voucher_ids.append(line.voucher_id.id)
1471 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1472 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1474 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1475 voucher_obj = self.pool.get('account.voucher')
1476 wf_service = netsvc.LocalService("workflow")
1477 move_line_obj = self.pool.get('account.move.line')
1478 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1479 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1480 if st_line.voucher_id:
1481 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1482 if st_line.voucher_id.state == 'cancel':
1483 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1484 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1486 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1487 bank_st_line_obj.write(cr, uid, [st_line_id], {
1488 'move_ids': [(4, v.move_id.id, False)]
1491 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1492 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1494 account_bank_statement()
1496 class account_bank_statement_line(osv.osv):
1497 _inherit = 'account.bank.statement.line'
1499 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1503 for line in self.browse(cursor, user, ids, context=context):
1505 res[line.id] = line.voucher_id.amount#
1510 def _check_amount(self, cr, uid, ids, context=None):
1511 for obj in self.browse(cr, uid, ids, context=context):
1513 diff = abs(obj.amount) - obj.voucher_id.amount
1514 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1519 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1523 'amount_reconciled': fields.function(_amount_reconciled,
1524 string='Amount reconciled', type='float'),
1525 'voucher_id': fields.many2one('account.voucher', 'Payment'),
1528 def unlink(self, cr, uid, ids, context=None):
1529 voucher_obj = self.pool.get('account.voucher')
1530 statement_line = self.browse(cr, uid, ids, context=context)
1532 for st_line in statement_line:
1533 if st_line.voucher_id:
1534 unlink_ids.append(st_line.voucher_id.id)
1535 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1536 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1538 account_bank_statement_line()
1540 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1542 for operation in operations:
1544 if not isinstance(operation, (list, tuple)):
1545 result = target_osv.read(cr, uid, operation, fields, context=context)
1546 elif operation[0] == 0:
1547 # may be necessary to check if all the fields are here and get the default values?
1548 result = operation[2]
1549 elif operation[0] == 1:
1550 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1551 if not result: result = {}
1552 result.update(operation[2])
1553 elif operation[0] == 4:
1554 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1556 results.append(result)
1560 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: