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 ##############################################################################
22 from osv import osv, fields
24 from datetime import datetime
25 import decimal_precision as dp
27 class account_asset_category(osv.osv):
28 _name = 'account.asset.category'
29 _description = 'Asset category'
32 'name': fields.char('Name', size=64, required=True, select=1),
33 'note': fields.text('Note'),
34 'journal_analytic_id': fields.many2one('account.analytic.journal', 'Analytic journal'),
35 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
36 'account_asset_id': fields.many2one('account.account', 'Asset Account', required=True),
37 'account_depreciation_id': fields.many2one('account.account', 'Depreciation Account', required=True),
38 'account_expense_depreciation_id': fields.many2one('account.account', 'Depr. Expense Account', required=True),
39 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
40 'company_id': fields.many2one('res.company', 'Company', required=True),
41 'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True),
42 'method_delay': fields.integer('Number of Depreciation'),
43 'method_period': fields.integer('Period Length'),
44 'method_progress_factor': fields.float('Progressif Factor'),
45 'method_time': fields.selection([('delay','Delay'),('end','Ending Period')], 'Time Method', required=True),
46 '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'),
47 '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."),
51 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.category', context=context),
54 'method_time': 'delay',
56 'method_progress_factor': 0.3,
59 account_asset_category()
61 #class one2many_mod_asset(fields.one2many):
63 # def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
64 # prinasset_property_id if context is None:
71 # #compute depreciation board
72 # depreciation_line_ids = obj.pool.get('account.asset.asset').compute_depreciation_board(cr, user, ids, context=context)
73 # for key, value in depreciation_line_ids.items():
74 # #write values on asset
75 # obj.pool.get(self._obj).write(cr, user, key, {'depreciation_line_ids': [6,0,value]})
76 # return depreciation_line_ids
78 class account_asset_asset(osv.osv):
79 _name = 'account.asset.asset'
80 _description = 'Asset'
82 def _get_period(self, cr, uid, context={}):
83 periods = self.pool.get('account.period').find(cr, uid)
89 def _get_last_depreciation_date(self, cr, uid, ids, context=None):
91 @param id: ids of a account.asset.asset objects
92 @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
95 SELECT a.id as id, COALESCE(MAX(l.date),a.purchase_date) AS date
96 FROM account_asset_asset a
97 LEFT JOIN account_move_line l ON (l.asset_id = a.id)
99 GROUP BY a.id, a.purchase_date """, (tuple(ids),))
100 return dict(cr.fetchall())
102 def compute_depreciation_board(self, cr, uid,ids, context=None):
103 depreciation_lin_obj = self.pool.get('account.asset.depreciation.line')
104 for asset in self.browse(cr, uid, ids, context=context):
105 undone_dotation_number = asset.method_delay
106 amount_to_depr = residual_amount = asset.purchase_value
107 if asset.prorata and asset.method == 'linear':
108 undone_dotation_number += 1
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 if month == 12: month = 0
114 year = depreciation_date.year
115 total_days = (year % 4) and 365 or 366
117 for i in range(1,undone_dotation_number+1):
118 if i == undone_dotation_number + 1:
119 amount = residual_amount
121 if asset.method == 'linear':
122 amount = amount_to_depr / undone_dotation_number
124 amount = amount_to_depr / asset.method_delay
126 days = total_days - float(depreciation_date.strftime('%j'))
127 amount = (amount_to_depr / asset.method_delay) / total_days * days
128 elif i == undone_dotation_number:
129 amount = (amount_to_depr / asset.method_delay) / total_days * (total_days - days)
131 amount = residual_amount * asset.method_progress_factor
132 residual_amount -= amount
135 'asset_id': asset.id,
137 'name': str(asset.id) +'/'+ str(i),
138 'remaining_value': residual_amount,
139 'depreciated_value': amount_to_depr - residual_amount,
140 'depreciation_date': depreciation_date.strftime('%Y-%m-%d'),
142 if asset.depreciation_line_ids:
143 depr_vals.append(vals)
145 depreciation_lin_obj.create(cr, uid, vals, context=context)
146 month += asset.method_period
147 depreciation_date = datetime(year + (month / 12), (month % 12 or 12), day)
148 for vals in depr_vals:
149 for dep in asset.depreciation_line_ids:
150 if dep.sequence == vals['sequence'] and not dep.move_check:
151 depreciation_lin_obj.write(cr, uid, [dep.id], vals, context=context)
154 def validate(self, cr, uid, ids, context={}):
155 return self.write(cr, uid, ids, {
161 def _amount_residual(self, cr, uid, ids, name, args, context={}):
163 l.asset_id as id, SUM(abs(l.debit-l.credit)) AS amount
167 l.asset_id IN %s GROUP BY l.asset_id """, (tuple(ids),))
168 res=dict(cr.fetchall())
169 for asset in self.browse(cr, uid, ids, context):
170 res[asset.id] = asset.purchase_value - res.get(asset.id, 0.0) - asset.salvage_value
172 res.setdefault(id, 0.0)
176 'period_id': fields.many2one('account.period', 'First Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
177 'account_move_line_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
179 'name': fields.char('Asset', size=64, required=True, select=1),
180 'code': fields.char('Reference ', size=16, select=1),
181 'purchase_value': fields.float('Gross value ', required=True, size=16, select=1),
182 'currency_id': fields.many2one('res.currency','Currency',required=True,size=5,select=1),
183 'company_id': fields.many2one('res.company', 'Company', required=True),
184 'note': fields.text('Note'),
185 'category_id': fields.many2one('account.asset.category', 'Asset category',required=True, change_default=True),
186 'localisation': fields.char('Localisation', size=32, select=2),
187 'parent_id': fields.many2one('account.asset.asset', 'Parent Asset'),
188 'child_ids': fields.one2many('account.asset.asset', 'parent_id', 'Children Assets'),
189 'purchase_date': fields.date('Purchase Date', required=True),
190 'state': fields.selection([('draft','Draft'),('open','Running'),('close','Close')], 'state', required=True),
191 'active': fields.boolean('Active', select=2),
192 'partner_id': fields.many2one('res.partner', 'Partner'),
194 'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
195 'method_delay': fields.integer('During (interval)', readonly=True, states={'draft':[('readonly',False)]}),
196 'method_period': fields.integer('Depre. all (period)', readonly=True, states={'draft':[('readonly',False)]}),
197 'method_end': fields.date('Ending date'),
198 'method_progress_factor': fields.float('Progressif Factor', readonly=True, states={'draft':[('readonly',False)]}),
199 'value_residual': fields.function(_amount_residual, method=True, digits_compute=dp.get_precision('Account'), string='Residual Value'),
200 'method_time': fields.selection([('delay','Delay'),('end','Ending Period')], 'Time Method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
201 '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'),
202 'history_ids': fields.one2many('account.asset.history', 'asset_id', 'History', readonly=True),
203 'depreciation_line_ids': fields.one2many('account.asset.depreciation.line', 'asset_id', 'Depreciation Lines', readonly=True,),
204 '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."),
207 'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'account.asset.code'),
208 'purchase_date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d'),
209 'active': lambda obj, cr, uid, context: True,
210 'state': lambda obj, cr, uid, context: 'draft',
211 'period_id': _get_period,
212 'method': lambda obj, cr, uid, context: 'linear',
213 'method_delay': lambda obj, cr, uid, context: 5,
214 'method_time': lambda obj, cr, uid, context: 'delay',
215 'method_period': lambda obj, cr, uid, context: 12,
216 'method_progress_factor': lambda obj, cr, uid, context: 0.3,
217 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
218 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.asset',context=context),
221 def _check_prorata(self, cr, uid, ids, context=None):
222 for asset in self.browse(cr, uid, ids, context=context):
223 if asset.prorata and asset.method != 'linear':
228 (_check_prorata, '\nProrata temporis can be applied only for linear method.', ['prorata']),
231 def onchange_category_id(self, cr, uid, ids, category_id, context=None):
233 asset_categ_obj = self.pool.get('account.asset.category')
235 category_obj = asset_categ_obj.browse(cr, uid, category_id, context=context)
237 'method': category_obj.method,
238 'method_delay': category_obj.method_delay,
239 'method_time': category_obj.method_time,
240 'method_period': category_obj.method_period,
241 'method_progress_factor': category_obj.method_progress_factor,
242 'prorata': category_obj.prorata,
246 def copy(self, cr, uid, id, default=None, context=None):
251 default.update({'depreciation_line_ids': [], 'state': 'draft'})
252 return super(account_asset_asset, self).copy(cr, uid, id, default, context=context)
254 def _compute_period(self, cr, uid, property, context={}):
255 if (len(property.entry_asset_ids or [])/2)>=property.method_delay:
257 if len(property.entry_asset_ids):
258 cp = property.entry_asset_ids[-1].period_id
259 cpid = self.pool.get('account.period').next(cr, uid, cp, property.method_period, context)
260 current_period = self.pool.get('account.period').browse(cr, uid, cpid, context)
262 current_period = property.asset_id.period_id
263 return current_period
265 def _compute_move(self, cr, uid, property, period, context={}):
266 #FIXME: fucntion not working OK
269 for move in property.asset_id.entry_ids:
270 total += move.debit-move.credit
271 for move in property.entry_asset_ids:
272 if move.account_id == property.account_asset_ids:
274 total += -move.credit
275 periods = (len(property.entry_asset_ids)/2) - property.method_delay
280 if property.method == 'linear':
281 amount = total / periods
283 amount = total * property.method_progress_factor
285 move_id = self.pool.get('account.move').create(cr, uid, {
286 'journal_id': property.journal_id.id,
287 'period_id': period.id,
288 'name': property.name or property.asset_id.name,
289 'ref': property.asset_id.code
292 id = self.pool.get('account.move.line').create(cr, uid, {
293 'name': property.name or property.asset_id.name,
295 'account_id': property.account_asset_id.id,
296 'debit': amount>0 and amount or 0.0,
297 'credit': amount<0 and -amount or 0.0,
298 'ref': property.asset_id.code,
299 'period_id': period.id,
300 'journal_id': property.journal_id.id,
301 'partner_id': property.asset_id.partner_id.id,
302 'date': time.strftime('%Y-%m-%d'),
304 id2 = self.pool.get('account.move.line').create(cr, uid, {
305 'name': property.name or property.asset_id.name,
307 'account_id': property.account_actif_id.id,
308 'credit': amount>0 and amount or 0.0,
309 'debit': amount<0 and -amount or 0.0,
310 'ref': property.asset_id.code,
311 'period_id': period.id,
312 'journal_id': property.journal_id.id,
313 'partner_id': property.asset_id.partner_id.id,
314 'date': time.strftime('%Y-%m-%d'),
317 self.pool.get('account.asset.asset').write(cr, uid, [property.id], {
318 'entry_asset_ids': [(4, id2, False),(4,id,False)]
320 if property.method_delay - (len(property.entry_asset_ids)/2)<=1:
321 #self.pool.get('account.asset.property')._close(cr, uid, property, context)
325 def _compute_entries(self, cr, uid, asset, period_id, context={}):
326 #FIXME: function not working CHECK all res
328 date_start = self.pool.get('account.period').browse(cr, uid, period_id, context).date_start
329 for property in asset.property_ids:
330 if property.state=='open':
331 period = self._compute_period(cr, uid, property, context)
332 if period and (period.date_start<=date_start):
333 result += self._compute_move(cr, uid, property, period, context)
335 account_asset_asset()
337 class account_asset_depreciation_line(osv.osv):
338 _name = 'account.asset.depreciation.line'
339 _description = 'Asset depreciation line'
341 def _get_move_check(self, cr, uid, ids, name, args, context=None):
343 for line in self.browse(cr, uid, ids, context=context):
344 res[line.id] = bool(line.move_id)
348 'name': fields.char('Depreciation Name', size=64, required=True, select=1),
349 'sequence': fields.integer('Sequence of the depreciation', required=True),
350 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
351 'amount': fields.float('Depreciation Amount', required=True),
352 'remaining_value': fields.float('Amount to Depreciate', required=True),
353 'depreciated_value': fields.float('Amount Already Depreciated', required=True),
354 'depreciation_date': fields.char('Depreciation Date', size=64, select=1),
355 'move_id': fields.many2one('account.move', 'Depreciation Entry'),
356 'move_check': fields.function(_get_move_check, method=True, type='boolean', string='Posted', store=True)
359 def create_move(self, cr, uid,ids, context=None):
362 asset_obj = self.pool.get('account.asset.asset')
363 period_obj = self.pool.get('account.period')
364 move_obj = self.pool.get('account.move')
365 move_line_obj = self.pool.get('account.move.line')
366 currency_obj = self.pool.get('res.currency')
367 for line in self.browse(cr, uid, ids, context=context):
368 depreciation_date = line.asset_id.prorata and line.asset_id.purchase_date or time.strftime('%Y-%m-%d')
369 period_ids = period_obj.find(cr, uid, depreciation_date, context=context)
370 company_currency = line.asset_id.company_id.currency_id.id
371 current_currency = line.asset_id.currency_id.id
372 context.update({'date': depreciation_date})
373 amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.amount, context=context)
374 sign = line.asset_id.category_id.journal_id.type = 'purchase' and 1 or -1
377 'date': depreciation_date,
379 'period_id': period_ids and period_ids[0] or False,
380 'journal_id': line.asset_id.category_id.journal_id.id,
382 move_id = move_obj.create(cr, uid, move_vals, context=context)
383 move_line_obj.create(cr, uid, {
387 'account_id': line.asset_id.category_id.account_depreciation_id.id,
390 'period_id': period_ids and period_ids[0] or False,
391 'journal_id': line.asset_id.category_id.journal_id.id,
392 'partner_id': line.asset_id.partner_id.id,
393 'currency_id': company_currency <> current_currency and current_currency or False,
394 'amount_currency': company_currency <> current_currency and - sign * line.amount or 0.0,
395 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
396 'date': depreciation_date,
398 move_line_obj.create(cr, uid, {
402 'account_id': line.asset_id.category_id.account_expense_depreciation_id.id,
405 'period_id': period_ids and period_ids[0] or False,
406 'journal_id': line.asset_id.category_id.journal_id.id,
407 'partner_id': line.asset_id.partner_id.id,
408 'currency_id': company_currency <> current_currency and current_currency or False,
409 'amount_currency': company_currency <> current_currency and sign * line.amount or 0.0,
410 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
411 'date': depreciation_date,
412 'asset_id': line.asset_id.id
414 self.write(cr, uid, line.id, {'move_id': move_id}, context=context)
417 account_asset_depreciation_line()
419 #class account_asset_property(osv.osv):
420 # def _amount_total(self, cr, uid, ids, name, args, context={}):
421 # id_set=",".join(map(str,ids))
422 # cr.execute("""SELECT l.asset_id,abs(SUM(l.debit-l.credit)) AS amount FROM
423 # account_asset_property p
425 # account_move_line l on (p.asset_id=l.asset_id)
426 # WHERE p.id IN ("""+id_set+") GROUP BY l.asset_id ")
427 # res=dict(cr.fetchall())
429 # res.setdefault(id, 0.0)
432 # def _close(self, cr, uid, property, context={}):
433 # if property.state<>'close':
434 # self.pool.get('account.asset.property').write(cr, uid, [property.id], {
437 # property.state='close'
438 # ok = property.asset_id.state=='open'
439 # for prop in property.asset_id.property_ids:
440 # ok = ok and prop.state=='close'
441 # self.pool.get('account.asset.asset').write(cr, uid, [property.asset_id.id], {
446 # _name = 'account.asset.property'
447 # _description = 'Asset property'
449 # 'name': fields.char('Method name', size=64, select=1),
450 # 'type': fields.selection([('direct','Direct'),('indirect','Indirect')], 'Depr. method type', select=2, required=True),
451 # 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
452 # 'account_asset_id': fields.many2one('account.account', 'Asset account', required=True),
453 # 'account_actif_id': fields.many2one('account.account', 'Depreciation account', required=True),
454 # 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
455 # 'journal_analytic_id': fields.many2one('account.analytic.journal', 'Analytic journal'),
456 # 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
458 # 'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
459 # 'method_delay': fields.integer('During', readonly=True, states={'draft':[('readonly',False)]}),
460 # 'method_period': fields.integer('Depre. all', readonly=True, states={'draft':[('readonly',False)]}),
461 # 'method_end': fields.date('Ending date'),
463 # 'date': fields.date('Date created'),
464 # #'test': fields.one2many('account.pre', 'asset_id', readonly=True, states={'draft':[('readonly',False)]}),
465 # 'entry_asset_ids': fields.many2many('account.move.line', 'account_move_asset_entry_rel', 'asset_property_id', 'move_id', 'Asset Entries'),
466 # 'board_ids': fields.one2many('account.asset.board', 'asset_id', 'Asset board'),
468 # 'value_total': fields.function(_amount_total, method=True, digits=(16,2),string='Gross value'),
469 # 'state': fields.selection([('draft','Draft'), ('open','Open'), ('close','Close')], 'State', required=True),
470 # 'history_ids': fields.one2many('account.asset.property.history', 'asset_property_id', 'History', readonly=True)
471 ## 'parent_id': fields.many2one('account.asset.asset', 'Parent asset'),
472 ## 'partner_id': fields.many2one('res.partner', 'Partner'),
473 ## 'note': fields.text('Note'),
477 # 'type': lambda obj, cr, uid, context: 'direct',
478 # 'state': lambda obj, cr, uid, context: 'draft',
479 # 'method': lambda obj, cr, uid, context: 'linear',
480 # 'method_time': lambda obj, cr, uid, context: 'delay',
481 # 'method_progress_factor': lambda obj, cr, uid, context: 0.3,
482 # 'method_delay': lambda obj, cr, uid, context: 5,
483 # 'method_period': lambda obj, cr, uid, context: 12,
484 # 'date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d')
486 #account_asset_property()
488 class account_move_line(osv.osv):
489 _inherit = 'account.move.line'
491 'asset_id': fields.many2one('account.asset.asset', 'Asset'),
492 'entry_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
497 class account_asset_history(osv.osv):
498 _name = 'account.asset.history'
499 _description = 'Asset history'
501 'name': fields.char('History name', size=64, select=1),
502 'user_id': fields.many2one('res.users', 'User', required=True),
503 'date': fields.date('Date', required=True),
504 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
505 'method_delay': fields.integer('Number of interval'),
506 'method_period': fields.integer('Period per interval'),
507 'method_end': fields.date('Ending date'),
508 'note': fields.text('Note'),
511 'date': lambda *args: time.strftime('%Y-%m-%d'),
512 'user_id': lambda self,cr, uid,ctx: uid
514 account_asset_history()
516 class account_asset_board(osv.osv):
517 _name = 'account.asset.board'
518 _description = 'Asset board'
520 'name': fields.char('Asset name', size=64, required=True, select=1),
521 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True, select=1),
522 'value_gross': fields.float('Gross value', required=True, select=1),
523 'value_asset': fields.float('Asset Value', required=True, select=1),
524 'value_asset_cumul': fields.float('Cumul. value', required=True, select=1),
525 'value_net': fields.float('Net value', required=True, select=1),
531 create or replace view account_asset_board as (
534 min(l.id) as asset_id,
537 0.0 as value_asset_cumul,
542 l.state <> 'draft' and
545 account_asset_board()
547 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: