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 _
31 class account_voucher(osv.osv):
32 def _check_paid(self, cr, uid, ids, name, args, context=None):
34 for voucher in self.browse(cr, uid, ids, context=context):
36 for line in voucher.move_ids:
37 if (line.account_id.type, 'in', ('receivable', 'payable')) and not line.reconcile_id:
42 def _get_type(self, cr, uid, context=None):
45 return context.get('type', False)
47 def _get_period(self, cr, uid, context=None):
48 if context is None: context = {}
49 if context.get('period_id', False):
50 return context.get('period_id')
51 periods = self.pool.get('account.period').find(cr, uid)
52 return periods and periods[0] or False
54 def _get_journal(self, cr, uid, context=None):
55 if context is None: context = {}
56 journal_pool = self.pool.get('account.journal')
57 invoice_pool = self.pool.get('account.invoice')
58 if context.get('invoice_id', False):
59 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
60 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
61 return journal_id and journal_id[0] or False
62 if context.get('journal_id', False):
63 return context.get('journal_id')
64 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
65 return context.get('search_default_journal_id')
67 ttype = context.get('type', 'bank')
68 if ttype in ('payment', 'receipt'):
70 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
71 return res and res[0] or False
73 def _get_tax(self, cr, uid, context=None):
74 if context is None: context = {}
75 journal_pool = self.pool.get('account.journal')
76 journal_id = context.get('journal_id', False)
78 ttype = context.get('type', 'bank')
79 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
86 journal = journal_pool.browse(cr, uid, journal_id, context=context)
87 account_id = journal.default_credit_account_id or journal.default_debit_account_id
88 if account_id and account_id.tax_ids:
89 tax_id = account_id.tax_ids[0].id
93 def _get_payment_rate_currency(self, cr, uid, context=None):
95 Return the default value for field payment_rate_currency_id: the currency of the journal
96 if there is one, otherwise the currency of the user's company
98 if context is None: context = {}
99 journal_pool = self.pool.get('account.journal')
100 journal_id = context.get('journal_id', False)
102 journal = journal_pool.browse(cr, uid, journal_id, context=context)
104 return journal.currency.id
105 #no journal given in the context, use company currency as default
106 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
108 def _get_currency(self, cr, uid, context=None):
109 if context is None: context = {}
110 journal_pool = self.pool.get('account.journal')
111 journal_id = context.get('journal_id', False)
113 journal = journal_pool.browse(cr, uid, journal_id, context=context)
115 return journal.currency.id
118 def _get_partner(self, cr, uid, context=None):
119 if context is None: context = {}
120 return context.get('partner_id', False)
122 def _get_reference(self, cr, uid, context=None):
123 if context is None: context = {}
124 return context.get('reference', False)
126 def _get_narration(self, cr, uid, context=None):
127 if context is None: context = {}
128 return context.get('narration', False)
130 def _get_amount(self, cr, uid, context=None):
133 return context.get('amount', 0.0)
135 def name_get(self, cr, uid, ids, context=None):
138 if context is None: context = {}
139 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
141 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
142 mod_obj = self.pool.get('ir.model.data')
143 if context is None: context = {}
144 if not view_id and context.get('invoice_type', False):
145 if context.get('invoice_type', False) in ('out_invoice', 'out_refund'):
146 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
148 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
149 result = result and result[1] or False
151 if not view_id and view_type == 'form' and context.get('line_type', False):
152 if context.get('line_type', False) == 'customer':
153 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
155 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
156 result = result and result[1] or False
159 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
160 doc = etree.XML(res['arch'])
162 if context.get('type', 'sale') in ('purchase', 'payment'):
163 nodes = doc.xpath("//field[@name='partner_id']")
165 node.set('domain', "[('supplier', '=', True)]")
166 res['arch'] = etree.tostring(doc)
169 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount):
171 for l in line_dr_ids:
173 for l in line_cr_ids:
174 credit += l['amount']
175 return abs(amount - abs(credit - debit))
177 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, context=None):
178 context = context or {}
179 if not line_dr_ids and not line_cr_ids:
181 line_osv = self.pool.get("account.voucher.line")
182 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
183 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
185 #loop into the lines to see if there is an amount allocated on a voucher line with a currency different than the voucher currency
186 is_multi_currency = False
187 for voucher_line in line_dr_ids+line_cr_ids:
188 if voucher_line.get('currency_id',False) != voucher_currency:
189 is_multi_currency = True
191 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount), 'is_multi_currency': is_multi_currency}}
193 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
194 if not ids: return {}
195 currency_obj = self.pool.get('res.currency')
198 for voucher in self.browse(cr, uid, ids, context=context):
199 for l in voucher.line_dr_ids:
201 for l in voucher.line_cr_ids:
203 currency = voucher.currency_id or voucher.company_id.currency_id
204 res[voucher.id] = currency_obj.round(cr, uid, currency, abs(voucher.amount - abs(credit - debit)))
207 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
208 if not ids: return {}
210 voucher_rate = company_currency_rate = 1.0
211 for voucher in self.browse(cr, uid, ids, context=context):
212 if voucher.currency_id:
214 ctx.update({'date': voucher.date})
215 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
216 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
217 company_currency_rate = voucher.payment_rate
219 company_currency_rate = voucher.company_id.currency_id.rate
220 res[voucher.id] = voucher.amount / voucher_rate * company_currency_rate
223 _name = 'account.voucher'
224 _description = 'Accounting Voucher'
225 _order = "date desc, id desc"
226 # _rec_name = 'number'
228 'type':fields.selection([
230 ('purchase','Purchase'),
231 ('payment','Payment'),
232 ('receipt','Receipt'),
233 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
234 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
235 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
236 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
237 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
238 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
239 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
240 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
241 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
242 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
243 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
244 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
245 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
246 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
247 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
248 'state':fields.selection(
250 ('proforma','Pro-forma'),
252 ('cancel','Cancelled')
253 ], 'State', readonly=True, size=32,
254 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Voucher. \
255 \n* The \'Pro-forma\' when voucher is in Pro-forma state,voucher does not have an voucher number. \
256 \n* The \'Posted\' state is used when user create voucher,a voucher number is generated and voucher entries are created in account \
257 \n* The \'Cancelled\' state is used when user cancel voucher.'),
258 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
259 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
260 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
261 'number': fields.char('Number', size=32, readonly=True,),
262 'move_id':fields.many2one('account.move', 'Account Entry'),
263 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
264 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
265 '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'),
266 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
267 'pay_now':fields.selection([
268 ('pay_now','Pay Directly'),
269 ('pay_later','Pay Later or Group Funds'),
270 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
271 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
272 'pre_line':fields.boolean('Previous Payments ?', required=False),
273 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
274 'payment_option':fields.selection([
275 ('without_writeoff', 'Keep Open'),
276 ('with_writeoff', 'Reconcile Payment Balance'),
277 ], '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)"),
278 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
279 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
280 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
281 '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."),
282 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
283 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
284 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
285 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
286 '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'),
289 'period_id': _get_period,
290 'partner_id': _get_partner,
291 'journal_id':_get_journal,
292 'currency_id': _get_currency,
293 'reference': _get_reference,
294 'narration':_get_narration,
295 'amount': _get_amount,
298 'pay_now': 'pay_later',
300 'date': lambda *a: time.strftime('%Y-%m-%d'),
301 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
303 'payment_option': 'without_writeoff',
304 'comment': _('Write-Off'),
306 'payment_rate_currency_id': _get_payment_rate_currency,
309 def compute_tax(self, cr, uid, ids, context=None):
310 tax_pool = self.pool.get('account.tax')
311 partner_pool = self.pool.get('res.partner')
312 position_pool = self.pool.get('account.fiscal.position')
313 voucher_line_pool = self.pool.get('account.voucher.line')
314 voucher_pool = self.pool.get('account.voucher')
315 if context is None: context = {}
317 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
319 for line in voucher.line_ids:
320 voucher_amount += line.untax_amount or line.amount
321 line.amount = line.untax_amount or line.amount
322 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
324 if not voucher.tax_id:
325 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
328 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
329 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
330 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
331 tax = tax_pool.browse(cr, uid, taxes, context=context)
333 total = voucher_amount
336 if not tax[0].price_include:
337 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_amount, 1).get('taxes', []):
338 total_tax += tax_line.get('amount', 0.0)
341 for line in voucher.line_ids:
345 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
346 line_tax += tax_line.get('amount', 0.0)
347 line_total += tax_line.get('price_unit')
348 total_tax += line_tax
349 untax_amount = line.untax_amount or line.amount
350 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
352 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
355 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
356 context = context or {}
357 tax_pool = self.pool.get('account.tax')
358 partner_pool = self.pool.get('res.partner')
359 position_pool = self.pool.get('account.fiscal.position')
360 line_pool = self.pool.get('account.voucher.line')
367 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
371 for line in line_ids:
373 line_amount = line.get('amount',0.0)
374 voucher_total += line_amount
376 total = voucher_total
379 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
381 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
382 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
383 tax = tax_pool.browse(cr, uid, taxes, context=context)
385 if not tax[0].price_include:
386 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_total, 1).get('taxes', []):
387 total_tax += tax_line.get('amount')
391 'amount':total or voucher_total,
392 'tax_amount':total_tax
398 def onchange_term_id(self, cr, uid, ids, term_id, amount):
399 term_pool = self.pool.get('account.payment.term')
402 default = {'date_due':False}
403 if term_id and amount:
404 terms = term_pool.compute(cr, uid, term_id, amount)
406 due_date = terms[-1][0]
410 return {'value':default}
412 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):
414 Returns a dict that contains new values and context
416 @param partner_id: latest value from user input for field partner_id
417 @param args: other arguments
418 @param context: context arguments, like lang, time zone
420 @return: Returns a dict which contains new values, and context
426 if not partner_id or not journal_id:
429 partner_pool = self.pool.get('res.partner')
430 journal_pool = self.pool.get('account.journal')
432 journal = journal_pool.browse(cr, uid, journal_id, context=context)
433 partner = partner_pool.browse(cr, uid, partner_id, context=context)
436 if journal.type in ('sale','sale_refund'):
437 account_id = partner.property_account_receivable.id
439 elif journal.type in ('purchase', 'purchase_refund','expense'):
440 account_id = partner.property_account_payable.id
443 if not journal.default_credit_account_id or not journal.default_debit_account_id:
444 raise osv.except_osv(_('Error !'), _('Please define default credit/debit account on the %s !') % (journal.name))
445 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
448 default['value']['account_id'] = account_id
449 default['value']['type'] = ttype or tr_type
451 vals = self.onchange_journal(cr, uid, ids, journal_id, line_ids, tax_id, partner_id, company_id, context)
452 default['value'].update(vals.get('value'))
456 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
457 res = {'value': {'paid_amount_in_company_currency': amount}}
458 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
459 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
460 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
461 if company_currency.id == payment_rate_currency_id:
464 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
465 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
468 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):
471 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
473 ctx.update({'date': date})
474 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
475 for key in vals.keys():
476 res[key].update(vals[key])
479 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
481 Returns a dict that contains new values and context
483 @param partner_id: latest value from user input for field partner_id
484 @param args: other arguments
485 @param context: context arguments, like lang, time zone
487 @return: Returns a dict which contains new values, and context
491 context_multi_currency = context.copy()
493 context_multi_currency.update({'date': date})
495 currency_pool = self.pool.get('res.currency')
496 move_line_pool = self.pool.get('account.move.line')
497 partner_pool = self.pool.get('res.partner')
498 journal_pool = self.pool.get('account.journal')
499 line_pool = self.pool.get('account.voucher.line')
503 'value': {'line_ids': [] ,'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
507 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
509 line_pool.unlink(cr, uid, line_ids)
511 if not partner_id or not journal_id:
514 journal = journal_pool.browse(cr, uid, journal_id, context=context)
515 partner = partner_pool.browse(cr, uid, partner_id, context=context)
516 currency_id = currency_id or journal.company_id.currency_id.id
518 if journal.type in ('sale','sale_refund'):
519 account_id = partner.property_account_receivable.id
520 elif journal.type in ('purchase', 'purchase_refund','expense'):
521 account_id = partner.property_account_payable.id
523 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
525 default['value']['account_id'] = account_id
527 if journal.type not in ('cash', 'bank'):
532 account_type = 'receivable'
533 if ttype == 'payment':
534 account_type = 'payable'
535 total_debit = price or 0.0
537 total_credit = price or 0.0
538 account_type = 'receivable'
540 if not context.get('move_line_ids', False):
541 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
543 ids = context['move_line_ids']
544 invoice_id = context.get('invoice_id', False)
545 company_currency = journal.company_id.currency_id.id
546 move_line_found = False
548 #order the lines by most old first
550 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
552 for line in account_move_lines:
553 if line.credit and line.reconcile_partial_id and ttype == 'receipt':
555 if line.debit and line.reconcile_partial_id and ttype == 'payment':
558 if line.invoice.id == invoice_id:
559 #if the invoice linked to the voucher line is equal to the invoice_id in context
560 #then we assign the amount on that line, whatever the other voucher lines
561 move_line_found = line.id
563 elif currency_id == company_currency:
564 #otherwise treatments is the same but with other field names
565 if line.amount_residual == price:
566 #if the amount residual is equal the amount voucher, we assign it to that voucher
567 #line, whatever the other voucher lines
568 move_line_found = line.id
570 #otherwise we will split the voucher amount on each line (by most old first)
571 total_credit += line.credit or 0.0
572 total_debit += line.debit or 0.0
573 elif currency_id == line.currency_id.id:
574 if line.amount_residual_currency == price:
575 move_line_found = line.id
577 total_credit += line.credit and line.amount_currency or 0.0
578 total_debit += line.debit and line.amount_currency or 0.0
580 #voucher line creation
581 for line in account_move_lines:
582 if line.credit and line.reconcile_partial_id and ttype == 'receipt':
584 if line.debit and line.reconcile_partial_id and ttype == 'payment':
586 if line.currency_id and currency_id==line.currency_id.id:
587 amount_original = abs(line.amount_currency)
588 amount_unreconciled = abs(line.amount_residual_currency)
590 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
591 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
592 line_currency_id = line.currency_id and line.currency_id.id or company_currency
594 'name':line.move_id.name,
595 'type': line.credit and 'dr' or 'cr',
596 'move_line_id':line.id,
597 'account_id':line.account_id.id,
598 'amount_original': amount_original,
599 'amount': (move_line_found == line.id) and min(price, amount_unreconciled) or 0.0,
600 'date_original':line.date,
601 'date_due':line.date_maturity,
602 'amount_unreconciled': amount_unreconciled,
603 'currency_id': line_currency_id,
606 #split voucher amount by most old first, but only for lines in the same currency
607 if not move_line_found:
608 if currency_id == line_currency_id:
610 amount = min(amount_unreconciled, abs(total_debit))
611 rs['amount'] = amount
612 total_debit -= amount
614 amount = min(amount_unreconciled, abs(total_credit))
615 rs['amount'] = amount
616 total_credit -= amount
618 if rs['amount_unreconciled'] == rs['amount']:
619 rs['reconcile'] = True
621 if rs['type'] == 'cr':
622 default['value']['line_cr_ids'].append(rs)
624 default['value']['line_dr_ids'].append(rs)
626 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
627 default['value']['pre_line'] = 1
628 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
629 default['value']['pre_line'] = 1
630 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price)
633 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
637 #set the default payment rate of the voucher and compute the paid amount in company currency
638 if currency_id and currency_id == payment_rate_currency_id:
640 ctx.update({'date': date})
641 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
642 for key in vals.keys():
643 res[key].update(vals[key])
646 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
648 @param date: latest value from user input for field date
649 @param args: other arguments
650 @param context: context arguments, like lang, time zone
651 @return: Returns a dict which contains new values, and context
656 #set the period of the voucher
657 period_pool = self.pool.get('account.period')
659 ctx.update({'company_id': company_id})
660 pids = period_pool.find(cr, uid, date, context=ctx)
662 res['value'].update({'period_id':pids[0]})
663 if payment_rate_currency_id:
664 ctx.update({'date': date})
666 if payment_rate_currency_id != currency_id:
667 payment_rate = self.pool.get('res.currency').browse(cr, uid, payment_rate_currency_id, context=ctx).rate
668 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
669 vals['value'].update({'payment_rate': payment_rate})
670 for key in vals.keys():
671 res[key].update(vals[key])
674 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
677 journal_pool = self.pool.get('account.journal')
678 journal = journal_pool.browse(cr, uid, journal_id, context=context)
679 account_id = journal.default_credit_account_id or journal.default_debit_account_id
681 if account_id and account_id.tax_ids:
682 tax_id = account_id.tax_ids[0].id
684 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
685 vals['value'].update({'tax_id':tax_id,'amount': amount})
688 currency_id = journal.currency.id
690 ctx.update({'date': date})
691 #on change of the journal, we need to TODO coment me
694 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, currency_id, company_id, context=ctx)
695 for key in res.keys():
696 vals[key].update(res[key])
697 vals['value'].update({'payment_rate': payment_rate})
698 vals['value'].update({'currency_id': currency_id})
699 vals['value'].update({'payment_rate_currency_id': currency_id})
700 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
701 for key in res.keys():
702 vals[key].update(res[key])
705 def proforma_voucher(self, cr, uid, ids, context=None):
706 self.action_move_line_create(cr, uid, ids, context=context)
709 def action_cancel_draft(self, cr, uid, ids, context=None):
710 wf_service = netsvc.LocalService("workflow")
711 for voucher_id in ids:
712 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
713 self.write(cr, uid, ids, {'state':'draft'})
716 def cancel_voucher(self, cr, uid, ids, context=None):
717 reconcile_pool = self.pool.get('account.move.reconcile')
718 move_pool = self.pool.get('account.move')
720 for voucher in self.browse(cr, uid, ids, context=context):
722 for line in voucher.move_ids:
723 if line.reconcile_id:
724 recs += [line.reconcile_id.id]
725 if line.reconcile_partial_id:
726 recs += [line.reconcile_partial_id.id]
728 reconcile_pool.unlink(cr, uid, recs)
731 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
732 move_pool.unlink(cr, uid, [voucher.move_id.id])
737 self.write(cr, uid, ids, res)
740 def unlink(self, cr, uid, ids, context=None):
741 for t in self.read(cr, uid, ids, ['state'], context=context):
742 if t['state'] not in ('draft', 'cancel'):
743 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Voucher(s) which are already opened or paid !'))
744 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
746 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
750 res = {'account_id':False}
751 partner_pool = self.pool.get('res.partner')
752 journal_pool = self.pool.get('account.journal')
753 if pay_now == 'pay_later':
754 partner = partner_pool.browse(cr, uid, partner_id)
755 journal = journal_pool.browse(cr, uid, journal_id)
756 if journal.type in ('sale','sale_refund'):
757 account_id = partner.property_account_receivable.id
758 elif journal.type in ('purchase', 'purchase_refund','expense'):
759 account_id = partner.property_account_payable.id
761 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
762 res['account_id'] = account_id
765 def _sel_context(self, cr, uid, voucher_id,context=None):
767 Select the context to use accordingly if it needs to be multicurrency or not.
769 :param voucher_id: Id of the actual voucher
770 :return: The returned context will be the same as given in parameter if the voucher currency is the same
771 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
772 the date of the voucher.
775 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
776 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
777 if current_currency <> company_currency:
778 context_multi_currency = context.copy()
779 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
780 context_multi_currency.update({'date': voucher_brw.date})
781 return context_multi_currency
784 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
786 Return a dict to be use to create the first account move line of given voucher.
788 :param voucher_id: Id of voucher what we are creating account_move.
789 :param move_id: Id of account move where this line will be added.
790 :param company_currency: id of currency of the company to which the voucher belong
791 :param current_currency: id of currency of the voucher
792 :return: mapping between fieldname and value of account move line to create
795 move_line_obj = self.pool.get('account.move.line')
796 currency_obj = self.pool.get('res.currency')
797 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
799 # TODO: is there any other alternative then the voucher type ??
800 # ANSWER: We can have payment and receipt "In Advance".
801 # TODO: Make this logic available.
802 # -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
803 if voucher_brw.type in ('purchase', 'payment'):
804 credit = voucher_brw.paid_amount_in_company_currency
805 elif voucher_brw.type in ('sale', 'receipt'):
806 debit = voucher_brw.paid_amount_in_company_currency
807 if debit < 0: credit = -debit; debit = 0.0
808 if credit < 0: debit = -credit; credit = 0.0
809 sign = debit - credit < 0 and -1 or 1
810 #set the first line of the voucher
812 'name': voucher_brw.name or '/',
815 'account_id': voucher_brw.account_id.id,
817 'journal_id': voucher_brw.journal_id.id,
818 'period_id': voucher_brw.period_id.id,
819 'partner_id': voucher_brw.partner_id.id,
820 'currency_id': company_currency <> current_currency and current_currency or False,
821 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
822 'date': voucher_brw.date,
823 'date_maturity': voucher_brw.date_due
827 def account_move_get(self, cr, uid, voucher_id, context=None):
829 This method prepare the creation of the account move related to the given voucher.
831 :param voucher_id: Id of voucher for which we are creating account_move.
832 :return: mapping between fieldname and value of account move to create
835 move_obj = self.pool.get('account.move')
836 seq_obj = self.pool.get('ir.sequence')
837 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
838 if voucher_brw.number:
839 name = voucher_brw.number
840 elif voucher_brw.journal_id.sequence_id:
841 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id)
843 raise osv.except_osv(_('Error !'),
844 _('Please define a sequence on the journal !'))
845 if not voucher_brw.reference:
846 ref = name.replace('/','')
848 ref = voucher_brw.reference
852 'journal_id': voucher_brw.journal_id.id,
853 'narration': voucher_brw.narration,
854 'date': voucher_brw.date,
856 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
860 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
862 Prepare the two lines due to currency rate difference.
864 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting entries
865 :param move_id: Account move wher the move lines will be.
866 :param amount_residual: Amount to be posted.
867 :param company_currency: id of currency of the company to which the voucher belong
868 :param current_currency: id of currency of the voucher
869 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
870 :rtype: tuple of dict
872 if amount_residual > 0:
873 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
875 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! "))
877 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
879 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! "))
882 'journal_id': line.voucher_id.journal_id.id,
883 'period_id': line.voucher_id.period_id.id,
884 'name': _('change')+': '+(line.name or '/'),
885 'account_id': line.account_id.id,
887 'partner_id': line.voucher_id.partner_id.id,
888 'currency_id': company_currency <> current_currency and current_currency or False,
889 'amount_currency': 0.0,
891 'credit': amount_residual > 0 and amount_residual or 0.0,
892 'debit': amount_residual < 0 and -amount_residual or 0.0,
893 'date': line.voucher_id.date,
895 move_line_counterpart = {
896 'journal_id': line.voucher_id.journal_id.id,
897 'period_id': line.voucher_id.period_id.id,
898 'name': _('change')+': '+(line.name or '/'),
899 'account_id': account_id.id,
901 'amount_currency': 0.0,
902 'partner_id': line.voucher_id.partner_id.id,
903 'currency_id': company_currency <> current_currency and current_currency or False,
905 'debit': amount_residual > 0 and amount_residual or 0.0,
906 'credit': amount_residual < 0 and -amount_residual or 0.0,
907 'date': line.voucher_id.date,
909 return (move_line, move_line_counterpart)
911 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
913 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
914 payment_rate_currency_id is relevant) either the rate encoded in the system.
916 :param amount: float. The amount to convert
917 :param voucher: id of the voucher on which we want the conversion
918 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
919 field in order to select the good rate to use.
920 :return: the amount in the currency of the voucher's company
923 currency_obj = self.pool.get('res.currency')
924 voucher = self.browse(cr, uid, voucher_id, context=context)
926 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
927 # the rate specified on the voucher is for the company currency
928 rate_between_voucher_and_base = voucher.currency_id.rate or 1.0
929 rate_between_base_and_company = voucher.payment_rate or 1.0
930 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount / rate_between_voucher_and_base * rate_between_base_and_company))
932 # the rate specified on the voucher is not relevant, we use all the rates in the system
933 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
936 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
938 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
939 It returns Tuple with tot_line what is total of difference between debit and credit and
940 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
942 :param voucher_id: Voucher id what we are working with
943 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
944 :param move_id: Account move wher those lines will be joined.
945 :param company_currency: id of currency of the company to which the voucher belong
946 :param current_currency: id of currency of the voucher
947 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
948 :rtype: tuple(float, list of int)
952 move_line_obj = self.pool.get('account.move.line')
953 currency_obj = self.pool.get('res.currency')
954 tot_line = line_total
957 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
959 ctx.update({'date': voucher_brw.date})
960 for line in voucher_brw.line_ids:
961 #create one move line per voucher line where amount is not 0.0
964 # convert the amount set on the voucher line into the currency of the voucher's company
965 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
966 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
967 # currency rate difference
968 if line.amount == line.amount_unreconciled:
969 currency_rate_difference = line.move_line_id.amount_residual - amount
971 currency_rate_difference = 0.0
973 'journal_id': voucher_brw.journal_id.id,
974 'period_id': voucher_brw.period_id.id,
975 'name': line.name or '/',
976 'account_id': line.account_id.id,
978 'partner_id': voucher_brw.partner_id.id,
979 '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,
980 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
984 'date': voucher_brw.date
988 if line.type == 'dr':
993 if (line.type=='dr'):
995 move_line['debit'] = amount
998 move_line['credit'] = amount
1000 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1002 'account_tax_id': voucher_brw.tax_id.id,
1005 if move_line.get('account_tax_id', False):
1006 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1007 if not (tax_data.base_code_id and tax_data.tax_code_id):
1008 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))
1010 # compute the amount in foreign currency
1011 amount_currency = False
1012 if line.move_line_id:
1013 # We want to set it on the account move line as soon as the original line had a foreign currency
1014 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1015 # we compute the amount in that foreign currency.
1016 if line.move_line_id.currency_id.id == current_currency:
1017 # if the voucher and the voucher line share the same currency, there is no computation to do
1018 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1019 amount_currency = sign * (line.amount)
1020 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1021 # if the rate is specified on the voucher, we must use it
1022 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate
1024 # otherwise we use the rates of the system (giving the voucher date in the context)
1025 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1027 move_line['amount_currency'] = amount_currency
1028 voucher_line = move_line_obj.create(cr, uid, move_line)
1029 rec_ids = [voucher_line, line.move_line_id.id]
1031 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1032 # Change difference entry
1033 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1034 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1035 move_line_obj.create(cr, uid, exch_lines[1], context)
1036 rec_ids.append(new_id)
1038 if line.move_line_id.id:
1039 rec_lst_ids.append(rec_ids)
1041 return (tot_line, rec_lst_ids)
1043 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1045 Set a dict to be use to create the writeoff move line.
1047 :param voucher_id: Id of voucher what we are creating account_move.
1048 :param line_total: Amount remaining to be allocated on lines.
1049 :param move_id: Id of account move where this line will be added.
1050 :param name: Description of account move line.
1051 :param company_currency: id of currency of the company to which the voucher belong
1052 :param current_currency: id of currency of the voucher
1053 :return: mapping between fieldname and value of account move line to create
1056 move_line_obj = self.pool.get('account.move.line')
1057 currency_obj = self.pool.get('res.currency')
1060 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1061 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1063 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1067 if voucher_brw.payment_option == 'with_writeoff':
1068 account_id = voucher_brw.writeoff_acc_id.id
1069 write_off_name = voucher_brw.comment
1070 elif voucher_brw.type in ('sale', 'receipt'):
1071 account_id = voucher_brw.partner_id.property_account_receivable.id
1073 account_id = voucher_brw.partner_id.property_account_payable.id
1075 'name': write_off_name or name,
1076 'account_id': account_id,
1078 'partner_id': voucher_brw.partner_id.id,
1079 'date': voucher_brw.date,
1080 'credit': diff > 0 and diff or 0.0,
1081 'debit': diff < 0 and -diff or 0.0,
1082 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
1083 'currency_id': company_currency <> current_currency and current_currency or False,
1088 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1090 Get the currency of the actual company.
1092 :param voucher_id: Id of the voucher what i want to obtain company currency.
1093 :return: currency id of the company of the voucher
1096 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1098 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1100 Get the currency of the voucher.
1102 :param voucher_id: Id of the voucher what i want to obtain current currency.
1103 :return: currency id of the voucher
1106 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1107 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1109 def action_move_line_create(self, cr, uid, ids, context=None):
1111 Confirm the vouchers given in ids and create the journal entries for each of them
1115 move_pool = self.pool.get('account.move')
1116 move_line_pool = self.pool.get('account.move.line')
1117 currency_pool = self.pool.get('res.currency')
1118 tax_obj = self.pool.get('account.tax')
1119 seq_obj = self.pool.get('ir.sequence')
1120 for voucher in self.browse(cr, uid, ids, context=context):
1123 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1124 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1125 # we select the context to use accordingly if it's a multicurrency case or not
1126 context = self._sel_context(cr, uid, voucher.id, context)
1127 # But for the operations made by _convert_amount, we always need to give the date in the context
1128 ctx = context.copy()
1129 ctx.update({'date': voucher.date})
1130 # Create the account move record.
1131 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1132 # Get the name of the account_move just created
1133 name = move_pool.browse(cr, uid, move_id, context=context).name
1134 # Create the first line of the voucher
1135 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)
1136 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1137 line_total = move_line_brw.debit - move_line_brw.credit
1139 if voucher.type == 'sale':
1140 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1141 elif voucher.type == 'purchase':
1142 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1143 # Create one move line per voucher line where amount is not 0.0
1144 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1146 # Create the writeoff line if needed
1147 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1149 ml_writeoff_id = move_line_pool.create(cr, uid, ml_writeoff, context)
1150 # We post the voucher.
1151 self.write(cr, uid, [voucher.id], {
1156 if voucher.journal_id.entry_posted:
1157 move_pool.post(cr, uid, [move_id], context={})
1158 # We automatically reconcile the account move lines.
1159 for rec_ids in rec_list_ids:
1160 if len(rec_ids) >= 2:
1161 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)
1164 def copy(self, cr, uid, id, default={}, context=None):
1169 'line_cr_ids': False,
1170 'line_dr_ids': False,
1173 if 'date' not in default:
1174 default['date'] = time.strftime('%Y-%m-%d')
1175 return super(account_voucher, self).copy(cr, uid, id, default, context)
1179 class account_voucher_line(osv.osv):
1180 _name = 'account.voucher.line'
1181 _description = 'Voucher Lines'
1182 _order = "move_line_id"
1184 # If the payment is in the same currency than the invoice, we keep the same amount
1185 # Otherwise, we compute from company currency to payment currency
1186 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1187 currency_pool = self.pool.get('res.currency')
1189 for line in self.browse(cr, uid, ids, context=context):
1190 ctx = context.copy()
1191 ctx.update({'date': line.voucher_id.date})
1193 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1194 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1195 move_line = line.move_line_id or False
1198 res['amount_original'] = 0.0
1199 res['amount_unreconciled'] = 0.0
1200 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1201 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1202 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)
1203 elif move_line and move_line.credit > 0:
1204 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1205 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1207 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1208 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1210 rs_data[line.id] = res
1213 def _currency_id(self, cr, uid, ids, name, args, context=None):
1215 This function returns the currency id of a voucher line. It's either the currency of the
1216 associated move line (if any) or the currency of the voucher or the company currency.
1219 for line in self.browse(cr, uid, ids, context=context):
1220 move_line = line.move_line_id
1222 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1224 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1228 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1229 'name':fields.char('Description', size=256),
1230 'account_id':fields.many2one('account.account','Account', required=True),
1231 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1232 'untax_amount':fields.float('Untax Amount'),
1233 'amount':fields.float('Allocation', digits_compute=dp.get_precision('Account')),
1234 'reconcile': fields.boolean('Full Reconcile'),
1235 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1236 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1237 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1238 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1239 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1240 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True),
1241 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True),
1242 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1243 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1249 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1250 vals = { 'amount': 0.0}
1252 vals = { 'amount': amount_unreconciled}
1253 return {'value': vals}
1255 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1258 vals['reconcile'] = (amount == amount_unreconciled)
1259 return {'value': vals}
1261 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1263 Returns a dict that contains new values and context
1265 @param move_line_id: latest value from user input for field move_line_id
1266 @param args: other arguments
1267 @param context: context arguments, like lang, time zone
1269 @return: Returns a dict which contains new values, and context
1272 move_line_pool = self.pool.get('account.move.line')
1274 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1275 if move_line.credit:
1280 'account_id': move_line.account_id.id,
1282 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1288 def default_get(self, cr, user, fields_list, context=None):
1290 Returns default values for fields
1291 @param fields_list: list of fields, for which default values are required to be read
1292 @param context: context arguments, like lang, time zone
1294 @return: Returns a dict that contains default values for fields
1298 journal_id = context.get('journal_id', False)
1299 partner_id = context.get('partner_id', False)
1300 journal_pool = self.pool.get('account.journal')
1301 partner_pool = self.pool.get('res.partner')
1302 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1303 if (not journal_id) or ('account_id' not in fields_list):
1305 journal = journal_pool.browse(cr, user, journal_id, context=context)
1308 if journal.type in ('sale', 'sale_refund'):
1309 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1311 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1312 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1315 partner = partner_pool.browse(cr, user, partner_id, context=context)
1316 if context.get('type') == 'payment':
1318 account_id = partner.property_account_payable.id
1319 elif context.get('type') == 'receipt':
1320 account_id = partner.property_account_receivable.id
1323 'account_id':account_id,
1327 account_voucher_line()
1329 class account_bank_statement(osv.osv):
1330 _inherit = 'account.bank.statement'
1332 def button_cancel(self, cr, uid, ids, context=None):
1333 voucher_obj = self.pool.get('account.voucher')
1334 for st in self.browse(cr, uid, ids, context=context):
1336 for line in st.line_ids:
1338 voucher_ids.append(line.voucher_id.id)
1339 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1340 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1342 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1343 voucher_obj = self.pool.get('account.voucher')
1344 wf_service = netsvc.LocalService("workflow")
1345 move_line_obj = self.pool.get('account.move.line')
1346 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1347 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1348 if st_line.voucher_id:
1349 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1350 if st_line.voucher_id.state == 'cancel':
1351 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1352 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1354 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1355 bank_st_line_obj.write(cr, uid, [st_line_id], {
1356 'move_ids': [(4, v.move_id.id, False)]
1359 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1360 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1362 account_bank_statement()
1364 class account_bank_statement_line(osv.osv):
1365 _inherit = 'account.bank.statement.line'
1367 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1371 for line in self.browse(cursor, user, ids, context=context):
1373 res[line.id] = line.voucher_id.amount#
1378 def _check_amount(self, cr, uid, ids, context=None):
1379 for obj in self.browse(cr, uid, ids, context=context):
1381 diff = abs(obj.amount) - obj.voucher_id.amount
1382 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1387 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1391 'amount_reconciled': fields.function(_amount_reconciled,
1392 string='Amount reconciled', type='float'),
1393 'voucher_id': fields.many2one('account.voucher', 'Payment'),
1396 def unlink(self, cr, uid, ids, context=None):
1397 voucher_obj = self.pool.get('account.voucher')
1398 statement_line = self.browse(cr, uid, ids, context=context)
1400 for st_line in statement_line:
1401 if st_line.voucher_id:
1402 unlink_ids.append(st_line.voucher_id.id)
1403 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1404 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1406 account_bank_statement_line()
1408 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1410 for operation in operations:
1412 if not isinstance(operation, (list, tuple)):
1413 result = target_osv.read(cr, uid, operation, fields, context=context)
1414 elif operation[0] == 0:
1415 # may be necessary to check if all the fields are here and get the default values?
1416 result = operation[2]
1417 elif operation[0] == 1:
1418 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1419 if not result: result = {}
1420 result.update(operation[2])
1421 elif operation[0] == 4:
1422 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1424 results.append(result)
1427 class res_company(osv.osv):
1428 _inherit = "res.company"
1430 'income_currency_exchange_account_id': fields.many2one(
1432 string="Income Currency Rate",
1433 domain="[('type', '=', 'other')]",),
1434 'expense_currency_exchange_account_id': fields.many2one(
1436 string="Expense Currency Rate",
1437 domain="[('type', '=', 'other')]",),
1442 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: