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 # For all cases: amount to depreciate is (Purchase Value - Salvage Value)
109 amount_to_depr = residual_amount = asset.purchase_value - asset.salvage_value
110 depreciation_date = datetime.strptime(self._get_last_depreciation_date(cr, uid, [asset.id], context)[asset.id], '%Y-%m-%d')
111 day = depreciation_date.day
112 month = depreciation_date.month
113 year = depreciation_date.year
114 total_days = (year % 4) and 365 or 366
115 undone_dotation_number = asset.method_delay
116 if asset.method_time == 'end':
117 end_date = datetime.strptime(asset.method_end, '%Y-%m-%d')
118 undone_dotation_number = (end_date - depreciation_date).days / total_days
119 if asset.prorata or asset.method_time == 'end':
120 undone_dotation_number += 1
121 for i in range(1, undone_dotation_number+1):
122 if i == undone_dotation_number + 1:
123 amount = residual_amount
125 if asset.method == 'linear':
126 amount = amount_to_depr / undone_dotation_number
128 amount = amount_to_depr / asset.method_delay
130 days = total_days - float(depreciation_date.strftime('%j'))
131 amount = (amount_to_depr / asset.method_delay) / total_days * days
132 elif i == undone_dotation_number:
133 amount = (amount_to_depr / asset.method_delay) / total_days * (total_days - days)
135 amount = residual_amount * asset.method_progress_factor
136 residual_amount -= amount
139 'asset_id': asset.id,
141 'name': str(asset.id) +'/' + str(i),
142 'remaining_value': residual_amount,
143 'depreciated_value': amount_to_depr - residual_amount,
144 'depreciation_date': depreciation_date.strftime('%Y-%m-%d'),
146 dep_id = [dep.id for dep in asset.depreciation_line_ids if not dep.move_check and dep.sequence == vals['sequence']]
147 if asset.depreciation_line_ids:
148 depreciation_lin_obj.write(cr, uid, dep_id, vals, context=context)
150 depreciation_lin_obj.create(cr, uid, vals, context=context)
151 # Considering Depr. Period as months
152 depreciation_date = (datetime(year, month, day) + relativedelta(months=+asset.method_period))
153 day = depreciation_date.day
154 month = depreciation_date.month
155 year = depreciation_date.year
158 def validate(self, cr, uid, ids, context={}):
159 return self.write(cr, uid, ids, {
163 def _amount_residual(self, cr, uid, ids, name, args, context=None):
165 l.asset_id as id, round(SUM(abs(l.debit-l.credit))) AS amount
169 l.asset_id IN %s GROUP BY l.asset_id """, (tuple(ids),))
170 res=dict(cr.fetchall())
171 for asset in self.browse(cr, uid, ids, context):
172 res[asset.id] = asset.purchase_value - res.get(asset.id, 0.0) - asset.salvage_value
174 res.setdefault(id, 0.0)
178 'period_id': fields.many2one('account.period', 'First Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
179 'account_move_line_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
181 'name': fields.char('Asset', size=64, required=True, select=1),
182 'code': fields.char('Reference ', size=16, select=1),
183 'purchase_value': fields.float('Gross value ', required=True, size=16, select=1),
184 'currency_id': fields.many2one('res.currency','Currency',required=True,size=5,select=1),
185 'company_id': fields.many2one('res.company', 'Company', required=True),
186 'note': fields.text('Note'),
187 'category_id': fields.many2one('account.asset.category', 'Asset category',required=True, change_default=True),
188 'localisation': fields.char('Localisation', size=32, select=2),
189 'parent_id': fields.many2one('account.asset.asset', 'Parent Asset'),
190 'child_ids': fields.one2many('account.asset.asset', 'parent_id', 'Children Assets'),
191 'purchase_date': fields.date('Purchase Date', required=True),
192 'state': fields.selection([('draft','Draft'),('open','Running'),('close','Close')], 'State', required=True),
193 'active': fields.boolean('Active', select=2),
194 'partner_id': fields.many2one('res.partner', 'Partner'),
196 'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation Method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
197 'method_delay': fields.integer('Number of Depreciations', readonly=True, states={'draft':[('readonly',False)]}),
198 'method_period': fields.integer('Period Length', readonly=True, states={'draft':[('readonly',False)]}, help="State here the time during 2 depreciations, in months"),
199 'method_end': fields.date('Ending date', readonly=True, states={'draft':[('readonly',False)]}),
200 'method_progress_factor': fields.float('Progressif Factor', readonly=True, states={'draft':[('readonly',False)]}),
201 'value_residual': fields.function(_amount_residual, method=True, digits_compute=dp.get_precision('Account'), string='Residual Value'),
202 'method_time': fields.selection([('delay','Delay'),('end','Ending Period')], 'Time Method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
203 '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'),
204 'history_ids': fields.one2many('account.asset.history', 'asset_id', 'History', readonly=True),
205 'depreciation_line_ids': fields.one2many('account.asset.depreciation.line', 'asset_id', 'Depreciation Lines'),
206 '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."),
209 'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'account.asset.code'),
210 'purchase_date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d'),
211 'active': lambda obj, cr, uid, context: True,
212 'state': lambda obj, cr, uid, context: 'draft',
213 'period_id': _get_period,
214 'method': lambda obj, cr, uid, context: 'linear',
215 'method_delay': lambda obj, cr, uid, context: 5,
216 'method_time': lambda obj, cr, uid, context: 'delay',
217 'method_period': lambda obj, cr, uid, context: 12,
218 'method_progress_factor': lambda obj, cr, uid, context: 0.3,
219 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
220 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.asset',context=context),
223 def _check_prorata(self, cr, uid, ids, context=None):
224 for asset in self.browse(cr, uid, ids, context=context):
225 if asset.prorata and (asset.method != 'linear' or asset.method_time != 'delay'):
230 (_check_prorata, '\nProrata temporis can be applied only for computation method linear and time method delay.', ['prorata']),
233 def onchange_category_id(self, cr, uid, ids, category_id, context=None):
235 asset_categ_obj = self.pool.get('account.asset.category')
237 category_obj = asset_categ_obj.browse(cr, uid, category_id, context=context)
239 'method': category_obj.method,
240 'method_delay': category_obj.method_delay,
241 'method_time': category_obj.method_time,
242 'method_period': category_obj.method_period,
243 'method_progress_factor': category_obj.method_progress_factor,
244 'method_end': category_obj.method_end,
245 'prorata': category_obj.prorata,
249 def copy(self, cr, uid, id, default=None, context=None):
254 default.update({'depreciation_line_ids': [], 'state': 'draft'})
255 return super(account_asset_asset, self).copy(cr, uid, id, default, context=context)
257 def _compute_period(self, cr, uid, property, context={}):
258 if (len(property.entry_asset_ids or [])/2)>=property.method_delay:
260 if len(property.entry_asset_ids):
261 cp = property.entry_asset_ids[-1].period_id
262 cpid = self.pool.get('account.period').next(cr, uid, cp, property.method_period, context)
263 current_period = self.pool.get('account.period').browse(cr, uid, cpid, context)
265 current_period = property.asset_id.period_id
266 return current_period
268 def _compute_move(self, cr, uid, property, period, context={}):
269 #FIXME: fucntion not working OK
272 for move in property.asset_id.entry_ids:
273 total += move.debit-move.credit
274 for move in property.entry_asset_ids:
275 if move.account_id == property.account_asset_ids:
277 total += -move.credit
278 periods = (len(property.entry_asset_ids)/2) - property.method_delay
283 if property.method == 'linear':
284 amount = total / periods
286 amount = total * property.method_progress_factor
288 move_id = self.pool.get('account.move').create(cr, uid, {
289 'journal_id': property.journal_id.id,
290 'period_id': period.id,
291 'name': property.name or property.asset_id.name,
292 'ref': property.asset_id.code
295 id = self.pool.get('account.move.line').create(cr, uid, {
296 'name': property.name or property.asset_id.name,
298 'account_id': property.account_asset_id.id,
299 'debit': amount>0 and amount or 0.0,
300 'credit': amount<0 and -amount or 0.0,
301 'ref': property.asset_id.code,
302 'period_id': period.id,
303 'journal_id': property.journal_id.id,
304 'partner_id': property.asset_id.partner_id.id,
305 'date': time.strftime('%Y-%m-%d'),
307 id2 = self.pool.get('account.move.line').create(cr, uid, {
308 'name': property.name or property.asset_id.name,
310 'account_id': property.account_actif_id.id,
311 'credit': amount>0 and amount or 0.0,
312 'debit': amount<0 and -amount or 0.0,
313 'ref': property.asset_id.code,
314 'period_id': period.id,
315 'journal_id': property.journal_id.id,
316 'partner_id': property.asset_id.partner_id.id,
317 'date': time.strftime('%Y-%m-%d'),
320 self.pool.get('account.asset.asset').write(cr, uid, [property.id], {
321 'entry_asset_ids': [(4, id2, False),(4,id,False)]
323 if property.method_delay - (len(property.entry_asset_ids)/2)<=1:
324 #self.pool.get('account.asset.property')._close(cr, uid, property, context)
328 def _compute_entries(self, cr, uid, asset, period_id, context={}):
329 #FIXME: function not working CHECK all res
331 date_start = self.pool.get('account.period').browse(cr, uid, period_id, context).date_start
332 for property in asset.property_ids:
333 if property.state=='open':
334 period = self._compute_period(cr, uid, property, context)
335 if period and (period.date_start<=date_start):
336 result += self._compute_move(cr, uid, property, period, context)
338 account_asset_asset()
340 class account_asset_depreciation_line(osv.osv):
341 _name = 'account.asset.depreciation.line'
342 _description = 'Asset depreciation line'
344 def _get_move_check(self, cr, uid, ids, name, args, context=None):
346 for line in self.browse(cr, uid, ids, context=context):
347 res[line.id] = bool(line.move_id)
351 'name': fields.char('Depreciation Name', size=64, required=True, select=1),
352 'sequence': fields.integer('Sequence of the depreciation', required=True),
353 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
354 'amount': fields.float('Depreciation Amount', required=True),
355 'remaining_value': fields.float('Amount to Depreciate', required=True),
356 'depreciated_value': fields.float('Amount Already Depreciated', required=True),
357 'depreciation_date': fields.char('Depreciation Date', size=64, select=1),
358 'move_id': fields.many2one('account.move', 'Depreciation Entry'),
359 'move_check': fields.function(_get_move_check, method=True, type='boolean', string='Posted', store=True)
362 def create_move(self, cr, uid,ids, context=None):
365 asset_obj = self.pool.get('account.asset.asset')
366 period_obj = self.pool.get('account.period')
367 move_obj = self.pool.get('account.move')
368 move_line_obj = self.pool.get('account.move.line')
369 currency_obj = self.pool.get('res.currency')
370 for line in self.browse(cr, uid, ids, context=context):
371 depreciation_date = line.asset_id.prorata and line.asset_id.purchase_date or time.strftime('%Y-%m-%d')
372 period_ids = period_obj.find(cr, uid, depreciation_date, context=context)
373 company_currency = line.asset_id.company_id.currency_id.id
374 current_currency = line.asset_id.currency_id.id
375 context.update({'date': depreciation_date})
376 amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.amount, context=context)
377 sign = line.asset_id.category_id.journal_id.type = 'purchase' and 1 or -1
380 'date': depreciation_date,
382 'period_id': period_ids and period_ids[0] or False,
383 'journal_id': line.asset_id.category_id.journal_id.id,
385 move_id = move_obj.create(cr, uid, move_vals, context=context)
386 move_line_obj.create(cr, uid, {
390 'account_id': line.asset_id.category_id.account_depreciation_id.id,
393 'period_id': period_ids and period_ids[0] or False,
394 'journal_id': line.asset_id.category_id.journal_id.id,
395 'partner_id': line.asset_id.partner_id.id,
396 'currency_id': company_currency <> current_currency and current_currency or False,
397 'amount_currency': company_currency <> current_currency and - sign * line.amount or 0.0,
398 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
399 'date': depreciation_date,
401 move_line_obj.create(cr, uid, {
405 'account_id': line.asset_id.category_id.account_expense_depreciation_id.id,
408 'period_id': period_ids and period_ids[0] or False,
409 'journal_id': line.asset_id.category_id.journal_id.id,
410 'partner_id': line.asset_id.partner_id.id,
411 'currency_id': company_currency <> current_currency and current_currency or False,
412 'amount_currency': company_currency <> current_currency and sign * line.amount or 0.0,
413 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
414 'date': depreciation_date,
415 'asset_id': line.asset_id.id
417 self.write(cr, uid, line.id, {'move_id': move_id}, context=context)
420 account_asset_depreciation_line()
422 #class account_asset_property(osv.osv):
423 # def _amount_total(self, cr, uid, ids, name, args, context={}):
424 # id_set=",".join(map(str,ids))
425 # cr.execute("""SELECT l.asset_id,abs(SUM(l.debit-l.credit)) AS amount FROM
426 # account_asset_property p
428 # account_move_line l on (p.asset_id=l.asset_id)
429 # WHERE p.id IN ("""+id_set+") GROUP BY l.asset_id ")
430 # res=dict(cr.fetchall())
432 # res.setdefault(id, 0.0)
435 # def _close(self, cr, uid, property, context={}):
436 # if property.state<>'close':
437 # self.pool.get('account.asset.property').write(cr, uid, [property.id], {
440 # property.state='close'
441 # ok = property.asset_id.state=='open'
442 # for prop in property.asset_id.property_ids:
443 # ok = ok and prop.state=='close'
444 # self.pool.get('account.asset.asset').write(cr, uid, [property.asset_id.id], {
449 # _name = 'account.asset.property'
450 # _description = 'Asset property'
452 # 'name': fields.char('Method name', size=64, select=1),
453 # 'type': fields.selection([('direct','Direct'),('indirect','Indirect')], 'Depr. method type', select=2, required=True),
454 # 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
455 # 'account_asset_id': fields.many2one('account.account', 'Asset account', required=True),
456 # 'account_actif_id': fields.many2one('account.account', 'Depreciation account', required=True),
457 # 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
458 # 'journal_analytic_id': fields.many2one('account.analytic.journal', 'Analytic journal'),
459 # 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
461 # 'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
462 # 'method_delay': fields.integer('During', readonly=True, states={'draft':[('readonly',False)]}),
463 # 'method_period': fields.integer('Depre. all', readonly=True, states={'draft':[('readonly',False)]}),
464 # 'method_end': fields.date('Ending date'),
466 # 'date': fields.date('Date created'),
467 # #'test': fields.one2many('account.pre', 'asset_id', readonly=True, states={'draft':[('readonly',False)]}),
468 # 'entry_asset_ids': fields.many2many('account.move.line', 'account_move_asset_entry_rel', 'asset_property_id', 'move_id', 'Asset Entries'),
469 # 'board_ids': fields.one2many('account.asset.board', 'asset_id', 'Asset board'),
471 # 'value_total': fields.function(_amount_total, method=True, digits=(16,2),string='Gross value'),
472 # 'state': fields.selection([('draft','Draft'), ('open','Open'), ('close','Close')], 'State', required=True),
473 # 'history_ids': fields.one2many('account.asset.property.history', 'asset_property_id', 'History', readonly=True)
474 ## 'parent_id': fields.many2one('account.asset.asset', 'Parent asset'),
475 ## 'partner_id': fields.many2one('res.partner', 'Partner'),
476 ## 'note': fields.text('Note'),
480 # 'type': lambda obj, cr, uid, context: 'direct',
481 # 'state': lambda obj, cr, uid, context: 'draft',
482 # 'method': lambda obj, cr, uid, context: 'linear',
483 # 'method_time': lambda obj, cr, uid, context: 'delay',
484 # 'method_progress_factor': lambda obj, cr, uid, context: 0.3,
485 # 'method_delay': lambda obj, cr, uid, context: 5,
486 # 'method_period': lambda obj, cr, uid, context: 12,
487 # 'date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d')
489 #account_asset_property()
491 class account_move_line(osv.osv):
492 _inherit = 'account.move.line'
494 'asset_id': fields.many2one('account.asset.asset', 'Asset'),
495 'entry_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
500 class account_asset_history(osv.osv):
501 _name = 'account.asset.history'
502 _description = 'Asset history'
504 'name': fields.char('History name', size=64, select=1),
505 'user_id': fields.many2one('res.users', 'User', required=True),
506 'date': fields.date('Date', required=True),
507 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
508 'method_delay': fields.integer('Number of interval'),
509 'method_period': fields.integer('Period per interval'),
510 'method_end': fields.date('Ending date'),
511 'note': fields.text('Note'),
514 'date': lambda *args: time.strftime('%Y-%m-%d'),
515 'user_id': lambda self,cr, uid,ctx: uid
517 account_asset_history()
519 class account_asset_board(osv.osv):
520 _name = 'account.asset.board'
521 _description = 'Asset board'
523 'name': fields.char('Asset name', size=64, required=True, select=1),
524 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True, select=1),
525 'value_gross': fields.float('Gross value', required=True, select=1),
526 'value_asset': fields.float('Asset Value', required=True, select=1),
527 'value_asset_cumul': fields.float('Cumul. value', required=True, select=1),
528 'value_net': fields.float('Net value', required=True, select=1),
534 create or replace view account_asset_board as (
537 min(l.id) as asset_id,
540 0.0 as value_asset_cumul,
545 l.state <> 'draft' and
548 account_asset_board()
550 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: