1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 from lxml import etree
26 from osv import osv, fields
27 import decimal_precision as dp
28 from tools.translate import _
30 class res_company(osv.osv):
31 _inherit = "res.company"
33 'income_currency_exchange_account_id': fields.many2one(
35 string="Gain Exchange Rate Account",
36 domain="[('type', '=', 'other')]",),
37 'expense_currency_exchange_account_id': fields.many2one(
39 string="Loss Exchange Rate Account",
40 domain="[('type', '=', 'other')]",),
45 class account_config_settings(osv.osv_memory):
46 _inherit = 'account.config.settings'
48 'income_currency_exchange_account_id': fields.related(
49 'company_id', 'income_currency_exchange_account_id',
51 relation='account.account',
52 string="Gain Exchange Rate Account"),
53 'expense_currency_exchange_account_id': fields.related(
54 'company_id', 'expense_currency_exchange_account_id',
56 relation='account.account',
57 string="Loss Exchange Rate Account"),
60 class account_voucher(osv.osv):
61 def _check_paid(self, cr, uid, ids, name, args, context=None):
63 for voucher in self.browse(cr, uid, ids, context=context):
64 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
67 def _get_type(self, cr, uid, context=None):
70 return context.get('type', False)
72 def _get_period(self, cr, uid, context=None):
73 if context is None: context = {}
74 if context.get('period_id', False):
75 return context.get('period_id')
76 periods = self.pool.get('account.period').find(cr, uid)
77 return periods and periods[0] or False
79 def _make_journal_search(self, cr, uid, ttype, context=None):
80 journal_pool = self.pool.get('account.journal')
81 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
83 def _get_journal(self, cr, uid, context=None):
84 if context is None: context = {}
85 invoice_pool = self.pool.get('account.invoice')
86 journal_pool = self.pool.get('account.journal')
87 if context.get('invoice_id', False):
88 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
89 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
90 return journal_id and journal_id[0] or False
91 if context.get('journal_id', False):
92 return context.get('journal_id')
93 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
94 return context.get('search_default_journal_id')
96 ttype = context.get('type', 'bank')
97 if ttype in ('payment', 'receipt'):
99 res = self._make_journal_search(cr, uid, ttype, context=context)
100 return res and res[0] or False
102 def _get_tax(self, cr, uid, context=None):
103 if context is None: context = {}
104 journal_pool = self.pool.get('account.journal')
105 journal_id = context.get('journal_id', False)
107 ttype = context.get('type', 'bank')
108 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
115 journal = journal_pool.browse(cr, uid, journal_id, context=context)
116 account_id = journal.default_credit_account_id or journal.default_debit_account_id
117 if account_id and account_id.tax_ids:
118 tax_id = account_id.tax_ids[0].id
122 def _get_payment_rate_currency(self, cr, uid, context=None):
124 Return the default value for field payment_rate_currency_id: the currency of the journal
125 if there is one, otherwise the currency of the user's company
127 if context is None: context = {}
128 journal_pool = self.pool.get('account.journal')
129 journal_id = context.get('journal_id', False)
131 journal = journal_pool.browse(cr, uid, journal_id, context=context)
133 return journal.currency.id
134 #no journal given in the context, use company currency as default
135 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
137 def _get_currency(self, cr, uid, context=None):
138 if context is None: context = {}
139 journal_pool = self.pool.get('account.journal')
140 journal_id = context.get('journal_id', False)
142 journal = journal_pool.browse(cr, uid, journal_id, context=context)
144 return journal.currency.id
147 def _get_partner(self, cr, uid, context=None):
148 if context is None: context = {}
149 return context.get('partner_id', False)
151 def _get_reference(self, cr, uid, context=None):
152 if context is None: context = {}
153 return context.get('reference', False)
155 def _get_narration(self, cr, uid, context=None):
156 if context is None: context = {}
157 return context.get('narration', False)
159 def _get_amount(self, cr, uid, context=None):
162 return context.get('amount', 0.0)
164 def name_get(self, cr, uid, ids, context=None):
167 if context is None: context = {}
168 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
170 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
171 mod_obj = self.pool.get('ir.model.data')
172 if context is None: context = {}
174 if view_type == 'form':
175 if not view_id and context.get('invoice_type'):
176 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
177 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
179 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
180 result = result and result[1] or False
182 if not view_id and context.get('line_type'):
183 if context.get('line_type') == 'customer':
184 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
186 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
187 result = result and result[1] or False
190 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
191 doc = etree.XML(res['arch'])
193 if context.get('type', 'sale') in ('purchase', 'payment'):
194 nodes = doc.xpath("//field[@name='partner_id']")
196 node.set('domain', "[('supplier', '=', True)]")
197 res['arch'] = etree.tostring(doc)
200 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
202 sign = type == 'payment' and -1 or 1
203 for l in line_dr_ids:
205 for l in line_cr_ids:
206 credit += l['amount']
207 return amount - sign * (credit - debit)
209 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
210 context = context or {}
211 if not line_dr_ids and not line_cr_ids:
213 line_osv = self.pool.get("account.voucher.line")
214 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
215 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
217 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
218 is_multi_currency = False
220 # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
221 is_multi_currency = True
223 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
224 for voucher_line in line_dr_ids+line_cr_ids:
225 company_currency = False
226 company_currency = voucher_line.get('move_line_id', False) and self.pool.get('account.move.line').browse(cr, uid, voucher_line.get('move_line_id'), context=context).company_id.currency_id.id
227 if voucher_line.get('currency_id', company_currency) != company_currency:
228 is_multi_currency = True
230 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
232 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
233 if not ids: return {}
234 currency_obj = self.pool.get('res.currency')
237 for voucher in self.browse(cr, uid, ids, context=context):
238 sign = voucher.type == 'payment' and -1 or 1
239 for l in voucher.line_dr_ids:
241 for l in voucher.line_cr_ids:
243 currency = voucher.currency_id or voucher.company_id.currency_id
244 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
247 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
248 if not ids: return {}
251 for voucher in self.browse(cr, uid, ids, context=context):
252 if voucher.currency_id:
253 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
254 rate = 1 / voucher.payment_rate
257 ctx.update({'date': voucher.date})
258 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
259 company_currency_rate = voucher.company_id.currency_id.rate
260 rate = voucher_rate * company_currency_rate
261 res[voucher.id] = voucher.amount / rate
264 _name = 'account.voucher'
265 _description = 'Accounting Voucher'
266 _inherit = ['mail.thread']
267 _order = "date desc, id desc"
268 # _rec_name = 'number'
270 'active': fields.boolean('Active', help="By default, reconciliation vouchers made on draft bank statements are set as inactive, which allow to hide the customer/supplier payment while the bank statement isn't confirmed."),
271 'type':fields.selection([
273 ('purchase','Purchase'),
274 ('payment','Payment'),
275 ('receipt','Receipt'),
276 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
277 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
278 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
279 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
280 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
281 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
282 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
283 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
284 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
285 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
286 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
287 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
288 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
289 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
290 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
291 'state':fields.selection(
293 ('cancel','Cancelled'),
294 ('proforma','Pro-forma'),
296 ], 'Status', readonly=True, size=32,
297 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
298 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
299 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
300 \n* The \'Cancelled\' status is used when user cancel voucher.'),
301 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
302 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
303 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
304 'number': fields.char('Number', size=32, readonly=True,),
305 'move_id':fields.many2one('account.move', 'Account Entry'),
306 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
307 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
308 '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'),
309 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
310 'pay_now':fields.selection([
311 ('pay_now','Pay Directly'),
312 ('pay_later','Pay Later or Group Funds'),
313 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
314 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
315 'pre_line':fields.boolean('Previous Payments ?', required=False),
316 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
317 'payment_option':fields.selection([
318 ('without_writeoff', 'Keep Open'),
319 ('with_writeoff', 'Reconcile Payment Balance'),
320 ], '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)"),
321 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
322 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
323 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
324 '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."),
325 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
326 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
327 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
328 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
329 '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'),
333 'period_id': _get_period,
334 'partner_id': _get_partner,
335 'journal_id':_get_journal,
336 'currency_id': _get_currency,
337 'reference': _get_reference,
338 'narration':_get_narration,
339 'amount': _get_amount,
342 'pay_now': 'pay_now',
344 'date': lambda *a: time.strftime('%Y-%m-%d'),
345 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
347 'payment_option': 'without_writeoff',
348 'comment': _('Write-Off'),
350 'payment_rate_currency_id': _get_payment_rate_currency,
353 def create(self, cr, uid, vals, context=None):
354 voucher = super(account_voucher, self).create(cr, uid, vals, context=context)
355 self.create_send_note(cr, uid, [voucher], context=context)
358 def compute_tax(self, cr, uid, ids, context=None):
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 voucher_line_pool = self.pool.get('account.voucher.line')
363 voucher_pool = self.pool.get('account.voucher')
364 if context is None: context = {}
366 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
368 for line in voucher.line_ids:
369 voucher_amount += line.untax_amount or line.amount
370 line.amount = line.untax_amount or line.amount
371 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
373 if not voucher.tax_id:
374 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
377 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
378 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
379 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
380 tax = tax_pool.browse(cr, uid, taxes, context=context)
382 total = voucher_amount
385 if not tax[0].price_include:
386 for line in voucher.line_ids:
387 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
388 total_tax += tax_line.get('amount', 0.0)
391 for line in voucher.line_ids:
395 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
396 line_tax += tax_line.get('amount', 0.0)
397 line_total += tax_line.get('price_unit')
398 total_tax += line_tax
399 untax_amount = line.untax_amount or line.amount
400 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
402 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
405 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
406 context = context or {}
407 tax_pool = self.pool.get('account.tax')
408 partner_pool = self.pool.get('res.partner')
409 position_pool = self.pool.get('account.fiscal.position')
410 line_pool = self.pool.get('account.voucher.line')
417 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
420 for line in line_ids:
422 line_amount = line.get('amount',0.0)
425 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
427 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
428 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
429 tax = tax_pool.browse(cr, uid, taxes, context=context)
431 if not tax[0].price_include:
432 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
433 total_tax += tax_line.get('amount')
435 voucher_total += line_amount
436 total = voucher_total + total_tax
439 'amount': total or voucher_total,
440 'tax_amount': total_tax
446 def onchange_term_id(self, cr, uid, ids, term_id, amount):
447 term_pool = self.pool.get('account.payment.term')
450 default = {'date_due':False}
451 if term_id and amount:
452 terms = term_pool.compute(cr, uid, term_id, amount)
454 due_date = terms[-1][0]
458 return {'value':default}
460 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):
462 Returns a dict that contains new values and context
464 @param partner_id: latest value from user input for field partner_id
465 @param args: other arguments
466 @param context: context arguments, like lang, time zone
468 @return: Returns a dict which contains new values, and context
474 if not partner_id or not journal_id:
477 partner_pool = self.pool.get('res.partner')
478 journal_pool = self.pool.get('account.journal')
480 journal = journal_pool.browse(cr, uid, journal_id, context=context)
481 partner = partner_pool.browse(cr, uid, partner_id, context=context)
484 if journal.type in ('sale','sale_refund'):
485 account_id = partner.property_account_receivable.id
487 elif journal.type in ('purchase', 'purchase_refund','expense'):
488 account_id = partner.property_account_payable.id
491 if not journal.default_credit_account_id or not journal.default_debit_account_id:
492 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
493 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
496 default['value']['account_id'] = account_id
497 default['value']['type'] = ttype or tr_type
499 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)
500 default['value'].update(vals.get('value'))
504 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
505 res = {'value': {'paid_amount_in_company_currency': amount}}
506 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
507 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
508 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
509 if company_currency.id == payment_rate_currency_id:
512 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
513 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
516 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):
519 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
521 ctx.update({'date': date})
522 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
523 for key in vals.keys():
524 res[key].update(vals[key])
527 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
530 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
531 currency_obj = self.pool.get('res.currency')
532 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
533 company_id = journal.company_id.id
535 payment_rate_currency_id = currency_id
537 ctx.update({'date': date})
539 if ttype == 'receipt':
540 o2m_to_loop = 'line_cr_ids'
541 elif ttype == 'payment':
542 o2m_to_loop = 'line_dr_ids'
543 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
544 for voucher_line in vals['value'][o2m_to_loop]:
545 if voucher_line['currency_id'] != currency_id:
546 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
547 # is not in the voucher currency
548 payment_rate_currency_id = voucher_line['currency_id']
549 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
550 voucher_currency_id = currency_id or journal.company_id.currency_id.id
551 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
553 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
554 for key in res.keys():
555 vals[key].update(res[key])
556 vals['value'].update({'payment_rate': payment_rate})
557 if payment_rate_currency_id:
558 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
561 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
564 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
565 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
566 for key in vals.keys():
567 res[key].update(vals[key])
568 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
569 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
570 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
571 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
572 # onchange returns a value for them
574 del(res['value']['line_dr_ids'])
575 del(res['value']['pre_line'])
576 del(res['value']['payment_rate'])
577 elif ttype == 'purchase':
578 del(res['value']['line_cr_ids'])
579 del(res['value']['pre_line'])
580 del(res['value']['payment_rate'])
583 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
585 Returns a dict that contains new values and context
587 @param partner_id: latest value from user input for field partner_id
588 @param args: other arguments
589 @param context: context arguments, like lang, time zone
591 @return: Returns a dict which contains new values, and context
593 def _remove_noise_in_o2m():
594 """if the line is partially reconciled, then we must pay attention to display it only once and
596 This function returns True if the line is considered as noise and should not be displayed
598 if line.reconcile_partial_id:
599 sign = 1 if ttype == 'receipt' else -1
600 if currency_id == line.currency_id.id:
601 if line.amount_residual_currency * sign <= 0:
604 if line.amount_residual * sign <= 0:
610 context_multi_currency = context.copy()
612 context_multi_currency.update({'date': date})
614 currency_pool = self.pool.get('res.currency')
615 move_line_pool = self.pool.get('account.move.line')
616 partner_pool = self.pool.get('res.partner')
617 journal_pool = self.pool.get('account.journal')
618 line_pool = self.pool.get('account.voucher.line')
622 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
626 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
628 line_pool.unlink(cr, uid, line_ids)
630 if not partner_id or not journal_id:
633 journal = journal_pool.browse(cr, uid, journal_id, context=context)
634 partner = partner_pool.browse(cr, uid, partner_id, context=context)
635 currency_id = currency_id or journal.company_id.currency_id.id
637 if journal.type in ('sale','sale_refund'):
638 account_id = partner.property_account_receivable.id
639 elif journal.type in ('purchase', 'purchase_refund','expense'):
640 account_id = partner.property_account_payable.id
642 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
644 default['value']['account_id'] = account_id
646 if journal.type not in ('cash', 'bank'):
651 account_type = 'receivable'
652 if ttype == 'payment':
653 account_type = 'payable'
654 total_debit = price or 0.0
656 total_credit = price or 0.0
657 account_type = 'receivable'
659 if not context.get('move_line_ids', False):
660 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
662 ids = context['move_line_ids']
663 invoice_id = context.get('invoice_id', False)
664 company_currency = journal.company_id.currency_id.id
665 move_line_found = False
667 #order the lines by most old first
669 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
671 #compute the total debit/credit and look for a matching open amount or invoice
672 for line in account_move_lines:
673 if _remove_noise_in_o2m():
677 if line.invoice.id == invoice_id:
678 #if the invoice linked to the voucher line is equal to the invoice_id in context
679 #then we assign the amount on that line, whatever the other voucher lines
680 move_line_found = line.id
682 elif currency_id == company_currency:
683 #otherwise treatments is the same but with other field names
684 if line.amount_residual == price:
685 #if the amount residual is equal the amount voucher, we assign it to that voucher
686 #line, whatever the other voucher lines
687 move_line_found = line.id
689 #otherwise we will split the voucher amount on each line (by most old first)
690 total_credit += line.credit or 0.0
691 total_debit += line.debit or 0.0
692 elif currency_id == line.currency_id.id:
693 if line.amount_residual_currency == price:
694 move_line_found = line.id
696 total_credit += line.credit and line.amount_currency or 0.0
697 total_debit += line.debit and line.amount_currency or 0.0
699 #voucher line creation
700 for line in account_move_lines:
702 if _remove_noise_in_o2m():
705 if line.currency_id and currency_id==line.currency_id.id:
706 amount_original = abs(line.amount_currency)
707 amount_unreconciled = abs(line.amount_residual_currency)
709 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
710 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
711 line_currency_id = line.currency_id and line.currency_id.id or company_currency
713 'name':line.move_id.name,
714 'type': line.credit and 'dr' or 'cr',
715 'move_line_id':line.id,
716 'account_id':line.account_id.id,
717 'amount_original': amount_original,
718 'amount': (move_line_found == line.id) and min(abs(price), amount_unreconciled) or 0.0,
719 'date_original':line.date,
720 'date_due':line.date_maturity,
721 'amount_unreconciled': amount_unreconciled,
722 'currency_id': line_currency_id,
724 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
725 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
726 if not move_line_found:
727 if currency_id == line_currency_id:
729 amount = min(amount_unreconciled, abs(total_debit))
730 rs['amount'] = amount
731 total_debit -= amount
733 amount = min(amount_unreconciled, abs(total_credit))
734 rs['amount'] = amount
735 total_credit -= amount
737 if rs['amount_unreconciled'] == rs['amount']:
738 rs['reconcile'] = True
740 if rs['type'] == 'cr':
741 default['value']['line_cr_ids'].append(rs)
743 default['value']['line_dr_ids'].append(rs)
745 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
746 default['value']['pre_line'] = 1
747 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
748 default['value']['pre_line'] = 1
749 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
752 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
756 #set the default payment rate of the voucher and compute the paid amount in company currency
757 if currency_id and currency_id == payment_rate_currency_id:
759 ctx.update({'date': date})
760 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
761 for key in vals.keys():
762 res[key].update(vals[key])
765 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
767 @param date: latest value from user input for field date
768 @param args: other arguments
769 @param context: context arguments, like lang, time zone
770 @return: Returns a dict which contains new values, and context
775 #set the period of the voucher
776 period_pool = self.pool.get('account.period')
777 currency_obj = self.pool.get('res.currency')
779 ctx.update({'company_id': company_id})
780 pids = period_pool.find(cr, uid, date, context=ctx)
782 res['value'].update({'period_id':pids[0]})
783 if payment_rate_currency_id:
784 ctx.update({'date': date})
786 if payment_rate_currency_id != currency_id:
787 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
788 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
789 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
790 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
791 vals['value'].update({'payment_rate': payment_rate})
792 for key in vals.keys():
793 res[key].update(vals[key])
796 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
799 journal_pool = self.pool.get('account.journal')
800 journal = journal_pool.browse(cr, uid, journal_id, context=context)
801 account_id = journal.default_credit_account_id or journal.default_debit_account_id
803 if account_id and account_id.tax_ids:
804 tax_id = account_id.tax_ids[0].id
807 if ttype in ('sale', 'purchase'):
808 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
809 vals['value'].update({'tax_id':tax_id,'amount': amount})
812 currency_id = journal.currency.id
813 vals['value'].update({'currency_id': currency_id})
814 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
815 for key in res.keys():
816 vals[key].update(res[key])
819 def button_proforma_voucher(self, cr, uid, ids, context=None):
820 context = context or {}
821 wf_service = netsvc.LocalService("workflow")
823 wf_service.trg_validate(uid, 'account.voucher', vid, 'proforma_voucher', cr)
824 return {'type': 'ir.actions.act_window_close'}
826 def proforma_voucher(self, cr, uid, ids, context=None):
827 self.action_move_line_create(cr, uid, ids, context=context)
830 def action_cancel_draft(self, cr, uid, ids, context=None):
831 wf_service = netsvc.LocalService("workflow")
832 for voucher_id in ids:
833 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
834 self.write(cr, uid, ids, {'state':'draft'})
837 def cancel_voucher(self, cr, uid, ids, context=None):
838 reconcile_pool = self.pool.get('account.move.reconcile')
839 move_pool = self.pool.get('account.move')
841 for voucher in self.browse(cr, uid, ids, context=context):
843 for line in voucher.move_ids:
844 if line.reconcile_id:
845 recs += [line.reconcile_id.id]
846 if line.reconcile_partial_id:
847 recs += [line.reconcile_partial_id.id]
849 reconcile_pool.unlink(cr, uid, recs)
852 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
853 move_pool.unlink(cr, uid, [voucher.move_id.id])
858 self.write(cr, uid, ids, res)
861 def unlink(self, cr, uid, ids, context=None):
862 for t in self.read(cr, uid, ids, ['state'], context=context):
863 if t['state'] not in ('draft', 'cancel'):
864 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
865 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
867 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
871 res = {'account_id':False}
872 partner_pool = self.pool.get('res.partner')
873 journal_pool = self.pool.get('account.journal')
874 if pay_now == 'pay_later':
875 partner = partner_pool.browse(cr, uid, partner_id)
876 journal = journal_pool.browse(cr, uid, journal_id)
877 if journal.type in ('sale','sale_refund'):
878 account_id = partner.property_account_receivable.id
879 elif journal.type in ('purchase', 'purchase_refund','expense'):
880 account_id = partner.property_account_payable.id
882 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
883 res['account_id'] = account_id
886 def _sel_context(self, cr, uid, voucher_id, context=None):
888 Select the context to use accordingly if it needs to be multicurrency or not.
890 :param voucher_id: Id of the actual voucher
891 :return: The returned context will be the same as given in parameter if the voucher currency is the same
892 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
893 the date of the voucher.
896 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
897 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
898 if current_currency <> company_currency:
899 context_multi_currency = context.copy()
900 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
901 context_multi_currency.update({'date': voucher_brw.date})
902 return context_multi_currency
905 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
907 Return a dict to be use to create the first account move line of given voucher.
909 :param voucher_id: Id of voucher what we are creating account_move.
910 :param move_id: Id of account move where this line will be added.
911 :param company_currency: id of currency of the company to which the voucher belong
912 :param current_currency: id of currency of the voucher
913 :return: mapping between fieldname and value of account move line to create
916 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
918 # TODO: is there any other alternative then the voucher type ??
919 # ANSWER: We can have payment and receipt "In Advance".
920 # TODO: Make this logic available.
921 # -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
922 if voucher_brw.type in ('purchase', 'payment'):
923 credit = voucher_brw.paid_amount_in_company_currency
924 elif voucher_brw.type in ('sale', 'receipt'):
925 debit = voucher_brw.paid_amount_in_company_currency
926 if debit < 0: credit = -debit; debit = 0.0
927 if credit < 0: debit = -credit; credit = 0.0
928 sign = debit - credit < 0 and -1 or 1
929 #set the first line of the voucher
931 'name': voucher_brw.name or '/',
934 'account_id': voucher_brw.account_id.id,
936 'journal_id': voucher_brw.journal_id.id,
937 'period_id': voucher_brw.period_id.id,
938 'partner_id': voucher_brw.partner_id.id,
939 'currency_id': company_currency <> current_currency and current_currency or False,
940 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
941 'date': voucher_brw.date,
942 'date_maturity': voucher_brw.date_due
946 def account_move_get(self, cr, uid, voucher_id, context=None):
948 This method prepare the creation of the account move related to the given voucher.
950 :param voucher_id: Id of voucher for which we are creating account_move.
951 :return: mapping between fieldname and value of account move to create
954 seq_obj = self.pool.get('ir.sequence')
955 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
956 if voucher_brw.number:
957 name = voucher_brw.number
958 elif voucher_brw.journal_id.sequence_id:
959 if not voucher_brw.journal_id.sequence_id.active:
960 raise osv.except_osv(_('Configuration Error !'),
961 _('Please activate the sequence of selected journal !'))
962 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
964 raise osv.except_osv(_('Error!'),
965 _('Please define a sequence on the journal.'))
966 if not voucher_brw.reference:
967 ref = name.replace('/','')
969 ref = voucher_brw.reference
973 'journal_id': voucher_brw.journal_id.id,
974 'narration': voucher_brw.narration,
975 'date': voucher_brw.date,
977 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
981 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
983 Prepare the two lines in company currency due to currency rate difference.
985 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
987 :param move_id: Account move wher the move lines will be.
988 :param amount_residual: Amount to be posted.
989 :param company_currency: id of currency of the company to which the voucher belong
990 :param current_currency: id of currency of the voucher
991 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
992 :rtype: tuple of dict
994 if amount_residual > 0:
995 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
997 raise osv.except_osv(_('Insufficient Configuration!'),_("You should configure the 'Loss Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
999 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1001 raise osv.except_osv(_('Insufficient Configuration!'),_("You should configure the 'Gain Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
1002 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1003 # the receivable/payable account may have a secondary currency, which render this field mandatory
1004 account_currency_id = company_currency <> current_currency and current_currency or False
1006 'journal_id': line.voucher_id.journal_id.id,
1007 'period_id': line.voucher_id.period_id.id,
1008 'name': _('change')+': '+(line.name or '/'),
1009 'account_id': line.account_id.id,
1011 'partner_id': line.voucher_id.partner_id.id,
1012 'currency_id': account_currency_id,
1013 'amount_currency': 0.0,
1015 'credit': amount_residual > 0 and amount_residual or 0.0,
1016 'debit': amount_residual < 0 and -amount_residual or 0.0,
1017 'date': line.voucher_id.date,
1019 move_line_counterpart = {
1020 'journal_id': line.voucher_id.journal_id.id,
1021 'period_id': line.voucher_id.period_id.id,
1022 'name': _('change')+': '+(line.name or '/'),
1023 'account_id': account_id.id,
1025 'amount_currency': 0.0,
1026 'partner_id': line.voucher_id.partner_id.id,
1027 'currency_id': account_currency_id,
1029 'debit': amount_residual > 0 and amount_residual or 0.0,
1030 'credit': amount_residual < 0 and -amount_residual or 0.0,
1031 'date': line.voucher_id.date,
1033 return (move_line, move_line_counterpart)
1035 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1037 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1038 payment_rate_currency_id is relevant) either the rate encoded in the system.
1040 :param amount: float. The amount to convert
1041 :param voucher: id of the voucher on which we want the conversion
1042 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1043 field in order to select the good rate to use.
1044 :return: the amount in the currency of the voucher's company
1047 currency_obj = self.pool.get('res.currency')
1048 voucher = self.browse(cr, uid, voucher_id, context=context)
1050 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1051 # the rate specified on the voucher is for the company currency
1052 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1054 # the rate specified on the voucher is not relevant, we use all the rates in the system
1055 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1058 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1060 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1061 It returns Tuple with tot_line what is total of difference between debit and credit and
1062 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1064 :param voucher_id: Voucher id what we are working with
1065 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1066 :param move_id: Account move wher those lines will be joined.
1067 :param company_currency: id of currency of the company to which the voucher belong
1068 :param current_currency: id of currency of the voucher
1069 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1070 :rtype: tuple(float, list of int)
1074 move_line_obj = self.pool.get('account.move.line')
1075 currency_obj = self.pool.get('res.currency')
1076 tax_obj = self.pool.get('account.tax')
1077 tot_line = line_total
1080 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1081 ctx = context.copy()
1082 ctx.update({'date': voucher_brw.date})
1083 for line in voucher_brw.line_ids:
1084 #create one move line per voucher line where amount is not 0.0
1087 # convert the amount set on the voucher line into the currency of the voucher's company
1088 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1089 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1090 # currency rate difference
1091 if line.amount == line.amount_unreconciled:
1092 if not line.move_line_id.amount_residual:
1093 raise osv.except_osv(_('Wrong bank statement line'),_("You have to delete the bank statement line which the payment was reconciled to manually. Please check the payment of the partner %s by the amount of %s.")%(line.voucher_id.partner_id.name, line.voucher_id.amount))
1094 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1095 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1097 currency_rate_difference = 0.0
1099 'journal_id': voucher_brw.journal_id.id,
1100 'period_id': voucher_brw.period_id.id,
1101 'name': line.name or '/',
1102 'account_id': line.account_id.id,
1104 'partner_id': voucher_brw.partner_id.id,
1105 '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,
1106 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1110 'date': voucher_brw.date
1114 if line.type == 'dr':
1119 if (line.type=='dr'):
1121 move_line['debit'] = amount
1124 move_line['credit'] = amount
1126 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1128 'account_tax_id': voucher_brw.tax_id.id,
1131 if move_line.get('account_tax_id', False):
1132 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1133 if not (tax_data.base_code_id and tax_data.tax_code_id):
1134 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))
1136 # compute the amount in foreign currency
1137 foreign_currency_diff = 0.0
1138 amount_currency = False
1139 if line.move_line_id:
1140 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1141 # We want to set it on the account move line as soon as the original line had a foreign currency
1142 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1143 # we compute the amount in that foreign currency.
1144 if line.move_line_id.currency_id.id == current_currency:
1145 # if the voucher and the voucher line share the same currency, there is no computation to do
1146 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1147 amount_currency = sign * (line.amount)
1148 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1149 # if the rate is specified on the voucher, we must use it
1150 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1151 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1153 # otherwise we use the rates of the system (giving the voucher date in the context)
1154 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1155 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1156 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1157 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1159 move_line['amount_currency'] = amount_currency
1160 voucher_line = move_line_obj.create(cr, uid, move_line)
1161 rec_ids = [voucher_line, line.move_line_id.id]
1163 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1164 # Change difference entry in company currency
1165 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1166 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1167 move_line_obj.create(cr, uid, exch_lines[1], context)
1168 rec_ids.append(new_id)
1170 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):
1171 # Change difference entry in voucher currency
1172 move_line_foreign_currency = {
1173 'journal_id': line.voucher_id.journal_id.id,
1174 'period_id': line.voucher_id.period_id.id,
1175 'name': _('change')+': '+(line.name or '/'),
1176 'account_id': line.account_id.id,
1178 'partner_id': line.voucher_id.partner_id.id,
1179 'currency_id': line.move_line_id.currency_id.id,
1180 'amount_currency': -1 * foreign_currency_diff,
1184 'date': line.voucher_id.date,
1186 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1187 rec_ids.append(new_id)
1189 if line.move_line_id.id:
1190 rec_lst_ids.append(rec_ids)
1192 return (tot_line, rec_lst_ids)
1194 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1196 Set a dict to be use to create the writeoff move line.
1198 :param voucher_id: Id of voucher what we are creating account_move.
1199 :param line_total: Amount remaining to be allocated on lines.
1200 :param move_id: Id of account move where this line will be added.
1201 :param name: Description of account move line.
1202 :param company_currency: id of currency of the company to which the voucher belong
1203 :param current_currency: id of currency of the voucher
1204 :return: mapping between fieldname and value of account move line to create
1207 currency_obj = self.pool.get('res.currency')
1210 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1211 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1213 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1217 if voucher_brw.payment_option == 'with_writeoff':
1218 account_id = voucher_brw.writeoff_acc_id.id
1219 write_off_name = voucher_brw.comment
1220 elif voucher_brw.type in ('sale', 'receipt'):
1221 account_id = voucher_brw.partner_id.property_account_receivable.id
1223 account_id = voucher_brw.partner_id.property_account_payable.id
1224 sign = voucher_brw.type == 'payment' and -1 or 1
1226 'name': write_off_name or name,
1227 'account_id': account_id,
1229 'partner_id': voucher_brw.partner_id.id,
1230 'date': voucher_brw.date,
1231 'credit': diff > 0 and diff or 0.0,
1232 'debit': diff < 0 and -diff or 0.0,
1233 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher_brw.writeoff_amount) or False,
1234 'currency_id': company_currency <> current_currency and current_currency or False,
1235 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1240 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1242 Get the currency of the actual company.
1244 :param voucher_id: Id of the voucher what i want to obtain company currency.
1245 :return: currency id of the company of the voucher
1248 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1250 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1252 Get the currency of the voucher.
1254 :param voucher_id: Id of the voucher what i want to obtain current currency.
1255 :return: currency id of the voucher
1258 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1259 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1261 def action_move_line_create(self, cr, uid, ids, context=None):
1263 Confirm the vouchers given in ids and create the journal entries for each of them
1267 move_pool = self.pool.get('account.move')
1268 move_line_pool = self.pool.get('account.move.line')
1269 for voucher in self.browse(cr, uid, ids, context=context):
1272 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1273 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1274 # we select the context to use accordingly if it's a multicurrency case or not
1275 context = self._sel_context(cr, uid, voucher.id, context)
1276 # But for the operations made by _convert_amount, we always need to give the date in the context
1277 ctx = context.copy()
1278 ctx.update({'date': voucher.date})
1279 # Create the account move record.
1280 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1281 # Get the name of the account_move just created
1282 name = move_pool.browse(cr, uid, move_id, context=context).name
1283 # Create the first line of the voucher
1284 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)
1285 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1286 line_total = move_line_brw.debit - move_line_brw.credit
1288 if voucher.type == 'sale':
1289 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1290 elif voucher.type == 'purchase':
1291 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1292 # Create one move line per voucher line where amount is not 0.0
1293 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1295 # Create the writeoff line if needed
1296 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1298 move_line_pool.create(cr, uid, ml_writeoff, context)
1299 # We post the voucher.
1300 self.write(cr, uid, [voucher.id], {
1305 self.post_send_note(cr, uid, [voucher.id], context=context)
1306 if voucher.journal_id.entry_posted:
1307 move_pool.post(cr, uid, [move_id], context={})
1308 # We automatically reconcile the account move lines.
1310 for rec_ids in rec_list_ids:
1311 if len(rec_ids) >= 2:
1312 reconcile = 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)
1314 self.reconcile_send_note(cr, uid, [voucher.id], context=context)
1317 def copy(self, cr, uid, id, default=None, context=None):
1324 'line_cr_ids': False,
1325 'line_dr_ids': False,
1328 if 'date' not in default:
1329 default['date'] = time.strftime('%Y-%m-%d')
1330 return super(account_voucher, self).copy(cr, uid, id, default, context)
1332 # -----------------------------------------
1333 # OpenChatter notifications and need_action
1334 # -----------------------------------------
1336 'sale': 'Sales Receipt',
1337 'purchase': 'Purchase Receipt',
1338 'payment': 'Supplier Payment',
1339 'receipt': 'Customer Payment',
1343 def create_send_note(self, cr, uid, ids, context=None):
1344 for obj in self.browse(cr, uid, ids, context=context):
1345 message = "%s <b>created</b>." % self._document_type[obj.type or False]
1346 self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
1348 def post_send_note(self, cr, uid, ids, context=None):
1349 for obj in self.browse(cr, uid, ids, context=context):
1350 message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
1351 self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
1353 def reconcile_send_note(self, cr, uid, ids, context=None):
1354 for obj in self.browse(cr, uid, ids, context=context):
1355 message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
1356 self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
1360 class account_voucher_line(osv.osv):
1361 _name = 'account.voucher.line'
1362 _description = 'Voucher Lines'
1363 _order = "move_line_id"
1365 # If the payment is in the same currency than the invoice, we keep the same amount
1366 # Otherwise, we compute from company currency to payment currency
1367 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1368 currency_pool = self.pool.get('res.currency')
1370 for line in self.browse(cr, uid, ids, context=context):
1371 ctx = context.copy()
1372 ctx.update({'date': line.voucher_id.date})
1374 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1375 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1376 move_line = line.move_line_id or False
1379 res['amount_original'] = 0.0
1380 res['amount_unreconciled'] = 0.0
1381 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1382 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1383 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)
1384 elif move_line and move_line.credit > 0:
1385 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1386 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1388 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1389 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1391 rs_data[line.id] = res
1394 def _currency_id(self, cr, uid, ids, name, args, context=None):
1396 This function returns the currency id of a voucher line. It's either the currency of the
1397 associated move line (if any) or the currency of the voucher or the company currency.
1400 for line in self.browse(cr, uid, ids, context=context):
1401 move_line = line.move_line_id
1403 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1405 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1409 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1410 'name':fields.char('Description', size=256),
1411 'account_id':fields.many2one('account.account','Account', required=True),
1412 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1413 'untax_amount':fields.float('Untax Amount'),
1414 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1415 'reconcile': fields.boolean('Full Reconcile'),
1416 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1417 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1418 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1419 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1420 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1421 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1422 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1423 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1424 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1430 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1431 vals = {'amount': 0.0}
1433 vals = { 'amount': amount_unreconciled}
1434 return {'value': vals}
1436 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1439 vals['reconcile'] = (amount == amount_unreconciled)
1440 return {'value': vals}
1442 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1444 Returns a dict that contains new values and context
1446 @param move_line_id: latest value from user input for field move_line_id
1447 @param args: other arguments
1448 @param context: context arguments, like lang, time zone
1450 @return: Returns a dict which contains new values, and context
1453 move_line_pool = self.pool.get('account.move.line')
1455 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1456 if move_line.credit:
1461 'account_id': move_line.account_id.id,
1463 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1469 def default_get(self, cr, user, fields_list, context=None):
1471 Returns default values for fields
1472 @param fields_list: list of fields, for which default values are required to be read
1473 @param context: context arguments, like lang, time zone
1475 @return: Returns a dict that contains default values for fields
1479 journal_id = context.get('journal_id', False)
1480 partner_id = context.get('partner_id', False)
1481 journal_pool = self.pool.get('account.journal')
1482 partner_pool = self.pool.get('res.partner')
1483 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1484 if (not journal_id) or ('account_id' not in fields_list):
1486 journal = journal_pool.browse(cr, user, journal_id, context=context)
1489 if journal.type in ('sale', 'sale_refund'):
1490 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1492 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1493 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1496 partner = partner_pool.browse(cr, user, partner_id, context=context)
1497 if context.get('type') == 'payment':
1499 account_id = partner.property_account_payable.id
1500 elif context.get('type') == 'receipt':
1501 account_id = partner.property_account_receivable.id
1504 'account_id':account_id,
1508 account_voucher_line()
1510 class account_bank_statement(osv.osv):
1511 _inherit = 'account.bank.statement'
1513 def button_confirm_bank(self, cr, uid, ids, context=None):
1514 voucher_obj = self.pool.get('account.voucher')
1516 for statement in self.browse(cr, uid, ids, context=context):
1517 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1519 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1520 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1522 def button_cancel(self, cr, uid, ids, context=None):
1523 voucher_obj = self.pool.get('account.voucher')
1524 for st in self.browse(cr, uid, ids, context=context):
1526 for line in st.line_ids:
1528 voucher_ids.append(line.voucher_id.id)
1529 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1530 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1532 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1533 voucher_obj = self.pool.get('account.voucher')
1534 wf_service = netsvc.LocalService("workflow")
1535 move_line_obj = self.pool.get('account.move.line')
1536 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1537 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1538 if st_line.voucher_id:
1539 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1540 if st_line.voucher_id.state == 'cancel':
1541 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1542 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1544 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1545 bank_st_line_obj.write(cr, uid, [st_line_id], {
1546 'move_ids': [(4, v.move_id.id, False)]
1549 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1550 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1552 account_bank_statement()
1554 class account_bank_statement_line(osv.osv):
1555 _inherit = 'account.bank.statement.line'
1557 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1558 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1559 if 'value' not in res:
1561 res['value'].update({'voucher_id' : False})
1564 def onchange_amount(self, cr, uid, ids, amount, context=None):
1565 return {'value' : {'voucher_id' : False}}
1567 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1571 for line in self.browse(cursor, user, ids, context=context):
1573 res[line.id] = line.voucher_id.amount#
1578 def _check_amount(self, cr, uid, ids, context=None):
1579 for obj in self.browse(cr, uid, ids, context=context):
1581 diff = abs(obj.amount) - obj.voucher_id.amount
1582 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1587 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1591 'amount_reconciled': fields.function(_amount_reconciled,
1592 string='Amount reconciled', type='float'),
1593 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1596 def unlink(self, cr, uid, ids, context=None):
1597 voucher_obj = self.pool.get('account.voucher')
1598 statement_line = self.browse(cr, uid, ids, context=context)
1600 for st_line in statement_line:
1601 if st_line.voucher_id:
1602 unlink_ids.append(st_line.voucher_id.id)
1603 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1604 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1606 account_bank_statement_line()
1608 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1610 for operation in operations:
1612 if not isinstance(operation, (list, tuple)):
1613 result = target_osv.read(cr, uid, operation, fields, context=context)
1614 elif operation[0] == 0:
1615 # may be necessary to check if all the fields are here and get the default values?
1616 result = operation[2]
1617 elif operation[0] == 1:
1618 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1619 if not result: result = {}
1620 result.update(operation[2])
1621 elif operation[0] == 4:
1622 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1624 results.append(result)
1628 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: