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 'journal_analytic_id': fields.many2one('account.analytic.journal', 'Analytic journal'),
37 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
38 'account_asset_id': fields.many2one('account.account', 'Asset Account', required=True),
39 'account_depreciation_id': fields.many2one('account.account', 'Depreciation Account', required=True),
40 'account_expense_depreciation_id': fields.many2one('account.account', 'Depr. Expense Account', required=True),
41 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
42 'company_id': fields.many2one('res.company', 'Company', required=True),
43 'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True),
44 'method_delay': fields.integer('Number of Depreciation'),
45 'method_period': fields.integer('Period Length', help="State here the time between 2 depreciations, in months"),
46 'method_progress_factor': fields.float('Progressif Factor'),
47 'method_time': fields.selection([('delay','Delay'),('end','Ending Period')], 'Time Method', required=True),
48 'method_end': fields.date('Ending date'),
49 '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'),
50 '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."),
54 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.category', context=context),
57 'method_time': 'delay',
59 'method_progress_factor': 0.3,
62 account_asset_category()
64 #class one2many_mod_asset(fields.one2many):
66 # def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
67 # prinasset_property_id if context is None:
74 # #compute depreciation board
75 # depreciation_line_ids = obj.pool.get('account.asset.asset').compute_depreciation_board(cr, user, ids, context=context)
76 # for key, value in depreciation_line_ids.items():
77 # #write values on asset
78 # obj.pool.get(self._obj).write(cr, user, key, {'depreciation_line_ids': [6,0,value]})
79 # return depreciation_line_ids
81 class account_asset_asset(osv.osv):
82 _name = 'account.asset.asset'
83 _description = 'Asset'
85 def _get_period(self, cr, uid, context={}):
86 periods = self.pool.get('account.period').find(cr, uid)
92 def _get_last_depreciation_date(self, cr, uid, ids, context=None):
94 @param id: ids of a account.asset.asset objects
95 @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
98 SELECT a.id as id, COALESCE(MAX(l.date),a.purchase_date) AS date
99 FROM account_asset_asset a
100 LEFT JOIN account_move_line l ON (l.asset_id = a.id)
102 GROUP BY a.id, a.purchase_date """, (tuple(ids),))
103 return dict(cr.fetchall())
105 def compute_depreciation_board(self, cr, uid,ids, context=None):
106 depreciation_lin_obj = self.pool.get('account.asset.depreciation.line')
107 for asset in self.browse(cr, uid, ids, context=context):
108 if asset.value_residual == 0.0:
110 posted_depreciation_line_ids = depreciation_lin_obj.search(cr, uid, [('asset_id', '=', asset.id), ('move_check', '=', True)])
111 old_depreciation_line_ids = depreciation_lin_obj.search(cr, uid, [('asset_id', '=', asset.id), ('move_id', '=', False)])
112 if old_depreciation_line_ids:
113 depreciation_lin_obj.unlink(cr, uid, old_depreciation_line_ids, context=context)
115 amount_to_depr = residual_amount = asset.value_residual
117 depreciation_date = datetime.strptime(self._get_last_depreciation_date(cr, uid, [asset.id], context)[asset.id], '%Y-%m-%d')
118 day = depreciation_date.day
119 month = depreciation_date.month
120 year = depreciation_date.year
121 total_days = (year % 4) and 365 or 366
122 undone_dotation_number = asset.method_delay
123 if asset.method_time == 'end':
124 end_date = datetime.strptime(asset.method_end, '%Y-%m-%d')
125 undone_dotation_number = (end_date - depreciation_date).days / total_days
126 if asset.prorata or asset.method_time == 'end':
127 undone_dotation_number += 1
128 for x in range(len(posted_depreciation_line_ids), undone_dotation_number):
130 if i == undone_dotation_number:
131 amount = residual_amount
133 if asset.method == 'linear':
134 amount = amount_to_depr / (undone_dotation_number - len(posted_depreciation_line_ids))
136 amount = amount_to_depr / asset.method_delay
138 days = total_days - float(depreciation_date.strftime('%j'))
139 amount = (amount_to_depr / asset.method_delay) / total_days * days
140 elif i == undone_dotation_number:
141 amount = (amount_to_depr / asset.method_delay) / total_days * (total_days - days)
143 amount = residual_amount * asset.method_progress_factor
144 residual_amount -= amount
147 'asset_id': asset.id,
149 'name': str(asset.id) +'/' + str(i),
150 'remaining_value': residual_amount,
151 'depreciated_value': (asset.purchase_value - asset.salvage_value) - (residual_amount + amount),
152 'depreciation_date': depreciation_date.strftime('%Y-%m-%d'),
154 depreciation_lin_obj.create(cr, uid, vals, context=context)
155 # Considering Depr. Period as months
156 depreciation_date = (datetime(year, month, day) + relativedelta(months=+asset.method_period))
157 day = depreciation_date.day
158 month = depreciation_date.month
159 year = depreciation_date.year
162 def validate(self, cr, uid, ids, context={}):
163 return self.write(cr, uid, ids, {
167 def _amount_residual(self, cr, uid, ids, name, args, context=None):
169 l.asset_id as id, round(SUM(abs(l.debit-l.credit))) AS amount
173 l.asset_id IN %s GROUP BY l.asset_id """, (tuple(ids),))
174 res=dict(cr.fetchall())
175 for asset in self.browse(cr, uid, ids, context):
176 res[asset.id] = asset.purchase_value - res.get(asset.id, 0.0) - asset.salvage_value
178 res.setdefault(id, 0.0)
182 'period_id': fields.many2one('account.period', 'First Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
183 'account_move_line_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
185 'name': fields.char('Asset', size=64, required=True, select=1),
186 'code': fields.char('Reference ', size=16, select=1),
187 'purchase_value': fields.float('Gross value ', required=True, size=16, select=1),
188 'currency_id': fields.many2one('res.currency','Currency',required=True,size=5,select=1),
189 'company_id': fields.many2one('res.company', 'Company', required=True),
190 'note': fields.text('Note'),
191 'category_id': fields.many2one('account.asset.category', 'Asset category',required=True, change_default=True),
192 'localisation': fields.char('Localisation', size=32, select=2),
193 'parent_id': fields.many2one('account.asset.asset', 'Parent Asset'),
194 'child_ids': fields.one2many('account.asset.asset', 'parent_id', 'Children Assets'),
195 'purchase_date': fields.date('Purchase Date', required=True),
196 'state': fields.selection([('draft','Draft'),('open','Running'),('close','Close')], 'State', required=True),
197 'active': fields.boolean('Active', select=2),
198 'partner_id': fields.many2one('res.partner', 'Partner'),
199 '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) \
200 \nProgressive: Calculated on basis of Gross Value * Progressif Factor"),
201 'method_delay': fields.integer('Number of Depreciation', readonly=True, states={'draft':[('readonly',False)]}, help="Calculates Depreciation within specified interval"),
202 'method_period': fields.integer('Period Length', readonly=True, states={'draft':[('readonly',False)]}, help="State here the time during 2 depreciations, in months"),
203 'method_end': fields.date('Ending date', readonly=True, states={'draft':[('readonly',False)]}),
204 'method_progress_factor': fields.float('Progressif Factor', readonly=True, states={'draft':[('readonly',False)]}),
205 'value_residual': fields.function(_amount_residual, method=True, digits_compute=dp.get_precision('Account'), string='Residual Value'),
206 '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"),
207 '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'),
208 'history_ids': fields.one2many('account.asset.history', 'asset_id', 'History', readonly=True),
209 'depreciation_line_ids': fields.one2many('account.asset.depreciation.line', 'asset_id', 'Depreciation Lines'),
210 '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."),
213 'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'account.asset.code'),
214 'purchase_date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d'),
215 'active': lambda obj, cr, uid, context: True,
216 'state': lambda obj, cr, uid, context: 'draft',
217 'period_id': _get_period,
218 'method': lambda obj, cr, uid, context: 'linear',
219 'method_delay': lambda obj, cr, uid, context: 5,
220 'method_time': lambda obj, cr, uid, context: 'delay',
221 'method_period': lambda obj, cr, uid, context: 12,
222 'method_progress_factor': lambda obj, cr, uid, context: 0.3,
223 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
224 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.asset',context=context),
227 def _check_prorata(self, cr, uid, ids, context=None):
228 for asset in self.browse(cr, uid, ids, context=context):
229 if asset.prorata and (asset.method != 'linear' or asset.method_time != 'delay'):
234 (_check_prorata, '\nProrata temporis can be applied only for computation method linear and time method delay.', ['prorata']),
237 def onchange_category_id(self, cr, uid, ids, category_id, context=None):
239 asset_categ_obj = self.pool.get('account.asset.category')
241 category_obj = asset_categ_obj.browse(cr, uid, category_id, context=context)
243 'method': category_obj.method,
244 'method_delay': category_obj.method_delay,
245 'method_time': category_obj.method_time,
246 'method_period': category_obj.method_period,
247 'method_progress_factor': category_obj.method_progress_factor,
248 'method_end': category_obj.method_end,
249 'prorata': category_obj.prorata,
253 def onchange_method_time(self, cr, uid, ids, method='linear', method_time='delay', context=None):
255 if method != 'linear' or method_time != 'delay':
256 res['value'] = {'prorata': False}
259 def copy(self, cr, uid, id, default=None, context=None):
264 default.update({'depreciation_line_ids': [], 'state': 'draft'})
265 return super(account_asset_asset, self).copy(cr, uid, id, default, context=context)
267 def _compute_period(self, cr, uid, property, context={}):
268 if (len(property.entry_asset_ids or [])/2)>=property.method_delay:
270 if len(property.entry_asset_ids):
271 cp = property.entry_asset_ids[-1].period_id
272 cpid = self.pool.get('account.period').next(cr, uid, cp, property.method_period, context)
273 current_period = self.pool.get('account.period').browse(cr, uid, cpid, context)
275 current_period = property.asset_id.period_id
276 return current_period
278 def _compute_move(self, cr, uid, property, period, context={}):
279 #FIXME: fucntion not working OK
282 for move in property.asset_id.entry_ids:
283 total += move.debit-move.credit
284 for move in property.entry_asset_ids:
285 if move.account_id == property.account_asset_ids:
287 total += -move.credit
288 periods = (len(property.entry_asset_ids)/2) - property.method_delay
293 if property.method == 'linear':
294 amount = total / periods
296 amount = total * property.method_progress_factor
298 move_id = self.pool.get('account.move').create(cr, uid, {
299 'journal_id': property.journal_id.id,
300 'period_id': period.id,
301 'name': property.name or property.asset_id.name,
302 'ref': property.asset_id.code
305 id = self.pool.get('account.move.line').create(cr, uid, {
306 'name': property.name or property.asset_id.name,
308 'account_id': property.account_asset_id.id,
309 'debit': amount>0 and amount or 0.0,
310 'credit': amount<0 and -amount or 0.0,
311 'ref': property.asset_id.code,
312 'period_id': period.id,
313 'journal_id': property.journal_id.id,
314 'partner_id': property.asset_id.partner_id.id,
315 'date': time.strftime('%Y-%m-%d'),
317 id2 = self.pool.get('account.move.line').create(cr, uid, {
318 'name': property.name or property.asset_id.name,
320 'account_id': property.account_actif_id.id,
321 'credit': amount>0 and amount or 0.0,
322 'debit': amount<0 and -amount or 0.0,
323 'ref': property.asset_id.code,
324 'period_id': period.id,
325 'journal_id': property.journal_id.id,
326 'partner_id': property.asset_id.partner_id.id,
327 'date': time.strftime('%Y-%m-%d'),
330 self.pool.get('account.asset.asset').write(cr, uid, [property.id], {
331 'entry_asset_ids': [(4, id2, False),(4,id,False)]
333 if property.method_delay - (len(property.entry_asset_ids)/2)<=1:
334 #self.pool.get('account.asset.property')._close(cr, uid, property, context)
338 def _compute_entries(self, cr, uid, asset, period_id, context={}):
339 #FIXME: function not working CHECK all res
341 date_start = self.pool.get('account.period').browse(cr, uid, period_id, context).date_start
342 for property in asset.property_ids:
343 if property.state=='open':
344 period = self._compute_period(cr, uid, property, context)
345 if period and (period.date_start<=date_start):
346 result += self._compute_move(cr, uid, property, period, context)
348 account_asset_asset()
350 class account_asset_depreciation_line(osv.osv):
351 _name = 'account.asset.depreciation.line'
352 _description = 'Asset depreciation line'
354 def _get_move_check(self, cr, uid, ids, name, args, context=None):
356 for line in self.browse(cr, uid, ids, context=context):
357 res[line.id] = bool(line.move_id)
361 'name': fields.char('Depreciation Name', size=64, required=True, select=1),
362 'sequence': fields.integer('Sequence of the depreciation', required=True),
363 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
364 'amount': fields.float('Depreciation Amount', required=True),
365 'remaining_value': fields.float('Amount to Depreciate', required=True),
366 'depreciated_value': fields.float('Amount Already Depreciated', required=True),
367 'depreciation_date': fields.char('Depreciation Date', size=64, select=1),
368 'move_id': fields.many2one('account.move', 'Depreciation Entry'),
369 'move_check': fields.function(_get_move_check, method=True, type='boolean', string='Posted', store=True)
372 def create_move(self, cr, uid,ids, context=None):
375 asset_obj = self.pool.get('account.asset.asset')
376 period_obj = self.pool.get('account.period')
377 move_obj = self.pool.get('account.move')
378 move_line_obj = self.pool.get('account.move.line')
379 currency_obj = self.pool.get('res.currency')
380 for line in self.browse(cr, uid, ids, context=context):
381 depreciation_date = line.asset_id.prorata and line.asset_id.purchase_date or time.strftime('%Y-%m-%d')
382 period_ids = period_obj.find(cr, uid, depreciation_date, context=context)
383 company_currency = line.asset_id.company_id.currency_id.id
384 current_currency = line.asset_id.currency_id.id
385 context.update({'date': depreciation_date})
386 amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.amount, context=context)
387 sign = line.asset_id.category_id.journal_id.type = 'purchase' and 1 or -1
390 'date': depreciation_date,
392 'period_id': period_ids and period_ids[0] or False,
393 'journal_id': line.asset_id.category_id.journal_id.id,
395 move_id = move_obj.create(cr, uid, move_vals, context=context)
396 move_line_obj.create(cr, uid, {
400 'account_id': line.asset_id.category_id.account_depreciation_id.id,
403 'period_id': period_ids and period_ids[0] or False,
404 'journal_id': line.asset_id.category_id.journal_id.id,
405 'partner_id': line.asset_id.partner_id.id,
406 'currency_id': company_currency <> current_currency and current_currency or False,
407 'amount_currency': company_currency <> current_currency and - sign * line.amount or 0.0,
408 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
409 'date': depreciation_date,
411 move_line_obj.create(cr, uid, {
415 'account_id': line.asset_id.category_id.account_expense_depreciation_id.id,
418 'period_id': period_ids and period_ids[0] or False,
419 'journal_id': line.asset_id.category_id.journal_id.id,
420 'partner_id': line.asset_id.partner_id.id,
421 'currency_id': company_currency <> current_currency and current_currency or False,
422 'amount_currency': company_currency <> current_currency and sign * line.amount or 0.0,
423 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
424 'date': depreciation_date,
425 'asset_id': line.asset_id.id
427 self.write(cr, uid, line.id, {'move_id': move_id}, context=context)
430 account_asset_depreciation_line()
432 #class account_asset_property(osv.osv):
433 # def _amount_total(self, cr, uid, ids, name, args, context={}):
434 # id_set=",".join(map(str,ids))
435 # cr.execute("""SELECT l.asset_id,abs(SUM(l.debit-l.credit)) AS amount FROM
436 # account_asset_property p
438 # account_move_line l on (p.asset_id=l.asset_id)
439 # WHERE p.id IN ("""+id_set+") GROUP BY l.asset_id ")
440 # res=dict(cr.fetchall())
442 # res.setdefault(id, 0.0)
445 # def _close(self, cr, uid, property, context={}):
446 # if property.state<>'close':
447 # self.pool.get('account.asset.property').write(cr, uid, [property.id], {
450 # property.state='close'
451 # ok = property.asset_id.state=='open'
452 # for prop in property.asset_id.property_ids:
453 # ok = ok and prop.state=='close'
454 # self.pool.get('account.asset.asset').write(cr, uid, [property.asset_id.id], {
459 # _name = 'account.asset.property'
460 # _description = 'Asset property'
462 # 'name': fields.char('Method name', size=64, select=1),
463 # 'type': fields.selection([('direct','Direct'),('indirect','Indirect')], 'Depr. method type', select=2, required=True),
464 # 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
465 # 'account_asset_id': fields.many2one('account.account', 'Asset account', required=True),
466 # 'account_actif_id': fields.many2one('account.account', 'Depreciation account', required=True),
467 # 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
468 # 'journal_analytic_id': fields.many2one('account.analytic.journal', 'Analytic journal'),
469 # 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
471 # 'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
472 # 'method_delay': fields.integer('During', readonly=True, states={'draft':[('readonly',False)]}),
473 # 'method_period': fields.integer('Depre. all', readonly=True, states={'draft':[('readonly',False)]}),
474 # 'method_end': fields.date('Ending date'),
476 # 'date': fields.date('Date created'),
477 # #'test': fields.one2many('account.pre', 'asset_id', readonly=True, states={'draft':[('readonly',False)]}),
478 # 'entry_asset_ids': fields.many2many('account.move.line', 'account_move_asset_entry_rel', 'asset_property_id', 'move_id', 'Asset Entries'),
479 # 'board_ids': fields.one2many('account.asset.board', 'asset_id', 'Asset board'),
481 # 'value_total': fields.function(_amount_total, method=True, digits=(16,2),string='Gross value'),
482 # 'state': fields.selection([('draft','Draft'), ('open','Open'), ('close','Close')], 'State', required=True),
483 # 'history_ids': fields.one2many('account.asset.property.history', 'asset_property_id', 'History', readonly=True)
484 ## 'parent_id': fields.many2one('account.asset.asset', 'Parent asset'),
485 ## 'partner_id': fields.many2one('res.partner', 'Partner'),
486 ## 'note': fields.text('Note'),
490 # 'type': lambda obj, cr, uid, context: 'direct',
491 # 'state': lambda obj, cr, uid, context: 'draft',
492 # 'method': lambda obj, cr, uid, context: 'linear',
493 # 'method_time': lambda obj, cr, uid, context: 'delay',
494 # 'method_progress_factor': lambda obj, cr, uid, context: 0.3,
495 # 'method_delay': lambda obj, cr, uid, context: 5,
496 # 'method_period': lambda obj, cr, uid, context: 12,
497 # 'date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d')
499 #account_asset_property()
501 class account_move_line(osv.osv):
502 _inherit = 'account.move.line'
504 'asset_id': fields.many2one('account.asset.asset', 'Asset'),
505 'entry_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
510 class account_asset_history(osv.osv):
511 _name = 'account.asset.history'
512 _description = 'Asset history'
514 'name': fields.char('History name', size=64, select=1),
515 'user_id': fields.many2one('res.users', 'User', required=True),
516 'date': fields.date('Date', required=True),
517 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
518 'method_delay': fields.integer('Number of interval'),
519 'method_period': fields.integer('Period per interval'),
520 'method_end': fields.date('Ending date'),
521 'note': fields.text('Note'),
524 'date': lambda *args: time.strftime('%Y-%m-%d'),
525 'user_id': lambda self,cr, uid,ctx: uid
527 account_asset_history()
529 class account_asset_board(osv.osv):
530 _name = 'account.asset.board'
531 _description = 'Asset board'
533 'name': fields.char('Asset name', size=64, required=True, select=1),
534 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True, select=1),
535 'value_gross': fields.float('Gross value', required=True, select=1),
536 'value_asset': fields.float('Asset Value', required=True, select=1),
537 'value_asset_cumul': fields.float('Cumul. value', required=True, select=1),
538 'value_net': fields.float('Net value', required=True, select=1),
544 create or replace view account_asset_board as (
547 min(l.id) as asset_id,
550 0.0 as value_asset_cumul,
555 l.state <> 'draft' and
558 account_asset_board()
560 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: