1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 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 datetime import datetime
24 from dateutil.relativedelta import relativedelta
26 from osv import osv, fields
27 import decimal_precision as dp
29 class account_asset_category(osv.osv):
30 _name = 'account.asset.category'
31 _description = 'Asset category'
34 'name': fields.char('Name', size=64, required=True, select=1),
35 'note': fields.text('Note'),
36 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
37 'account_asset_id': fields.many2one('account.account', 'Asset Account', required=True),
38 'account_depreciation_id': fields.many2one('account.account', 'Depreciation Account', required=True),
39 'account_expense_depreciation_id': fields.many2one('account.account', 'Depr. Expense Account', required=True),
40 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
41 'company_id': fields.many2one('res.company', 'Company', required=True),
42 'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True),
43 'method_delay': fields.integer('Number of Depreciation'),
44 'method_period': fields.integer('Period Length', help="State here the time between 2 depreciations, in months"),
45 'method_progress_factor': fields.float('Progressif Factor'),
46 'method_time': fields.selection([('delay','Delay'),('end','Ending Period')], 'Time Method', required=True),
47 'method_end': fields.date('Ending date'),
48 'prorata':fields.boolean('Prorata Temporis', help='Indicates that the accounting entries for this asset have to be done from the purchase date instead of the first January'),
49 'open_asset': fields.boolean('Skip Draft State', help="Check this if you want to automatically confirm the assets of this category when created by invoice."),
53 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.category', context=context),
56 'method_time': 'delay',
58 'method_progress_factor': 0.3,
61 def onchange_account_asset(self, cr, uid, ids, account_asset_id, context=None):
64 res['value'] = {'account_depreciation_id': account_asset_id}
67 account_asset_category()
69 #class one2many_mod_asset(fields.one2many):
71 # def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
72 # prinasset_property_id if context is None:
79 # #compute depreciation board
80 # depreciation_line_ids = obj.pool.get('account.asset.asset').compute_depreciation_board(cr, user, ids, context=context)
81 # for key, value in depreciation_line_ids.items():
82 # #write values on asset
83 # obj.pool.get(self._obj).write(cr, user, key, {'depreciation_line_ids': [6,0,value]})
84 # return depreciation_line_ids
86 class account_asset_asset(osv.osv):
87 _name = 'account.asset.asset'
88 _description = 'Asset'
90 def _get_period(self, cr, uid, context={}):
91 periods = self.pool.get('account.period').find(cr, uid)
97 def _get_last_depreciation_date(self, cr, uid, ids, context=None):
99 @param id: ids of a account.asset.asset objects
100 @return: Returns a dictionary of the effective dates of the last depreciation entry made for given asset ids. If there isn't any, return the purchase date of this asset
103 SELECT a.id as id, COALESCE(MAX(l.date),a.purchase_date) AS date
104 FROM account_asset_asset a
105 LEFT JOIN account_move_line l ON (l.asset_id = a.id)
107 GROUP BY a.id, a.purchase_date """, (tuple(ids),))
108 return dict(cr.fetchall())
110 def compute_depreciation_board(self, cr, uid,ids, context=None):
111 depreciation_lin_obj = self.pool.get('account.asset.depreciation.line')
112 for asset in self.browse(cr, uid, ids, context=context):
113 if asset.value_residual == 0.0:
115 posted_depreciation_line_ids = depreciation_lin_obj.search(cr, uid, [('asset_id', '=', asset.id), ('move_check', '=', True)])
116 old_depreciation_line_ids = depreciation_lin_obj.search(cr, uid, [('asset_id', '=', asset.id), ('move_id', '=', False)])
117 if old_depreciation_line_ids:
118 depreciation_lin_obj.unlink(cr, uid, old_depreciation_line_ids, context=context)
120 amount_to_depr = residual_amount = asset.value_residual
122 depreciation_date = datetime.strptime(self._get_last_depreciation_date(cr, uid, [asset.id], context)[asset.id], '%Y-%m-%d')
123 day = depreciation_date.day
124 month = depreciation_date.month
125 year = depreciation_date.year
126 total_days = (year % 4) and 365 or 366
127 undone_dotation_number = asset.method_delay
128 if asset.method_time == 'end':
129 end_date = datetime.strptime(asset.method_end, '%Y-%m-%d')
130 undone_dotation_number = (end_date - depreciation_date).days / total_days
131 if asset.prorata or asset.method_time == 'end':
132 undone_dotation_number += 1
133 for x in range(len(posted_depreciation_line_ids), undone_dotation_number):
135 if i == undone_dotation_number:
136 amount = residual_amount
138 if asset.method == 'linear':
139 amount = amount_to_depr / (undone_dotation_number - len(posted_depreciation_line_ids))
141 amount = amount_to_depr / asset.method_delay
143 days = total_days - float(depreciation_date.strftime('%j'))
144 amount = (amount_to_depr / asset.method_delay) / total_days * days
145 elif i == undone_dotation_number:
146 amount = (amount_to_depr / asset.method_delay) / total_days * (total_days - days)
148 amount = residual_amount * asset.method_progress_factor
149 residual_amount -= amount
152 'asset_id': asset.id,
154 'name': str(asset.id) +'/' + str(i),
155 'remaining_value': residual_amount,
156 'depreciated_value': (asset.purchase_value - asset.salvage_value) - (residual_amount + amount),
157 'depreciation_date': depreciation_date.strftime('%Y-%m-%d'),
159 depreciation_lin_obj.create(cr, uid, vals, context=context)
160 # Considering Depr. Period as months
161 depreciation_date = (datetime(year, month, day) + relativedelta(months=+asset.method_period))
162 day = depreciation_date.day
163 month = depreciation_date.month
164 year = depreciation_date.year
167 def validate(self, cr, uid, ids, context={}):
168 return self.write(cr, uid, ids, {
172 def set_to_close(self, cr, uid, ids, context=None):
173 return self.write(cr, uid, ids, {'state': 'close'}, context=context)
175 def _amount_residual(self, cr, uid, ids, name, args, context=None):
177 l.asset_id as id, round(SUM(abs(l.debit-l.credit))) AS amount
181 l.asset_id IN %s GROUP BY l.asset_id """, (tuple(ids),))
182 res=dict(cr.fetchall())
183 for asset in self.browse(cr, uid, ids, context):
184 res[asset.id] = asset.purchase_value - res.get(asset.id, 0.0) - asset.salvage_value
186 res.setdefault(id, 0.0)
190 'period_id': fields.many2one('account.period', 'First Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
191 'account_move_line_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
193 'name': fields.char('Asset', size=64, required=True, select=1),
194 'code': fields.char('Reference ', size=16, select=1),
195 'purchase_value': fields.float('Gross value ', required=True, size=16, select=1),
196 'currency_id': fields.many2one('res.currency','Currency',required=True,size=5,select=1),
197 'company_id': fields.many2one('res.company', 'Company', required=True),
198 'note': fields.text('Note'),
199 'category_id': fields.many2one('account.asset.category', 'Asset category',required=True, change_default=True),
200 'localisation': fields.char('Localisation', size=32, select=2),
201 'parent_id': fields.many2one('account.asset.asset', 'Parent Asset'),
202 'child_ids': fields.one2many('account.asset.asset', 'parent_id', 'Children Assets'),
203 'purchase_date': fields.date('Purchase Date', required=True),
204 'state': fields.selection([('draft','Draft'),('open','Running'),('close','Close')], 'State', required=True),
205 'active': fields.boolean('Active', select=2),
206 'partner_id': fields.many2one('res.partner', 'Partner'),
207 'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation Method', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="Linear: Calculated on basis of Gross Value/During (interval) \
208 \nProgressive: Calculated on basis of Gross Value * Progressif Factor"),
209 'method_delay': fields.integer('Number of Depreciation', readonly=True, states={'draft':[('readonly',False)]}, help="Calculates Depreciation within specified interval"),
210 'method_period': fields.integer('Period Length', readonly=True, states={'draft':[('readonly',False)]}, help="State here the time during 2 depreciations, in months"),
211 'method_end': fields.date('Ending date', readonly=True, states={'draft':[('readonly',False)]}),
212 'method_progress_factor': fields.float('Progressif Factor', readonly=True, states={'draft':[('readonly',False)]}),
213 'value_residual': fields.function(_amount_residual, method=True, digits_compute=dp.get_precision('Account'), string='Residual Value'),
214 'method_time': fields.selection([('delay','Delay'),('end','Ending Period')], 'Time Method', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="Delay: Allow users to enter number of periods to generate depreciation lines \n Ending Period: Calculates depreciation lines on the basis of Ending Period Date and Number of Depreciation"),
215 'prorata':fields.boolean('Prorata Temporis', Readonly="True", help='Indicates that the accounting entries for this asset have to be done from the purchase date instead of the first January'),
216 'history_ids': fields.one2many('account.asset.history', 'asset_id', 'History', readonly=True),
217 'depreciation_line_ids': fields.one2many('account.asset.depreciation.line', 'asset_id', 'Depreciation Lines'),
218 'salvage_value': fields.float('Salvage Value', digits_compute=dp.get_precision('Account'), help="It is the amount you plan to have that you cannot depreciate."),
221 'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'account.asset.code'),
222 'purchase_date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d'),
223 'active': lambda obj, cr, uid, context: True,
224 'state': lambda obj, cr, uid, context: 'draft',
225 'period_id': _get_period,
226 'method': lambda obj, cr, uid, context: 'linear',
227 'method_delay': lambda obj, cr, uid, context: 5,
228 'method_time': lambda obj, cr, uid, context: 'delay',
229 'method_period': lambda obj, cr, uid, context: 12,
230 'method_progress_factor': lambda obj, cr, uid, context: 0.3,
231 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
232 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.asset',context=context),
235 def _check_prorata(self, cr, uid, ids, context=None):
236 for asset in self.browse(cr, uid, ids, context=context):
237 if asset.prorata and (asset.method != 'linear' or asset.method_time != 'delay'):
242 (_check_prorata, '\nProrata temporis can be applied only for computation method linear and time method delay.', ['prorata']),
245 def onchange_category_id(self, cr, uid, ids, category_id, context=None):
247 asset_categ_obj = self.pool.get('account.asset.category')
249 category_obj = asset_categ_obj.browse(cr, uid, category_id, context=context)
251 'method': category_obj.method,
252 'method_delay': category_obj.method_delay,
253 'method_time': category_obj.method_time,
254 'method_period': category_obj.method_period,
255 'method_progress_factor': category_obj.method_progress_factor,
256 'method_end': category_obj.method_end,
257 'prorata': category_obj.prorata,
261 def onchange_method_time(self, cr, uid, ids, method='linear', method_time='delay', context=None):
263 if method != 'linear' or method_time != 'delay':
264 res['value'] = {'prorata': False}
267 def copy(self, cr, uid, id, default=None, context=None):
272 default.update({'depreciation_line_ids': [], 'state': 'draft'})
273 return super(account_asset_asset, self).copy(cr, uid, id, default, context=context)
275 def _compute_period(self, cr, uid, property, context={}):
276 if (len(property.entry_asset_ids or [])/2)>=property.method_delay:
278 if len(property.entry_asset_ids):
279 cp = property.entry_asset_ids[-1].period_id
280 cpid = self.pool.get('account.period').next(cr, uid, cp, property.method_period, context)
281 current_period = self.pool.get('account.period').browse(cr, uid, cpid, context)
283 current_period = property.asset_id.period_id
284 return current_period
286 def _compute_move(self, cr, uid, property, period, context={}):
287 #FIXME: fucntion not working OK
290 for move in property.asset_id.entry_ids:
291 total += move.debit-move.credit
292 for move in property.entry_asset_ids:
293 if move.account_id == property.account_asset_ids:
295 total += -move.credit
296 periods = (len(property.entry_asset_ids)/2) - property.method_delay
301 if property.method == 'linear':
302 amount = total / periods
304 amount = total * property.method_progress_factor
306 move_id = self.pool.get('account.move').create(cr, uid, {
307 'journal_id': property.journal_id.id,
308 'period_id': period.id,
309 'name': property.name or property.asset_id.name,
310 'ref': property.asset_id.code
313 id = self.pool.get('account.move.line').create(cr, uid, {
314 'name': property.name or property.asset_id.name,
316 'account_id': property.account_asset_id.id,
317 'debit': amount>0 and amount or 0.0,
318 'credit': amount<0 and -amount or 0.0,
319 'ref': property.asset_id.code,
320 'period_id': period.id,
321 'journal_id': property.journal_id.id,
322 'partner_id': property.asset_id.partner_id.id,
323 'date': time.strftime('%Y-%m-%d'),
325 id2 = self.pool.get('account.move.line').create(cr, uid, {
326 'name': property.name or property.asset_id.name,
328 'account_id': property.account_actif_id.id,
329 'credit': amount>0 and amount or 0.0,
330 'debit': amount<0 and -amount or 0.0,
331 'ref': property.asset_id.code,
332 'period_id': period.id,
333 'journal_id': property.journal_id.id,
334 'partner_id': property.asset_id.partner_id.id,
335 'date': time.strftime('%Y-%m-%d'),
338 self.pool.get('account.asset.asset').write(cr, uid, [property.id], {
339 'entry_asset_ids': [(4, id2, False),(4,id,False)]
341 if property.method_delay - (len(property.entry_asset_ids)/2)<=1:
342 #self.pool.get('account.asset.property')._close(cr, uid, property, context)
346 def _compute_entries(self, cr, uid, asset, period_id, context={}):
347 #FIXME: function not working CHECK all res
349 date_start = self.pool.get('account.period').browse(cr, uid, period_id, context).date_start
350 for property in asset.property_ids:
351 if property.state=='open':
352 period = self._compute_period(cr, uid, property, context)
353 if period and (period.date_start<=date_start):
354 result += self._compute_move(cr, uid, property, period, context)
357 account_asset_asset()
359 class account_asset_depreciation_line(osv.osv):
360 _name = 'account.asset.depreciation.line'
361 _description = 'Asset depreciation line'
363 def _get_move_check(self, cr, uid, ids, name, args, context=None):
365 for line in self.browse(cr, uid, ids, context=context):
366 res[line.id] = bool(line.move_id)
370 'name': fields.char('Depreciation Name', size=64, required=True, select=1),
371 'sequence': fields.integer('Sequence of the depreciation', required=True),
372 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
373 'amount': fields.float('Depreciation Amount', required=True),
374 'remaining_value': fields.float('Amount to Depreciate', required=True),
375 'depreciated_value': fields.float('Amount Already Depreciated', required=True),
376 'depreciation_date': fields.char('Depreciation Date', size=64, select=1),
377 'move_id': fields.many2one('account.move', 'Depreciation Entry'),
378 'move_check': fields.function(_get_move_check, method=True, type='boolean', string='Posted', store=True)
381 def create_move(self, cr, uid, ids, context=None):
384 asset_obj = self.pool.get('account.asset.asset')
385 period_obj = self.pool.get('account.period')
386 move_obj = self.pool.get('account.move')
387 move_line_obj = self.pool.get('account.move.line')
388 currency_obj = self.pool.get('res.currency')
389 for line in self.browse(cr, uid, ids, context=context):
390 rem_value = line.remaining_value
391 depreciation_date = line.asset_id.prorata and line.asset_id.purchase_date or time.strftime('%Y-%m-%d')
392 period_ids = period_obj.find(cr, uid, depreciation_date, context=context)
393 company_currency = line.asset_id.company_id.currency_id.id
394 current_currency = line.asset_id.currency_id.id
395 context.update({'date': depreciation_date})
396 amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.amount, context=context)
397 sign = line.asset_id.category_id.journal_id.type = 'purchase' and 1 or -1
400 'date': depreciation_date,
402 'period_id': period_ids and period_ids[0] or False,
403 'journal_id': line.asset_id.category_id.journal_id.id,
405 move_id = move_obj.create(cr, uid, move_vals, context=context)
406 move_line_obj.create(cr, uid, {
410 'account_id': line.asset_id.category_id.account_depreciation_id.id,
413 'period_id': period_ids and period_ids[0] or False,
414 'journal_id': line.asset_id.category_id.journal_id.id,
415 'partner_id': line.asset_id.partner_id.id,
416 'currency_id': company_currency <> current_currency and current_currency or False,
417 'amount_currency': company_currency <> current_currency and - sign * line.amount or 0.0,
418 'date': depreciation_date,
420 move_line_obj.create(cr, uid, {
424 'account_id': line.asset_id.category_id.account_expense_depreciation_id.id,
427 'period_id': period_ids and period_ids[0] or False,
428 'journal_id': line.asset_id.category_id.journal_id.id,
429 'partner_id': line.asset_id.partner_id.id,
430 'currency_id': company_currency <> current_currency and current_currency or False,
431 'amount_currency': company_currency <> current_currency and sign * line.amount or 0.0,
432 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
433 'date': depreciation_date,
434 'asset_id': line.asset_id.id
436 self.write(cr, uid, line.id, {'move_id': move_id}, context=context)
438 asset_obj.write(cr, uid, line.asset_id.id, {'state': 'close'}, context=context)
441 account_asset_depreciation_line()
443 #class account_asset_property(osv.osv):
444 # def _amount_total(self, cr, uid, ids, name, args, context={}):
445 # id_set=",".join(map(str,ids))
446 # cr.execute("""SELECT l.asset_id,abs(SUM(l.debit-l.credit)) AS amount FROM
447 # account_asset_property p
449 # account_move_line l on (p.asset_id=l.asset_id)
450 # WHERE p.id IN ("""+id_set+") GROUP BY l.asset_id ")
451 # res=dict(cr.fetchall())
453 # res.setdefault(id, 0.0)
456 # def _close(self, cr, uid, property, context={}):
457 # if property.state<>'close':
458 # self.pool.get('account.asset.property').write(cr, uid, [property.id], {
461 # property.state='close'
462 # ok = property.asset_id.state=='open'
463 # for prop in property.asset_id.property_ids:
464 # ok = ok and prop.state=='close'
465 # self.pool.get('account.asset.asset').write(cr, uid, [property.asset_id.id], {
470 # _name = 'account.asset.property'
471 # _description = 'Asset property'
473 # 'name': fields.char('Method name', size=64, select=1),
474 # 'type': fields.selection([('direct','Direct'),('indirect','Indirect')], 'Depr. method type', select=2, required=True),
475 # 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
476 # 'account_asset_id': fields.many2one('account.account', 'Asset account', required=True),
477 # 'account_actif_id': fields.many2one('account.account', 'Depreciation account', required=True),
478 # 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
479 # 'journal_analytic_id': fields.many2one('account.analytic.journal', 'Analytic journal'),
480 # 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
482 # 'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
483 # 'method_delay': fields.integer('During', readonly=True, states={'draft':[('readonly',False)]}),
484 # 'method_period': fields.integer('Depre. all', readonly=True, states={'draft':[('readonly',False)]}),
485 # 'method_end': fields.date('Ending date'),
487 # 'date': fields.date('Date created'),
488 # #'test': fields.one2many('account.pre', 'asset_id', readonly=True, states={'draft':[('readonly',False)]}),
489 # 'entry_asset_ids': fields.many2many('account.move.line', 'account_move_asset_entry_rel', 'asset_property_id', 'move_id', 'Asset Entries'),
490 # 'board_ids': fields.one2many('account.asset.board', 'asset_id', 'Asset board'),
492 # 'value_total': fields.function(_amount_total, method=True, digits=(16,2),string='Gross value'),
493 # 'state': fields.selection([('draft','Draft'), ('open','Open'), ('close','Close')], 'State', required=True),
494 # 'history_ids': fields.one2many('account.asset.property.history', 'asset_property_id', 'History', readonly=True)
495 ## 'parent_id': fields.many2one('account.asset.asset', 'Parent asset'),
496 ## 'partner_id': fields.many2one('res.partner', 'Partner'),
497 ## 'note': fields.text('Note'),
501 # 'type': lambda obj, cr, uid, context: 'direct',
502 # 'state': lambda obj, cr, uid, context: 'draft',
503 # 'method': lambda obj, cr, uid, context: 'linear',
504 # 'method_time': lambda obj, cr, uid, context: 'delay',
505 # 'method_progress_factor': lambda obj, cr, uid, context: 0.3,
506 # 'method_delay': lambda obj, cr, uid, context: 5,
507 # 'method_period': lambda obj, cr, uid, context: 12,
508 # 'date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d')
510 #account_asset_property()
512 class account_move_line(osv.osv):
513 _inherit = 'account.move.line'
515 'asset_id': fields.many2one('account.asset.asset', 'Asset'),
516 'entry_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
521 class account_asset_history(osv.osv):
522 _name = 'account.asset.history'
523 _description = 'Asset history'
525 'name': fields.char('History name', size=64, select=1),
526 'user_id': fields.many2one('res.users', 'User', required=True),
527 'date': fields.date('Date', required=True),
528 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
529 'method_delay': fields.integer('Number of interval'),
530 'method_period': fields.integer('Period per interval'),
531 'method_end': fields.date('Ending date'),
532 'note': fields.text('Note'),
535 'date': lambda *args: time.strftime('%Y-%m-%d'),
536 'user_id': lambda self,cr, uid,ctx: uid
538 account_asset_history()
540 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: