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 = {}
145 if view_type == 'form':
146 if not view_id and context.get('invoice_type'):
147 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
148 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
150 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
151 result = result and result[1] or False
153 if not view_id and context.get('line_type'):
154 if context.get('line_type') == 'customer':
155 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
157 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
158 result = result and result[1] or False
161 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
162 doc = etree.XML(res['arch'])
164 if context.get('type', 'sale') in ('purchase', 'payment'):
165 nodes = doc.xpath("//field[@name='partner_id']")
167 node.set('domain', "[('supplier', '=', True)]")
168 res['arch'] = etree.tostring(doc)
171 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount):
173 for l in line_dr_ids:
175 for l in line_cr_ids:
176 credit += l['amount']
177 return abs(amount - abs(credit - debit))
179 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, context=None):
180 context = context or {}
181 if not line_dr_ids and not line_cr_ids:
183 line_osv = self.pool.get("account.voucher.line")
184 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
185 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
187 #loop into the lines to see if there is an amount allocated on a voucher line with a currency different than the voucher currency
188 is_multi_currency = False
189 for voucher_line in line_dr_ids+line_cr_ids:
190 if voucher_line.get('currency_id',False) != voucher_currency:
191 is_multi_currency = True
193 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount), 'is_multi_currency': is_multi_currency}}
195 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
196 if not ids: return {}
197 currency_obj = self.pool.get('res.currency')
200 for voucher in self.browse(cr, uid, ids, context=context):
201 for l in voucher.line_dr_ids:
203 for l in voucher.line_cr_ids:
205 currency = voucher.currency_id or voucher.company_id.currency_id
206 res[voucher.id] = currency_obj.round(cr, uid, currency, abs(voucher.amount - abs(credit - debit)))
209 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
210 if not ids: return {}
212 voucher_rate = company_currency_rate = 1.0
213 for voucher in self.browse(cr, uid, ids, context=context):
214 if voucher.currency_id:
216 ctx.update({'date': voucher.date})
217 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
218 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
219 company_currency_rate = voucher.payment_rate
221 company_currency_rate = voucher.company_id.currency_id.rate
222 res[voucher.id] = voucher.amount / voucher_rate * company_currency_rate
225 _name = 'account.voucher'
226 _description = 'Accounting Voucher'
227 _order = "date desc, id desc"
228 # _rec_name = 'number'
230 'type':fields.selection([
232 ('purchase','Purchase'),
233 ('payment','Payment'),
234 ('receipt','Receipt'),
235 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
236 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
237 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
238 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
239 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
240 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
241 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
242 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
243 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
244 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
245 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
246 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
247 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
248 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
249 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
250 'state':fields.selection(
252 ('proforma','Pro-forma'),
254 ('cancel','Cancelled')
255 ], 'State', readonly=True, size=32,
256 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Voucher. \
257 \n* The \'Pro-forma\' when voucher is in Pro-forma state,voucher does not have an voucher number. \
258 \n* The \'Posted\' state is used when user create voucher,a voucher number is generated and voucher entries are created in account \
259 \n* The \'Cancelled\' state is used when user cancel voucher.'),
260 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
261 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
262 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
263 'number': fields.char('Number', size=32, readonly=True,),
264 'move_id':fields.many2one('account.move', 'Account Entry'),
265 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
266 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
267 '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'),
268 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
269 'pay_now':fields.selection([
270 ('pay_now','Pay Directly'),
271 ('pay_later','Pay Later or Group Funds'),
272 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
273 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
274 'pre_line':fields.boolean('Previous Payments ?', required=False),
275 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
276 'payment_option':fields.selection([
277 ('without_writeoff', 'Keep Open'),
278 ('with_writeoff', 'Reconcile Payment Balance'),
279 ], '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)"),
280 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
281 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
282 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
283 '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."),
284 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
285 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
286 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
287 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
288 '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'),
291 'period_id': _get_period,
292 'partner_id': _get_partner,
293 'journal_id':_get_journal,
294 'currency_id': _get_currency,
295 'reference': _get_reference,
296 'narration':_get_narration,
297 'amount': _get_amount,
300 'pay_now': 'pay_later',
302 'date': lambda *a: time.strftime('%Y-%m-%d'),
303 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
305 'payment_option': 'without_writeoff',
306 'comment': _('Write-Off'),
308 'payment_rate_currency_id': _get_payment_rate_currency,
311 def compute_tax(self, cr, uid, ids, context=None):
312 tax_pool = self.pool.get('account.tax')
313 partner_pool = self.pool.get('res.partner')
314 position_pool = self.pool.get('account.fiscal.position')
315 voucher_line_pool = self.pool.get('account.voucher.line')
316 voucher_pool = self.pool.get('account.voucher')
317 if context is None: context = {}
319 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
321 for line in voucher.line_ids:
322 voucher_amount += line.untax_amount or line.amount
323 line.amount = line.untax_amount or line.amount
324 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
326 if not voucher.tax_id:
327 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
330 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
331 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
332 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
333 tax = tax_pool.browse(cr, uid, taxes, context=context)
335 total = voucher_amount
338 if not tax[0].price_include:
339 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_amount, 1).get('taxes', []):
340 total_tax += tax_line.get('amount', 0.0)
343 for line in voucher.line_ids:
347 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
348 line_tax += tax_line.get('amount', 0.0)
349 line_total += tax_line.get('price_unit')
350 total_tax += line_tax
351 untax_amount = line.untax_amount or line.amount
352 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
354 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
357 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
358 context = context or {}
359 tax_pool = self.pool.get('account.tax')
360 partner_pool = self.pool.get('res.partner')
361 position_pool = self.pool.get('account.fiscal.position')
362 line_pool = self.pool.get('account.voucher.line')
369 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 accounts on the journal "%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, time.strftime('%Y-%m-%d'), price, ttype, 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.recompute_voucher_lines(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 recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
482 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
483 currency_obj = self.pool.get('res.currency')
484 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
485 company_id = journal.company_id.id
487 payment_rate_currency_id = currency_id
489 ctx.update({'date': date})
491 if ttype == 'receipt':
492 o2m_to_loop = 'line_cr_ids'
493 elif ttype == 'payment':
494 o2m_to_loop = 'line_dr_ids'
495 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
496 for voucher_line in vals['value'][o2m_to_loop]:
497 if voucher_line['currency_id'] != currency_id:
498 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
499 # is not in the voucher currency
500 payment_rate_currency_id = voucher_line['currency_id']
501 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
502 voucher_currency_id = currency_id or journal.company_id.currency_id.id
503 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
505 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
506 for key in res.keys():
507 vals[key].update(res[key])
508 vals['value'].update({'payment_rate': payment_rate})
509 if payment_rate_currency_id:
510 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
513 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
516 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
517 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
518 for key in vals.keys():
519 res[key].update(vals[key])
522 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
524 Returns a dict that contains new values and context
526 @param partner_id: latest value from user input for field partner_id
527 @param args: other arguments
528 @param context: context arguments, like lang, time zone
530 @return: Returns a dict which contains new values, and context
534 context_multi_currency = context.copy()
536 context_multi_currency.update({'date': date})
538 currency_pool = self.pool.get('res.currency')
539 move_line_pool = self.pool.get('account.move.line')
540 partner_pool = self.pool.get('res.partner')
541 journal_pool = self.pool.get('account.journal')
542 line_pool = self.pool.get('account.voucher.line')
546 'value': {'line_ids': [] ,'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
550 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
552 line_pool.unlink(cr, uid, line_ids)
554 if not partner_id or not journal_id:
557 journal = journal_pool.browse(cr, uid, journal_id, context=context)
558 partner = partner_pool.browse(cr, uid, partner_id, context=context)
559 currency_id = currency_id or journal.company_id.currency_id.id
561 if journal.type in ('sale','sale_refund'):
562 account_id = partner.property_account_receivable.id
563 elif journal.type in ('purchase', 'purchase_refund','expense'):
564 account_id = partner.property_account_payable.id
566 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
568 default['value']['account_id'] = account_id
570 if journal.type not in ('cash', 'bank'):
575 account_type = 'receivable'
576 if ttype == 'payment':
577 account_type = 'payable'
578 total_debit = price or 0.0
580 total_credit = price or 0.0
581 account_type = 'receivable'
583 if not context.get('move_line_ids', False):
584 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
586 ids = context['move_line_ids']
587 invoice_id = context.get('invoice_id', False)
588 company_currency = journal.company_id.currency_id.id
589 move_line_found = False
591 #order the lines by most old first
593 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
595 for line in account_move_lines:
596 if line.credit and line.reconcile_partial_id and ttype == 'receipt':
598 if line.debit and line.reconcile_partial_id and ttype == 'payment':
601 if line.invoice.id == invoice_id:
602 #if the invoice linked to the voucher line is equal to the invoice_id in context
603 #then we assign the amount on that line, whatever the other voucher lines
604 move_line_found = line.id
606 elif currency_id == company_currency:
607 #otherwise treatments is the same but with other field names
608 if line.amount_residual == price:
609 #if the amount residual is equal the amount voucher, we assign it to that voucher
610 #line, whatever the other voucher lines
611 move_line_found = line.id
613 #otherwise we will split the voucher amount on each line (by most old first)
614 total_credit += line.credit or 0.0
615 total_debit += line.debit or 0.0
616 elif currency_id == line.currency_id.id:
617 if line.amount_residual_currency == price:
618 move_line_found = line.id
620 total_credit += line.credit and line.amount_currency or 0.0
621 total_debit += line.debit and line.amount_currency or 0.0
623 #voucher line creation
624 for line in account_move_lines:
625 if line.credit and line.reconcile_partial_id and ttype == 'receipt':
627 if line.debit and line.reconcile_partial_id and ttype == 'payment':
629 if line.currency_id and currency_id==line.currency_id.id:
630 amount_original = abs(line.amount_currency)
631 amount_unreconciled = abs(line.amount_residual_currency)
633 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
634 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
635 line_currency_id = line.currency_id and line.currency_id.id or company_currency
637 'name':line.move_id.name,
638 'type': line.credit and 'dr' or 'cr',
639 'move_line_id':line.id,
640 'account_id':line.account_id.id,
641 'amount_original': amount_original,
642 'amount': (move_line_found == line.id) and min(price, amount_unreconciled) or 0.0,
643 'date_original':line.date,
644 'date_due':line.date_maturity,
645 'amount_unreconciled': amount_unreconciled,
646 'currency_id': line_currency_id,
649 #split voucher amount by most old first, but only for lines in the same currency
650 if not move_line_found:
651 if currency_id == line_currency_id:
653 amount = min(amount_unreconciled, abs(total_debit))
654 rs['amount'] = amount
655 total_debit -= amount
657 amount = min(amount_unreconciled, abs(total_credit))
658 rs['amount'] = amount
659 total_credit -= amount
661 if rs['amount_unreconciled'] == rs['amount']:
662 rs['reconcile'] = True
664 if rs['type'] == 'cr':
665 default['value']['line_cr_ids'].append(rs)
667 default['value']['line_dr_ids'].append(rs)
669 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
670 default['value']['pre_line'] = 1
671 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
672 default['value']['pre_line'] = 1
673 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price)
676 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
680 #set the default payment rate of the voucher and compute the paid amount in company currency
681 if currency_id and currency_id == payment_rate_currency_id:
683 ctx.update({'date': date})
684 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
685 for key in vals.keys():
686 res[key].update(vals[key])
689 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
691 @param date: latest value from user input for field date
692 @param args: other arguments
693 @param context: context arguments, like lang, time zone
694 @return: Returns a dict which contains new values, and context
699 #set the period of the voucher
700 period_pool = self.pool.get('account.period')
701 currency_obj = self.pool.get('res.currency')
703 ctx.update({'company_id': company_id})
704 pids = period_pool.find(cr, uid, date, context=ctx)
706 res['value'].update({'period_id':pids[0]})
707 if payment_rate_currency_id:
708 ctx.update({'date': date})
710 if payment_rate_currency_id != currency_id:
711 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
712 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
713 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
714 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
715 vals['value'].update({'payment_rate': payment_rate})
716 for key in vals.keys():
717 res[key].update(vals[key])
720 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
723 journal_pool = self.pool.get('account.journal')
724 journal = journal_pool.browse(cr, uid, journal_id, context=context)
725 account_id = journal.default_credit_account_id or journal.default_debit_account_id
727 if account_id and account_id.tax_ids:
728 tax_id = account_id.tax_ids[0].id
730 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
731 vals['value'].update({'tax_id':tax_id,'amount': amount})
734 currency_id = journal.currency.id
735 vals['value'].update({'currency_id': currency_id})
736 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
737 for key in res.keys():
738 vals[key].update(res[key])
741 def proforma_voucher(self, cr, uid, ids, context=None):
742 self.action_move_line_create(cr, uid, ids, context=context)
745 def action_cancel_draft(self, cr, uid, ids, context=None):
746 wf_service = netsvc.LocalService("workflow")
747 for voucher_id in ids:
748 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
749 self.write(cr, uid, ids, {'state':'draft'})
752 def cancel_voucher(self, cr, uid, ids, context=None):
753 reconcile_pool = self.pool.get('account.move.reconcile')
754 move_pool = self.pool.get('account.move')
756 for voucher in self.browse(cr, uid, ids, context=context):
758 for line in voucher.move_ids:
759 if line.reconcile_id:
760 recs += [line.reconcile_id.id]
761 if line.reconcile_partial_id:
762 recs += [line.reconcile_partial_id.id]
764 reconcile_pool.unlink(cr, uid, recs)
767 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
768 move_pool.unlink(cr, uid, [voucher.move_id.id])
773 self.write(cr, uid, ids, res)
776 def unlink(self, cr, uid, ids, context=None):
777 for t in self.read(cr, uid, ids, ['state'], context=context):
778 if t['state'] not in ('draft', 'cancel'):
779 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Voucher(s) which are already opened or paid !'))
780 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
782 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
786 res = {'account_id':False}
787 partner_pool = self.pool.get('res.partner')
788 journal_pool = self.pool.get('account.journal')
789 if pay_now == 'pay_later':
790 partner = partner_pool.browse(cr, uid, partner_id)
791 journal = journal_pool.browse(cr, uid, journal_id)
792 if journal.type in ('sale','sale_refund'):
793 account_id = partner.property_account_receivable.id
794 elif journal.type in ('purchase', 'purchase_refund','expense'):
795 account_id = partner.property_account_payable.id
797 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
798 res['account_id'] = account_id
801 def _sel_context(self, cr, uid, voucher_id,context=None):
803 Select the context to use accordingly if it needs to be multicurrency or not.
805 :param voucher_id: Id of the actual voucher
806 :return: The returned context will be the same as given in parameter if the voucher currency is the same
807 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
808 the date of the voucher.
811 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
812 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
813 if current_currency <> company_currency:
814 context_multi_currency = context.copy()
815 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
816 context_multi_currency.update({'date': voucher_brw.date})
817 return context_multi_currency
820 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
822 Return a dict to be use to create the first account move line of given voucher.
824 :param voucher_id: Id of voucher what we are creating account_move.
825 :param move_id: Id of account move where this line will be added.
826 :param company_currency: id of currency of the company to which the voucher belong
827 :param current_currency: id of currency of the voucher
828 :return: mapping between fieldname and value of account move line to create
831 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
833 # TODO: is there any other alternative then the voucher type ??
834 # ANSWER: We can have payment and receipt "In Advance".
835 # TODO: Make this logic available.
836 # -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
837 if voucher_brw.type in ('purchase', 'payment'):
838 credit = voucher_brw.paid_amount_in_company_currency
839 elif voucher_brw.type in ('sale', 'receipt'):
840 debit = voucher_brw.paid_amount_in_company_currency
841 if debit < 0: credit = -debit; debit = 0.0
842 if credit < 0: debit = -credit; credit = 0.0
843 sign = debit - credit < 0 and -1 or 1
844 #set the first line of the voucher
846 'name': voucher_brw.name or '/',
849 'account_id': voucher_brw.account_id.id,
851 'journal_id': voucher_brw.journal_id.id,
852 'period_id': voucher_brw.period_id.id,
853 'partner_id': voucher_brw.partner_id.id,
854 'currency_id': company_currency <> current_currency and current_currency or False,
855 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
856 'date': voucher_brw.date,
857 'date_maturity': voucher_brw.date_due
861 def account_move_get(self, cr, uid, voucher_id, context=None):
863 This method prepare the creation of the account move related to the given voucher.
865 :param voucher_id: Id of voucher for which we are creating account_move.
866 :return: mapping between fieldname and value of account move to create
869 seq_obj = self.pool.get('ir.sequence')
870 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
871 if voucher_brw.number:
872 name = voucher_brw.number
873 elif voucher_brw.journal_id.sequence_id:
874 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id)
876 raise osv.except_osv(_('Error !'),
877 _('Please define a sequence on the journal !'))
878 if not voucher_brw.reference:
879 ref = name.replace('/','')
881 ref = voucher_brw.reference
885 'journal_id': voucher_brw.journal_id.id,
886 'narration': voucher_brw.narration,
887 'date': voucher_brw.date,
889 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
893 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
895 Prepare the two lines in company currency due to currency rate difference.
897 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
899 :param move_id: Account move wher the move lines will be.
900 :param amount_residual: Amount to be posted.
901 :param company_currency: id of currency of the company to which the voucher belong
902 :param current_currency: id of currency of the voucher
903 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
904 :rtype: tuple of dict
906 if amount_residual > 0:
907 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
909 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! "))
911 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
913 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! "))
914 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
915 # the receivable/payable account may have a secondary currency, which render this field mandatory
916 account_currency_id = company_currency <> current_currency and current_currency or False
918 'journal_id': line.voucher_id.journal_id.id,
919 'period_id': line.voucher_id.period_id.id,
920 'name': _('change')+': '+(line.name or '/'),
921 'account_id': line.account_id.id,
923 'partner_id': line.voucher_id.partner_id.id,
924 'currency_id': account_currency_id,
925 'amount_currency': 0.0,
927 'credit': amount_residual > 0 and amount_residual or 0.0,
928 'debit': amount_residual < 0 and -amount_residual or 0.0,
929 'date': line.voucher_id.date,
931 move_line_counterpart = {
932 'journal_id': line.voucher_id.journal_id.id,
933 'period_id': line.voucher_id.period_id.id,
934 'name': _('change')+': '+(line.name or '/'),
935 'account_id': account_id.id,
937 'amount_currency': 0.0,
938 'partner_id': line.voucher_id.partner_id.id,
939 'currency_id': account_currency_id,
941 'debit': amount_residual > 0 and amount_residual or 0.0,
942 'credit': amount_residual < 0 and -amount_residual or 0.0,
943 'date': line.voucher_id.date,
945 return (move_line, move_line_counterpart)
947 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
949 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
950 payment_rate_currency_id is relevant) either the rate encoded in the system.
952 :param amount: float. The amount to convert
953 :param voucher: id of the voucher on which we want the conversion
954 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
955 field in order to select the good rate to use.
956 :return: the amount in the currency of the voucher's company
959 currency_obj = self.pool.get('res.currency')
960 voucher = self.browse(cr, uid, voucher_id, context=context)
962 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
963 # the rate specified on the voucher is for the company currency
964 rate_between_voucher_and_base = voucher.currency_id.rate or 1.0
965 rate_between_base_and_company = voucher.payment_rate or 1.0
966 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount / rate_between_voucher_and_base * rate_between_base_and_company))
968 # the rate specified on the voucher is not relevant, we use all the rates in the system
969 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
972 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
974 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
975 It returns Tuple with tot_line what is total of difference between debit and credit and
976 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
978 :param voucher_id: Voucher id what we are working with
979 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
980 :param move_id: Account move wher those lines will be joined.
981 :param company_currency: id of currency of the company to which the voucher belong
982 :param current_currency: id of currency of the voucher
983 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
984 :rtype: tuple(float, list of int)
988 move_line_obj = self.pool.get('account.move.line')
989 currency_obj = self.pool.get('res.currency')
990 tax_obj = self.pool.get('account.tax')
991 tot_line = line_total
994 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
996 ctx.update({'date': voucher_brw.date})
997 for line in voucher_brw.line_ids:
998 #create one move line per voucher line where amount is not 0.0
1001 # convert the amount set on the voucher line into the currency of the voucher's company
1002 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1003 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1004 # currency rate difference
1005 if line.amount == line.amount_unreconciled:
1006 currency_rate_difference = line.move_line_id.amount_residual - amount
1008 currency_rate_difference = 0.0
1010 'journal_id': voucher_brw.journal_id.id,
1011 'period_id': voucher_brw.period_id.id,
1012 'name': line.name or '/',
1013 'account_id': line.account_id.id,
1015 'partner_id': voucher_brw.partner_id.id,
1016 '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,
1017 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1021 'date': voucher_brw.date
1025 if line.type == 'dr':
1030 if (line.type=='dr'):
1032 move_line['debit'] = amount
1035 move_line['credit'] = amount
1037 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1039 'account_tax_id': voucher_brw.tax_id.id,
1042 if move_line.get('account_tax_id', False):
1043 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1044 if not (tax_data.base_code_id and tax_data.tax_code_id):
1045 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))
1047 # compute the amount in foreign currency
1048 foreign_currency_diff = 0.0
1049 amount_currency = False
1050 if line.move_line_id:
1051 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1052 # We want to set it on the account move line as soon as the original line had a foreign currency
1053 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1054 # we compute the amount in that foreign currency.
1055 if line.move_line_id.currency_id.id == current_currency:
1056 # if the voucher and the voucher line share the same currency, there is no computation to do
1057 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1058 amount_currency = sign * (line.amount)
1059 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1060 # if the rate is specified on the voucher, we must use it
1061 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1062 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1064 # otherwise we use the rates of the system (giving the voucher date in the context)
1065 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1066 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1067 foreign_currency_diff = line.move_line_id.amount_residual_currency + amount_currency
1069 move_line['amount_currency'] = amount_currency
1070 voucher_line = move_line_obj.create(cr, uid, move_line)
1071 rec_ids = [voucher_line, line.move_line_id.id]
1073 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1074 # Change difference entry in company currency
1075 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1076 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1077 move_line_obj.create(cr, uid, exch_lines[1], context)
1078 rec_ids.append(new_id)
1080 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):
1081 # Change difference entry in voucher currency
1082 move_line_foreign_currency = {
1083 'journal_id': line.voucher_id.journal_id.id,
1084 'period_id': line.voucher_id.period_id.id,
1085 'name': _('change')+': '+(line.name or '/'),
1086 'account_id': line.account_id.id,
1088 'partner_id': line.voucher_id.partner_id.id,
1089 'currency_id': line.move_line_id.currency_id.id,
1090 'amount_currency': -1 * foreign_currency_diff,
1094 'date': line.voucher_id.date,
1096 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1097 rec_ids.append(new_id)
1099 if line.move_line_id.id:
1100 rec_lst_ids.append(rec_ids)
1102 return (tot_line, rec_lst_ids)
1104 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1106 Set a dict to be use to create the writeoff move line.
1108 :param voucher_id: Id of voucher what we are creating account_move.
1109 :param line_total: Amount remaining to be allocated on lines.
1110 :param move_id: Id of account move where this line will be added.
1111 :param name: Description of account move line.
1112 :param company_currency: id of currency of the company to which the voucher belong
1113 :param current_currency: id of currency of the voucher
1114 :return: mapping between fieldname and value of account move line to create
1117 currency_obj = self.pool.get('res.currency')
1120 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1121 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1123 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1127 if voucher_brw.payment_option == 'with_writeoff':
1128 account_id = voucher_brw.writeoff_acc_id.id
1129 write_off_name = voucher_brw.comment
1130 elif voucher_brw.type in ('sale', 'receipt'):
1131 account_id = voucher_brw.partner_id.property_account_receivable.id
1133 account_id = voucher_brw.partner_id.property_account_payable.id
1135 'name': write_off_name or name,
1136 'account_id': account_id,
1138 'partner_id': voucher_brw.partner_id.id,
1139 'date': voucher_brw.date,
1140 'credit': diff > 0 and diff or 0.0,
1141 'debit': diff < 0 and -diff or 0.0,
1142 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
1143 'currency_id': company_currency <> current_currency and current_currency or False,
1144 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1149 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1151 Get the currency of the actual company.
1153 :param voucher_id: Id of the voucher what i want to obtain company currency.
1154 :return: currency id of the company of the voucher
1157 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1159 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1161 Get the currency of the voucher.
1163 :param voucher_id: Id of the voucher what i want to obtain current currency.
1164 :return: currency id of the voucher
1167 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1168 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1170 def action_move_line_create(self, cr, uid, ids, context=None):
1172 Confirm the vouchers given in ids and create the journal entries for each of them
1176 move_pool = self.pool.get('account.move')
1177 move_line_pool = self.pool.get('account.move.line')
1178 for voucher in self.browse(cr, uid, ids, context=context):
1181 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1182 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1183 # we select the context to use accordingly if it's a multicurrency case or not
1184 context = self._sel_context(cr, uid, voucher.id, context)
1185 # But for the operations made by _convert_amount, we always need to give the date in the context
1186 ctx = context.copy()
1187 ctx.update({'date': voucher.date})
1188 # Create the account move record.
1189 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1190 # Get the name of the account_move just created
1191 name = move_pool.browse(cr, uid, move_id, context=context).name
1192 # Create the first line of the voucher
1193 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)
1194 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1195 line_total = move_line_brw.debit - move_line_brw.credit
1197 if voucher.type == 'sale':
1198 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1199 elif voucher.type == 'purchase':
1200 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1201 # Create one move line per voucher line where amount is not 0.0
1202 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1204 # Create the writeoff line if needed
1205 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1207 move_line_pool.create(cr, uid, ml_writeoff, context)
1208 # We post the voucher.
1209 self.write(cr, uid, [voucher.id], {
1214 if voucher.journal_id.entry_posted:
1215 move_pool.post(cr, uid, [move_id], context={})
1216 # We automatically reconcile the account move lines.
1217 for rec_ids in rec_list_ids:
1218 if len(rec_ids) >= 2:
1219 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)
1222 def copy(self, cr, uid, id, default={}, context=None):
1227 'line_cr_ids': False,
1228 'line_dr_ids': False,
1231 if 'date' not in default:
1232 default['date'] = time.strftime('%Y-%m-%d')
1233 return super(account_voucher, self).copy(cr, uid, id, default, context)
1237 class account_voucher_line(osv.osv):
1238 _name = 'account.voucher.line'
1239 _description = 'Voucher Lines'
1240 _order = "move_line_id"
1242 # If the payment is in the same currency than the invoice, we keep the same amount
1243 # Otherwise, we compute from company currency to payment currency
1244 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1245 currency_pool = self.pool.get('res.currency')
1247 for line in self.browse(cr, uid, ids, context=context):
1248 ctx = context.copy()
1249 ctx.update({'date': line.voucher_id.date})
1251 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1252 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1253 move_line = line.move_line_id or False
1256 res['amount_original'] = 0.0
1257 res['amount_unreconciled'] = 0.0
1258 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1259 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1260 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)
1261 elif move_line and move_line.credit > 0:
1262 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1263 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1265 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1266 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1268 rs_data[line.id] = res
1271 def _currency_id(self, cr, uid, ids, name, args, context=None):
1273 This function returns the currency id of a voucher line. It's either the currency of the
1274 associated move line (if any) or the currency of the voucher or the company currency.
1277 for line in self.browse(cr, uid, ids, context=context):
1278 move_line = line.move_line_id
1280 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1282 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1286 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1287 'name':fields.char('Description', size=256),
1288 'account_id':fields.many2one('account.account','Account', required=True),
1289 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1290 'untax_amount':fields.float('Untax Amount'),
1291 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1292 'reconcile': fields.boolean('Full Reconcile'),
1293 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1294 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1295 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1296 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1297 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1298 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True),
1299 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True),
1300 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1301 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1307 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1308 vals = { 'amount': 0.0}
1310 vals = { 'amount': amount_unreconciled}
1311 return {'value': vals}
1313 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1316 vals['reconcile'] = (amount == amount_unreconciled)
1317 return {'value': vals}
1319 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1321 Returns a dict that contains new values and context
1323 @param move_line_id: latest value from user input for field move_line_id
1324 @param args: other arguments
1325 @param context: context arguments, like lang, time zone
1327 @return: Returns a dict which contains new values, and context
1330 move_line_pool = self.pool.get('account.move.line')
1332 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1333 if move_line.credit:
1338 'account_id': move_line.account_id.id,
1340 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1346 def default_get(self, cr, user, fields_list, context=None):
1348 Returns default values for fields
1349 @param fields_list: list of fields, for which default values are required to be read
1350 @param context: context arguments, like lang, time zone
1352 @return: Returns a dict that contains default values for fields
1356 journal_id = context.get('journal_id', False)
1357 partner_id = context.get('partner_id', False)
1358 journal_pool = self.pool.get('account.journal')
1359 partner_pool = self.pool.get('res.partner')
1360 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1361 if (not journal_id) or ('account_id' not in fields_list):
1363 journal = journal_pool.browse(cr, user, journal_id, context=context)
1366 if journal.type in ('sale', 'sale_refund'):
1367 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1369 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1370 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1373 partner = partner_pool.browse(cr, user, partner_id, context=context)
1374 if context.get('type') == 'payment':
1376 account_id = partner.property_account_payable.id
1377 elif context.get('type') == 'receipt':
1378 account_id = partner.property_account_receivable.id
1381 'account_id':account_id,
1385 account_voucher_line()
1387 class account_bank_statement(osv.osv):
1388 _inherit = 'account.bank.statement'
1390 def button_cancel(self, cr, uid, ids, context=None):
1391 voucher_obj = self.pool.get('account.voucher')
1392 for st in self.browse(cr, uid, ids, context=context):
1394 for line in st.line_ids:
1396 voucher_ids.append(line.voucher_id.id)
1397 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1398 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1400 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1401 voucher_obj = self.pool.get('account.voucher')
1402 wf_service = netsvc.LocalService("workflow")
1403 move_line_obj = self.pool.get('account.move.line')
1404 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1405 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1406 if st_line.voucher_id:
1407 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1408 if st_line.voucher_id.state == 'cancel':
1409 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1410 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1412 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1413 bank_st_line_obj.write(cr, uid, [st_line_id], {
1414 'move_ids': [(4, v.move_id.id, False)]
1417 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1418 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1420 account_bank_statement()
1422 class account_bank_statement_line(osv.osv):
1423 _inherit = 'account.bank.statement.line'
1425 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1429 for line in self.browse(cursor, user, ids, context=context):
1431 res[line.id] = line.voucher_id.amount#
1436 def _check_amount(self, cr, uid, ids, context=None):
1437 for obj in self.browse(cr, uid, ids, context=context):
1439 diff = abs(obj.amount) - obj.voucher_id.amount
1440 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1445 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1449 'amount_reconciled': fields.function(_amount_reconciled,
1450 string='Amount reconciled', type='float'),
1451 'voucher_id': fields.many2one('account.voucher', 'Payment'),
1454 def unlink(self, cr, uid, ids, context=None):
1455 voucher_obj = self.pool.get('account.voucher')
1456 statement_line = self.browse(cr, uid, ids, context=context)
1458 for st_line in statement_line:
1459 if st_line.voucher_id:
1460 unlink_ids.append(st_line.voucher_id.id)
1461 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1462 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1464 account_bank_statement_line()
1466 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1468 for operation in operations:
1470 if not isinstance(operation, (list, tuple)):
1471 result = target_osv.read(cr, uid, operation, fields, context=context)
1472 elif operation[0] == 0:
1473 # may be necessary to check if all the fields are here and get the default values?
1474 result = operation[2]
1475 elif operation[0] == 1:
1476 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1477 if not result: result = {}
1478 result.update(operation[2])
1479 elif operation[0] == 4:
1480 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1482 results.append(result)
1485 class res_company(osv.osv):
1486 _inherit = "res.company"
1488 'income_currency_exchange_account_id': fields.many2one(
1490 string="Income Currency Rate",
1491 domain="[('type', '=', 'other')]",),
1492 'expense_currency_exchange_account_id': fields.many2one(
1494 string="Expense Currency Rate",
1495 domain="[('type', '=', 'other')]",),
1500 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: