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
25 from openerp import netsvc
26 from openerp.osv import fields, osv
27 import openerp.addons.decimal_precision as dp
28 from openerp.tools.translate import _
29 from openerp.tools import float_compare
30 from openerp.report import report_sxw
32 class res_currency(osv.osv):
33 _inherit = "res.currency"
35 def _current_rate(self, cr, uid, ids, name, arg, context=None):
38 res = super(res_currency, self)._current_rate(cr, uid, ids, name, arg, context=context)
39 if context.get('voucher_special_currency') in ids and context.get('voucher_special_currency_rate'):
40 res[context.get('voucher_special_currency')] = context.get('voucher_special_currency_rate')
44 # same definition of rate that in base in order to just overwrite the function
45 'rate': fields.function(_current_rate, string='Current Rate', digits=(12,6),
46 help='The rate of the currency to the currency of rate 1.'),
50 class res_company(osv.osv):
51 _inherit = "res.company"
53 'income_currency_exchange_account_id': fields.many2one(
55 string="Gain Exchange Rate Account",
56 domain="[('type', '=', 'other')]",),
57 'expense_currency_exchange_account_id': fields.many2one(
59 string="Loss Exchange Rate Account",
60 domain="[('type', '=', 'other')]",),
65 class account_config_settings(osv.osv_memory):
66 _inherit = 'account.config.settings'
68 'income_currency_exchange_account_id': fields.related(
69 'company_id', 'income_currency_exchange_account_id',
71 relation='account.account',
72 string="Gain Exchange Rate Account",
73 domain="[('type', '=', 'other')]"),
74 'expense_currency_exchange_account_id': fields.related(
75 'company_id', 'expense_currency_exchange_account_id',
77 relation='account.account',
78 string="Loss Exchange Rate Account",
79 domain="[('type', '=', 'other')]"),
81 def onchange_company_id(self, cr, uid, ids, company_id, context=None):
82 res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id, context=context)
84 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
85 res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False,
86 'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False})
88 res['value'].update({'income_currency_exchange_account_id': False,
89 'expense_currency_exchange_account_id': False})
92 class account_voucher(osv.osv):
93 def _check_paid(self, cr, uid, ids, name, args, context=None):
95 for voucher in self.browse(cr, uid, ids, context=context):
96 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
99 def _get_type(self, cr, uid, context=None):
102 return context.get('type', False)
104 def _get_period(self, cr, uid, context=None):
105 if context is None: context = {}
106 if context.get('period_id', False):
107 return context.get('period_id')
108 ctx = dict(context, account_period_prefer_normal=True)
109 periods = self.pool.get('account.period').find(cr, uid, context=ctx)
110 return periods and periods[0] or False
112 def _make_journal_search(self, cr, uid, ttype, context=None):
113 journal_pool = self.pool.get('account.journal')
114 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
116 def _get_journal(self, cr, uid, context=None):
117 if context is None: context = {}
118 invoice_pool = self.pool.get('account.invoice')
119 journal_pool = self.pool.get('account.journal')
120 if context.get('invoice_id', False):
121 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
122 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
123 return journal_id and journal_id[0] or False
124 if context.get('journal_id', False):
125 return context.get('journal_id')
126 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
127 return context.get('search_default_journal_id')
129 ttype = context.get('type', 'bank')
130 if ttype in ('payment', 'receipt'):
132 res = self._make_journal_search(cr, uid, ttype, context=context)
133 return res and res[0] or False
135 def _get_tax(self, cr, uid, context=None):
136 if context is None: context = {}
137 journal_pool = self.pool.get('account.journal')
138 journal_id = context.get('journal_id', False)
140 ttype = context.get('type', 'bank')
141 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
148 journal = journal_pool.browse(cr, uid, journal_id, context=context)
149 account_id = journal.default_credit_account_id or journal.default_debit_account_id
150 if account_id and account_id.tax_ids:
151 tax_id = account_id.tax_ids[0].id
155 def _get_payment_rate_currency(self, cr, uid, context=None):
157 Return the default value for field payment_rate_currency_id: the currency of the journal
158 if there is one, otherwise the currency of the user's company
160 if context is None: context = {}
161 journal_pool = self.pool.get('account.journal')
162 journal_id = context.get('journal_id', False)
164 journal = journal_pool.browse(cr, uid, journal_id, context=context)
166 return journal.currency.id
167 #no journal given in the context, use company currency as default
168 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
170 def _get_currency(self, cr, uid, context=None):
171 if context is None: context = {}
172 journal_pool = self.pool.get('account.journal')
173 journal_id = context.get('journal_id', False)
175 journal = journal_pool.browse(cr, uid, journal_id, context=context)
177 return journal.currency.id
178 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
180 def _get_partner(self, cr, uid, context=None):
181 if context is None: context = {}
182 return context.get('partner_id', False)
184 def _get_reference(self, cr, uid, context=None):
185 if context is None: context = {}
186 return context.get('reference', False)
188 def _get_narration(self, cr, uid, context=None):
189 if context is None: context = {}
190 return context.get('narration', False)
192 def _get_amount(self, cr, uid, context=None):
195 return context.get('amount', 0.0)
197 def name_get(self, cr, uid, ids, context=None):
200 if context is None: context = {}
201 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
203 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
204 mod_obj = self.pool.get('ir.model.data')
205 if context is None: context = {}
207 if view_type == 'form':
208 if not view_id and context.get('invoice_type'):
209 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
210 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
212 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
213 result = result and result[1] or False
215 if not view_id and context.get('line_type'):
216 if context.get('line_type') == 'customer':
217 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
219 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
220 result = result and result[1] or False
223 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
224 doc = etree.XML(res['arch'])
226 if context.get('type', 'sale') in ('purchase', 'payment'):
227 nodes = doc.xpath("//field[@name='partner_id']")
229 node.set('context', "{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}")
230 if context.get('invoice_type','') in ('in_invoice', 'in_refund'):
231 node.set('string', _("Supplier"))
232 res['arch'] = etree.tostring(doc)
235 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
237 sign = type == 'payment' and -1 or 1
238 for l in line_dr_ids:
240 for l in line_cr_ids:
241 credit += l['amount']
242 return amount - sign * (credit - debit)
244 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
245 context = context or {}
246 if not line_dr_ids and not line_cr_ids:
247 return {'value':{'writeoff_amount': 0.0}}
248 line_osv = self.pool.get("account.voucher.line")
249 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
250 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
251 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
252 is_multi_currency = False
253 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to see the options
254 for voucher_line in line_dr_ids+line_cr_ids:
255 line_id = voucher_line.get('id') and self.pool.get('account.voucher.line').browse(cr, uid, voucher_line['id'], context=context).move_line_id.id or voucher_line.get('move_line_id')
256 if line_id and self.pool.get('account.move.line').browse(cr, uid, line_id, context=context).currency_id:
257 is_multi_currency = True
259 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
261 def _get_journal_currency(self, cr, uid, ids, name, args, context=None):
263 for voucher in self.browse(cr, uid, ids, context=context):
264 res[voucher.id] = voucher.journal_id.currency and voucher.journal_id.currency.id or voucher.company_id.currency_id.id
267 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
268 if not ids: return {}
269 currency_obj = self.pool.get('res.currency')
272 for voucher in self.browse(cr, uid, ids, context=context):
273 sign = voucher.type == 'payment' and -1 or 1
274 for l in voucher.line_dr_ids:
276 for l in voucher.line_cr_ids:
278 currency = voucher.currency_id or voucher.company_id.currency_id
279 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
282 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
287 for v in self.browse(cr, uid, ids, context=context):
288 ctx.update({'date': v.date})
289 #make a new call to browse in order to have the right date in the context, to get the right currency rate
290 voucher = self.browse(cr, uid, v.id, context=ctx)
292 'voucher_special_currency': voucher.payment_rate_currency_id and voucher.payment_rate_currency_id.id or False,
293 'voucher_special_currency_rate': voucher.currency_id.rate * voucher.payment_rate,})
294 res[voucher.id] = self.pool.get('res.currency').compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, voucher.amount, context=ctx)
297 def _get_currency_help_label(self, cr, uid, currency_id, payment_rate, payment_rate_currency_id, context=None):
299 This function builds a string to help the users to understand the behavior of the payment rate fields they can specify on the voucher.
300 This string is only used to improve the usability in the voucher form view and has no other effect.
302 :param currency_id: the voucher currency
303 :type currency_id: integer
304 :param payment_rate: the value of the payment_rate field of the voucher
305 :type payment_rate: float
306 :param payment_rate_currency_id: the value of the payment_rate_currency_id field of the voucher
307 :type payment_rate_currency_id: integer
308 :return: translated string giving a tip on what's the effect of the current payment rate specified
311 rml_parser = report_sxw.rml_parse(cr, uid, 'currency_help_label', context=context)
312 currency_pool = self.pool.get('res.currency')
313 currency_str = payment_rate_str = ''
315 currency_str = rml_parser.formatLang(1, currency_obj=currency_pool.browse(cr, uid, currency_id, context=context))
316 if payment_rate_currency_id:
317 payment_rate_str = rml_parser.formatLang(payment_rate, currency_obj=currency_pool.browse(cr, uid, payment_rate_currency_id, context=context))
318 currency_help_label = _('At the operation date, the exchange rate was\n%s = %s') % (currency_str, payment_rate_str)
319 return currency_help_label
321 def _fnct_currency_help_label(self, cr, uid, ids, name, args, context=None):
323 for voucher in self.browse(cr, uid, ids, context=context):
324 res[voucher.id] = self._get_currency_help_label(cr, uid, voucher.currency_id.id, voucher.payment_rate, voucher.payment_rate_currency_id.id, context=context)
327 _name = 'account.voucher'
328 _description = 'Accounting Voucher'
329 _inherit = ['mail.thread']
330 _order = "date desc, id desc"
331 # _rec_name = 'number'
334 'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
339 '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."),
340 'type':fields.selection([
342 ('purchase','Purchase'),
343 ('payment','Payment'),
344 ('receipt','Receipt'),
345 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
346 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
347 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
348 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
349 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
350 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
351 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
352 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
353 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
354 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
355 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
356 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
357 'currency_id': fields.function(_get_journal_currency, type='many2one', relation='res.currency', string='Currency', readonly=True, required=True),
358 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
359 'state':fields.selection(
361 ('cancel','Cancelled'),
362 ('proforma','Pro-forma'),
364 ], 'Status', readonly=True, size=32, track_visibility='onchange',
365 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
366 \n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
367 \n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
368 \n* The \'Cancelled\' status is used when user cancel voucher.'),
369 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
370 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
371 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
372 'number': fields.char('Number', size=32, readonly=True,),
373 'move_id':fields.many2one('account.move', 'Account Entry'),
374 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
375 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
376 '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'),
377 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
378 'pay_now':fields.selection([
379 ('pay_now','Pay Directly'),
380 ('pay_later','Pay Later or Group Funds'),
381 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
382 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
383 'pre_line':fields.boolean('Previous Payments ?', required=False),
384 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
385 'payment_option':fields.selection([
386 ('without_writeoff', 'Keep Open'),
387 ('with_writeoff', 'Reconcile Payment Balance'),
388 ], '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)"),
389 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
390 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
391 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
392 '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."),
393 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
394 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
395 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
396 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
397 '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'),
398 'currency_help_label': fields.function(_fnct_currency_help_label, type='text', string="Helping Sentence", help="This sentence helps you to know how to specify the payment rate by giving you the direct effect it has"),
402 'period_id': _get_period,
403 'partner_id': _get_partner,
404 'journal_id':_get_journal,
405 'currency_id': _get_currency,
406 'reference': _get_reference,
407 'narration':_get_narration,
408 'amount': _get_amount,
411 'pay_now': 'pay_now',
413 'date': lambda *a: time.strftime('%Y-%m-%d'),
414 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
416 'payment_option': 'without_writeoff',
417 'comment': _('Write-Off'),
419 'payment_rate_currency_id': _get_payment_rate_currency,
422 def compute_tax(self, cr, uid, ids, context=None):
423 tax_pool = self.pool.get('account.tax')
424 partner_pool = self.pool.get('res.partner')
425 position_pool = self.pool.get('account.fiscal.position')
426 voucher_line_pool = self.pool.get('account.voucher.line')
427 voucher_pool = self.pool.get('account.voucher')
428 if context is None: context = {}
430 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
432 for line in voucher.line_ids:
433 voucher_amount += line.untax_amount or line.amount
434 line.amount = line.untax_amount or line.amount
435 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
437 if not voucher.tax_id:
438 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
441 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
442 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
443 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
444 tax = tax_pool.browse(cr, uid, taxes, context=context)
446 total = voucher_amount
449 if not tax[0].price_include:
450 for line in voucher.line_ids:
451 for tax_line in tax_pool.compute_all(cr, uid, tax, line.amount, 1).get('taxes', []):
452 total_tax += tax_line.get('amount', 0.0)
455 for line in voucher.line_ids:
459 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
460 line_tax += tax_line.get('amount', 0.0)
461 line_total += tax_line.get('price_unit')
462 total_tax += line_tax
463 untax_amount = line.untax_amount or line.amount
464 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
466 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
469 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
470 context = context or {}
471 tax_pool = self.pool.get('account.tax')
472 partner_pool = self.pool.get('res.partner')
473 position_pool = self.pool.get('account.fiscal.position')
474 line_pool = self.pool.get('account.voucher.line')
483 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
486 for line in line_ids:
488 line_amount = line.get('amount',0.0)
491 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
493 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
494 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
495 tax = tax_pool.browse(cr, uid, taxes, context=context)
497 if not tax[0].price_include:
498 for tax_line in tax_pool.compute_all(cr, uid, tax, line_amount, 1).get('taxes', []):
499 total_tax += tax_line.get('amount')
501 voucher_total += line_amount
502 total = voucher_total + total_tax
505 'amount': total or voucher_total,
506 'tax_amount': total_tax
512 def onchange_term_id(self, cr, uid, ids, term_id, amount):
513 term_pool = self.pool.get('account.payment.term')
516 default = {'date_due':False}
517 if term_id and amount:
518 terms = term_pool.compute(cr, uid, term_id, amount)
520 due_date = terms[-1][0]
524 return {'value':default}
526 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):
528 Returns a dict that contains new values and context
530 @param partner_id: latest value from user input for field partner_id
531 @param args: other arguments
532 @param context: context arguments, like lang, time zone
534 @return: Returns a dict which contains new values, and context
540 if not partner_id or not journal_id:
543 partner_pool = self.pool.get('res.partner')
544 journal_pool = self.pool.get('account.journal')
546 journal = journal_pool.browse(cr, uid, journal_id, context=context)
547 partner = partner_pool.browse(cr, uid, partner_id, context=context)
550 if journal.type in ('sale','sale_refund'):
551 account_id = partner.property_account_receivable.id
553 elif journal.type in ('purchase', 'purchase_refund','expense'):
554 account_id = partner.property_account_payable.id
557 if not journal.default_credit_account_id or not journal.default_debit_account_id:
558 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
559 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
562 default['value']['account_id'] = account_id
563 default['value']['type'] = ttype or tr_type
565 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)
566 default['value'].update(vals.get('value'))
570 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
571 res = {'value': {'paid_amount_in_company_currency': amount, 'currency_help_label': self._get_currency_help_label(cr, uid, currency_id, rate, payment_rate_currency_id, context=context)}}
572 if rate and amount and currency_id:
573 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
574 #context should contain the date, the payment currency and the payment rate specified on the voucher
575 amount_in_company_currency = self.pool.get('res.currency').compute(cr, uid, currency_id, company_currency.id, amount, context=context)
576 res['value']['paid_amount_in_company_currency'] = amount_in_company_currency
579 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):
583 ctx.update({'date': date})
584 #read the voucher rate with the right date in the context
585 currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
586 voucher_rate = self.pool.get('res.currency').read(cr, uid, currency_id, ['rate'], context=ctx)['rate']
588 'voucher_special_currency': payment_rate_currency_id,
589 'voucher_special_currency_rate': rate * voucher_rate})
590 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=ctx)
591 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
592 for key in vals.keys():
593 res[key].update(vals[key])
596 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
599 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
600 currency_obj = self.pool.get('res.currency')
601 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
602 company_id = journal.company_id.id
604 currency_id = currency_id or journal.company_id.currency_id.id
605 payment_rate_currency_id = currency_id
607 ctx.update({'date': date})
609 if ttype == 'receipt':
610 o2m_to_loop = 'line_cr_ids'
611 elif ttype == 'payment':
612 o2m_to_loop = 'line_dr_ids'
613 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
614 for voucher_line in vals['value'][o2m_to_loop]:
615 if voucher_line['currency_id'] != currency_id:
616 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
617 # is not in the voucher currency
618 payment_rate_currency_id = voucher_line['currency_id']
619 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
620 payment_rate = tmp / currency_obj.browse(cr, uid, currency_id, context=ctx).rate
622 vals['value'].update({
623 'payment_rate': payment_rate,
624 'currency_id': currency_id,
625 'payment_rate_currency_id': payment_rate_currency_id
627 #read the voucher rate with the right date in the context
628 voucher_rate = self.pool.get('res.currency').read(cr, uid, currency_id, ['rate'], context=ctx)['rate']
630 'voucher_special_currency_rate': payment_rate * voucher_rate,
631 'voucher_special_currency': payment_rate_currency_id})
632 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
633 for key in res.keys():
634 vals[key].update(res[key])
637 def basic_onchange_partner(self, cr, uid, ids, partner_id, journal_id, ttype, context=None):
638 partner_pool = self.pool.get('res.partner')
639 journal_pool = self.pool.get('account.journal')
640 res = {'value': {'account_id': False}}
641 if not partner_id or not journal_id:
644 journal = journal_pool.browse(cr, uid, journal_id, context=context)
645 partner = partner_pool.browse(cr, uid, partner_id, context=context)
647 if journal.type in ('sale','sale_refund'):
648 account_id = partner.property_account_receivable.id
649 elif journal.type in ('purchase', 'purchase_refund','expense'):
650 account_id = partner.property_account_payable.id
652 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
654 res['value']['account_id'] = account_id
657 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
662 #TODO: comment me and use me directly in the sales/purchases views
663 res = self.basic_onchange_partner(cr, uid, ids, partner_id, journal_id, ttype, context=context)
664 if ttype in ['sale', 'purchase']:
667 # not passing the payment_rate currency and the payment_rate in the context but it's ok because they are reset in recompute_payment_rate
668 ctx.update({'date': date})
669 vals = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=ctx)
670 vals2 = self.recompute_payment_rate(cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=context)
671 for key in vals.keys():
672 res[key].update(vals[key])
673 for key in vals2.keys():
674 res[key].update(vals2[key])
675 #TODO: can probably be removed now
676 #TODO: onchange_partner_id() should not returns [pre_line, line_dr_ids, payment_rate...] for type sale, and not
677 # [pre_line, line_cr_ids, payment_rate...] for type purchase.
678 # We should definitively split account.voucher object in two and make distinct on_change functions. In the
679 # meanwhile, bellow lines must be there because the fields aren't present in the view, what crashes if the
680 # onchange returns a value for them
682 del(res['value']['line_dr_ids'])
683 del(res['value']['pre_line'])
684 del(res['value']['payment_rate'])
685 elif ttype == 'purchase':
686 del(res['value']['line_cr_ids'])
687 del(res['value']['pre_line'])
688 del(res['value']['payment_rate'])
691 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
693 Returns a dict that contains new values and context
695 @param partner_id: latest value from user input for field partner_id
696 @param args: other arguments
697 @param context: context arguments, like lang, time zone
699 @return: Returns a dict which contains new values, and context
701 def _remove_noise_in_o2m():
702 """if the line is partially reconciled, then we must pay attention to display it only once and
704 This function returns True if the line is considered as noise and should not be displayed
706 if line.reconcile_partial_id:
707 if currency_id == line.currency_id.id:
708 if line.amount_residual_currency <= 0:
711 if line.amount_residual <= 0:
717 context_multi_currency = context.copy()
719 currency_pool = self.pool.get('res.currency')
720 move_line_pool = self.pool.get('account.move.line')
721 partner_pool = self.pool.get('res.partner')
722 journal_pool = self.pool.get('account.journal')
723 line_pool = self.pool.get('account.voucher.line')
727 'value': {'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
731 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
733 line_pool.unlink(cr, uid, line_ids)
735 if not partner_id or not journal_id:
738 journal = journal_pool.browse(cr, uid, journal_id, context=context)
739 partner = partner_pool.browse(cr, uid, partner_id, context=context)
740 currency_id = currency_id or journal.company_id.currency_id.id
745 if context.get('account_id'):
746 account_type = self.pool['account.account'].browse(cr, uid, context['account_id'], context=context).type
747 if ttype == 'payment':
749 account_type = 'payable'
750 total_debit = price or 0.0
752 total_credit = price or 0.0
754 account_type = 'receivable'
756 if not context.get('move_line_ids', False):
757 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
759 ids = context['move_line_ids']
760 invoice_id = context.get('invoice_id', False)
761 company_currency = journal.company_id.currency_id.id
762 move_lines_found = []
764 #order the lines by most old first
766 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
768 #compute the total debit/credit and look for a matching open amount or invoice
769 for line in account_move_lines:
770 if _remove_noise_in_o2m():
774 if line.invoice.id == invoice_id:
775 #if the invoice linked to the voucher line is equal to the invoice_id in context
776 #then we assign the amount on that line, whatever the other voucher lines
777 move_lines_found.append(line.id)
778 elif currency_id == company_currency:
779 #otherwise treatments is the same but with other field names
780 if line.amount_residual == price:
781 #if the amount residual is equal the amount voucher, we assign it to that voucher
782 #line, whatever the other voucher lines
783 move_lines_found.append(line.id)
785 #otherwise we will split the voucher amount on each line (by most old first)
786 total_credit += line.credit or 0.0
787 total_debit += line.debit or 0.0
788 elif currency_id == line.currency_id.id:
789 if line.amount_residual_currency == price:
790 move_lines_found.append(line.id)
792 total_credit += line.credit and line.amount_currency or 0.0
793 total_debit += line.debit and line.amount_currency or 0.0
795 remaining_amount = price
796 #voucher line creation
797 for line in account_move_lines:
799 if _remove_noise_in_o2m():
802 if line.currency_id and currency_id == line.currency_id.id:
803 amount_original = abs(line.amount_currency)
804 amount_unreconciled = abs(line.amount_residual_currency)
806 #always use the amount booked in the company currency as the basis of the conversion into the voucher currency
807 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0, context=context_multi_currency)
808 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual), context=context_multi_currency)
809 line_currency_id = line.currency_id and line.currency_id.id or company_currency
811 'name':line.move_id.name,
812 'type': line.credit and 'dr' or 'cr',
813 'move_line_id':line.id,
814 'account_id':line.account_id.id,
815 'amount_original': amount_original,
816 'amount': (line.id in move_lines_found) and min(abs(remaining_amount), amount_unreconciled) or 0.0,
817 'date_original':line.date,
818 'date_due':line.date_maturity,
819 'amount_unreconciled': amount_unreconciled,
820 'currency_id': line_currency_id,
822 remaining_amount -= rs['amount']
823 #in case a corresponding move_line hasn't been found, we now try to assign the voucher amount
824 #on existing invoices: we split voucher amount by most old first, but only for lines in the same currency
825 if not move_lines_found:
826 if currency_id == line_currency_id:
828 amount = min(amount_unreconciled, abs(total_debit))
829 rs['amount'] = amount
830 total_debit -= amount
832 amount = min(amount_unreconciled, abs(total_credit))
833 rs['amount'] = amount
834 total_credit -= amount
836 if rs['amount_unreconciled'] == rs['amount']:
837 rs['reconcile'] = True
839 if rs['type'] == 'cr':
840 default['value']['line_cr_ids'].append(rs)
842 default['value']['line_dr_ids'].append(rs)
844 if len(default['value']['line_cr_ids']) > 0:
845 default['value']['pre_line'] = 1
846 elif len(default['value']['line_dr_ids']) > 0:
847 default['value']['pre_line'] = 1
848 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
851 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
856 #set the default payment rate of the voucher and compute the paid amount in company currency
858 ctx.update({'date': date})
859 #read the voucher rate with the right date in the context
860 voucher_rate = self.pool.get('res.currency').read(cr, uid, currency_id, ['rate'], context=ctx)['rate']
862 'voucher_special_currency_rate': payment_rate * voucher_rate,
863 'voucher_special_currency': payment_rate_currency_id})
864 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
865 for key in vals.keys():
866 res[key].update(vals[key])
869 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
871 @param date: latest value from user input for field date
872 @param args: other arguments
873 @param context: context arguments, like lang, time zone
874 @return: Returns a dict which contains new values, and context
879 #set the period of the voucher
880 period_pool = self.pool.get('account.period')
881 currency_obj = self.pool.get('res.currency')
883 ctx.update({'company_id': company_id, 'account_period_prefer_normal': True})
884 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
885 pids = period_pool.find(cr, uid, date, context=ctx)
887 res['value'].update({'period_id':pids[0]})
888 if payment_rate_currency_id:
889 ctx.update({'date': date})
891 if payment_rate_currency_id != currency_id:
892 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
893 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
894 vals = self.onchange_payment_rate_currency(cr, uid, ids, voucher_currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
895 vals['value'].update({'payment_rate': payment_rate})
896 for key in vals.keys():
897 res[key].update(vals[key])
900 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
905 journal_pool = self.pool.get('account.journal')
906 journal = journal_pool.browse(cr, uid, journal_id, context=context)
907 account_id = journal.default_credit_account_id or journal.default_debit_account_id
909 if account_id and account_id.tax_ids:
910 tax_id = account_id.tax_ids[0].id
913 if ttype in ('sale', 'purchase'):
914 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
915 vals['value'].update({'tax_id':tax_id,'amount': amount})
918 currency_id = journal.currency.id
920 currency_id = journal.company_id.currency_id.id
921 vals['value'].update({'currency_id': currency_id})
922 #in case we want to register the payment directly from an invoice, it's confusing to allow to switch the journal
923 #without seeing that the amount is expressed in the journal currency, and not in the invoice currency. So to avoid
924 #this common mistake, we simply reset the amount to 0 if the currency is not the invoice currency.
925 if context.get('payment_expected_currency') and currency_id != context.get('payment_expected_currency'):
926 vals['value']['amount'] = 0
929 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
930 for key in res.keys():
931 vals[key].update(res[key])
934 def button_proforma_voucher(self, cr, uid, ids, context=None):
935 context = context or {}
936 wf_service = netsvc.LocalService("workflow")
938 wf_service.trg_validate(uid, 'account.voucher', vid, 'proforma_voucher', cr)
939 return {'type': 'ir.actions.act_window_close'}
941 def proforma_voucher(self, cr, uid, ids, context=None):
942 self.action_move_line_create(cr, uid, ids, context=context)
945 def action_cancel_draft(self, cr, uid, ids, context=None):
946 wf_service = netsvc.LocalService("workflow")
947 for voucher_id in ids:
948 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
949 self.write(cr, uid, ids, {'state':'draft'})
952 def cancel_voucher(self, cr, uid, ids, context=None):
953 reconcile_pool = self.pool.get('account.move.reconcile')
954 move_pool = self.pool.get('account.move')
955 move_line_pool = self.pool.get('account.move.line')
956 for voucher in self.browse(cr, uid, ids, context=context):
957 # refresh to make sure you don't unlink an already removed move
959 for line in voucher.move_ids:
960 # refresh to make sure you don't unreconcile an already unreconciled entry
962 if line.reconcile_id:
963 move_lines = [move_line.id for move_line in line.reconcile_id.line_id]
964 move_lines.remove(line.id)
965 reconcile_pool.unlink(cr, uid, [line.reconcile_id.id])
966 if len(move_lines) >= 2:
967 move_line_pool.reconcile_partial(cr, uid, move_lines, 'auto',context=context)
969 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
970 move_pool.unlink(cr, uid, [voucher.move_id.id])
975 self.write(cr, uid, ids, res)
978 def unlink(self, cr, uid, ids, context=None):
979 for t in self.read(cr, uid, ids, ['state'], context=context):
980 if t['state'] not in ('draft', 'cancel'):
981 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
982 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
984 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
989 partner_pool = self.pool.get('res.partner')
990 journal_pool = self.pool.get('account.journal')
991 if pay_now == 'pay_later':
992 partner = partner_pool.browse(cr, uid, partner_id)
993 journal = journal_pool.browse(cr, uid, journal_id)
994 if journal.type in ('sale','sale_refund'):
995 account_id = partner.property_account_receivable.id
996 elif journal.type in ('purchase', 'purchase_refund','expense'):
997 account_id = partner.property_account_payable.id
999 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
1001 res['account_id'] = account_id
1002 return {'value':res}
1004 def _sel_context(self, cr, uid, voucher_id, context=None):
1006 Select the context to use accordingly if it needs to be multicurrency or not.
1008 :param voucher_id: Id of the actual voucher
1009 :return: The returned context will be the same as given in parameter if the voucher currency is the same
1010 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
1011 the date of the voucher.
1014 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
1015 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
1016 if current_currency <> company_currency:
1017 context_multi_currency = context.copy()
1018 voucher = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1019 context_multi_currency.update({'date': voucher.date})
1020 return context_multi_currency
1023 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
1025 Return a dict to be use to create the first account move line of given voucher.
1027 :param voucher_id: Id of voucher what we are creating account_move.
1028 :param move_id: Id of account move where this line will be added.
1029 :param company_currency: id of currency of the company to which the voucher belong
1030 :param current_currency: id of currency of the voucher
1031 :return: mapping between fieldname and value of account move line to create
1034 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1035 debit = credit = 0.0
1036 # TODO: is there any other alternative then the voucher type ??
1037 # ANSWER: We can have payment and receipt "In Advance".
1038 # TODO: Make this logic available.
1039 # -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
1040 if voucher.type in ('purchase', 'payment'):
1041 credit = voucher.paid_amount_in_company_currency
1042 elif voucher.type in ('sale', 'receipt'):
1043 debit = voucher.paid_amount_in_company_currency
1044 if debit < 0: credit = -debit; debit = 0.0
1045 if credit < 0: debit = -credit; credit = 0.0
1046 sign = debit - credit < 0 and -1 or 1
1047 #set the first line of the voucher
1049 'name': voucher.name or '/',
1052 'account_id': voucher.account_id.id,
1054 'journal_id': voucher.journal_id.id,
1055 'period_id': voucher.period_id.id,
1056 'partner_id': voucher.partner_id.id,
1057 'currency_id': company_currency <> current_currency and current_currency or False,
1058 'amount_currency': (sign * abs(voucher.amount) # amount < 0 for refunds
1059 if company_currency != current_currency else 0.0),
1060 'date': voucher.date,
1061 'date_maturity': voucher.date_due
1065 def account_move_get(self, cr, uid, voucher_id, context=None):
1067 This method prepare the creation of the account move related to the given voucher.
1069 :param voucher_id: Id of voucher for which we are creating account_move.
1070 :return: mapping between fieldname and value of account move to create
1073 seq_obj = self.pool.get('ir.sequence')
1074 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1076 name = voucher.number
1077 elif voucher.journal_id.sequence_id:
1078 if not voucher.journal_id.sequence_id.active:
1079 raise osv.except_osv(_('Configuration Error !'),
1080 _('Please activate the sequence of selected journal !'))
1082 c.update({'fiscalyear_id': voucher.period_id.fiscalyear_id.id})
1083 name = seq_obj.next_by_id(cr, uid, voucher.journal_id.sequence_id.id, context=c)
1085 raise osv.except_osv(_('Error!'),
1086 _('Please define a sequence on the journal.'))
1087 if not voucher.reference:
1088 ref = name.replace('/','')
1090 ref = voucher.reference
1094 'journal_id': voucher.journal_id.id,
1095 'narration': voucher.narration,
1096 'date': voucher.date,
1098 'period_id': voucher.period_id.id,
1102 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
1104 Prepare the two lines in company currency due to currency rate difference.
1106 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
1108 :param move_id: Account move wher the move lines will be.
1109 :param amount_residual: Amount to be posted.
1110 :param company_currency: id of currency of the company to which the voucher belong
1111 :param current_currency: id of currency of the voucher
1112 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
1113 :rtype: tuple of dict
1115 if amount_residual > 0:
1116 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
1118 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."))
1120 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
1122 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."))
1123 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
1124 # the receivable/payable account may have a secondary currency, which render this field mandatory
1125 if line.account_id.currency_id:
1126 account_currency_id = line.account_id.currency_id.id
1128 account_currency_id = company_currency <> current_currency and current_currency or False
1130 'journal_id': line.voucher_id.journal_id.id,
1131 'period_id': line.voucher_id.period_id.id,
1132 'name': _('change')+': '+(line.name or '/'),
1133 'account_id': line.account_id.id,
1135 'partner_id': line.voucher_id.partner_id.id,
1136 'currency_id': account_currency_id,
1137 'amount_currency': 0.0,
1139 'credit': amount_residual > 0 and amount_residual or 0.0,
1140 'debit': amount_residual < 0 and -amount_residual or 0.0,
1141 'date': line.voucher_id.date,
1143 move_line_counterpart = {
1144 'journal_id': line.voucher_id.journal_id.id,
1145 'period_id': line.voucher_id.period_id.id,
1146 'name': _('change')+': '+(line.name or '/'),
1147 'account_id': account_id.id,
1149 'amount_currency': 0.0,
1150 'partner_id': line.voucher_id.partner_id.id,
1151 'currency_id': account_currency_id,
1153 'debit': amount_residual > 0 and amount_residual or 0.0,
1154 'credit': amount_residual < 0 and -amount_residual or 0.0,
1155 'date': line.voucher_id.date,
1157 return (move_line, move_line_counterpart)
1159 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
1161 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
1162 payment_rate_currency_id is relevant) either the rate encoded in the system.
1164 :param amount: float. The amount to convert
1165 :param voucher: id of the voucher on which we want the conversion
1166 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
1167 field in order to select the good rate to use.
1168 :return: the amount in the currency of the voucher's company
1173 currency_obj = self.pool.get('res.currency')
1174 voucher = self.browse(cr, uid, voucher_id, context=context)
1175 return currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1177 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1179 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1180 It returns Tuple with tot_line what is total of difference between debit and credit and
1181 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1183 :param voucher_id: Voucher id what we are working with
1184 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1185 :param move_id: Account move wher those lines will be joined.
1186 :param company_currency: id of currency of the company to which the voucher belong
1187 :param current_currency: id of currency of the voucher
1188 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1189 :rtype: tuple(float, list of int)
1193 move_line_obj = self.pool.get('account.move.line')
1194 currency_obj = self.pool.get('res.currency')
1195 tax_obj = self.pool.get('account.tax')
1196 tot_line = line_total
1199 date = self.read(cr, uid, voucher_id, ['date'], context=context)['date']
1200 ctx = context.copy()
1201 ctx.update({'date': date})
1202 voucher = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context=ctx)
1203 voucher_currency = voucher.journal_id.currency or voucher.company_id.currency_id
1205 'voucher_special_currency_rate': voucher_currency.rate * voucher.payment_rate ,
1206 'voucher_special_currency': voucher.payment_rate_currency_id and voucher.payment_rate_currency_id.id or False,})
1207 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1208 for line in voucher.line_ids:
1209 #create one move line per voucher line where amount is not 0.0
1210 # AND (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
1211 if not line.amount and not (line.move_line_id and not float_compare(line.move_line_id.debit, line.move_line_id.credit, precision_digits=prec) and not float_compare(line.move_line_id.debit, 0.0, precision_digits=prec)):
1213 # convert the amount set on the voucher line into the currency of the voucher's company
1214 # this calls res_curreny.compute() with the right context, so that it will take either the rate on the voucher if it is relevant or will use the default behaviour
1215 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher.id, context=ctx)
1216 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1217 # currency rate difference
1218 if line.amount == line.amount_unreconciled:
1219 if not line.move_line_id:
1220 raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
1221 sign = line.type =='dr' and -1 or 1
1222 currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
1224 currency_rate_difference = 0.0
1226 'journal_id': voucher.journal_id.id,
1227 'period_id': voucher.period_id.id,
1228 'name': line.name or '/',
1229 'account_id': line.account_id.id,
1231 'partner_id': voucher.partner_id.id,
1232 '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,
1233 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1237 'date': voucher.date
1241 if line.type == 'dr':
1246 if (line.type=='dr'):
1248 move_line['debit'] = amount
1251 move_line['credit'] = amount
1253 if voucher.tax_id and voucher.type in ('sale', 'purchase'):
1255 'account_tax_id': voucher.tax_id.id,
1258 if move_line.get('account_tax_id', False):
1259 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1260 if not (tax_data.base_code_id and tax_data.tax_code_id):
1261 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))
1263 # compute the amount in foreign currency
1264 foreign_currency_diff = 0.0
1265 amount_currency = False
1266 if line.move_line_id:
1267 # We want to set it on the account move line as soon as the original line had a foreign currency
1268 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1269 # we compute the amount in that foreign currency.
1270 if line.move_line_id.currency_id.id == current_currency:
1271 # if the voucher and the voucher line share the same currency, there is no computation to do
1272 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1273 amount_currency = sign * (line.amount)
1275 # if the rate is specified on the voucher, it will be used thanks to the special keys in the context
1276 # otherwise we use the rates of the system
1277 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1278 if line.amount == line.amount_unreconciled:
1279 foreign_currency_diff = line.move_line_id.amount_residual_currency - abs(amount_currency)
1281 move_line['amount_currency'] = amount_currency
1282 voucher_line = move_line_obj.create(cr, uid, move_line)
1283 rec_ids = [voucher_line, line.move_line_id.id]
1285 if not currency_obj.is_zero(cr, uid, voucher.company_id.currency_id, currency_rate_difference):
1286 # Change difference entry in company currency
1287 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1288 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1289 move_line_obj.create(cr, uid, exch_lines[1], context)
1290 rec_ids.append(new_id)
1292 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):
1293 # Change difference entry in voucher currency
1294 move_line_foreign_currency = {
1295 'journal_id': line.voucher_id.journal_id.id,
1296 'period_id': line.voucher_id.period_id.id,
1297 'name': _('change')+': '+(line.name or '/'),
1298 'account_id': line.account_id.id,
1300 'partner_id': line.voucher_id.partner_id.id,
1301 'currency_id': line.move_line_id.currency_id.id,
1302 'amount_currency': -1 * foreign_currency_diff,
1306 'date': line.voucher_id.date,
1308 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1309 rec_ids.append(new_id)
1310 if line.move_line_id.id:
1311 rec_lst_ids.append(rec_ids)
1312 return (tot_line, rec_lst_ids)
1314 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1316 Set a dict to be use to create the writeoff move line.
1318 :param voucher_id: Id of voucher what we are creating account_move.
1319 :param line_total: Amount remaining to be allocated on lines.
1320 :param move_id: Id of account move where this line will be added.
1321 :param name: Description of account move line.
1322 :param company_currency: id of currency of the company to which the voucher belong
1323 :param current_currency: id of currency of the voucher
1324 :return: mapping between fieldname and value of account move line to create
1327 currency_obj = self.pool.get('res.currency')
1330 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1331 current_currency_obj = voucher.currency_id or voucher.journal_id.company_id.currency_id
1333 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1337 if voucher.payment_option == 'with_writeoff':
1338 account_id = voucher.writeoff_acc_id.id
1339 write_off_name = voucher.comment
1340 elif voucher.partner_id:
1341 if voucher.type in ('sale', 'receipt'):
1342 account_id = voucher.partner_id.property_account_receivable.id
1344 account_id = voucher.partner_id.property_account_payable.id
1346 # fallback on account of voucher
1347 account_id = voucher.account_id.id
1348 sign = voucher.type == 'payment' and -1 or 1
1350 'name': write_off_name or name,
1351 'account_id': account_id,
1353 'partner_id': voucher.partner_id.id,
1354 'date': voucher.date,
1355 'credit': diff > 0 and diff or 0.0,
1356 'debit': diff < 0 and -diff or 0.0,
1357 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher.writeoff_amount) or 0.0,
1358 'currency_id': company_currency <> current_currency and current_currency or False,
1359 'analytic_account_id': voucher.analytic_id and voucher.analytic_id.id or False,
1364 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1366 Get the currency of the actual company.
1368 :param voucher_id: Id of the voucher what i want to obtain company currency.
1369 :return: currency id of the company of the voucher
1372 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1374 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1376 Get the currency of the voucher.
1378 :param voucher_id: Id of the voucher what i want to obtain current currency.
1379 :return: currency id of the voucher
1382 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1383 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1385 def action_move_line_create(self, cr, uid, ids, context=None):
1387 Confirm the vouchers given in ids and create the journal entries for each of them
1391 move_pool = self.pool.get('account.move')
1392 move_line_pool = self.pool.get('account.move.line')
1393 for voucher in self.browse(cr, uid, ids, context=context):
1394 local_context = dict(context, force_company=voucher.journal_id.company_id.id)
1397 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1398 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1399 # we select the context to use accordingly if it's a multicurrency case or not
1400 context = self._sel_context(cr, uid, voucher.id, context)
1401 # But for the operations made by _convert_amount, we always need to give the date in the context
1402 ctx = context.copy()
1403 ctx.update({'date': voucher.date})
1404 # Create the account move record.
1405 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1406 # Get the name of the account_move just created
1407 name = move_pool.browse(cr, uid, move_id, context=context).name
1408 # Create the first line of the voucher
1409 move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, company_currency, current_currency, local_context), local_context)
1410 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1411 line_total = move_line_brw.debit - move_line_brw.credit
1413 if voucher.type == 'sale':
1414 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1415 elif voucher.type == 'purchase':
1416 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1417 # Create one move line per voucher line where amount is not 0.0
1418 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1420 # Create the writeoff line if needed
1421 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, local_context)
1423 move_line_pool.create(cr, uid, ml_writeoff, local_context)
1424 # We post the voucher.
1425 self.write(cr, uid, [voucher.id], {
1430 if voucher.journal_id.entry_posted:
1431 move_pool.post(cr, uid, [move_id], context={})
1432 # We automatically reconcile the account move lines.
1434 for rec_ids in rec_list_ids:
1435 if len(rec_ids) >= 2:
1436 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)
1439 def copy(self, cr, uid, id, default=None, context=None):
1446 'line_cr_ids': False,
1447 'line_dr_ids': False,
1450 if 'date' not in default:
1451 default['date'] = time.strftime('%Y-%m-%d')
1452 return super(account_voucher, self).copy(cr, uid, id, default, context)
1455 class account_voucher_line(osv.osv):
1456 _name = 'account.voucher.line'
1457 _description = 'Voucher Lines'
1458 _order = "move_line_id"
1460 # If the payment is in the same currency than the invoice, we keep the same amount
1461 # Otherwise, we compute from invoice currency to payment currency
1462 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1463 currency_pool = self.pool.get('res.currency')
1465 for line in self.browse(cr, uid, ids, context=context):
1466 ctx = context.copy()
1467 ctx.update({'date': line.voucher_id.date})
1468 voucher_rate = self.pool.get('res.currency').read(cr, uid, line.voucher_id.currency_id.id, ['rate'], context=ctx)['rate']
1470 'voucher_special_currency': line.voucher_id.payment_rate_currency_id and line.voucher_id.payment_rate_currency_id.id or False,
1471 'voucher_special_currency_rate': line.voucher_id.payment_rate * voucher_rate})
1473 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1474 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1475 move_line = line.move_line_id or False
1478 res['amount_original'] = 0.0
1479 res['amount_unreconciled'] = 0.0
1480 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1481 res['amount_original'] = abs(move_line.amount_currency)
1482 res['amount_unreconciled'] = abs(move_line.amount_residual_currency)
1484 #always use the amount booked in the company currency as the basis of the conversion into the voucher currency
1485 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit or move_line.debit or 0.0, context=ctx)
1486 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1488 rs_data[line.id] = res
1491 def _currency_id(self, cr, uid, ids, name, args, context=None):
1493 This function returns the currency id of a voucher line. It's either the currency of the
1494 associated move line (if any) or the currency of the voucher or the company currency.
1497 for line in self.browse(cr, uid, ids, context=context):
1498 move_line = line.move_line_id
1500 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1502 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1506 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1507 'name':fields.char('Description', size=256),
1508 'account_id':fields.many2one('account.account','Account', required=True),
1509 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1510 'untax_amount':fields.float('Untax Amount'),
1511 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1512 'reconcile': fields.boolean('Full Reconcile'),
1513 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1514 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1515 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1516 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1517 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1518 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1519 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1520 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1521 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1527 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1528 vals = {'amount': 0.0}
1530 vals = { 'amount': amount_unreconciled}
1531 return {'value': vals}
1533 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1536 vals['reconcile'] = (amount == amount_unreconciled)
1537 return {'value': vals}
1539 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1541 Returns a dict that contains new values and context
1543 @param move_line_id: latest value from user input for field move_line_id
1544 @param args: other arguments
1545 @param context: context arguments, like lang, time zone
1547 @return: Returns a dict which contains new values, and context
1550 move_line_pool = self.pool.get('account.move.line')
1552 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1553 if move_line.credit:
1558 'account_id': move_line.account_id.id,
1560 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1566 def default_get(self, cr, user, fields_list, context=None):
1568 Returns default values for fields
1569 @param fields_list: list of fields, for which default values are required to be read
1570 @param context: context arguments, like lang, time zone
1572 @return: Returns a dict that contains default values for fields
1576 journal_id = context.get('journal_id', False)
1577 partner_id = context.get('partner_id', False)
1578 journal_pool = self.pool.get('account.journal')
1579 partner_pool = self.pool.get('res.partner')
1580 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1581 if (not journal_id) or ('account_id' not in fields_list):
1583 journal = journal_pool.browse(cr, user, journal_id, context=context)
1586 if journal.type in ('sale', 'sale_refund'):
1587 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1589 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1590 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1593 partner = partner_pool.browse(cr, user, partner_id, context=context)
1594 if context.get('type') == 'payment':
1596 account_id = partner.property_account_payable.id
1597 elif context.get('type') == 'receipt':
1598 account_id = partner.property_account_receivable.id
1601 'account_id':account_id,
1605 account_voucher_line()
1607 class account_bank_statement(osv.osv):
1608 _inherit = 'account.bank.statement'
1610 def button_confirm_bank(self, cr, uid, ids, context=None):
1611 voucher_obj = self.pool.get('account.voucher')
1613 for statement in self.browse(cr, uid, ids, context=context):
1614 voucher_ids += [line.voucher_id.id for line in statement.line_ids if line.voucher_id]
1616 voucher_obj.write(cr, uid, voucher_ids, {'active': True}, context=context)
1617 return super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context)
1619 def button_cancel(self, cr, uid, ids, context=None):
1620 voucher_obj = self.pool.get('account.voucher')
1621 for st in self.browse(cr, uid, ids, context=context):
1623 for line in st.line_ids:
1625 voucher_ids.append(line.voucher_id.id)
1626 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1627 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1629 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1630 voucher_obj = self.pool.get('account.voucher')
1631 wf_service = netsvc.LocalService("workflow")
1632 move_line_obj = self.pool.get('account.move.line')
1633 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1634 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1635 if st_line.voucher_id:
1636 voucher_obj.write(cr, uid, [st_line.voucher_id.id],
1637 {'number': next_number,
1638 'date': st_line.date,
1639 'period_id': st_line.statement_id.period_id.id},
1641 if st_line.voucher_id.state == 'cancel':
1642 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1643 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1645 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1646 bank_st_line_obj.write(cr, uid, [st_line_id], {
1647 'move_ids': [(4, v.move_id.id, False)]
1650 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1651 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1653 def write(self, cr, uid, ids, vals, context=None):
1654 # Restrict to modify the journal if we already have some voucher of reconciliation created/generated.
1655 # Because the voucher keeps in memory the journal it was created with.
1656 for bk_st in self.browse(cr, uid, ids, context=context):
1657 if vals.get('journal_id') and bk_st.line_ids:
1658 if any([x.voucher_id and True or False for x in bk_st.line_ids]):
1659 raise osv.except_osv(_('Unable to Change Journal!'), _('You can not change the journal as you already reconciled some statement lines!'))
1660 return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context)
1662 account_bank_statement()
1664 class account_bank_statement_line(osv.osv):
1665 _inherit = 'account.bank.statement.line'
1667 def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
1668 res = super(account_bank_statement_line, self).onchange_partner_id(cr, uid, ids, partner_id, context=context)
1669 if 'value' not in res:
1671 res['value'].update({'voucher_id' : False})
1674 def onchange_amount(self, cr, uid, ids, amount, context=None):
1675 return {'value' : {'voucher_id' : False}}
1677 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1681 for line in self.browse(cursor, user, ids, context=context):
1683 res[line.id] = line.voucher_id.amount#
1688 def _check_amount(self, cr, uid, ids, context=None):
1689 for obj in self.browse(cr, uid, ids, context=context):
1691 diff = abs(obj.amount) - abs(obj.voucher_id.amount)
1692 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1697 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1701 'amount_reconciled': fields.function(_amount_reconciled,
1702 string='Amount reconciled', type='float'),
1703 'voucher_id': fields.many2one('account.voucher', 'Reconciliation'),
1706 def unlink(self, cr, uid, ids, context=None):
1707 voucher_obj = self.pool.get('account.voucher')
1708 statement_line = self.browse(cr, uid, ids, context=context)
1710 for st_line in statement_line:
1711 if st_line.voucher_id:
1712 unlink_ids.append(st_line.voucher_id.id)
1713 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1714 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1716 account_bank_statement_line()
1718 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1720 for operation in operations:
1722 if not isinstance(operation, (list, tuple)):
1723 result = target_osv.read(cr, uid, operation, fields, context=context)
1724 elif operation[0] == 0:
1725 # may be necessary to check if all the fields are here and get the default values?
1726 result = operation[2]
1727 elif operation[0] == 1:
1728 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1729 if not result: result = {}
1730 result.update(operation[2])
1731 elif operation[0] == 4:
1732 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1734 results.append(result)
1738 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: