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)
373 for line in line_ids:
375 line_amount = line.get('amount',0.0)
376 voucher_total += line_amount
378 total = voucher_total
381 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
383 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
384 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
385 tax = tax_pool.browse(cr, uid, taxes, context=context)
387 if not tax[0].price_include:
388 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_total, 1).get('taxes', []):
389 total_tax += tax_line.get('amount')
393 'amount':total or voucher_total,
394 'tax_amount':total_tax
400 def onchange_term_id(self, cr, uid, ids, term_id, amount):
401 term_pool = self.pool.get('account.payment.term')
404 default = {'date_due':False}
405 if term_id and amount:
406 terms = term_pool.compute(cr, uid, term_id, amount)
408 due_date = terms[-1][0]
412 return {'value':default}
414 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):
416 Returns a dict that contains new values and context
418 @param partner_id: latest value from user input for field partner_id
419 @param args: other arguments
420 @param context: context arguments, like lang, time zone
422 @return: Returns a dict which contains new values, and context
428 if not partner_id or not journal_id:
431 partner_pool = self.pool.get('res.partner')
432 journal_pool = self.pool.get('account.journal')
434 journal = journal_pool.browse(cr, uid, journal_id, context=context)
435 partner = partner_pool.browse(cr, uid, partner_id, context=context)
438 if journal.type in ('sale','sale_refund'):
439 account_id = partner.property_account_receivable.id
441 elif journal.type in ('purchase', 'purchase_refund','expense'):
442 account_id = partner.property_account_payable.id
445 if not journal.default_credit_account_id or not journal.default_debit_account_id:
446 raise osv.except_osv(_('Error !'), _('Please define default credit/debit account on the %s !') % (journal.name))
447 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
450 default['value']['account_id'] = account_id
451 default['value']['type'] = ttype or tr_type
453 vals = self.onchange_journal(cr, uid, ids, journal_id, line_ids, tax_id, partner_id, company_id, context)
454 default['value'].update(vals.get('value'))
458 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
459 res = {'value': {'paid_amount_in_company_currency': amount}}
460 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
461 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
462 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
463 if company_currency.id == payment_rate_currency_id:
466 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
467 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
470 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):
473 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
475 ctx.update({'date': date})
476 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
477 for key in vals.keys():
478 res[key].update(vals[key])
481 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
484 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
485 currency_obj = self.pool.get('res.currency')
486 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
487 company_id = journal.company_id.id
489 payment_rate_currency_id = currency_id
491 ctx.update({'date': date})
493 if ttype == 'receipt':
494 o2m_to_loop = 'line_cr_ids'
495 elif ttype == 'payment':
496 o2m_to_loop = 'line_dr_ids'
497 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
498 for voucher_line in vals['value'][o2m_to_loop]:
499 if voucher_line['currency_id'] != currency_id:
500 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
501 # is not in the voucher currency
502 payment_rate_currency_id = voucher_line['currency_id']
503 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
504 voucher_currency_id = currency_id or journal.company_id.currency_id.id
505 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
507 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
508 for key in res.keys():
509 vals[key].update(res[key])
510 vals['value'].update({'payment_rate': payment_rate})
511 if payment_rate_currency_id:
512 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
515 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
518 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
519 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
520 for key in vals.keys():
521 res[key].update(vals[key])
524 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
526 Returns a dict that contains new values and context
528 @param partner_id: latest value from user input for field partner_id
529 @param args: other arguments
530 @param context: context arguments, like lang, time zone
532 @return: Returns a dict which contains new values, and context
536 context_multi_currency = context.copy()
538 context_multi_currency.update({'date': date})
540 currency_pool = self.pool.get('res.currency')
541 move_line_pool = self.pool.get('account.move.line')
542 partner_pool = self.pool.get('res.partner')
543 journal_pool = self.pool.get('account.journal')
544 line_pool = self.pool.get('account.voucher.line')
548 'value': {'line_ids': [] ,'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
552 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
554 line_pool.unlink(cr, uid, line_ids)
556 if not partner_id or not journal_id:
559 journal = journal_pool.browse(cr, uid, journal_id, context=context)
560 partner = partner_pool.browse(cr, uid, partner_id, context=context)
561 currency_id = currency_id or journal.company_id.currency_id.id
563 if journal.type in ('sale','sale_refund'):
564 account_id = partner.property_account_receivable.id
565 elif journal.type in ('purchase', 'purchase_refund','expense'):
566 account_id = partner.property_account_payable.id
568 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
570 default['value']['account_id'] = account_id
572 if journal.type not in ('cash', 'bank'):
577 account_type = 'receivable'
578 if ttype == 'payment':
579 account_type = 'payable'
580 total_debit = price or 0.0
582 total_credit = price or 0.0
583 account_type = 'receivable'
585 if not context.get('move_line_ids', False):
586 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
588 ids = context['move_line_ids']
589 invoice_id = context.get('invoice_id', False)
590 company_currency = journal.company_id.currency_id.id
591 move_line_found = False
593 #order the lines by most old first
595 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
597 for line in account_move_lines:
598 if line.credit and line.reconcile_partial_id and ttype == 'receipt':
600 if line.debit and line.reconcile_partial_id and ttype == 'payment':
603 if line.invoice.id == invoice_id:
604 #if the invoice linked to the voucher line is equal to the invoice_id in context
605 #then we assign the amount on that line, whatever the other voucher lines
606 move_line_found = line.id
608 elif currency_id == company_currency:
609 #otherwise treatments is the same but with other field names
610 if line.amount_residual == price:
611 #if the amount residual is equal the amount voucher, we assign it to that voucher
612 #line, whatever the other voucher lines
613 move_line_found = line.id
615 #otherwise we will split the voucher amount on each line (by most old first)
616 total_credit += line.credit or 0.0
617 total_debit += line.debit or 0.0
618 elif currency_id == line.currency_id.id:
619 if line.amount_residual_currency == price:
620 move_line_found = line.id
622 total_credit += line.credit and line.amount_currency or 0.0
623 total_debit += line.debit and line.amount_currency or 0.0
625 #voucher line creation
626 for line in account_move_lines:
627 if line.credit and line.reconcile_partial_id and ttype == 'receipt':
629 if line.debit and line.reconcile_partial_id and ttype == 'payment':
631 if line.currency_id and currency_id==line.currency_id.id:
632 amount_original = abs(line.amount_currency)
633 amount_unreconciled = abs(line.amount_residual_currency)
635 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
636 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
637 line_currency_id = line.currency_id and line.currency_id.id or company_currency
639 'name':line.move_id.name,
640 'type': line.credit and 'dr' or 'cr',
641 'move_line_id':line.id,
642 'account_id':line.account_id.id,
643 'amount_original': amount_original,
644 'amount': (move_line_found == line.id) and min(price, amount_unreconciled) or 0.0,
645 'date_original':line.date,
646 'date_due':line.date_maturity,
647 'amount_unreconciled': amount_unreconciled,
648 'currency_id': line_currency_id,
651 #split voucher amount by most old first, but only for lines in the same currency
652 if not move_line_found:
653 if currency_id == line_currency_id:
655 amount = min(amount_unreconciled, abs(total_debit))
656 rs['amount'] = amount
657 total_debit -= amount
659 amount = min(amount_unreconciled, abs(total_credit))
660 rs['amount'] = amount
661 total_credit -= amount
663 if rs['amount_unreconciled'] == rs['amount']:
664 rs['reconcile'] = True
666 if rs['type'] == 'cr':
667 default['value']['line_cr_ids'].append(rs)
669 default['value']['line_dr_ids'].append(rs)
671 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
672 default['value']['pre_line'] = 1
673 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
674 default['value']['pre_line'] = 1
675 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price)
678 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
682 #set the default payment rate of the voucher and compute the paid amount in company currency
683 if currency_id and currency_id == payment_rate_currency_id:
685 ctx.update({'date': date})
686 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
687 for key in vals.keys():
688 res[key].update(vals[key])
691 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
693 @param date: latest value from user input for field date
694 @param args: other arguments
695 @param context: context arguments, like lang, time zone
696 @return: Returns a dict which contains new values, and context
701 #set the period of the voucher
702 period_pool = self.pool.get('account.period')
703 currency_obj = self.pool.get('res.currency')
705 ctx.update({'company_id': company_id})
706 pids = period_pool.find(cr, uid, date, context=ctx)
708 res['value'].update({'period_id':pids[0]})
709 if payment_rate_currency_id:
710 ctx.update({'date': date})
712 if payment_rate_currency_id != currency_id:
713 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
714 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
715 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
716 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
717 vals['value'].update({'payment_rate': payment_rate})
718 for key in vals.keys():
719 res[key].update(vals[key])
722 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
725 journal_pool = self.pool.get('account.journal')
726 journal = journal_pool.browse(cr, uid, journal_id, context=context)
727 account_id = journal.default_credit_account_id or journal.default_debit_account_id
729 if account_id and account_id.tax_ids:
730 tax_id = account_id.tax_ids[0].id
732 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
733 vals['value'].update({'tax_id':tax_id,'amount': amount})
736 currency_id = journal.currency.id
737 vals['value'].update({'currency_id': currency_id})
738 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
739 for key in res.keys():
740 vals[key].update(res[key])
743 def proforma_voucher(self, cr, uid, ids, context=None):
744 self.action_move_line_create(cr, uid, ids, context=context)
747 def action_cancel_draft(self, cr, uid, ids, context=None):
748 wf_service = netsvc.LocalService("workflow")
749 for voucher_id in ids:
750 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
751 self.write(cr, uid, ids, {'state':'draft'})
754 def cancel_voucher(self, cr, uid, ids, context=None):
755 reconcile_pool = self.pool.get('account.move.reconcile')
756 move_pool = self.pool.get('account.move')
758 for voucher in self.browse(cr, uid, ids, context=context):
760 for line in voucher.move_ids:
761 if line.reconcile_id:
762 recs += [line.reconcile_id.id]
763 if line.reconcile_partial_id:
764 recs += [line.reconcile_partial_id.id]
766 reconcile_pool.unlink(cr, uid, recs)
769 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
770 move_pool.unlink(cr, uid, [voucher.move_id.id])
775 self.write(cr, uid, ids, res)
778 def unlink(self, cr, uid, ids, context=None):
779 for t in self.read(cr, uid, ids, ['state'], context=context):
780 if t['state'] not in ('draft', 'cancel'):
781 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Voucher(s) which are already opened or paid !'))
782 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
784 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
788 res = {'account_id':False}
789 partner_pool = self.pool.get('res.partner')
790 journal_pool = self.pool.get('account.journal')
791 if pay_now == 'pay_later':
792 partner = partner_pool.browse(cr, uid, partner_id)
793 journal = journal_pool.browse(cr, uid, journal_id)
794 if journal.type in ('sale','sale_refund'):
795 account_id = partner.property_account_receivable.id
796 elif journal.type in ('purchase', 'purchase_refund','expense'):
797 account_id = partner.property_account_payable.id
799 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
800 res['account_id'] = account_id
803 def _sel_context(self, cr, uid, voucher_id,context=None):
805 Select the context to use accordingly if it needs to be multicurrency or not.
807 :param voucher_id: Id of the actual voucher
808 :return: The returned context will be the same as given in parameter if the voucher currency is the same
809 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
810 the date of the voucher.
813 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
814 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
815 if current_currency <> company_currency:
816 context_multi_currency = context.copy()
817 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
818 context_multi_currency.update({'date': voucher_brw.date})
819 return context_multi_currency
822 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
824 Return a dict to be use to create the first account move line of given voucher.
826 :param voucher_id: Id of voucher what we are creating account_move.
827 :param move_id: Id of account move where this line will be added.
828 :param company_currency: id of currency of the company to which the voucher belong
829 :param current_currency: id of currency of the voucher
830 :return: mapping between fieldname and value of account move line to create
833 move_line_obj = self.pool.get('account.move.line')
834 currency_obj = self.pool.get('res.currency')
835 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
837 # TODO: is there any other alternative then the voucher type ??
838 # ANSWER: We can have payment and receipt "In Advance".
839 # TODO: Make this logic available.
840 # -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
841 if voucher_brw.type in ('purchase', 'payment'):
842 credit = voucher_brw.paid_amount_in_company_currency
843 elif voucher_brw.type in ('sale', 'receipt'):
844 debit = voucher_brw.paid_amount_in_company_currency
845 if debit < 0: credit = -debit; debit = 0.0
846 if credit < 0: debit = -credit; credit = 0.0
847 sign = debit - credit < 0 and -1 or 1
848 #set the first line of the voucher
850 'name': voucher_brw.name or '/',
853 'account_id': voucher_brw.account_id.id,
855 'journal_id': voucher_brw.journal_id.id,
856 'period_id': voucher_brw.period_id.id,
857 'partner_id': voucher_brw.partner_id.id,
858 'currency_id': company_currency <> current_currency and current_currency or False,
859 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
860 'date': voucher_brw.date,
861 'date_maturity': voucher_brw.date_due
865 def account_move_get(self, cr, uid, voucher_id, context=None):
867 This method prepare the creation of the account move related to the given voucher.
869 :param voucher_id: Id of voucher for which we are creating account_move.
870 :return: mapping between fieldname and value of account move to create
873 move_obj = self.pool.get('account.move')
874 seq_obj = self.pool.get('ir.sequence')
875 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
876 if voucher_brw.number:
877 name = voucher_brw.number
878 elif voucher_brw.journal_id.sequence_id:
879 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id)
881 raise osv.except_osv(_('Error !'),
882 _('Please define a sequence on the journal !'))
883 if not voucher_brw.reference:
884 ref = name.replace('/','')
886 ref = voucher_brw.reference
890 'journal_id': voucher_brw.journal_id.id,
891 'narration': voucher_brw.narration,
892 'date': voucher_brw.date,
894 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
898 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
900 Prepare the two lines in company currency due to currency rate difference.
902 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
904 :param move_id: Account move wher the move lines will be.
905 :param amount_residual: Amount to be posted.
906 :param company_currency: id of currency of the company to which the voucher belong
907 :param current_currency: id of currency of the voucher
908 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
909 :rtype: tuple of dict
911 if amount_residual > 0:
912 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
914 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! "))
916 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
918 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! "))
919 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
920 # the receivable/payable account may have a secondary currency, which render this field mandatory
921 account_currency_id = company_currency <> current_currency and current_currency or False
923 'journal_id': line.voucher_id.journal_id.id,
924 'period_id': line.voucher_id.period_id.id,
925 'name': _('change')+': '+(line.name or '/'),
926 'account_id': line.account_id.id,
928 'partner_id': line.voucher_id.partner_id.id,
929 'currency_id': account_currency_id,
930 'amount_currency': 0.0,
932 'credit': amount_residual > 0 and amount_residual or 0.0,
933 'debit': amount_residual < 0 and -amount_residual or 0.0,
934 'date': line.voucher_id.date,
936 move_line_counterpart = {
937 'journal_id': line.voucher_id.journal_id.id,
938 'period_id': line.voucher_id.period_id.id,
939 'name': _('change')+': '+(line.name or '/'),
940 'account_id': account_id.id,
942 'amount_currency': 0.0,
943 'partner_id': line.voucher_id.partner_id.id,
944 'currency_id': account_currency_id,
946 'debit': amount_residual > 0 and amount_residual or 0.0,
947 'credit': amount_residual < 0 and -amount_residual or 0.0,
948 'date': line.voucher_id.date,
950 return (move_line, move_line_counterpart)
952 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
954 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
955 payment_rate_currency_id is relevant) either the rate encoded in the system.
957 :param amount: float. The amount to convert
958 :param voucher: id of the voucher on which we want the conversion
959 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
960 field in order to select the good rate to use.
961 :return: the amount in the currency of the voucher's company
964 currency_obj = self.pool.get('res.currency')
965 voucher = self.browse(cr, uid, voucher_id, context=context)
967 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
968 # the rate specified on the voucher is for the company currency
969 rate_between_voucher_and_base = voucher.currency_id.rate or 1.0
970 rate_between_base_and_company = voucher.payment_rate or 1.0
971 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount / rate_between_voucher_and_base * rate_between_base_and_company))
973 # the rate specified on the voucher is not relevant, we use all the rates in the system
974 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
977 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
979 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
980 It returns Tuple with tot_line what is total of difference between debit and credit and
981 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
983 :param voucher_id: Voucher id what we are working with
984 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
985 :param move_id: Account move wher those lines will be joined.
986 :param company_currency: id of currency of the company to which the voucher belong
987 :param current_currency: id of currency of the voucher
988 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
989 :rtype: tuple(float, list of int)
993 move_line_obj = self.pool.get('account.move.line')
994 currency_obj = self.pool.get('res.currency')
995 tot_line = line_total
998 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1000 ctx.update({'date': voucher_brw.date})
1001 for line in voucher_brw.line_ids:
1002 #create one move line per voucher line where amount is not 0.0
1005 # convert the amount set on the voucher line into the currency of the voucher's company
1006 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1007 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1008 # currency rate difference
1009 if line.amount == line.amount_unreconciled:
1010 currency_rate_difference = line.move_line_id.amount_residual - amount
1012 currency_rate_difference = 0.0
1014 'journal_id': voucher_brw.journal_id.id,
1015 'period_id': voucher_brw.period_id.id,
1016 'name': line.name or '/',
1017 'account_id': line.account_id.id,
1019 'partner_id': voucher_brw.partner_id.id,
1020 '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,
1021 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1025 'date': voucher_brw.date
1029 if line.type == 'dr':
1034 if (line.type=='dr'):
1036 move_line['debit'] = amount
1039 move_line['credit'] = amount
1041 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1043 'account_tax_id': voucher_brw.tax_id.id,
1046 if move_line.get('account_tax_id', False):
1047 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1048 if not (tax_data.base_code_id and tax_data.tax_code_id):
1049 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))
1051 # compute the amount in foreign currency
1052 foreign_currency_diff = 0.0
1053 amount_currency = False
1054 if line.move_line_id:
1055 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1056 # We want to set it on the account move line as soon as the original line had a foreign currency
1057 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1058 # we compute the amount in that foreign currency.
1059 if line.move_line_id.currency_id.id == current_currency:
1060 # if the voucher and the voucher line share the same currency, there is no computation to do
1061 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1062 amount_currency = sign * (line.amount)
1063 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1064 # if the rate is specified on the voucher, we must use it
1065 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1066 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1068 # otherwise we use the rates of the system (giving the voucher date in the context)
1069 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1070 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1071 foreign_currency_diff = line.move_line_id.amount_residual_currency + amount_currency
1073 move_line['amount_currency'] = amount_currency
1074 voucher_line = move_line_obj.create(cr, uid, move_line)
1075 rec_ids = [voucher_line, line.move_line_id.id]
1077 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1078 # Change difference entry in company currency
1079 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1080 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1081 move_line_obj.create(cr, uid, exch_lines[1], context)
1082 rec_ids.append(new_id)
1084 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):
1085 # Change difference entry in voucher currency
1086 move_line_foreign_currency = {
1087 'journal_id': line.voucher_id.journal_id.id,
1088 'period_id': line.voucher_id.period_id.id,
1089 'name': _('change')+': '+(line.name or '/'),
1090 'account_id': line.account_id.id,
1092 'partner_id': line.voucher_id.partner_id.id,
1093 'currency_id': line.move_line_id.currency_id.id,
1094 'amount_currency': -1 * foreign_currency_diff,
1098 'date': line.voucher_id.date,
1100 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1101 rec_ids.append(new_id)
1103 if line.move_line_id.id:
1104 rec_lst_ids.append(rec_ids)
1106 return (tot_line, rec_lst_ids)
1108 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1110 Set a dict to be use to create the writeoff move line.
1112 :param voucher_id: Id of voucher what we are creating account_move.
1113 :param line_total: Amount remaining to be allocated on lines.
1114 :param move_id: Id of account move where this line will be added.
1115 :param name: Description of account move line.
1116 :param company_currency: id of currency of the company to which the voucher belong
1117 :param current_currency: id of currency of the voucher
1118 :return: mapping between fieldname and value of account move line to create
1121 move_line_obj = self.pool.get('account.move.line')
1122 currency_obj = self.pool.get('res.currency')
1125 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1126 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1128 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1132 if voucher_brw.payment_option == 'with_writeoff':
1133 account_id = voucher_brw.writeoff_acc_id.id
1134 write_off_name = voucher_brw.comment
1135 elif voucher_brw.type in ('sale', 'receipt'):
1136 account_id = voucher_brw.partner_id.property_account_receivable.id
1138 account_id = voucher_brw.partner_id.property_account_payable.id
1140 'name': write_off_name or name,
1141 'account_id': account_id,
1143 'partner_id': voucher_brw.partner_id.id,
1144 'date': voucher_brw.date,
1145 'credit': diff > 0 and diff or 0.0,
1146 'debit': diff < 0 and -diff or 0.0,
1147 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
1148 'currency_id': company_currency <> current_currency and current_currency or False,
1149 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1154 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1156 Get the currency of the actual company.
1158 :param voucher_id: Id of the voucher what i want to obtain company currency.
1159 :return: currency id of the company of the voucher
1162 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1164 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1166 Get the currency of the voucher.
1168 :param voucher_id: Id of the voucher what i want to obtain current currency.
1169 :return: currency id of the voucher
1172 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1173 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1175 def action_move_line_create(self, cr, uid, ids, context=None):
1177 Confirm the vouchers given in ids and create the journal entries for each of them
1181 move_pool = self.pool.get('account.move')
1182 move_line_pool = self.pool.get('account.move.line')
1183 currency_pool = self.pool.get('res.currency')
1184 tax_obj = self.pool.get('account.tax')
1185 seq_obj = self.pool.get('ir.sequence')
1186 for voucher in self.browse(cr, uid, ids, context=context):
1189 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1190 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1191 # we select the context to use accordingly if it's a multicurrency case or not
1192 context = self._sel_context(cr, uid, voucher.id, context)
1193 # But for the operations made by _convert_amount, we always need to give the date in the context
1194 ctx = context.copy()
1195 ctx.update({'date': voucher.date})
1196 # Create the account move record.
1197 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1198 # Get the name of the account_move just created
1199 name = move_pool.browse(cr, uid, move_id, context=context).name
1200 # Create the first line of the voucher
1201 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)
1202 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1203 line_total = move_line_brw.debit - move_line_brw.credit
1205 if voucher.type == 'sale':
1206 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1207 elif voucher.type == 'purchase':
1208 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1209 # Create one move line per voucher line where amount is not 0.0
1210 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1212 # Create the writeoff line if needed
1213 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1215 ml_writeoff_id = move_line_pool.create(cr, uid, ml_writeoff, context)
1216 # We post the voucher.
1217 self.write(cr, uid, [voucher.id], {
1222 if voucher.journal_id.entry_posted:
1223 move_pool.post(cr, uid, [move_id], context={})
1224 # We automatically reconcile the account move lines.
1225 for rec_ids in rec_list_ids:
1226 if len(rec_ids) >= 2:
1227 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)
1230 def copy(self, cr, uid, id, default={}, context=None):
1235 'line_cr_ids': False,
1236 'line_dr_ids': False,
1239 if 'date' not in default:
1240 default['date'] = time.strftime('%Y-%m-%d')
1241 return super(account_voucher, self).copy(cr, uid, id, default, context)
1245 class account_voucher_line(osv.osv):
1246 _name = 'account.voucher.line'
1247 _description = 'Voucher Lines'
1248 _order = "move_line_id"
1250 # If the payment is in the same currency than the invoice, we keep the same amount
1251 # Otherwise, we compute from company currency to payment currency
1252 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1253 currency_pool = self.pool.get('res.currency')
1255 for line in self.browse(cr, uid, ids, context=context):
1256 ctx = context.copy()
1257 ctx.update({'date': line.voucher_id.date})
1259 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1260 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1261 move_line = line.move_line_id or False
1264 res['amount_original'] = 0.0
1265 res['amount_unreconciled'] = 0.0
1266 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1267 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1268 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)
1269 elif move_line and move_line.credit > 0:
1270 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1271 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1273 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1274 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1276 rs_data[line.id] = res
1279 def _currency_id(self, cr, uid, ids, name, args, context=None):
1281 This function returns the currency id of a voucher line. It's either the currency of the
1282 associated move line (if any) or the currency of the voucher or the company currency.
1285 for line in self.browse(cr, uid, ids, context=context):
1286 move_line = line.move_line_id
1288 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1290 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1294 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1295 'name':fields.char('Description', size=256),
1296 'account_id':fields.many2one('account.account','Account', required=True),
1297 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1298 'untax_amount':fields.float('Untax Amount'),
1299 'amount':fields.float('Allocation', digits_compute=dp.get_precision('Account')),
1300 'reconcile': fields.boolean('Full Reconcile'),
1301 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1302 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1303 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1304 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1305 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1306 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True),
1307 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True),
1308 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1309 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1315 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1316 vals = { 'amount': 0.0}
1318 vals = { 'amount': amount_unreconciled}
1319 return {'value': vals}
1321 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1324 vals['reconcile'] = (amount == amount_unreconciled)
1325 return {'value': vals}
1327 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1329 Returns a dict that contains new values and context
1331 @param move_line_id: latest value from user input for field move_line_id
1332 @param args: other arguments
1333 @param context: context arguments, like lang, time zone
1335 @return: Returns a dict which contains new values, and context
1338 move_line_pool = self.pool.get('account.move.line')
1340 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1341 if move_line.credit:
1346 'account_id': move_line.account_id.id,
1348 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1354 def default_get(self, cr, user, fields_list, context=None):
1356 Returns default values for fields
1357 @param fields_list: list of fields, for which default values are required to be read
1358 @param context: context arguments, like lang, time zone
1360 @return: Returns a dict that contains default values for fields
1364 journal_id = context.get('journal_id', False)
1365 partner_id = context.get('partner_id', False)
1366 journal_pool = self.pool.get('account.journal')
1367 partner_pool = self.pool.get('res.partner')
1368 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1369 if (not journal_id) or ('account_id' not in fields_list):
1371 journal = journal_pool.browse(cr, user, journal_id, context=context)
1374 if journal.type in ('sale', 'sale_refund'):
1375 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1377 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1378 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1381 partner = partner_pool.browse(cr, user, partner_id, context=context)
1382 if context.get('type') == 'payment':
1384 account_id = partner.property_account_payable.id
1385 elif context.get('type') == 'receipt':
1386 account_id = partner.property_account_receivable.id
1389 'account_id':account_id,
1393 account_voucher_line()
1395 class account_bank_statement(osv.osv):
1396 _inherit = 'account.bank.statement'
1398 def button_cancel(self, cr, uid, ids, context=None):
1399 voucher_obj = self.pool.get('account.voucher')
1400 for st in self.browse(cr, uid, ids, context=context):
1402 for line in st.line_ids:
1404 voucher_ids.append(line.voucher_id.id)
1405 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1406 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1408 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1409 voucher_obj = self.pool.get('account.voucher')
1410 wf_service = netsvc.LocalService("workflow")
1411 move_line_obj = self.pool.get('account.move.line')
1412 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1413 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1414 if st_line.voucher_id:
1415 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1416 if st_line.voucher_id.state == 'cancel':
1417 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1418 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1420 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1421 bank_st_line_obj.write(cr, uid, [st_line_id], {
1422 'move_ids': [(4, v.move_id.id, False)]
1425 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1426 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1428 account_bank_statement()
1430 class account_bank_statement_line(osv.osv):
1431 _inherit = 'account.bank.statement.line'
1433 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1437 for line in self.browse(cursor, user, ids, context=context):
1439 res[line.id] = line.voucher_id.amount#
1444 def _check_amount(self, cr, uid, ids, context=None):
1445 for obj in self.browse(cr, uid, ids, context=context):
1447 diff = abs(obj.amount) - obj.voucher_id.amount
1448 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1453 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line', ['amount']),
1457 'amount_reconciled': fields.function(_amount_reconciled,
1458 string='Amount reconciled', type='float'),
1459 'voucher_id': fields.many2one('account.voucher', 'Payment'),
1462 def unlink(self, cr, uid, ids, context=None):
1463 voucher_obj = self.pool.get('account.voucher')
1464 statement_line = self.browse(cr, uid, ids, context=context)
1466 for st_line in statement_line:
1467 if st_line.voucher_id:
1468 unlink_ids.append(st_line.voucher_id.id)
1469 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1470 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1472 account_bank_statement_line()
1474 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1476 for operation in operations:
1478 if not isinstance(operation, (list, tuple)):
1479 result = target_osv.read(cr, uid, operation, fields, context=context)
1480 elif operation[0] == 0:
1481 # may be necessary to check if all the fields are here and get the default values?
1482 result = operation[2]
1483 elif operation[0] == 1:
1484 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1485 if not result: result = {}
1486 result.update(operation[2])
1487 elif operation[0] == 4:
1488 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1490 results.append(result)
1493 class res_company(osv.osv):
1494 _inherit = "res.company"
1496 'income_currency_exchange_account_id': fields.many2one(
1498 string="Income Currency Rate",
1499 domain="[('type', '=', 'other')]",),
1500 'expense_currency_exchange_account_id': fields.many2one(
1502 string="Expense Currency Rate",
1503 domain="[('type', '=', 'other')]",),
1508 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: