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 _order = "date desc, id desc"
254 # _rec_name = 'number'
256 'type':fields.selection([
258 ('purchase','Purchase'),
259 ('payment','Payment'),
260 ('receipt','Receipt'),
261 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
262 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
263 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
264 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
265 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
266 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
267 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
268 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
269 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
270 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
271 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
272 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
273 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
274 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
275 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
276 'state':fields.selection(
278 ('cancel','Cancelled'),
279 ('proforma','Pro-forma'),
281 ], 'Status', readonly=True, size=32,
282 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Voucher. \
283 \n* The \'Pro-forma\' when voucher is in Pro-forma state,voucher does not have an voucher number. \
284 \n* The \'Posted\' state is used when user create voucher,a voucher number is generated and voucher entries are created in account \
285 \n* The \'Cancelled\' state is used when user cancel voucher.'),
286 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
287 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
288 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
289 'number': fields.char('Number', size=32, readonly=True,),
290 'move_id':fields.many2one('account.move', 'Account Entry'),
291 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
292 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
293 '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'),
294 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
295 'pay_now':fields.selection([
296 ('pay_now','Pay Directly'),
297 ('pay_later','Pay Later or Group Funds'),
298 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
299 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
300 'pre_line':fields.boolean('Previous Payments ?', required=False),
301 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
302 'payment_option':fields.selection([
303 ('without_writeoff', 'Keep Open'),
304 ('with_writeoff', 'Reconcile Payment Balance'),
305 ], '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)"),
306 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
307 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
308 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
309 '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."),
310 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
311 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
312 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
313 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
314 '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'),
317 'period_id': _get_period,
318 'partner_id': _get_partner,
319 'journal_id':_get_journal,
320 'currency_id': _get_currency,
321 'reference': _get_reference,
322 'narration':_get_narration,
323 'amount': _get_amount,
326 'pay_now': 'pay_later',
328 'date': lambda *a: time.strftime('%Y-%m-%d'),
329 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
331 'payment_option': 'without_writeoff',
332 'comment': _('Write-Off'),
334 'payment_rate_currency_id': _get_payment_rate_currency,
337 def compute_tax(self, cr, uid, ids, context=None):
338 tax_pool = self.pool.get('account.tax')
339 partner_pool = self.pool.get('res.partner')
340 position_pool = self.pool.get('account.fiscal.position')
341 voucher_line_pool = self.pool.get('account.voucher.line')
342 voucher_pool = self.pool.get('account.voucher')
343 if context is None: context = {}
345 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
347 for line in voucher.line_ids:
348 voucher_amount += line.untax_amount or line.amount
349 line.amount = line.untax_amount or line.amount
350 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
352 if not voucher.tax_id:
353 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
356 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
357 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
358 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
359 tax = tax_pool.browse(cr, uid, taxes, context=context)
361 total = voucher_amount
364 if not tax[0].price_include:
365 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_amount, 1).get('taxes', []):
366 total_tax += tax_line.get('amount', 0.0)
369 for line in voucher.line_ids:
373 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
374 line_tax += tax_line.get('amount', 0.0)
375 line_total += tax_line.get('price_unit')
376 total_tax += line_tax
377 untax_amount = line.untax_amount or line.amount
378 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
380 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
383 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
384 context = context or {}
385 tax_pool = self.pool.get('account.tax')
386 partner_pool = self.pool.get('res.partner')
387 position_pool = self.pool.get('account.fiscal.position')
388 line_pool = self.pool.get('account.voucher.line')
395 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
397 for line in line_ids:
399 line_amount = line.get('amount',0.0)
400 voucher_total += line_amount
402 total = voucher_total
405 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
407 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
408 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
409 tax = tax_pool.browse(cr, uid, taxes, context=context)
411 if not tax[0].price_include:
412 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_total, 1).get('taxes', []):
413 total_tax += tax_line.get('amount')
417 'amount':total or voucher_total,
418 'tax_amount':total_tax
424 def onchange_term_id(self, cr, uid, ids, term_id, amount):
425 term_pool = self.pool.get('account.payment.term')
428 default = {'date_due':False}
429 if term_id and amount:
430 terms = term_pool.compute(cr, uid, term_id, amount)
432 due_date = terms[-1][0]
436 return {'value':default}
438 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):
440 Returns a dict that contains new values and context
442 @param partner_id: latest value from user input for field partner_id
443 @param args: other arguments
444 @param context: context arguments, like lang, time zone
446 @return: Returns a dict which contains new values, and context
452 if not partner_id or not journal_id:
455 partner_pool = self.pool.get('res.partner')
456 journal_pool = self.pool.get('account.journal')
458 journal = journal_pool.browse(cr, uid, journal_id, context=context)
459 partner = partner_pool.browse(cr, uid, partner_id, context=context)
462 if journal.type in ('sale','sale_refund'):
463 account_id = partner.property_account_receivable.id
465 elif journal.type in ('purchase', 'purchase_refund','expense'):
466 account_id = partner.property_account_payable.id
469 if not journal.default_credit_account_id or not journal.default_debit_account_id:
470 raise osv.except_osv(_('Error !'), _('Please define default credit/debit accounts on the journal "%s" !') % (journal.name))
471 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
474 default['value']['account_id'] = account_id
475 default['value']['type'] = ttype or tr_type
477 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)
478 default['value'].update(vals.get('value'))
482 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
483 res = {'value': {'paid_amount_in_company_currency': amount}}
484 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
485 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
486 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
487 if company_currency.id == payment_rate_currency_id:
490 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
491 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
494 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):
497 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
499 ctx.update({'date': date})
500 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
501 for key in vals.keys():
502 res[key].update(vals[key])
505 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
508 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
509 currency_obj = self.pool.get('res.currency')
510 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
511 company_id = journal.company_id.id
513 payment_rate_currency_id = currency_id
515 ctx.update({'date': date})
517 if ttype == 'receipt':
518 o2m_to_loop = 'line_cr_ids'
519 elif ttype == 'payment':
520 o2m_to_loop = 'line_dr_ids'
521 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
522 for voucher_line in vals['value'][o2m_to_loop]:
523 if voucher_line['currency_id'] != currency_id:
524 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
525 # is not in the voucher currency
526 payment_rate_currency_id = voucher_line['currency_id']
527 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
528 voucher_currency_id = currency_id or journal.company_id.currency_id.id
529 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
531 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
532 for key in res.keys():
533 vals[key].update(res[key])
534 vals['value'].update({'payment_rate': payment_rate})
535 if payment_rate_currency_id:
536 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
539 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
542 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
543 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
544 for key in vals.keys():
545 res[key].update(vals[key])
548 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
550 Returns a dict that contains new values and context
552 @param partner_id: latest value from user input for field partner_id
553 @param args: other arguments
554 @param context: context arguments, like lang, time zone
556 @return: Returns a dict which contains new values, and context
558 def _remove_noise_in_o2m():
559 """if the line is partially reconciled, then we must pay attention to display it only once and
561 This function returns True if the line is considered as noise and should not be displayed
563 if line.reconcile_partial_id:
564 sign = 1 if ttype == 'receipt' else -1
565 if currency_id == line.currency_id.id:
566 if line.amount_residual_currency * sign <= 0:
569 if line.amount_residual * sign <= 0:
575 context_multi_currency = context.copy()
577 context_multi_currency.update({'date': date})
579 currency_pool = self.pool.get('res.currency')
580 move_line_pool = self.pool.get('account.move.line')
581 partner_pool = self.pool.get('res.partner')
582 journal_pool = self.pool.get('account.journal')
583 line_pool = self.pool.get('account.voucher.line')
587 'value': {'line_ids': [] ,'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
591 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
593 line_pool.unlink(cr, uid, line_ids)
595 if not partner_id or not journal_id:
598 journal = journal_pool.browse(cr, uid, journal_id, context=context)
599 partner = partner_pool.browse(cr, uid, partner_id, context=context)
600 currency_id = currency_id or journal.company_id.currency_id.id
602 if journal.type in ('sale','sale_refund'):
603 account_id = partner.property_account_receivable.id
604 elif journal.type in ('purchase', 'purchase_refund','expense'):
605 account_id = partner.property_account_payable.id
607 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
609 default['value']['account_id'] = account_id
611 if journal.type not in ('cash', 'bank'):
616 account_type = 'receivable'
617 if ttype == 'payment':
618 account_type = 'payable'
619 total_debit = price or 0.0
621 total_credit = price or 0.0
622 account_type = 'receivable'
624 if not context.get('move_line_ids', False):
625 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
627 ids = context['move_line_ids']
628 invoice_id = context.get('invoice_id', False)
629 company_currency = journal.company_id.currency_id.id
630 move_line_found = False
632 #order the lines by most old first
634 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
636 #compute the total debit/credit and look for a matching open amount or invoice
637 for line in account_move_lines:
638 if _remove_noise_in_o2m():
642 if line.invoice.id == invoice_id:
643 #if the invoice linked to the voucher line is equal to the invoice_id in context
644 #then we assign the amount on that line, whatever the other voucher lines
645 move_line_found = line.id
647 elif currency_id == company_currency:
648 #otherwise treatments is the same but with other field names
649 if line.amount_residual == price:
650 #if the amount residual is equal the amount voucher, we assign it to that voucher
651 #line, whatever the other voucher lines
652 move_line_found = line.id
654 #otherwise we will split the voucher amount on each line (by most old first)
655 total_credit += line.credit or 0.0
656 total_debit += line.debit or 0.0
657 elif currency_id == line.currency_id.id:
658 if line.amount_residual_currency == price:
659 move_line_found = line.id
661 total_credit += line.credit and line.amount_currency or 0.0
662 total_debit += line.debit and line.amount_currency or 0.0
664 #voucher line creation
665 for line in account_move_lines:
666 if _remove_noise_in_o2m():
669 if line.currency_id and currency_id==line.currency_id.id:
670 amount_original = abs(line.amount_currency)
671 amount_unreconciled = abs(line.amount_residual_currency)
673 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
674 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
675 line_currency_id = line.currency_id and line.currency_id.id or company_currency
677 'name':line.move_id.name,
678 'type': line.credit and 'dr' or 'cr',
679 'move_line_id':line.id,
680 'account_id':line.account_id.id,
681 'amount_original': amount_original,
682 'amount': (move_line_found == line.id) and min(price, amount_unreconciled) or 0.0,
683 'date_original':line.date,
684 'date_due':line.date_maturity,
685 'amount_unreconciled': amount_unreconciled,
686 'currency_id': line_currency_id,
689 #split voucher amount by most old first, but only for lines in the same currency
690 if not move_line_found:
691 if currency_id == line_currency_id:
693 amount = min(amount_unreconciled, abs(total_debit))
694 rs['amount'] = amount
695 total_debit -= amount
697 amount = min(amount_unreconciled, abs(total_credit))
698 rs['amount'] = amount
699 total_credit -= amount
701 if rs['amount_unreconciled'] == rs['amount']:
702 rs['reconcile'] = True
704 if rs['type'] == 'cr':
705 default['value']['line_cr_ids'].append(rs)
707 default['value']['line_dr_ids'].append(rs)
709 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
710 default['value']['pre_line'] = 1
711 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
712 default['value']['pre_line'] = 1
713 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price)
716 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
720 #set the default payment rate of the voucher and compute the paid amount in company currency
721 if currency_id and currency_id == payment_rate_currency_id:
723 ctx.update({'date': date})
724 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
725 for key in vals.keys():
726 res[key].update(vals[key])
729 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
731 @param date: latest value from user input for field date
732 @param args: other arguments
733 @param context: context arguments, like lang, time zone
734 @return: Returns a dict which contains new values, and context
739 #set the period of the voucher
740 period_pool = self.pool.get('account.period')
741 currency_obj = self.pool.get('res.currency')
743 ctx.update({'company_id': company_id})
744 pids = period_pool.find(cr, uid, date, context=ctx)
746 res['value'].update({'period_id':pids[0]})
747 if payment_rate_currency_id:
748 ctx.update({'date': date})
750 if payment_rate_currency_id != currency_id:
751 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
752 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
753 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
754 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
755 vals['value'].update({'payment_rate': payment_rate})
756 for key in vals.keys():
757 res[key].update(vals[key])
760 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
763 journal_pool = self.pool.get('account.journal')
764 journal = journal_pool.browse(cr, uid, journal_id, context=context)
765 account_id = journal.default_credit_account_id or journal.default_debit_account_id
767 if account_id and account_id.tax_ids:
768 tax_id = account_id.tax_ids[0].id
770 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
771 vals['value'].update({'tax_id':tax_id,'amount': amount})
774 currency_id = journal.currency.id
775 vals['value'].update({'currency_id': currency_id})
776 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
777 for key in res.keys():
778 vals[key].update(res[key])
781 def proforma_voucher(self, cr, uid, ids, context=None):
782 self.action_move_line_create(cr, uid, ids, context=context)
783 return {'type': 'ir.actions.act_window_close'}
785 def action_cancel_draft(self, cr, uid, ids, context=None):
786 wf_service = netsvc.LocalService("workflow")
787 for voucher_id in ids:
788 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
789 self.write(cr, uid, ids, {'state':'draft'})
792 def cancel_voucher(self, cr, uid, ids, context=None):
793 reconcile_pool = self.pool.get('account.move.reconcile')
794 move_pool = self.pool.get('account.move')
796 for voucher in self.browse(cr, uid, ids, context=context):
798 for line in voucher.move_ids:
799 if line.reconcile_id:
800 recs += [line.reconcile_id.id]
801 if line.reconcile_partial_id:
802 recs += [line.reconcile_partial_id.id]
804 reconcile_pool.unlink(cr, uid, recs)
807 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
808 move_pool.unlink(cr, uid, [voucher.move_id.id])
813 self.write(cr, uid, ids, res)
816 def unlink(self, cr, uid, ids, context=None):
817 for t in self.read(cr, uid, ids, ['state'], context=context):
818 if t['state'] not in ('draft', 'cancel'):
819 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Voucher(s) which are already opened or paid !'))
820 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
822 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
826 res = {'account_id':False}
827 partner_pool = self.pool.get('res.partner')
828 journal_pool = self.pool.get('account.journal')
829 if pay_now == 'pay_later':
830 partner = partner_pool.browse(cr, uid, partner_id)
831 journal = journal_pool.browse(cr, uid, journal_id)
832 if journal.type in ('sale','sale_refund'):
833 account_id = partner.property_account_receivable.id
834 elif journal.type in ('purchase', 'purchase_refund','expense'):
835 account_id = partner.property_account_payable.id
837 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
838 res['account_id'] = account_id
841 def _sel_context(self, cr, uid, voucher_id,context=None):
843 Select the context to use accordingly if it needs to be multicurrency or not.
845 :param voucher_id: Id of the actual voucher
846 :return: The returned context will be the same as given in parameter if the voucher currency is the same
847 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
848 the date of the voucher.
851 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
852 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
853 if current_currency <> company_currency:
854 context_multi_currency = context.copy()
855 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
856 context_multi_currency.update({'date': voucher_brw.date})
857 return context_multi_currency
860 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
862 Return a dict to be use to create the first account move line of given voucher.
864 :param voucher_id: Id of voucher what we are creating account_move.
865 :param move_id: Id of account move where this line will be added.
866 :param company_currency: id of currency of the company to which the voucher belong
867 :param current_currency: id of currency of the voucher
868 :return: mapping between fieldname and value of account move line to create
871 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
873 # TODO: is there any other alternative then the voucher type ??
874 # ANSWER: We can have payment and receipt "In Advance".
875 # TODO: Make this logic available.
876 # -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
877 if voucher_brw.type in ('purchase', 'payment'):
878 credit = voucher_brw.paid_amount_in_company_currency
879 elif voucher_brw.type in ('sale', 'receipt'):
880 debit = voucher_brw.paid_amount_in_company_currency
881 if debit < 0: credit = -debit; debit = 0.0
882 if credit < 0: debit = -credit; credit = 0.0
883 sign = debit - credit < 0 and -1 or 1
884 #set the first line of the voucher
886 'name': voucher_brw.name or '/',
889 'account_id': voucher_brw.account_id.id,
891 'journal_id': voucher_brw.journal_id.id,
892 'period_id': voucher_brw.period_id.id,
893 'partner_id': voucher_brw.partner_id.id,
894 'currency_id': company_currency <> current_currency and current_currency or False,
895 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
896 'date': voucher_brw.date,
897 'date_maturity': voucher_brw.date_due
901 def account_move_get(self, cr, uid, voucher_id, context=None):
903 This method prepare the creation of the account move related to the given voucher.
905 :param voucher_id: Id of voucher for which we are creating account_move.
906 :return: mapping between fieldname and value of account move to create
909 seq_obj = self.pool.get('ir.sequence')
910 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
911 if voucher_brw.number:
912 name = voucher_brw.number
913 elif voucher_brw.journal_id.sequence_id:
914 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
916 raise osv.except_osv(_('Error !'),
917 _('Please define a sequence on the journal !'))
918 if not voucher_brw.reference:
919 ref = name.replace('/','')
921 ref = voucher_brw.reference
925 'journal_id': voucher_brw.journal_id.id,
926 'narration': voucher_brw.narration,
927 'date': voucher_brw.date,
929 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
933 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
935 Prepare the two lines in company currency due to currency rate difference.
937 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
939 :param move_id: Account move wher the move lines will be.
940 :param amount_residual: Amount to be posted.
941 :param company_currency: id of currency of the company to which the voucher belong
942 :param current_currency: id of currency of the voucher
943 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
944 :rtype: tuple of dict
946 if amount_residual > 0:
947 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
949 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! "))
951 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
953 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! "))
954 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
955 # the receivable/payable account may have a secondary currency, which render this field mandatory
956 account_currency_id = company_currency <> current_currency and current_currency or False
958 'journal_id': line.voucher_id.journal_id.id,
959 'period_id': line.voucher_id.period_id.id,
960 'name': _('change')+': '+(line.name or '/'),
961 'account_id': line.account_id.id,
963 'partner_id': line.voucher_id.partner_id.id,
964 'currency_id': account_currency_id,
965 'amount_currency': 0.0,
967 'credit': amount_residual > 0 and amount_residual or 0.0,
968 'debit': amount_residual < 0 and -amount_residual or 0.0,
969 'date': line.voucher_id.date,
971 move_line_counterpart = {
972 'journal_id': line.voucher_id.journal_id.id,
973 'period_id': line.voucher_id.period_id.id,
974 'name': _('change')+': '+(line.name or '/'),
975 'account_id': account_id.id,
977 'amount_currency': 0.0,
978 'partner_id': line.voucher_id.partner_id.id,
979 'currency_id': account_currency_id,
981 'debit': amount_residual > 0 and amount_residual or 0.0,
982 'credit': amount_residual < 0 and -amount_residual or 0.0,
983 'date': line.voucher_id.date,
985 return (move_line, move_line_counterpart)
987 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
989 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
990 payment_rate_currency_id is relevant) either the rate encoded in the system.
992 :param amount: float. The amount to convert
993 :param voucher: id of the voucher on which we want the conversion
994 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
995 field in order to select the good rate to use.
996 :return: the amount in the currency of the voucher's company
999 currency_obj = self.pool.get('res.currency')
1000 voucher = self.browse(cr, uid, voucher_id, context=context)
1002 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1003 # the rate specified on the voucher is for the company currency
1004 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1006 # the rate specified on the voucher is not relevant, we use all the rates in the system
1007 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1010 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1012 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1013 It returns Tuple with tot_line what is total of difference between debit and credit and
1014 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1016 :param voucher_id: Voucher id what we are working with
1017 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1018 :param move_id: Account move wher those lines will be joined.
1019 :param company_currency: id of currency of the company to which the voucher belong
1020 :param current_currency: id of currency of the voucher
1021 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1022 :rtype: tuple(float, list of int)
1026 move_line_obj = self.pool.get('account.move.line')
1027 currency_obj = self.pool.get('res.currency')
1028 tax_obj = self.pool.get('account.tax')
1029 tot_line = line_total
1032 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1033 ctx = context.copy()
1034 ctx.update({'date': voucher_brw.date})
1035 for line in voucher_brw.line_ids:
1036 #create one move line per voucher line where amount is not 0.0
1039 # convert the amount set on the voucher line into the currency of the voucher's company
1040 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1041 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1042 # currency rate difference
1043 if line.amount == line.amount_unreconciled:
1044 currency_rate_difference = line.move_line_id.amount_residual - amount
1046 currency_rate_difference = 0.0
1048 'journal_id': voucher_brw.journal_id.id,
1049 'period_id': voucher_brw.period_id.id,
1050 'name': line.name or '/',
1051 'account_id': line.account_id.id,
1053 'partner_id': voucher_brw.partner_id.id,
1054 '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,
1055 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1059 'date': voucher_brw.date
1063 if line.type == 'dr':
1068 if (line.type=='dr'):
1070 move_line['debit'] = amount
1073 move_line['credit'] = amount
1075 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1077 'account_tax_id': voucher_brw.tax_id.id,
1080 if move_line.get('account_tax_id', False):
1081 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1082 if not (tax_data.base_code_id and tax_data.tax_code_id):
1083 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))
1085 # compute the amount in foreign currency
1086 foreign_currency_diff = 0.0
1087 amount_currency = False
1088 if line.move_line_id:
1089 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1090 # We want to set it on the account move line as soon as the original line had a foreign currency
1091 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1092 # we compute the amount in that foreign currency.
1093 if line.move_line_id.currency_id.id == current_currency:
1094 # if the voucher and the voucher line share the same currency, there is no computation to do
1095 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1096 amount_currency = sign * (line.amount)
1097 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1098 # if the rate is specified on the voucher, we must use it
1099 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1100 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1102 # otherwise we use the rates of the system (giving the voucher date in the context)
1103 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1104 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1105 foreign_currency_diff = line.move_line_id.amount_residual_currency + amount_currency
1107 move_line['amount_currency'] = amount_currency
1108 voucher_line = move_line_obj.create(cr, uid, move_line)
1109 rec_ids = [voucher_line, line.move_line_id.id]
1111 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1112 # Change difference entry in company currency
1113 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1114 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1115 move_line_obj.create(cr, uid, exch_lines[1], context)
1116 rec_ids.append(new_id)
1118 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):
1119 # Change difference entry in voucher currency
1120 move_line_foreign_currency = {
1121 'journal_id': line.voucher_id.journal_id.id,
1122 'period_id': line.voucher_id.period_id.id,
1123 'name': _('change')+': '+(line.name or '/'),
1124 'account_id': line.account_id.id,
1126 'partner_id': line.voucher_id.partner_id.id,
1127 'currency_id': line.move_line_id.currency_id.id,
1128 'amount_currency': -1 * foreign_currency_diff,
1132 'date': line.voucher_id.date,
1134 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1135 rec_ids.append(new_id)
1137 if line.move_line_id.id:
1138 rec_lst_ids.append(rec_ids)
1140 return (tot_line, rec_lst_ids)
1142 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1144 Set a dict to be use to create the writeoff move line.
1146 :param voucher_id: Id of voucher what we are creating account_move.
1147 :param line_total: Amount remaining to be allocated on lines.
1148 :param move_id: Id of account move where this line will be added.
1149 :param name: Description of account move line.
1150 :param company_currency: id of currency of the company to which the voucher belong
1151 :param current_currency: id of currency of the voucher
1152 :return: mapping between fieldname and value of account move line to create
1155 currency_obj = self.pool.get('res.currency')
1158 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1159 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1161 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1165 if voucher_brw.payment_option == 'with_writeoff':
1166 account_id = voucher_brw.writeoff_acc_id.id
1167 write_off_name = voucher_brw.comment
1168 elif voucher_brw.type in ('sale', 'receipt'):
1169 account_id = voucher_brw.partner_id.property_account_receivable.id
1171 account_id = voucher_brw.partner_id.property_account_payable.id
1173 'name': write_off_name or name,
1174 'account_id': account_id,
1176 'partner_id': voucher_brw.partner_id.id,
1177 'date': voucher_brw.date,
1178 'credit': diff > 0 and diff or 0.0,
1179 'debit': diff < 0 and -diff or 0.0,
1180 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
1181 'currency_id': company_currency <> current_currency and current_currency or False,
1182 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1187 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1189 Get the currency of the actual company.
1191 :param voucher_id: Id of the voucher what i want to obtain company currency.
1192 :return: currency id of the company of the voucher
1195 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1197 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1199 Get the currency of the voucher.
1201 :param voucher_id: Id of the voucher what i want to obtain current currency.
1202 :return: currency id of the voucher
1205 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1206 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1208 def action_move_line_create(self, cr, uid, ids, context=None):
1210 Confirm the vouchers given in ids and create the journal entries for each of them
1214 move_pool = self.pool.get('account.move')
1215 move_line_pool = self.pool.get('account.move.line')
1216 for voucher in self.browse(cr, uid, ids, context=context):
1219 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1220 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1221 # we select the context to use accordingly if it's a multicurrency case or not
1222 context = self._sel_context(cr, uid, voucher.id, context)
1223 # But for the operations made by _convert_amount, we always need to give the date in the context
1224 ctx = context.copy()
1225 ctx.update({'date': voucher.date})
1226 # Create the account move record.
1227 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1228 # Get the name of the account_move just created
1229 name = move_pool.browse(cr, uid, move_id, context=context).name
1230 # Create the first line of the voucher
1231 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)
1232 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1233 line_total = move_line_brw.debit - move_line_brw.credit
1235 if voucher.type == 'sale':
1236 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1237 elif voucher.type == 'purchase':
1238 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1239 # Create one move line per voucher line where amount is not 0.0
1240 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1242 # Create the writeoff line if needed
1243 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1245 move_line_pool.create(cr, uid, ml_writeoff, context)
1246 # We post the voucher.
1247 self.write(cr, uid, [voucher.id], {
1252 if voucher.journal_id.entry_posted:
1253 move_pool.post(cr, uid, [move_id], context={})
1254 # We automatically reconcile the account move lines.
1255 for rec_ids in rec_list_ids:
1256 if len(rec_ids) >= 2:
1257 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)
1260 def copy(self, cr, uid, id, default={}, context=None):
1265 'line_cr_ids': False,
1266 'line_dr_ids': False,
1269 if 'date' not in default:
1270 default['date'] = time.strftime('%Y-%m-%d')
1271 return super(account_voucher, self).copy(cr, uid, id, default, context)
1275 class account_voucher_line(osv.osv):
1276 _name = 'account.voucher.line'
1277 _description = 'Voucher Lines'
1278 _order = "move_line_id"
1280 # If the payment is in the same currency than the invoice, we keep the same amount
1281 # Otherwise, we compute from company currency to payment currency
1282 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1283 currency_pool = self.pool.get('res.currency')
1285 for line in self.browse(cr, uid, ids, context=context):
1286 ctx = context.copy()
1287 ctx.update({'date': line.voucher_id.date})
1289 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1290 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1291 move_line = line.move_line_id or False
1294 res['amount_original'] = 0.0
1295 res['amount_unreconciled'] = 0.0
1296 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1297 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1298 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)
1299 elif move_line and move_line.credit > 0:
1300 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1301 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1303 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1304 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1306 rs_data[line.id] = res
1309 def _currency_id(self, cr, uid, ids, name, args, context=None):
1311 This function returns the currency id of a voucher line. It's either the currency of the
1312 associated move line (if any) or the currency of the voucher or the company currency.
1315 for line in self.browse(cr, uid, ids, context=context):
1316 move_line = line.move_line_id
1318 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1320 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1324 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1325 'name':fields.char('Description', size=256),
1326 'account_id':fields.many2one('account.account','Account', required=True),
1327 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1328 'untax_amount':fields.float('Untax Amount'),
1329 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1330 'reconcile': fields.boolean('Full Reconcile'),
1331 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1332 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1333 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1334 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1335 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1336 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1337 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1338 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1339 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1345 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1346 vals = { 'amount': 0.0}
1348 vals = { 'amount': amount_unreconciled}
1349 return {'value': vals}
1351 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1354 vals['reconcile'] = (amount == amount_unreconciled)
1355 return {'value': vals}
1357 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1359 Returns a dict that contains new values and context
1361 @param move_line_id: latest value from user input for field move_line_id
1362 @param args: other arguments
1363 @param context: context arguments, like lang, time zone
1365 @return: Returns a dict which contains new values, and context
1368 move_line_pool = self.pool.get('account.move.line')
1370 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1371 if move_line.credit:
1376 'account_id': move_line.account_id.id,
1378 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1384 def default_get(self, cr, user, fields_list, context=None):
1386 Returns default values for fields
1387 @param fields_list: list of fields, for which default values are required to be read
1388 @param context: context arguments, like lang, time zone
1390 @return: Returns a dict that contains default values for fields
1394 journal_id = context.get('journal_id', False)
1395 partner_id = context.get('partner_id', False)
1396 journal_pool = self.pool.get('account.journal')
1397 partner_pool = self.pool.get('res.partner')
1398 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1399 if (not journal_id) or ('account_id' not in fields_list):
1401 journal = journal_pool.browse(cr, user, journal_id, context=context)
1404 if journal.type in ('sale', 'sale_refund'):
1405 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1407 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1408 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1411 partner = partner_pool.browse(cr, user, partner_id, context=context)
1412 if context.get('type') == 'payment':
1414 account_id = partner.property_account_payable.id
1415 elif context.get('type') == 'receipt':
1416 account_id = partner.property_account_receivable.id
1419 'account_id':account_id,
1423 account_voucher_line()
1425 class account_bank_statement(osv.osv):
1426 _inherit = 'account.bank.statement'
1428 def button_cancel(self, cr, uid, ids, context=None):
1429 voucher_obj = self.pool.get('account.voucher')
1430 for st in self.browse(cr, uid, ids, context=context):
1432 for line in st.line_ids:
1434 voucher_ids.append(line.voucher_id.id)
1435 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1436 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1438 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1439 voucher_obj = self.pool.get('account.voucher')
1440 wf_service = netsvc.LocalService("workflow")
1441 move_line_obj = self.pool.get('account.move.line')
1442 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1443 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1444 if st_line.voucher_id:
1445 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1446 if st_line.voucher_id.state == 'cancel':
1447 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1448 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1450 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1451 bank_st_line_obj.write(cr, uid, [st_line_id], {
1452 'move_ids': [(4, v.move_id.id, False)]
1455 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1456 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1458 account_bank_statement()
1460 class account_bank_statement_line(osv.osv):
1461 _inherit = 'account.bank.statement.line'
1463 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1467 for line in self.browse(cursor, user, ids, context=context):
1469 res[line.id] = line.voucher_id.amount#
1474 def _check_amount(self, cr, uid, ids, context=None):
1475 for obj in self.browse(cr, uid, ids, context=context):
1477 diff = abs(obj.amount) - obj.voucher_id.amount
1478 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1483 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1487 'amount_reconciled': fields.function(_amount_reconciled,
1488 string='Amount reconciled', type='float'),
1489 'voucher_id': fields.many2one('account.voucher', 'Payment'),
1492 def unlink(self, cr, uid, ids, context=None):
1493 voucher_obj = self.pool.get('account.voucher')
1494 statement_line = self.browse(cr, uid, ids, context=context)
1496 for st_line in statement_line:
1497 if st_line.voucher_id:
1498 unlink_ids.append(st_line.voucher_id.id)
1499 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1500 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1502 account_bank_statement_line()
1504 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1506 for operation in operations:
1508 if not isinstance(operation, (list, tuple)):
1509 result = target_osv.read(cr, uid, operation, fields, context=context)
1510 elif operation[0] == 0:
1511 # may be necessary to check if all the fields are here and get the default values?
1512 result = operation[2]
1513 elif operation[0] == 1:
1514 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1515 if not result: result = {}
1516 result.update(operation[2])
1517 elif operation[0] == 4:
1518 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1520 results.append(result)
1524 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: