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
250 def _amount_total(self, cr, uid, ids, name, args, context=None):
253 for voucher in self.browse(cr, uid, ids, context=context):
254 for line in voucher.line_dr_ids:
255 amount += line.amount
256 res[voucher.id] = amount
258 _name = 'account.voucher'
259 _description = 'Accounting Voucher'
260 _order = "date desc, id desc"
261 # _rec_name = 'number'
263 'type':fields.selection([
265 ('purchase','Purchase'),
266 ('payment','Payment'),
267 ('receipt','Receipt'),
268 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
269 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
270 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
271 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
272 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
273 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
274 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
275 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
276 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
277 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
278 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
279 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
280 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
281 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
282 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
283 'state':fields.selection(
285 ('cancel','Cancelled'),
286 ('proforma','Pro-forma'),
288 ], 'Status', readonly=True, size=32,
289 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Voucher. \
290 \n* The \'Pro-forma\' when voucher is in Pro-forma state,voucher does not have an voucher number. \
291 \n* The \'Posted\' state is used when user create voucher,a voucher number is generated and voucher entries are created in account \
292 \n* The \'Cancelled\' state is used when user cancel voucher.'),
293 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
294 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
295 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
296 'number': fields.char('Number', size=32, readonly=True,),
297 'move_id':fields.many2one('account.move', 'Account Entry'),
298 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
299 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
300 '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'),
301 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
302 'pay_now':fields.selection([
303 ('pay_now','Pay Directly'),
304 ('pay_later','Pay Later or Group Funds'),
305 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
306 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
307 'pre_line':fields.boolean('Previous Payments ?', required=False),
308 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
309 'payment_option':fields.selection([
310 ('without_writeoff', 'Keep Open'),
311 ('with_writeoff', 'Reconcile Payment Balance'),
312 ], '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)"),
313 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
314 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
315 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
316 '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."),
317 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
318 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
319 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
320 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
321 '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'),
322 'amount_line_total': fields.function(_amount_total, digits_compute=dp.get_precision('Account'), string='Invoice Total'),
325 'period_id': _get_period,
326 'partner_id': _get_partner,
327 'journal_id':_get_journal,
328 'currency_id': _get_currency,
329 'reference': _get_reference,
330 'narration':_get_narration,
331 'amount': _get_amount,
334 'pay_now': 'pay_later',
336 'date': lambda *a: time.strftime('%Y-%m-%d'),
337 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
339 'payment_option': 'without_writeoff',
340 'comment': _('Write-Off'),
342 'payment_rate_currency_id': _get_payment_rate_currency,
345 def compute_tax(self, cr, uid, ids, context=None):
346 tax_pool = self.pool.get('account.tax')
347 partner_pool = self.pool.get('res.partner')
348 position_pool = self.pool.get('account.fiscal.position')
349 voucher_line_pool = self.pool.get('account.voucher.line')
350 voucher_pool = self.pool.get('account.voucher')
351 if context is None: context = {}
353 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
355 for line in voucher.line_ids:
356 voucher_amount += line.untax_amount or line.amount
357 line.amount = line.untax_amount or line.amount
358 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
360 if not voucher.tax_id:
361 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
364 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
365 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
366 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
367 tax = tax_pool.browse(cr, uid, taxes, context=context)
369 total = voucher_amount
372 if not tax[0].price_include:
373 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_amount, 1).get('taxes', []):
374 total_tax += tax_line.get('amount', 0.0)
377 for line in voucher.line_ids:
381 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
382 line_tax += tax_line.get('amount', 0.0)
383 line_total += tax_line.get('price_unit')
384 total_tax += line_tax
385 untax_amount = line.untax_amount or line.amount
386 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
388 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
391 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
392 context = context or {}
393 tax_pool = self.pool.get('account.tax')
394 partner_pool = self.pool.get('res.partner')
395 position_pool = self.pool.get('account.fiscal.position')
396 line_pool = self.pool.get('account.voucher.line')
403 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
405 for line in line_ids:
407 line_amount = line.get('amount',0.0)
408 voucher_total += line_amount
410 total = voucher_total
413 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
415 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
416 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
417 tax = tax_pool.browse(cr, uid, taxes, context=context)
419 if not tax[0].price_include:
420 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_total, 1).get('taxes', []):
421 total_tax += tax_line.get('amount')
425 'amount':total or voucher_total,
426 'tax_amount':total_tax
432 def onchange_term_id(self, cr, uid, ids, term_id, amount):
433 term_pool = self.pool.get('account.payment.term')
436 default = {'date_due':False}
437 if term_id and amount:
438 terms = term_pool.compute(cr, uid, term_id, amount)
440 due_date = terms[-1][0]
444 return {'value':default}
446 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):
448 Returns a dict that contains new values and context
450 @param partner_id: latest value from user input for field partner_id
451 @param args: other arguments
452 @param context: context arguments, like lang, time zone
454 @return: Returns a dict which contains new values, and context
460 if not partner_id or not journal_id:
463 partner_pool = self.pool.get('res.partner')
464 journal_pool = self.pool.get('account.journal')
466 journal = journal_pool.browse(cr, uid, journal_id, context=context)
467 partner = partner_pool.browse(cr, uid, partner_id, context=context)
470 if journal.type in ('sale','sale_refund'):
471 account_id = partner.property_account_receivable.id
473 elif journal.type in ('purchase', 'purchase_refund','expense'):
474 account_id = partner.property_account_payable.id
477 if not journal.default_credit_account_id or not journal.default_debit_account_id:
478 raise osv.except_osv(_('Error !'), _('Please define default credit/debit accounts on the journal "%s" !') % (journal.name))
479 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
482 default['value']['account_id'] = account_id
483 default['value']['type'] = ttype or tr_type
485 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)
486 default['value'].update(vals.get('value'))
490 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
491 res = {'value': {'paid_amount_in_company_currency': amount}}
492 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
493 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
494 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
495 if company_currency.id == payment_rate_currency_id:
498 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
499 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
502 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):
505 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
507 ctx.update({'date': date})
508 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
509 for key in vals.keys():
510 res[key].update(vals[key])
513 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
516 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
517 currency_obj = self.pool.get('res.currency')
518 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
519 company_id = journal.company_id.id
521 payment_rate_currency_id = currency_id
523 ctx.update({'date': date})
525 if ttype == 'receipt':
526 o2m_to_loop = 'line_cr_ids'
527 elif ttype == 'payment':
528 o2m_to_loop = 'line_dr_ids'
529 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
530 for voucher_line in vals['value'][o2m_to_loop]:
531 if voucher_line['currency_id'] != currency_id:
532 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
533 # is not in the voucher currency
534 payment_rate_currency_id = voucher_line['currency_id']
535 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
536 voucher_currency_id = currency_id or journal.company_id.currency_id.id
537 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
539 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
540 for key in res.keys():
541 vals[key].update(res[key])
542 vals['value'].update({'payment_rate': payment_rate})
543 if payment_rate_currency_id:
544 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
547 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
550 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
551 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
552 for key in vals.keys():
553 res[key].update(vals[key])
556 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
558 Returns a dict that contains new values and context
560 @param partner_id: latest value from user input for field partner_id
561 @param args: other arguments
562 @param context: context arguments, like lang, time zone
564 @return: Returns a dict which contains new values, and context
566 def _remove_noise_in_o2m():
567 """if the line is partially reconciled, then we must pay attention to display it only once and
569 This function returns True if the line is considered as noise and should not be displayed
571 if line.reconcile_partial_id:
572 sign = 1 if ttype == 'receipt' else -1
573 if currency_id == line.currency_id.id:
574 if line.amount_residual_currency * sign <= 0:
577 if line.amount_residual * sign <= 0:
583 context_multi_currency = context.copy()
585 context_multi_currency.update({'date': date})
587 currency_pool = self.pool.get('res.currency')
588 move_line_pool = self.pool.get('account.move.line')
589 partner_pool = self.pool.get('res.partner')
590 journal_pool = self.pool.get('account.journal')
591 line_pool = self.pool.get('account.voucher.line')
595 'value': {'line_ids': [] ,'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
599 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
601 line_pool.unlink(cr, uid, line_ids)
603 if not partner_id or not journal_id:
606 journal = journal_pool.browse(cr, uid, journal_id, context=context)
607 partner = partner_pool.browse(cr, uid, partner_id, context=context)
608 currency_id = currency_id or journal.company_id.currency_id.id
610 if journal.type in ('sale','sale_refund'):
611 account_id = partner.property_account_receivable.id
612 elif journal.type in ('purchase', 'purchase_refund','expense'):
613 account_id = partner.property_account_payable.id
615 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
617 default['value']['account_id'] = account_id
619 if journal.type not in ('cash', 'bank'):
624 account_type = 'receivable'
625 if ttype == 'payment':
626 account_type = 'payable'
627 total_debit = price or 0.0
629 total_credit = price or 0.0
630 account_type = 'receivable'
632 if not context.get('move_line_ids', False):
633 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
635 ids = context['move_line_ids']
636 invoice_id = context.get('invoice_id', False)
637 company_currency = journal.company_id.currency_id.id
638 move_line_found = False
640 #order the lines by most old first
642 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
644 #compute the total debit/credit and look for a matching open amount or invoice
645 for line in account_move_lines:
646 if _remove_noise_in_o2m():
650 if line.invoice.id == invoice_id:
651 #if the invoice linked to the voucher line is equal to the invoice_id in context
652 #then we assign the amount on that line, whatever the other voucher lines
653 move_line_found = line.id
655 elif currency_id == company_currency:
656 #otherwise treatments is the same but with other field names
657 if line.amount_residual == price:
658 #if the amount residual is equal the amount voucher, we assign it to that voucher
659 #line, whatever the other voucher lines
660 move_line_found = line.id
662 #otherwise we will split the voucher amount on each line (by most old first)
663 total_credit += line.credit or 0.0
664 total_debit += line.debit or 0.0
665 elif currency_id == line.currency_id.id:
666 if line.amount_residual_currency == price:
667 move_line_found = line.id
669 total_credit += line.credit and line.amount_currency or 0.0
670 total_debit += line.debit and line.amount_currency or 0.0
672 #voucher line creation
673 for line in account_move_lines:
674 if _remove_noise_in_o2m():
677 if line.currency_id and currency_id==line.currency_id.id:
678 amount_original = abs(line.amount_currency)
679 amount_unreconciled = abs(line.amount_residual_currency)
681 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
682 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
683 line_currency_id = line.currency_id and line.currency_id.id or company_currency
685 'name':line.move_id.name,
686 'type': line.credit and 'dr' or 'cr',
687 'move_line_id':line.id,
688 'account_id':line.account_id.id,
689 'amount_original': amount_original,
690 'amount': (move_line_found == line.id) and min(price, amount_unreconciled) or 0.0,
691 'date_original':line.date,
692 'date_due':line.date_maturity,
693 'amount_unreconciled': amount_unreconciled,
694 'currency_id': line_currency_id,
697 #split voucher amount by most old first, but only for lines in the same currency
698 if not move_line_found:
699 if currency_id == line_currency_id:
701 amount = min(amount_unreconciled, abs(total_debit))
702 rs['amount'] = amount
703 total_debit -= amount
705 amount = min(amount_unreconciled, abs(total_credit))
706 rs['amount'] = amount
707 total_credit -= amount
709 if rs['amount_unreconciled'] == rs['amount']:
710 rs['reconcile'] = True
712 if rs['type'] == 'cr':
713 default['value']['line_cr_ids'].append(rs)
715 default['value']['line_dr_ids'].append(rs)
717 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
718 default['value']['pre_line'] = 1
719 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
720 default['value']['pre_line'] = 1
721 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price)
724 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
728 #set the default payment rate of the voucher and compute the paid amount in company currency
729 if currency_id and currency_id == payment_rate_currency_id:
731 ctx.update({'date': date})
732 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
733 for key in vals.keys():
734 res[key].update(vals[key])
737 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
739 @param date: latest value from user input for field date
740 @param args: other arguments
741 @param context: context arguments, like lang, time zone
742 @return: Returns a dict which contains new values, and context
747 #set the period of the voucher
748 period_pool = self.pool.get('account.period')
749 currency_obj = self.pool.get('res.currency')
751 ctx.update({'company_id': company_id})
752 pids = period_pool.find(cr, uid, date, context=ctx)
754 res['value'].update({'period_id':pids[0]})
755 if payment_rate_currency_id:
756 ctx.update({'date': date})
758 if payment_rate_currency_id != currency_id:
759 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
760 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
761 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
762 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
763 vals['value'].update({'payment_rate': payment_rate})
764 for key in vals.keys():
765 res[key].update(vals[key])
768 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
771 journal_pool = self.pool.get('account.journal')
772 journal = journal_pool.browse(cr, uid, journal_id, context=context)
773 account_id = journal.default_credit_account_id or journal.default_debit_account_id
775 if account_id and account_id.tax_ids:
776 tax_id = account_id.tax_ids[0].id
778 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
779 vals['value'].update({'tax_id':tax_id,'amount': amount})
782 currency_id = journal.currency.id
783 vals['value'].update({'currency_id': currency_id})
784 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
785 for key in res.keys():
786 vals[key].update(res[key])
789 def proforma_voucher(self, cr, uid, ids, context=None):
790 self.action_move_line_create(cr, uid, ids, context=context)
791 return {'type': 'ir.actions.act_window_close'}
793 def action_cancel_draft(self, cr, uid, ids, context=None):
794 wf_service = netsvc.LocalService("workflow")
795 for voucher_id in ids:
796 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
797 self.write(cr, uid, ids, {'state':'draft'})
800 def cancel_voucher(self, cr, uid, ids, context=None):
801 reconcile_pool = self.pool.get('account.move.reconcile')
802 move_pool = self.pool.get('account.move')
804 for voucher in self.browse(cr, uid, ids, context=context):
806 for line in voucher.move_ids:
807 if line.reconcile_id:
808 recs += [line.reconcile_id.id]
809 if line.reconcile_partial_id:
810 recs += [line.reconcile_partial_id.id]
812 reconcile_pool.unlink(cr, uid, recs)
815 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
816 move_pool.unlink(cr, uid, [voucher.move_id.id])
821 self.write(cr, uid, ids, res)
824 def unlink(self, cr, uid, ids, context=None):
825 for t in self.read(cr, uid, ids, ['state'], context=context):
826 if t['state'] not in ('draft', 'cancel'):
827 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Voucher(s) which are already opened or paid !'))
828 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
830 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
834 res = {'account_id':False}
835 partner_pool = self.pool.get('res.partner')
836 journal_pool = self.pool.get('account.journal')
837 if pay_now == 'pay_later':
838 partner = partner_pool.browse(cr, uid, partner_id)
839 journal = journal_pool.browse(cr, uid, journal_id)
840 if journal.type in ('sale','sale_refund'):
841 account_id = partner.property_account_receivable.id
842 elif journal.type in ('purchase', 'purchase_refund','expense'):
843 account_id = partner.property_account_payable.id
845 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
846 res['account_id'] = account_id
849 def _sel_context(self, cr, uid, voucher_id,context=None):
851 Select the context to use accordingly if it needs to be multicurrency or not.
853 :param voucher_id: Id of the actual voucher
854 :return: The returned context will be the same as given in parameter if the voucher currency is the same
855 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
856 the date of the voucher.
859 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
860 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
861 if current_currency <> company_currency:
862 context_multi_currency = context.copy()
863 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
864 context_multi_currency.update({'date': voucher_brw.date})
865 return context_multi_currency
868 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
870 Return a dict to be use to create the first account move line of given voucher.
872 :param voucher_id: Id of voucher what we are creating account_move.
873 :param move_id: Id of account move where this line will be added.
874 :param company_currency: id of currency of the company to which the voucher belong
875 :param current_currency: id of currency of the voucher
876 :return: mapping between fieldname and value of account move line to create
879 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
881 # TODO: is there any other alternative then the voucher type ??
882 # ANSWER: We can have payment and receipt "In Advance".
883 # TODO: Make this logic available.
884 # -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
885 if voucher_brw.type in ('purchase', 'payment'):
886 credit = voucher_brw.paid_amount_in_company_currency
887 elif voucher_brw.type in ('sale', 'receipt'):
888 debit = voucher_brw.paid_amount_in_company_currency
889 if debit < 0: credit = -debit; debit = 0.0
890 if credit < 0: debit = -credit; credit = 0.0
891 sign = debit - credit < 0 and -1 or 1
892 #set the first line of the voucher
894 'name': voucher_brw.name or '/',
897 'account_id': voucher_brw.account_id.id,
899 'journal_id': voucher_brw.journal_id.id,
900 'period_id': voucher_brw.period_id.id,
901 'partner_id': voucher_brw.partner_id.id,
902 'currency_id': company_currency <> current_currency and current_currency or False,
903 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
904 'date': voucher_brw.date,
905 'date_maturity': voucher_brw.date_due
909 def account_move_get(self, cr, uid, voucher_id, context=None):
911 This method prepare the creation of the account move related to the given voucher.
913 :param voucher_id: Id of voucher for which we are creating account_move.
914 :return: mapping between fieldname and value of account move to create
917 seq_obj = self.pool.get('ir.sequence')
918 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
919 if voucher_brw.number:
920 name = voucher_brw.number
921 elif voucher_brw.journal_id.sequence_id:
922 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
924 raise osv.except_osv(_('Error !'),
925 _('Please define a sequence on the journal !'))
926 if not voucher_brw.reference:
927 ref = name.replace('/','')
929 ref = voucher_brw.reference
933 'journal_id': voucher_brw.journal_id.id,
934 'narration': voucher_brw.narration,
935 'date': voucher_brw.date,
937 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
941 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
943 Prepare the two lines in company currency due to currency rate difference.
945 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
947 :param move_id: Account move wher the move lines will be.
948 :param amount_residual: Amount to be posted.
949 :param company_currency: id of currency of the company to which the voucher belong
950 :param current_currency: id of currency of the voucher
951 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
952 :rtype: tuple of dict
954 if amount_residual > 0:
955 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
957 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! "))
959 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
961 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! "))
962 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
963 # the receivable/payable account may have a secondary currency, which render this field mandatory
964 account_currency_id = company_currency <> current_currency and current_currency or False
966 'journal_id': line.voucher_id.journal_id.id,
967 'period_id': line.voucher_id.period_id.id,
968 'name': _('change')+': '+(line.name or '/'),
969 'account_id': line.account_id.id,
971 'partner_id': line.voucher_id.partner_id.id,
972 'currency_id': account_currency_id,
973 'amount_currency': 0.0,
975 'credit': amount_residual > 0 and amount_residual or 0.0,
976 'debit': amount_residual < 0 and -amount_residual or 0.0,
977 'date': line.voucher_id.date,
979 move_line_counterpart = {
980 'journal_id': line.voucher_id.journal_id.id,
981 'period_id': line.voucher_id.period_id.id,
982 'name': _('change')+': '+(line.name or '/'),
983 'account_id': account_id.id,
985 'amount_currency': 0.0,
986 'partner_id': line.voucher_id.partner_id.id,
987 'currency_id': account_currency_id,
989 'debit': amount_residual > 0 and amount_residual or 0.0,
990 'credit': amount_residual < 0 and -amount_residual or 0.0,
991 'date': line.voucher_id.date,
993 return (move_line, move_line_counterpart)
995 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
997 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
998 payment_rate_currency_id is relevant) either the rate encoded in the system.
1000 :param amount: float. The amount to convert
1001 :param voucher: id of the voucher on which we want the conversion
1002 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1003 field in order to select the good rate to use.
1004 :return: the amount in the currency of the voucher's company
1007 currency_obj = self.pool.get('res.currency')
1008 voucher = self.browse(cr, uid, voucher_id, context=context)
1010 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1011 # the rate specified on the voucher is for the company currency
1012 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1014 # the rate specified on the voucher is not relevant, we use all the rates in the system
1015 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1018 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1020 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1021 It returns Tuple with tot_line what is total of difference between debit and credit and
1022 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1024 :param voucher_id: Voucher id what we are working with
1025 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1026 :param move_id: Account move wher those lines will be joined.
1027 :param company_currency: id of currency of the company to which the voucher belong
1028 :param current_currency: id of currency of the voucher
1029 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1030 :rtype: tuple(float, list of int)
1034 move_line_obj = self.pool.get('account.move.line')
1035 currency_obj = self.pool.get('res.currency')
1036 tax_obj = self.pool.get('account.tax')
1037 tot_line = line_total
1040 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1041 ctx = context.copy()
1042 ctx.update({'date': voucher_brw.date})
1043 for line in voucher_brw.line_ids:
1044 #create one move line per voucher line where amount is not 0.0
1047 # convert the amount set on the voucher line into the currency of the voucher's company
1048 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1049 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1050 # currency rate difference
1051 if line.amount == line.amount_unreconciled:
1052 currency_rate_difference = line.move_line_id.amount_residual - amount
1054 currency_rate_difference = 0.0
1056 'journal_id': voucher_brw.journal_id.id,
1057 'period_id': voucher_brw.period_id.id,
1058 'name': line.name or '/',
1059 'account_id': line.account_id.id,
1061 'partner_id': voucher_brw.partner_id.id,
1062 '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,
1063 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1067 'date': voucher_brw.date
1071 if line.type == 'dr':
1076 if (line.type=='dr'):
1078 move_line['debit'] = amount
1081 move_line['credit'] = amount
1083 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1085 'account_tax_id': voucher_brw.tax_id.id,
1088 if move_line.get('account_tax_id', False):
1089 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1090 if not (tax_data.base_code_id and tax_data.tax_code_id):
1091 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))
1093 # compute the amount in foreign currency
1094 foreign_currency_diff = 0.0
1095 amount_currency = False
1096 if line.move_line_id:
1097 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1098 # We want to set it on the account move line as soon as the original line had a foreign currency
1099 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1100 # we compute the amount in that foreign currency.
1101 if line.move_line_id.currency_id.id == current_currency:
1102 # if the voucher and the voucher line share the same currency, there is no computation to do
1103 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1104 amount_currency = sign * (line.amount)
1105 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1106 # if the rate is specified on the voucher, we must use it
1107 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1108 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1110 # otherwise we use the rates of the system (giving the voucher date in the context)
1111 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1112 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1113 foreign_currency_diff = line.move_line_id.amount_residual_currency + amount_currency
1115 move_line['amount_currency'] = amount_currency
1116 voucher_line = move_line_obj.create(cr, uid, move_line)
1117 rec_ids = [voucher_line, line.move_line_id.id]
1119 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1120 # Change difference entry in company currency
1121 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1122 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1123 move_line_obj.create(cr, uid, exch_lines[1], context)
1124 rec_ids.append(new_id)
1126 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):
1127 # Change difference entry in voucher currency
1128 move_line_foreign_currency = {
1129 'journal_id': line.voucher_id.journal_id.id,
1130 'period_id': line.voucher_id.period_id.id,
1131 'name': _('change')+': '+(line.name or '/'),
1132 'account_id': line.account_id.id,
1134 'partner_id': line.voucher_id.partner_id.id,
1135 'currency_id': line.move_line_id.currency_id.id,
1136 'amount_currency': -1 * foreign_currency_diff,
1140 'date': line.voucher_id.date,
1142 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1143 rec_ids.append(new_id)
1145 if line.move_line_id.id:
1146 rec_lst_ids.append(rec_ids)
1148 return (tot_line, rec_lst_ids)
1150 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1152 Set a dict to be use to create the writeoff move line.
1154 :param voucher_id: Id of voucher what we are creating account_move.
1155 :param line_total: Amount remaining to be allocated on lines.
1156 :param move_id: Id of account move where this line will be added.
1157 :param name: Description of account move line.
1158 :param company_currency: id of currency of the company to which the voucher belong
1159 :param current_currency: id of currency of the voucher
1160 :return: mapping between fieldname and value of account move line to create
1163 currency_obj = self.pool.get('res.currency')
1166 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1167 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1169 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1173 if voucher_brw.payment_option == 'with_writeoff':
1174 account_id = voucher_brw.writeoff_acc_id.id
1175 write_off_name = voucher_brw.comment
1176 elif voucher_brw.type in ('sale', 'receipt'):
1177 account_id = voucher_brw.partner_id.property_account_receivable.id
1179 account_id = voucher_brw.partner_id.property_account_payable.id
1181 'name': write_off_name or name,
1182 'account_id': account_id,
1184 'partner_id': voucher_brw.partner_id.id,
1185 'date': voucher_brw.date,
1186 'credit': diff > 0 and diff or 0.0,
1187 'debit': diff < 0 and -diff or 0.0,
1188 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
1189 'currency_id': company_currency <> current_currency and current_currency or False,
1190 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1195 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1197 Get the currency of the actual company.
1199 :param voucher_id: Id of the voucher what i want to obtain company currency.
1200 :return: currency id of the company of the voucher
1203 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1205 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1207 Get the currency of the voucher.
1209 :param voucher_id: Id of the voucher what i want to obtain current currency.
1210 :return: currency id of the voucher
1213 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1214 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1216 def action_move_line_create(self, cr, uid, ids, context=None):
1218 Confirm the vouchers given in ids and create the journal entries for each of them
1222 move_pool = self.pool.get('account.move')
1223 move_line_pool = self.pool.get('account.move.line')
1224 for voucher in self.browse(cr, uid, ids, context=context):
1227 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1228 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1229 # we select the context to use accordingly if it's a multicurrency case or not
1230 context = self._sel_context(cr, uid, voucher.id, context)
1231 # But for the operations made by _convert_amount, we always need to give the date in the context
1232 ctx = context.copy()
1233 ctx.update({'date': voucher.date})
1234 # Create the account move record.
1235 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1236 # Get the name of the account_move just created
1237 name = move_pool.browse(cr, uid, move_id, context=context).name
1238 # Create the first line of the voucher
1239 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)
1240 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1241 line_total = move_line_brw.debit - move_line_brw.credit
1243 if voucher.type == 'sale':
1244 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1245 elif voucher.type == 'purchase':
1246 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1247 # Create one move line per voucher line where amount is not 0.0
1248 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1250 # Create the writeoff line if needed
1251 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1253 move_line_pool.create(cr, uid, ml_writeoff, context)
1254 # We post the voucher.
1255 self.write(cr, uid, [voucher.id], {
1260 if voucher.journal_id.entry_posted:
1261 move_pool.post(cr, uid, [move_id], context={})
1262 # We automatically reconcile the account move lines.
1263 for rec_ids in rec_list_ids:
1264 if len(rec_ids) >= 2:
1265 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)
1268 def copy(self, cr, uid, id, default={}, context=None):
1273 'line_cr_ids': False,
1274 'line_dr_ids': False,
1277 if 'date' not in default:
1278 default['date'] = time.strftime('%Y-%m-%d')
1279 return super(account_voucher, self).copy(cr, uid, id, default, context)
1283 class account_voucher_line(osv.osv):
1284 _name = 'account.voucher.line'
1285 _description = 'Voucher Lines'
1286 _order = "move_line_id"
1288 # If the payment is in the same currency than the invoice, we keep the same amount
1289 # Otherwise, we compute from company currency to payment currency
1290 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1291 currency_pool = self.pool.get('res.currency')
1293 for line in self.browse(cr, uid, ids, context=context):
1294 ctx = context.copy()
1295 ctx.update({'date': line.voucher_id.date})
1297 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1298 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1299 move_line = line.move_line_id or False
1302 res['amount_original'] = 0.0
1303 res['amount_unreconciled'] = 0.0
1304 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1305 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1306 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)
1307 elif move_line and move_line.credit > 0:
1308 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1309 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1311 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1312 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1314 rs_data[line.id] = res
1317 def _currency_id(self, cr, uid, ids, name, args, context=None):
1319 This function returns the currency id of a voucher line. It's either the currency of the
1320 associated move line (if any) or the currency of the voucher or the company currency.
1323 for line in self.browse(cr, uid, ids, context=context):
1324 move_line = line.move_line_id
1326 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1328 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1332 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1333 'name':fields.char('Description', size=256),
1334 'account_id':fields.many2one('account.account','Account', required=True),
1335 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1336 'untax_amount':fields.float('Untax Amount'),
1337 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1338 'reconcile': fields.boolean('Full Reconcile'),
1339 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1340 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1341 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1342 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1343 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1344 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1345 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1346 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1347 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1353 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1354 vals = { 'amount': 0.0}
1356 vals = { 'amount': amount_unreconciled}
1357 return {'value': vals}
1359 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1362 vals['reconcile'] = (amount == amount_unreconciled)
1363 return {'value': vals}
1365 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1367 Returns a dict that contains new values and context
1369 @param move_line_id: latest value from user input for field move_line_id
1370 @param args: other arguments
1371 @param context: context arguments, like lang, time zone
1373 @return: Returns a dict which contains new values, and context
1376 move_line_pool = self.pool.get('account.move.line')
1378 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1379 if move_line.credit:
1384 'account_id': move_line.account_id.id,
1386 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1392 def default_get(self, cr, user, fields_list, context=None):
1394 Returns default values for fields
1395 @param fields_list: list of fields, for which default values are required to be read
1396 @param context: context arguments, like lang, time zone
1398 @return: Returns a dict that contains default values for fields
1402 journal_id = context.get('journal_id', False)
1403 partner_id = context.get('partner_id', False)
1404 journal_pool = self.pool.get('account.journal')
1405 partner_pool = self.pool.get('res.partner')
1406 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1407 if (not journal_id) or ('account_id' not in fields_list):
1409 journal = journal_pool.browse(cr, user, journal_id, context=context)
1412 if journal.type in ('sale', 'sale_refund'):
1413 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1415 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1416 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1419 partner = partner_pool.browse(cr, user, partner_id, context=context)
1420 if context.get('type') == 'payment':
1422 account_id = partner.property_account_payable.id
1423 elif context.get('type') == 'receipt':
1424 account_id = partner.property_account_receivable.id
1427 'account_id':account_id,
1431 account_voucher_line()
1433 class account_bank_statement(osv.osv):
1434 _inherit = 'account.bank.statement'
1436 def button_cancel(self, cr, uid, ids, context=None):
1437 voucher_obj = self.pool.get('account.voucher')
1438 for st in self.browse(cr, uid, ids, context=context):
1440 for line in st.line_ids:
1442 voucher_ids.append(line.voucher_id.id)
1443 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1444 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1446 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1447 voucher_obj = self.pool.get('account.voucher')
1448 wf_service = netsvc.LocalService("workflow")
1449 move_line_obj = self.pool.get('account.move.line')
1450 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1451 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1452 if st_line.voucher_id:
1453 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1454 if st_line.voucher_id.state == 'cancel':
1455 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1456 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1458 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1459 bank_st_line_obj.write(cr, uid, [st_line_id], {
1460 'move_ids': [(4, v.move_id.id, False)]
1463 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1464 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1466 account_bank_statement()
1468 class account_bank_statement_line(osv.osv):
1469 _inherit = 'account.bank.statement.line'
1471 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1475 for line in self.browse(cursor, user, ids, context=context):
1477 res[line.id] = line.voucher_id.amount#
1482 def _check_amount(self, cr, uid, ids, context=None):
1483 for obj in self.browse(cr, uid, ids, context=context):
1485 diff = abs(obj.amount) - obj.voucher_id.amount
1486 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1491 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1495 'amount_reconciled': fields.function(_amount_reconciled,
1496 string='Amount reconciled', type='float'),
1497 'voucher_id': fields.many2one('account.voucher', 'Payment'),
1500 def unlink(self, cr, uid, ids, context=None):
1501 voucher_obj = self.pool.get('account.voucher')
1502 statement_line = self.browse(cr, uid, ids, context=context)
1504 for st_line in statement_line:
1505 if st_line.voucher_id:
1506 unlink_ids.append(st_line.voucher_id.id)
1507 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1508 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1510 account_bank_statement_line()
1512 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1514 for operation in operations:
1516 if not isinstance(operation, (list, tuple)):
1517 result = target_osv.read(cr, uid, operation, fields, context=context)
1518 elif operation[0] == 0:
1519 # may be necessary to check if all the fields are here and get the default values?
1520 result = operation[2]
1521 elif operation[0] == 1:
1522 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1523 if not result: result = {}
1524 result.update(operation[2])
1525 elif operation[0] == 4:
1526 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1528 results.append(result)
1532 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: