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="Income Currency Rate",
36 domain="[('type', '=', 'other')]",),
37 'expense_currency_exchange_account_id': fields.many2one(
39 string="Expense Currency Rate",
40 domain="[('type', '=', 'other')]",),
45 class account_voucher(osv.osv):
46 def _check_paid(self, cr, uid, ids, name, args, context=None):
48 for voucher in self.browse(cr, uid, ids, context=context):
49 res[voucher.id] = any([((line.account_id.type, 'in', ('receivable', 'payable')) and line.reconcile_id) for line in voucher.move_ids])
52 def _get_type(self, cr, uid, context=None):
55 return context.get('type', False)
57 def _get_period(self, cr, uid, context=None):
58 if context is None: context = {}
59 if context.get('period_id', False):
60 return context.get('period_id')
61 periods = self.pool.get('account.period').find(cr, uid)
62 return periods and periods[0] or False
64 def _make_journal_search(self, cr, uid, ttype, context=None):
65 journal_pool = self.pool.get('account.journal')
66 return journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
68 def _get_journal(self, cr, uid, context=None):
69 if context is None: context = {}
70 invoice_pool = self.pool.get('account.invoice')
71 journal_pool = self.pool.get('account.journal')
72 if context.get('invoice_id', False):
73 currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id
74 journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1)
75 return journal_id and journal_id[0] or False
76 if context.get('journal_id', False):
77 return context.get('journal_id')
78 if not context.get('journal_id', False) and context.get('search_default_journal_id', False):
79 return context.get('search_default_journal_id')
81 ttype = context.get('type', 'bank')
82 if ttype in ('payment', 'receipt'):
84 res = self._make_journal_search(cr, uid, ttype, context=context)
85 return res and res[0] or False
87 def _get_tax(self, cr, uid, context=None):
88 if context is None: context = {}
89 journal_pool = self.pool.get('account.journal')
90 journal_id = context.get('journal_id', False)
92 ttype = context.get('type', 'bank')
93 res = journal_pool.search(cr, uid, [('type', '=', ttype)], limit=1)
100 journal = journal_pool.browse(cr, uid, journal_id, context=context)
101 account_id = journal.default_credit_account_id or journal.default_debit_account_id
102 if account_id and account_id.tax_ids:
103 tax_id = account_id.tax_ids[0].id
107 def _get_payment_rate_currency(self, cr, uid, context=None):
109 Return the default value for field payment_rate_currency_id: the currency of the journal
110 if there is one, otherwise the currency of the user's company
112 if context is None: context = {}
113 journal_pool = self.pool.get('account.journal')
114 journal_id = context.get('journal_id', False)
116 journal = journal_pool.browse(cr, uid, journal_id, context=context)
118 return journal.currency.id
119 #no journal given in the context, use company currency as default
120 return self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
122 def _get_currency(self, cr, uid, context=None):
123 if context is None: context = {}
124 journal_pool = self.pool.get('account.journal')
125 journal_id = context.get('journal_id', False)
127 journal = journal_pool.browse(cr, uid, journal_id, context=context)
129 return journal.currency.id
132 def _get_partner(self, cr, uid, context=None):
133 if context is None: context = {}
134 return context.get('partner_id', False)
136 def _get_reference(self, cr, uid, context=None):
137 if context is None: context = {}
138 return context.get('reference', False)
140 def _get_narration(self, cr, uid, context=None):
141 if context is None: context = {}
142 return context.get('narration', False)
144 def _get_amount(self, cr, uid, context=None):
147 return context.get('amount', 0.0)
149 def name_get(self, cr, uid, ids, context=None):
152 if context is None: context = {}
153 return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')]
155 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
156 mod_obj = self.pool.get('ir.model.data')
157 if context is None: context = {}
159 if view_type == 'form':
160 if not view_id and context.get('invoice_type'):
161 if context.get('invoice_type') in ('out_invoice', 'out_refund'):
162 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
164 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
165 result = result and result[1] or False
167 if not view_id and context.get('line_type'):
168 if context.get('line_type') == 'customer':
169 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_receipt_form')
171 result = mod_obj.get_object_reference(cr, uid, 'account_voucher', 'view_vendor_payment_form')
172 result = result and result[1] or False
175 res = super(account_voucher, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
176 doc = etree.XML(res['arch'])
178 if context.get('type', 'sale') in ('purchase', 'payment'):
179 nodes = doc.xpath("//field[@name='partner_id']")
181 node.set('domain', "[('supplier', '=', True)]")
182 res['arch'] = etree.tostring(doc)
185 def _compute_writeoff_amount(self, cr, uid, line_dr_ids, line_cr_ids, amount, type):
187 sign = type == 'payment' and -1 or 1
188 for l in line_dr_ids:
190 for l in line_cr_ids:
191 credit += l['amount']
192 return amount - sign * (credit - debit)
194 def onchange_line_ids(self, cr, uid, ids, line_dr_ids, line_cr_ids, amount, voucher_currency, type, context=None):
195 context = context or {}
196 if not line_dr_ids and not line_cr_ids:
198 line_osv = self.pool.get("account.voucher.line")
199 line_dr_ids = resolve_o2m_operations(cr, uid, line_osv, line_dr_ids, ['amount'], context)
200 line_cr_ids = resolve_o2m_operations(cr, uid, line_osv, line_cr_ids, ['amount'], context)
202 #compute the field is_multi_currency that is used to hide/display options linked to secondary currency on the voucher
203 is_multi_currency = False
205 # if the voucher currency is not False, it means it is different than the company currency and we need to display the options
206 is_multi_currency = True
208 #loop on the voucher lines to see if one of these has a secondary currency. If yes, we need to define the options
209 for voucher_line in line_dr_ids+line_cr_ids:
210 company_currency = False
211 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
212 if voucher_line.get('currency_id', company_currency) != company_currency:
213 is_multi_currency = True
215 return {'value': {'writeoff_amount': self._compute_writeoff_amount(cr, uid, line_dr_ids, line_cr_ids, amount, type), 'is_multi_currency': is_multi_currency}}
217 def _get_writeoff_amount(self, cr, uid, ids, name, args, context=None):
218 if not ids: return {}
219 currency_obj = self.pool.get('res.currency')
222 for voucher in self.browse(cr, uid, ids, context=context):
223 sign = voucher.type == 'payment' and -1 or 1
224 for l in voucher.line_dr_ids:
226 for l in voucher.line_cr_ids:
228 currency = voucher.currency_id or voucher.company_id.currency_id
229 res[voucher.id] = currency_obj.round(cr, uid, currency, voucher.amount - sign * (credit - debit))
232 def _paid_amount_in_company_currency(self, cr, uid, ids, name, args, context=None):
233 if not ids: return {}
236 for voucher in self.browse(cr, uid, ids, context=context):
237 if voucher.currency_id:
238 if voucher.company_id.currency_id.id == voucher.payment_rate_currency_id.id:
239 rate = 1 / voucher.payment_rate
242 ctx.update({'date': voucher.date})
243 voucher_rate = self.browse(cr, uid, voucher.id, context=ctx).currency_id.rate
244 company_currency_rate = voucher.company_id.currency_id.rate
245 rate = voucher_rate * company_currency_rate
246 res[voucher.id] = voucher.amount / rate
249 _name = 'account.voucher'
250 _description = 'Accounting Voucher'
251 _inherit = ['mail.thread']
252 _order = "date desc, id desc"
253 # _rec_name = 'number'
255 'type':fields.selection([
257 ('purchase','Purchase'),
258 ('payment','Payment'),
259 ('receipt','Receipt'),
260 ],'Default Type', readonly=True, states={'draft':[('readonly',False)]}),
261 'name':fields.char('Memo', size=256, readonly=True, states={'draft':[('readonly',False)]}),
262 'date':fields.date('Date', readonly=True, select=True, states={'draft':[('readonly',False)]}, help="Effective date for accounting entries"),
263 'journal_id':fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
264 'account_id':fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}),
265 'line_ids':fields.one2many('account.voucher.line','voucher_id','Voucher Lines', readonly=True, states={'draft':[('readonly',False)]}),
266 'line_cr_ids':fields.one2many('account.voucher.line','voucher_id','Credits',
267 domain=[('type','=','cr')], context={'default_type':'cr'}, readonly=True, states={'draft':[('readonly',False)]}),
268 'line_dr_ids':fields.one2many('account.voucher.line','voucher_id','Debits',
269 domain=[('type','=','dr')], context={'default_type':'dr'}, readonly=True, states={'draft':[('readonly',False)]}),
270 'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
271 'narration':fields.text('Notes', readonly=True, states={'draft':[('readonly',False)]}),
272 # 'currency_id':fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
273 'currency_id': fields.related('journal_id','currency', type='many2one', relation='res.currency', string='Currency', readonly=True),
274 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
275 'state':fields.selection(
277 ('cancel','Cancelled'),
278 ('proforma','Pro-forma'),
280 ], 'Status', readonly=True, size=32,
281 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Voucher. \
282 \n* The \'Pro-forma\' when voucher is in Pro-forma state,voucher does not have an voucher number. \
283 \n* The \'Posted\' state is used when user create voucher,a voucher number is generated and voucher entries are created in account \
284 \n* The \'Cancelled\' state is used when user cancel voucher.'),
285 'amount': fields.float('Total', digits_compute=dp.get_precision('Account'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
286 'tax_amount':fields.float('Tax Amount', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
287 'reference': fields.char('Ref #', size=64, readonly=True, states={'draft':[('readonly',False)]}, help="Transaction reference number."),
288 'number': fields.char('Number', size=32, readonly=True,),
289 'move_id':fields.many2one('account.move', 'Account Entry'),
290 'move_ids': fields.related('move_id','line_id', type='one2many', relation='account.move.line', string='Journal Items', readonly=True),
291 'partner_id':fields.many2one('res.partner', 'Partner', change_default=1, readonly=True, states={'draft':[('readonly',False)]}),
292 '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'),
293 'paid': fields.function(_check_paid, string='Paid', type='boolean', help="The Voucher has been totally paid."),
294 'pay_now':fields.selection([
295 ('pay_now','Pay Directly'),
296 ('pay_later','Pay Later or Group Funds'),
297 ],'Payment', select=True, readonly=True, states={'draft':[('readonly',False)]}),
298 'tax_id': fields.many2one('account.tax', 'Tax', readonly=True, states={'draft':[('readonly',False)]}, domain=[('price_include','=', False)], help="Only for tax excluded from price"),
299 'pre_line':fields.boolean('Previous Payments ?', required=False),
300 'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
301 'payment_option':fields.selection([
302 ('without_writeoff', 'Keep Open'),
303 ('with_writeoff', 'Reconcile Payment Balance'),
304 ], '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)"),
305 'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
306 'comment': fields.char('Counterpart Comment', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
307 'analytic_id': fields.many2one('account.analytic.account','Write-Off Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
308 '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."),
309 'payment_rate_currency_id': fields.many2one('res.currency', 'Payment Rate Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
310 'payment_rate': fields.float('Exchange Rate', digits=(12,6), required=True, readonly=True, states={'draft': [('readonly', False)]},
311 help='The specific rate that will be used, in this voucher, between the selected currency (in \'Payment Rate Currency\' field) and the voucher currency.'),
312 'paid_amount_in_company_currency': fields.function(_paid_amount_in_company_currency, string='Paid Amount in Company Currency', type='float', readonly=True),
313 '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'),
316 'period_id': _get_period,
317 'partner_id': _get_partner,
318 'journal_id':_get_journal,
319 'currency_id': _get_currency,
320 'reference': _get_reference,
321 'narration':_get_narration,
322 'amount': _get_amount,
325 'pay_now': 'pay_now',
327 'date': lambda *a: time.strftime('%Y-%m-%d'),
328 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.voucher',context=c),
330 'payment_option': 'without_writeoff',
331 'comment': _('Write-Off'),
333 'payment_rate_currency_id': _get_payment_rate_currency,
336 def create(self, cr, uid, vals, context=None):
337 voucher = super(account_voucher, self).create(cr, uid, vals, context=context)
338 self.create_send_note(cr, uid, [voucher], context=context)
341 def compute_tax(self, cr, uid, ids, context=None):
342 tax_pool = self.pool.get('account.tax')
343 partner_pool = self.pool.get('res.partner')
344 position_pool = self.pool.get('account.fiscal.position')
345 voucher_line_pool = self.pool.get('account.voucher.line')
346 voucher_pool = self.pool.get('account.voucher')
347 if context is None: context = {}
349 for voucher in voucher_pool.browse(cr, uid, ids, context=context):
351 for line in voucher.line_ids:
352 voucher_amount += line.untax_amount or line.amount
353 line.amount = line.untax_amount or line.amount
354 voucher_line_pool.write(cr, uid, [line.id], {'amount':line.amount, 'untax_amount':line.untax_amount})
356 if not voucher.tax_id:
357 self.write(cr, uid, [voucher.id], {'amount':voucher_amount, 'tax_amount':0.0})
360 tax = [tax_pool.browse(cr, uid, voucher.tax_id.id, context=context)]
361 partner = partner_pool.browse(cr, uid, voucher.partner_id.id, context=context) or False
362 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
363 tax = tax_pool.browse(cr, uid, taxes, context=context)
365 total = voucher_amount
368 if not tax[0].price_include:
369 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_amount, 1).get('taxes', []):
370 total_tax += tax_line.get('amount', 0.0)
373 for line in voucher.line_ids:
377 for tax_line in tax_pool.compute_all(cr, uid, tax, line.untax_amount or line.amount, 1).get('taxes', []):
378 line_tax += tax_line.get('amount', 0.0)
379 line_total += tax_line.get('price_unit')
380 total_tax += line_tax
381 untax_amount = line.untax_amount or line.amount
382 voucher_line_pool.write(cr, uid, [line.id], {'amount':line_total, 'untax_amount':untax_amount})
384 self.write(cr, uid, [voucher.id], {'amount':total, 'tax_amount':total_tax})
387 def onchange_price(self, cr, uid, ids, line_ids, tax_id, partner_id=False, context=None):
388 context = context or {}
389 tax_pool = self.pool.get('account.tax')
390 partner_pool = self.pool.get('res.partner')
391 position_pool = self.pool.get('account.fiscal.position')
392 line_pool = self.pool.get('account.voucher.line')
399 line_ids = resolve_o2m_operations(cr, uid, line_pool, line_ids, ["amount"], context)
401 for line in line_ids:
403 line_amount = line.get('amount',0.0)
404 voucher_total += line_amount
406 total = voucher_total
409 tax = [tax_pool.browse(cr, uid, tax_id, context=context)]
411 partner = partner_pool.browse(cr, uid, partner_id, context=context) or False
412 taxes = position_pool.map_tax(cr, uid, partner and partner.property_account_position or False, tax)
413 tax = tax_pool.browse(cr, uid, taxes, context=context)
415 if not tax[0].price_include:
416 for tax_line in tax_pool.compute_all(cr, uid, tax, voucher_total, 1).get('taxes', []):
417 total_tax += tax_line.get('amount')
421 'amount':total or voucher_total,
422 'tax_amount':total_tax
428 def onchange_term_id(self, cr, uid, ids, term_id, amount):
429 term_pool = self.pool.get('account.payment.term')
432 default = {'date_due':False}
433 if term_id and amount:
434 terms = term_pool.compute(cr, uid, term_id, amount)
436 due_date = terms[-1][0]
440 return {'value':default}
442 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):
444 Returns a dict that contains new values and context
446 @param partner_id: latest value from user input for field partner_id
447 @param args: other arguments
448 @param context: context arguments, like lang, time zone
450 @return: Returns a dict which contains new values, and context
456 if not partner_id or not journal_id:
459 partner_pool = self.pool.get('res.partner')
460 journal_pool = self.pool.get('account.journal')
462 journal = journal_pool.browse(cr, uid, journal_id, context=context)
463 partner = partner_pool.browse(cr, uid, partner_id, context=context)
466 if journal.type in ('sale','sale_refund'):
467 account_id = partner.property_account_receivable.id
469 elif journal.type in ('purchase', 'purchase_refund','expense'):
470 account_id = partner.property_account_payable.id
473 if not journal.default_credit_account_id or not journal.default_debit_account_id:
474 raise osv.except_osv(_('Error!'), _('Please define default credit/debit accounts on the journal "%s".') % (journal.name))
475 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
478 default['value']['account_id'] = account_id
479 default['value']['type'] = ttype or tr_type
481 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)
482 default['value'].update(vals.get('value'))
486 def onchange_rate(self, cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=None):
487 res = {'value': {'paid_amount_in_company_currency': amount}}
488 company_currency = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id
489 if rate and amount and currency_id:# and currency_id == payment_rate_currency_id:
490 voucher_rate = self.pool.get('res.currency').browse(cr, uid, currency_id, context).rate
491 if company_currency.id == payment_rate_currency_id:
494 company_rate = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.rate
495 res['value']['paid_amount_in_company_currency'] = amount / voucher_rate * company_rate
498 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):
501 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
503 ctx.update({'date': date})
504 vals = self.onchange_rate(cr, uid, ids, rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
505 for key in vals.keys():
506 res[key].update(vals[key])
509 def recompute_payment_rate(self, cr, uid, ids, vals, currency_id, date, ttype, journal_id, amount, context=None):
512 #on change of the journal, we need to set also the default value for payment_rate and payment_rate_currency_id
513 currency_obj = self.pool.get('res.currency')
514 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
515 company_id = journal.company_id.id
517 payment_rate_currency_id = currency_id
519 ctx.update({'date': date})
521 if ttype == 'receipt':
522 o2m_to_loop = 'line_cr_ids'
523 elif ttype == 'payment':
524 o2m_to_loop = 'line_dr_ids'
525 if o2m_to_loop and 'value' in vals and o2m_to_loop in vals['value']:
526 for voucher_line in vals['value'][o2m_to_loop]:
527 if voucher_line['currency_id'] != currency_id:
528 # we take as default value for the payment_rate_currency_id, the currency of the first invoice that
529 # is not in the voucher currency
530 payment_rate_currency_id = voucher_line['currency_id']
531 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
532 voucher_currency_id = currency_id or journal.company_id.currency_id.id
533 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
535 res = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
536 for key in res.keys():
537 vals[key].update(res[key])
538 vals['value'].update({'payment_rate': payment_rate})
539 if payment_rate_currency_id:
540 vals['value'].update({'payment_rate_currency_id': payment_rate_currency_id})
543 def onchange_partner_id(self, cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=None):
546 res = self.recompute_voucher_lines(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context=context)
547 vals = self.recompute_payment_rate(cr, uid, ids, res, currency_id, date, ttype, journal_id, amount, context=context)
548 for key in vals.keys():
549 res[key].update(vals[key])
552 def recompute_voucher_lines(self, cr, uid, ids, partner_id, journal_id, price, currency_id, ttype, date, context=None):
554 Returns a dict that contains new values and context
556 @param partner_id: latest value from user input for field partner_id
557 @param args: other arguments
558 @param context: context arguments, like lang, time zone
560 @return: Returns a dict which contains new values, and context
562 def _remove_noise_in_o2m():
563 """if the line is partially reconciled, then we must pay attention to display it only once and
565 This function returns True if the line is considered as noise and should not be displayed
567 if line.reconcile_partial_id:
568 sign = 1 if ttype == 'receipt' else -1
569 if currency_id == line.currency_id.id:
570 if line.amount_residual_currency * sign <= 0:
573 if line.amount_residual * sign <= 0:
579 context_multi_currency = context.copy()
581 context_multi_currency.update({'date': date})
583 currency_pool = self.pool.get('res.currency')
584 move_line_pool = self.pool.get('account.move.line')
585 partner_pool = self.pool.get('res.partner')
586 journal_pool = self.pool.get('account.journal')
587 line_pool = self.pool.get('account.voucher.line')
591 'value': {'line_ids': [] ,'line_dr_ids': [] ,'line_cr_ids': [] ,'pre_line': False,},
595 line_ids = ids and line_pool.search(cr, uid, [('voucher_id', '=', ids[0])]) or False
597 line_pool.unlink(cr, uid, line_ids)
599 if not partner_id or not journal_id:
602 journal = journal_pool.browse(cr, uid, journal_id, context=context)
603 partner = partner_pool.browse(cr, uid, partner_id, context=context)
604 currency_id = currency_id or journal.company_id.currency_id.id
606 if journal.type in ('sale','sale_refund'):
607 account_id = partner.property_account_receivable.id
608 elif journal.type in ('purchase', 'purchase_refund','expense'):
609 account_id = partner.property_account_payable.id
611 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
613 default['value']['account_id'] = account_id
615 if journal.type not in ('cash', 'bank'):
620 account_type = 'receivable'
621 if ttype == 'payment':
622 account_type = 'payable'
623 total_debit = price or 0.0
625 total_credit = price or 0.0
626 account_type = 'receivable'
628 if not context.get('move_line_ids', False):
629 ids = move_line_pool.search(cr, uid, [('state','=','valid'), ('account_id.type', '=', account_type), ('reconcile_id', '=', False), ('partner_id', '=', partner_id)], context=context)
631 ids = context['move_line_ids']
632 invoice_id = context.get('invoice_id', False)
633 company_currency = journal.company_id.currency_id.id
634 move_line_found = False
636 #order the lines by most old first
638 account_move_lines = move_line_pool.browse(cr, uid, ids, context=context)
640 #compute the total debit/credit and look for a matching open amount or invoice
641 for line in account_move_lines:
642 if _remove_noise_in_o2m():
646 if line.invoice.id == invoice_id:
647 #if the invoice linked to the voucher line is equal to the invoice_id in context
648 #then we assign the amount on that line, whatever the other voucher lines
649 move_line_found = line.id
651 elif currency_id == company_currency:
652 #otherwise treatments is the same but with other field names
653 if line.amount_residual == price:
654 #if the amount residual is equal the amount voucher, we assign it to that voucher
655 #line, whatever the other voucher lines
656 move_line_found = line.id
658 #otherwise we will split the voucher amount on each line (by most old first)
659 total_credit += line.credit or 0.0
660 total_debit += line.debit or 0.0
661 elif currency_id == line.currency_id.id:
662 if line.amount_residual_currency == price:
663 move_line_found = line.id
665 total_credit += line.credit and line.amount_currency or 0.0
666 total_debit += line.debit and line.amount_currency or 0.0
668 #voucher line creation
669 for line in account_move_lines:
670 if _remove_noise_in_o2m():
673 if line.currency_id and currency_id==line.currency_id.id:
674 amount_original = abs(line.amount_currency)
675 amount_unreconciled = abs(line.amount_residual_currency)
677 amount_original = currency_pool.compute(cr, uid, company_currency, currency_id, line.credit or line.debit or 0.0)
678 amount_unreconciled = currency_pool.compute(cr, uid, company_currency, currency_id, abs(line.amount_residual))
679 line_currency_id = line.currency_id and line.currency_id.id or company_currency
681 'name':line.move_id.name,
682 'type': line.credit and 'dr' or 'cr',
683 'move_line_id':line.id,
684 'account_id':line.account_id.id,
685 'amount_original': amount_original,
686 'amount': (move_line_found == line.id) and min(price, amount_unreconciled) or 0.0,
687 'date_original':line.date,
688 'date_due':line.date_maturity,
689 'amount_unreconciled': amount_unreconciled,
690 'currency_id': line_currency_id,
693 #split voucher amount by most old first, but only for lines in the same currency
694 if not move_line_found:
695 if currency_id == line_currency_id:
697 amount = min(amount_unreconciled, abs(total_debit))
698 rs['amount'] = amount
699 total_debit -= amount
701 amount = min(amount_unreconciled, abs(total_credit))
702 rs['amount'] = amount
703 total_credit -= amount
705 if rs['amount_unreconciled'] == rs['amount']:
706 rs['reconcile'] = True
708 if rs['type'] == 'cr':
709 default['value']['line_cr_ids'].append(rs)
711 default['value']['line_dr_ids'].append(rs)
713 if ttype == 'payment' and len(default['value']['line_cr_ids']) > 0:
714 default['value']['pre_line'] = 1
715 elif ttype == 'receipt' and len(default['value']['line_dr_ids']) > 0:
716 default['value']['pre_line'] = 1
717 default['value']['writeoff_amount'] = self._compute_writeoff_amount(cr, uid, default['value']['line_dr_ids'], default['value']['line_cr_ids'], price, ttype)
720 def onchange_payment_rate_currency(self, cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=None):
724 #set the default payment rate of the voucher and compute the paid amount in company currency
725 if currency_id and currency_id == payment_rate_currency_id:
727 ctx.update({'date': date})
728 vals = self.onchange_rate(cr, uid, ids, payment_rate, amount, currency_id, payment_rate_currency_id, company_id, context=ctx)
729 for key in vals.keys():
730 res[key].update(vals[key])
733 def onchange_date(self, cr, uid, ids, date, currency_id, payment_rate_currency_id, amount, company_id, context=None):
735 @param date: latest value from user input for field date
736 @param args: other arguments
737 @param context: context arguments, like lang, time zone
738 @return: Returns a dict which contains new values, and context
743 #set the period of the voucher
744 period_pool = self.pool.get('account.period')
745 currency_obj = self.pool.get('res.currency')
747 ctx.update({'company_id': company_id})
748 pids = period_pool.find(cr, uid, date, context=ctx)
750 res['value'].update({'period_id':pids[0]})
751 if payment_rate_currency_id:
752 ctx.update({'date': date})
754 if payment_rate_currency_id != currency_id:
755 tmp = currency_obj.browse(cr, uid, payment_rate_currency_id, context=ctx).rate
756 voucher_currency_id = currency_id or self.pool.get('res.company').browse(cr, uid, company_id, context=ctx).currency_id.id
757 payment_rate = tmp / currency_obj.browse(cr, uid, voucher_currency_id, context=ctx).rate
758 vals = self.onchange_payment_rate_currency(cr, uid, ids, currency_id, payment_rate, payment_rate_currency_id, date, amount, company_id, context=context)
759 vals['value'].update({'payment_rate': payment_rate})
760 for key in vals.keys():
761 res[key].update(vals[key])
764 def onchange_journal(self, cr, uid, ids, journal_id, line_ids, tax_id, partner_id, date, amount, ttype, company_id, context=None):
767 journal_pool = self.pool.get('account.journal')
768 journal = journal_pool.browse(cr, uid, journal_id, context=context)
769 account_id = journal.default_credit_account_id or journal.default_debit_account_id
771 if account_id and account_id.tax_ids:
772 tax_id = account_id.tax_ids[0].id
774 vals = self.onchange_price(cr, uid, ids, line_ids, tax_id, partner_id, context)
775 vals['value'].update({'tax_id':tax_id,'amount': amount})
778 currency_id = journal.currency.id
779 vals['value'].update({'currency_id': currency_id})
780 res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
781 for key in res.keys():
782 vals[key].update(res[key])
785 def proforma_voucher(self, cr, uid, ids, context=None):
786 self.action_move_line_create(cr, uid, ids, context=context)
787 return {'type': 'ir.actions.act_window_close'}
789 def action_cancel_draft(self, cr, uid, ids, context=None):
790 wf_service = netsvc.LocalService("workflow")
791 for voucher_id in ids:
792 wf_service.trg_create(uid, 'account.voucher', voucher_id, cr)
793 self.write(cr, uid, ids, {'state':'draft'})
796 def cancel_voucher(self, cr, uid, ids, context=None):
797 reconcile_pool = self.pool.get('account.move.reconcile')
798 move_pool = self.pool.get('account.move')
800 for voucher in self.browse(cr, uid, ids, context=context):
802 for line in voucher.move_ids:
803 if line.reconcile_id:
804 recs += [line.reconcile_id.id]
805 if line.reconcile_partial_id:
806 recs += [line.reconcile_partial_id.id]
808 reconcile_pool.unlink(cr, uid, recs)
811 move_pool.button_cancel(cr, uid, [voucher.move_id.id])
812 move_pool.unlink(cr, uid, [voucher.move_id.id])
817 self.write(cr, uid, ids, res)
820 def unlink(self, cr, uid, ids, context=None):
821 for t in self.read(cr, uid, ids, ['state'], context=context):
822 if t['state'] not in ('draft', 'cancel'):
823 raise osv.except_osv(_('Invalid Action!'), _('Cannot delete voucher(s) which are already opened or paid.'))
824 return super(account_voucher, self).unlink(cr, uid, ids, context=context)
826 def onchange_payment(self, cr, uid, ids, pay_now, journal_id, partner_id, ttype='sale'):
830 res = {'account_id':False}
831 partner_pool = self.pool.get('res.partner')
832 journal_pool = self.pool.get('account.journal')
833 if pay_now == 'pay_later':
834 partner = partner_pool.browse(cr, uid, partner_id)
835 journal = journal_pool.browse(cr, uid, journal_id)
836 if journal.type in ('sale','sale_refund'):
837 account_id = partner.property_account_receivable.id
838 elif journal.type in ('purchase', 'purchase_refund','expense'):
839 account_id = partner.property_account_payable.id
841 account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
842 res['account_id'] = account_id
845 def _sel_context(self, cr, uid, voucher_id,context=None):
847 Select the context to use accordingly if it needs to be multicurrency or not.
849 :param voucher_id: Id of the actual voucher
850 :return: The returned context will be the same as given in parameter if the voucher currency is the same
851 than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing
852 the date of the voucher.
855 company_currency = self._get_company_currency(cr, uid, voucher_id, context)
856 current_currency = self._get_current_currency(cr, uid, voucher_id, context)
857 if current_currency <> company_currency:
858 context_multi_currency = context.copy()
859 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
860 context_multi_currency.update({'date': voucher_brw.date})
861 return context_multi_currency
864 def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None):
866 Return a dict to be use to create the first account move line of given voucher.
868 :param voucher_id: Id of voucher what we are creating account_move.
869 :param move_id: Id of account move where this line will be added.
870 :param company_currency: id of currency of the company to which the voucher belong
871 :param current_currency: id of currency of the voucher
872 :return: mapping between fieldname and value of account move line to create
875 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
877 # TODO: is there any other alternative then the voucher type ??
878 # ANSWER: We can have payment and receipt "In Advance".
879 # TODO: Make this logic available.
880 # -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
881 if voucher_brw.type in ('purchase', 'payment'):
882 credit = voucher_brw.paid_amount_in_company_currency
883 elif voucher_brw.type in ('sale', 'receipt'):
884 debit = voucher_brw.paid_amount_in_company_currency
885 if debit < 0: credit = -debit; debit = 0.0
886 if credit < 0: debit = -credit; credit = 0.0
887 sign = debit - credit < 0 and -1 or 1
888 #set the first line of the voucher
890 'name': voucher_brw.name or '/',
893 'account_id': voucher_brw.account_id.id,
895 'journal_id': voucher_brw.journal_id.id,
896 'period_id': voucher_brw.period_id.id,
897 'partner_id': voucher_brw.partner_id.id,
898 'currency_id': company_currency <> current_currency and current_currency or False,
899 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0,
900 'date': voucher_brw.date,
901 'date_maturity': voucher_brw.date_due
905 def account_move_get(self, cr, uid, voucher_id, context=None):
907 This method prepare the creation of the account move related to the given voucher.
909 :param voucher_id: Id of voucher for which we are creating account_move.
910 :return: mapping between fieldname and value of account move to create
913 seq_obj = self.pool.get('ir.sequence')
914 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
915 if voucher_brw.number:
916 name = voucher_brw.number
917 elif voucher_brw.journal_id.sequence_id:
918 name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context)
920 raise osv.except_osv(_('Error!'),
921 _('Please define a sequence on the journal.'))
922 if not voucher_brw.reference:
923 ref = name.replace('/','')
925 ref = voucher_brw.reference
929 'journal_id': voucher_brw.journal_id.id,
930 'narration': voucher_brw.narration,
931 'date': voucher_brw.date,
933 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False
937 def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None):
939 Prepare the two lines in company currency due to currency rate difference.
941 :param line: browse record of the voucher.line for which we want to create currency rate difference accounting
943 :param move_id: Account move wher the move lines will be.
944 :param amount_residual: Amount to be posted.
945 :param company_currency: id of currency of the company to which the voucher belong
946 :param current_currency: id of currency of the voucher
947 :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value
948 :rtype: tuple of dict
950 if amount_residual > 0:
951 account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
953 raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Income Currency Rate' on the company, then create accounting entry for currency rate difference."))
955 account_id = line.voucher_id.company_id.income_currency_exchange_account_id
957 raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Expense Currency Rate' on the company, then create accounting entry for currency rate difference."))
958 # Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
959 # the receivable/payable account may have a secondary currency, which render this field mandatory
960 account_currency_id = company_currency <> current_currency and current_currency or False
962 'journal_id': line.voucher_id.journal_id.id,
963 'period_id': line.voucher_id.period_id.id,
964 'name': _('change')+': '+(line.name or '/'),
965 'account_id': line.account_id.id,
967 'partner_id': line.voucher_id.partner_id.id,
968 'currency_id': account_currency_id,
969 'amount_currency': 0.0,
971 'credit': amount_residual > 0 and amount_residual or 0.0,
972 'debit': amount_residual < 0 and -amount_residual or 0.0,
973 'date': line.voucher_id.date,
975 move_line_counterpart = {
976 'journal_id': line.voucher_id.journal_id.id,
977 'period_id': line.voucher_id.period_id.id,
978 'name': _('change')+': '+(line.name or '/'),
979 'account_id': account_id.id,
981 'amount_currency': 0.0,
982 'partner_id': line.voucher_id.partner_id.id,
983 'currency_id': account_currency_id,
985 'debit': amount_residual > 0 and amount_residual or 0.0,
986 'credit': amount_residual < 0 and -amount_residual or 0.0,
987 'date': line.voucher_id.date,
989 return (move_line, move_line_counterpart)
991 def _convert_amount(self, cr, uid, amount, voucher_id, context=None):
993 This function convert the amount given in company currency. It takes either the rate in the voucher (if the
994 payment_rate_currency_id is relevant) either the rate encoded in the system.
996 :param amount: float. The amount to convert
997 :param voucher: id of the voucher on which we want the conversion
998 :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
999 field in order to select the good rate to use.
1000 :return: the amount in the currency of the voucher's company
1003 currency_obj = self.pool.get('res.currency')
1004 voucher = self.browse(cr, uid, voucher_id, context=context)
1006 if voucher.payment_rate_currency_id.id == voucher.company_id.currency_id.id:
1007 # the rate specified on the voucher is for the company currency
1008 res = currency_obj.round(cr, uid, voucher.company_id.currency_id, (amount * voucher.payment_rate))
1010 # the rate specified on the voucher is not relevant, we use all the rates in the system
1011 res = currency_obj.compute(cr, uid, voucher.currency_id.id, voucher.company_id.currency_id.id, amount, context=context)
1014 def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None):
1016 Create one account move line, on the given account move, per voucher line where amount is not 0.0.
1017 It returns Tuple with tot_line what is total of difference between debit and credit and
1018 a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).
1020 :param voucher_id: Voucher id what we are working with
1021 :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
1022 :param move_id: Account move wher those lines will be joined.
1023 :param company_currency: id of currency of the company to which the voucher belong
1024 :param current_currency: id of currency of the voucher
1025 :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
1026 :rtype: tuple(float, list of int)
1030 move_line_obj = self.pool.get('account.move.line')
1031 currency_obj = self.pool.get('res.currency')
1032 tax_obj = self.pool.get('account.tax')
1033 tot_line = line_total
1036 voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
1037 ctx = context.copy()
1038 ctx.update({'date': voucher_brw.date})
1039 for line in voucher_brw.line_ids:
1040 #create one move line per voucher line where amount is not 0.0
1043 # convert the amount set on the voucher line into the currency of the voucher's company
1044 amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
1045 # if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
1046 # currency rate difference
1047 if line.amount == line.amount_unreconciled:
1048 if not line.move_line_id.amount_residual:
1049 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))
1050 currency_rate_difference = line.move_line_id.amount_residual - amount
1052 currency_rate_difference = 0.0
1054 'journal_id': voucher_brw.journal_id.id,
1055 'period_id': voucher_brw.period_id.id,
1056 'name': line.name or '/',
1057 'account_id': line.account_id.id,
1059 'partner_id': voucher_brw.partner_id.id,
1060 '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,
1061 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False,
1065 'date': voucher_brw.date
1069 if line.type == 'dr':
1074 if (line.type=='dr'):
1076 move_line['debit'] = amount
1079 move_line['credit'] = amount
1081 if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'):
1083 'account_tax_id': voucher_brw.tax_id.id,
1086 if move_line.get('account_tax_id', False):
1087 tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0]
1088 if not (tax_data.base_code_id and tax_data.tax_code_id):
1089 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))
1091 # compute the amount in foreign currency
1092 foreign_currency_diff = 0.0
1093 amount_currency = False
1094 if line.move_line_id:
1095 voucher_currency = voucher_brw.currency_id and voucher_brw.currency_id.id or voucher_brw.journal_id.company_id.currency_id.id
1096 # We want to set it on the account move line as soon as the original line had a foreign currency
1097 if line.move_line_id.currency_id and line.move_line_id.currency_id.id != company_currency:
1098 # we compute the amount in that foreign currency.
1099 if line.move_line_id.currency_id.id == current_currency:
1100 # if the voucher and the voucher line share the same currency, there is no computation to do
1101 sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1
1102 amount_currency = sign * (line.amount)
1103 elif line.move_line_id.currency_id.id == voucher_brw.payment_rate_currency_id.id:
1104 # if the rate is specified on the voucher, we must use it
1105 voucher_rate = currency_obj.browse(cr, uid, voucher_currency, context=ctx).rate
1106 amount_currency = (move_line['debit'] - move_line['credit']) * voucher_brw.payment_rate * voucher_rate
1108 # otherwise we use the rates of the system (giving the voucher date in the context)
1109 amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
1110 if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
1111 sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
1112 foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
1114 move_line['amount_currency'] = amount_currency
1115 voucher_line = move_line_obj.create(cr, uid, move_line)
1116 rec_ids = [voucher_line, line.move_line_id.id]
1118 if not currency_obj.is_zero(cr, uid, voucher_brw.company_id.currency_id, currency_rate_difference):
1119 # Change difference entry in company currency
1120 exch_lines = self._get_exchange_lines(cr, uid, line, move_id, currency_rate_difference, company_currency, current_currency, context=context)
1121 new_id = move_line_obj.create(cr, uid, exch_lines[0],context)
1122 move_line_obj.create(cr, uid, exch_lines[1], context)
1123 rec_ids.append(new_id)
1125 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):
1126 # Change difference entry in voucher currency
1127 move_line_foreign_currency = {
1128 'journal_id': line.voucher_id.journal_id.id,
1129 'period_id': line.voucher_id.period_id.id,
1130 'name': _('change')+': '+(line.name or '/'),
1131 'account_id': line.account_id.id,
1133 'partner_id': line.voucher_id.partner_id.id,
1134 'currency_id': line.move_line_id.currency_id.id,
1135 'amount_currency': -1 * foreign_currency_diff,
1139 'date': line.voucher_id.date,
1141 new_id = move_line_obj.create(cr, uid, move_line_foreign_currency, context=context)
1142 rec_ids.append(new_id)
1144 if line.move_line_id.id:
1145 rec_lst_ids.append(rec_ids)
1147 return (tot_line, rec_lst_ids)
1149 def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None):
1151 Set a dict to be use to create the writeoff move line.
1153 :param voucher_id: Id of voucher what we are creating account_move.
1154 :param line_total: Amount remaining to be allocated on lines.
1155 :param move_id: Id of account move where this line will be added.
1156 :param name: Description of account move line.
1157 :param company_currency: id of currency of the company to which the voucher belong
1158 :param current_currency: id of currency of the voucher
1159 :return: mapping between fieldname and value of account move line to create
1162 currency_obj = self.pool.get('res.currency')
1165 voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1166 current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id
1168 if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total):
1172 if voucher_brw.payment_option == 'with_writeoff':
1173 account_id = voucher_brw.writeoff_acc_id.id
1174 write_off_name = voucher_brw.comment
1175 elif voucher_brw.type in ('sale', 'receipt'):
1176 account_id = voucher_brw.partner_id.property_account_receivable.id
1178 account_id = voucher_brw.partner_id.property_account_payable.id
1180 'name': write_off_name or name,
1181 'account_id': account_id,
1183 'partner_id': voucher_brw.partner_id.id,
1184 'date': voucher_brw.date,
1185 'credit': diff > 0 and diff or 0.0,
1186 'debit': diff < 0 and -diff or 0.0,
1187 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False,
1188 'currency_id': company_currency <> current_currency and current_currency or False,
1189 'analytic_account_id': voucher_brw.analytic_id and voucher_brw.analytic_id.id or False,
1194 def _get_company_currency(self, cr, uid, voucher_id, context=None):
1196 Get the currency of the actual company.
1198 :param voucher_id: Id of the voucher what i want to obtain company currency.
1199 :return: currency id of the company of the voucher
1202 return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id
1204 def _get_current_currency(self, cr, uid, voucher_id, context=None):
1206 Get the currency of the voucher.
1208 :param voucher_id: Id of the voucher what i want to obtain current currency.
1209 :return: currency id of the voucher
1212 voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context)
1213 return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context)
1215 def action_move_line_create(self, cr, uid, ids, context=None):
1217 Confirm the vouchers given in ids and create the journal entries for each of them
1221 move_pool = self.pool.get('account.move')
1222 move_line_pool = self.pool.get('account.move.line')
1223 for voucher in self.browse(cr, uid, ids, context=context):
1226 company_currency = self._get_company_currency(cr, uid, voucher.id, context)
1227 current_currency = self._get_current_currency(cr, uid, voucher.id, context)
1228 # we select the context to use accordingly if it's a multicurrency case or not
1229 context = self._sel_context(cr, uid, voucher.id, context)
1230 # But for the operations made by _convert_amount, we always need to give the date in the context
1231 ctx = context.copy()
1232 ctx.update({'date': voucher.date})
1233 # Create the account move record.
1234 move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context)
1235 # Get the name of the account_move just created
1236 name = move_pool.browse(cr, uid, move_id, context=context).name
1237 # Create the first line of the voucher
1238 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)
1239 move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
1240 line_total = move_line_brw.debit - move_line_brw.credit
1242 if voucher.type == 'sale':
1243 line_total = line_total - self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1244 elif voucher.type == 'purchase':
1245 line_total = line_total + self._convert_amount(cr, uid, voucher.tax_amount, voucher.id, context=ctx)
1246 # Create one move line per voucher line where amount is not 0.0
1247 line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
1249 # Create the writeoff line if needed
1250 ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
1252 move_line_pool.create(cr, uid, ml_writeoff, context)
1253 # We post the voucher.
1254 self.write(cr, uid, [voucher.id], {
1259 self.post_send_note(cr, uid, [voucher.id], context=context)
1260 if voucher.journal_id.entry_posted:
1261 move_pool.post(cr, uid, [move_id], context={})
1262 # We automatically reconcile the account move lines.
1264 for rec_ids in rec_list_ids:
1265 if len(rec_ids) >= 2:
1266 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)
1268 self.reconcile_send_note(cr, uid, [voucher.id], context=context)
1271 def copy(self, cr, uid, id, default={}, context=None):
1276 'line_cr_ids': False,
1277 'line_dr_ids': False,
1280 if 'date' not in default:
1281 default['date'] = time.strftime('%Y-%m-%d')
1282 return super(account_voucher, self).copy(cr, uid, id, default, context)
1284 # -----------------------------------------
1285 # OpenChatter notifications and need_action
1286 # -----------------------------------------
1288 'sale': 'Sales Receipt',
1289 'purchase': 'Purchase Receipt',
1290 'payment': 'Supplier Payment',
1291 'receipt': 'Customer Payment',
1295 def create_send_note(self, cr, uid, ids, context=None):
1296 for obj in self.browse(cr, uid, ids, context=context):
1297 message = "%s <b>created</b>." % self._document_type[obj.type or False]
1298 self.message_post(cr, uid, [obj.id], body=message, context=context)
1300 def post_send_note(self, cr, uid, ids, context=None):
1301 for obj in self.browse(cr, uid, ids, context=context):
1302 message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
1303 self.message_post(cr, uid, [obj.id], body=message, context=context)
1305 def reconcile_send_note(self, cr, uid, ids, context=None):
1306 for obj in self.browse(cr, uid, ids, context=context):
1307 message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
1308 self.message_post(cr, uid, [obj.id], body=message, context=context)
1312 class account_voucher_line(osv.osv):
1313 _name = 'account.voucher.line'
1314 _description = 'Voucher Lines'
1315 _order = "move_line_id"
1317 # If the payment is in the same currency than the invoice, we keep the same amount
1318 # Otherwise, we compute from company currency to payment currency
1319 def _compute_balance(self, cr, uid, ids, name, args, context=None):
1320 currency_pool = self.pool.get('res.currency')
1322 for line in self.browse(cr, uid, ids, context=context):
1323 ctx = context.copy()
1324 ctx.update({'date': line.voucher_id.date})
1326 company_currency = line.voucher_id.journal_id.company_id.currency_id.id
1327 voucher_currency = line.voucher_id.currency_id and line.voucher_id.currency_id.id or company_currency
1328 move_line = line.move_line_id or False
1331 res['amount_original'] = 0.0
1332 res['amount_unreconciled'] = 0.0
1333 elif move_line.currency_id and voucher_currency==move_line.currency_id.id:
1334 res['amount_original'] = currency_pool.compute(cr, uid, move_line.currency_id.id, voucher_currency, abs(move_line.amount_currency), context=ctx)
1335 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)
1336 elif move_line and move_line.credit > 0:
1337 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.credit, context=ctx)
1338 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1340 res['amount_original'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, move_line.debit, context=ctx)
1341 res['amount_unreconciled'] = currency_pool.compute(cr, uid, company_currency, voucher_currency, abs(move_line.amount_residual), context=ctx)
1343 rs_data[line.id] = res
1346 def _currency_id(self, cr, uid, ids, name, args, context=None):
1348 This function returns the currency id of a voucher line. It's either the currency of the
1349 associated move line (if any) or the currency of the voucher or the company currency.
1352 for line in self.browse(cr, uid, ids, context=context):
1353 move_line = line.move_line_id
1355 res[line.id] = move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id
1357 res[line.id] = line.voucher_id.currency_id and line.voucher_id.currency_id.id or line.voucher_id.company_id.currency_id.id
1361 'voucher_id':fields.many2one('account.voucher', 'Voucher', required=1, ondelete='cascade'),
1362 'name':fields.char('Description', size=256),
1363 'account_id':fields.many2one('account.account','Account', required=True),
1364 'partner_id':fields.related('voucher_id', 'partner_id', type='many2one', relation='res.partner', string='Partner'),
1365 'untax_amount':fields.float('Untax Amount'),
1366 'amount':fields.float('Amount', digits_compute=dp.get_precision('Account')),
1367 'reconcile': fields.boolean('Full Reconcile'),
1368 'type':fields.selection([('dr','Debit'),('cr','Credit')], 'Dr/Cr'),
1369 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1370 'move_line_id': fields.many2one('account.move.line', 'Journal Item'),
1371 'date_original': fields.related('move_line_id','date', type='date', relation='account.move.line', string='Date', readonly=1),
1372 'date_due': fields.related('move_line_id','date_maturity', type='date', relation='account.move.line', string='Due Date', readonly=1),
1373 'amount_original': fields.function(_compute_balance, multi='dc', type='float', string='Original Amount', store=True, digits_compute=dp.get_precision('Account')),
1374 'amount_unreconciled': fields.function(_compute_balance, multi='dc', type='float', string='Open Balance', store=True, digits_compute=dp.get_precision('Account')),
1375 'company_id': fields.related('voucher_id','company_id', relation='res.company', type='many2one', string='Company', store=True, readonly=True),
1376 'currency_id': fields.function(_currency_id, string='Currency', type='many2one', relation='res.currency', readonly=True),
1382 def onchange_reconcile(self, cr, uid, ids, reconcile, amount, amount_unreconciled, context=None):
1383 vals = { 'amount': 0.0}
1385 vals = { 'amount': amount_unreconciled}
1386 return {'value': vals}
1388 def onchange_amount(self, cr, uid, ids, amount, amount_unreconciled, context=None):
1391 vals['reconcile'] = (amount == amount_unreconciled)
1392 return {'value': vals}
1394 def onchange_move_line_id(self, cr, user, ids, move_line_id, context=None):
1396 Returns a dict that contains new values and context
1398 @param move_line_id: latest value from user input for field move_line_id
1399 @param args: other arguments
1400 @param context: context arguments, like lang, time zone
1402 @return: Returns a dict which contains new values, and context
1405 move_line_pool = self.pool.get('account.move.line')
1407 move_line = move_line_pool.browse(cr, user, move_line_id, context=context)
1408 if move_line.credit:
1413 'account_id': move_line.account_id.id,
1415 'currency_id': move_line.currency_id and move_line.currency_id.id or move_line.company_id.currency_id.id,
1421 def default_get(self, cr, user, fields_list, context=None):
1423 Returns default values for fields
1424 @param fields_list: list of fields, for which default values are required to be read
1425 @param context: context arguments, like lang, time zone
1427 @return: Returns a dict that contains default values for fields
1431 journal_id = context.get('journal_id', False)
1432 partner_id = context.get('partner_id', False)
1433 journal_pool = self.pool.get('account.journal')
1434 partner_pool = self.pool.get('res.partner')
1435 values = super(account_voucher_line, self).default_get(cr, user, fields_list, context=context)
1436 if (not journal_id) or ('account_id' not in fields_list):
1438 journal = journal_pool.browse(cr, user, journal_id, context=context)
1441 if journal.type in ('sale', 'sale_refund'):
1442 account_id = journal.default_credit_account_id and journal.default_credit_account_id.id or False
1444 elif journal.type in ('purchase', 'expense', 'purchase_refund'):
1445 account_id = journal.default_debit_account_id and journal.default_debit_account_id.id or False
1448 partner = partner_pool.browse(cr, user, partner_id, context=context)
1449 if context.get('type') == 'payment':
1451 account_id = partner.property_account_payable.id
1452 elif context.get('type') == 'receipt':
1453 account_id = partner.property_account_receivable.id
1456 'account_id':account_id,
1460 account_voucher_line()
1462 class account_bank_statement(osv.osv):
1463 _inherit = 'account.bank.statement'
1465 def button_cancel(self, cr, uid, ids, context=None):
1466 voucher_obj = self.pool.get('account.voucher')
1467 for st in self.browse(cr, uid, ids, context=context):
1469 for line in st.line_ids:
1471 voucher_ids.append(line.voucher_id.id)
1472 voucher_obj.cancel_voucher(cr, uid, voucher_ids, context)
1473 return super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context)
1475 def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id, next_number, context=None):
1476 voucher_obj = self.pool.get('account.voucher')
1477 wf_service = netsvc.LocalService("workflow")
1478 move_line_obj = self.pool.get('account.move.line')
1479 bank_st_line_obj = self.pool.get('account.bank.statement.line')
1480 st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
1481 if st_line.voucher_id:
1482 voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
1483 if st_line.voucher_id.state == 'cancel':
1484 voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
1485 wf_service.trg_validate(uid, 'account.voucher', st_line.voucher_id.id, 'proforma_voucher', cr)
1487 v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
1488 bank_st_line_obj.write(cr, uid, [st_line_id], {
1489 'move_ids': [(4, v.move_id.id, False)]
1492 return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context)
1493 return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context)
1495 account_bank_statement()
1497 class account_bank_statement_line(osv.osv):
1498 _inherit = 'account.bank.statement.line'
1500 def _amount_reconciled(self, cursor, user, ids, name, args, context=None):
1504 for line in self.browse(cursor, user, ids, context=context):
1506 res[line.id] = line.voucher_id.amount#
1511 def _check_amount(self, cr, uid, ids, context=None):
1512 for obj in self.browse(cr, uid, ids, context=context):
1514 diff = abs(obj.amount) - obj.voucher_id.amount
1515 if not self.pool.get('res.currency').is_zero(cr, uid, obj.statement_id.currency, diff):
1520 (_check_amount, 'The amount of the voucher must be the same amount as the one on the statement line.', ['amount']),
1524 'amount_reconciled': fields.function(_amount_reconciled,
1525 string='Amount reconciled', type='float'),
1526 'voucher_id': fields.many2one('account.voucher', 'Payment'),
1529 def unlink(self, cr, uid, ids, context=None):
1530 voucher_obj = self.pool.get('account.voucher')
1531 statement_line = self.browse(cr, uid, ids, context=context)
1533 for st_line in statement_line:
1534 if st_line.voucher_id:
1535 unlink_ids.append(st_line.voucher_id.id)
1536 voucher_obj.unlink(cr, uid, unlink_ids, context=context)
1537 return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
1539 account_bank_statement_line()
1541 def resolve_o2m_operations(cr, uid, target_osv, operations, fields, context):
1543 for operation in operations:
1545 if not isinstance(operation, (list, tuple)):
1546 result = target_osv.read(cr, uid, operation, fields, context=context)
1547 elif operation[0] == 0:
1548 # may be necessary to check if all the fields are here and get the default values?
1549 result = operation[2]
1550 elif operation[0] == 1:
1551 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1552 if not result: result = {}
1553 result.update(operation[2])
1554 elif operation[0] == 4:
1555 result = target_osv.read(cr, uid, operation[1], fields, context=context)
1557 results.append(result)
1561 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: