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 old_depreciation_line_ids = depreciation_lin_obj.search(cr, uid, [('asset_id', '=', asset.id), ('move_id', '=', False)])
106 if old_depreciation_line_ids:
107 depreciation_lin_obj.unlink(cr, uid, old_depreciation_line_ids, context=context)
108 undone_dotation_number = asset.method_delay - len(asset.account_move_line_ids)
109 residual_amount = asset.value_residual
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 for i in range(1,undone_dotation_number+1):
115 if i == undone_dotation_number + 1:
116 amount = residual_amount
118 if asset.method == 'linear':
119 amount = asset.value_residual / undone_dotation_number
121 amount = residual_amount * asset.method_progress_factor
122 residual_amount -= amount
125 'asset_id': asset.id,
127 'name': str(asset.id) +'/'+ str(i),
128 'remaining_value': residual_amount,
129 'depreciated_value': asset.purchase_value - residual_amount,
130 'depreciation_date': depreciation_date.strftime('%Y-%m-%d'),
132 depreciation_lin_obj.create(cr, uid, vals, context=context)
133 month += asset.method_period
134 depreciation_date = datetime(year + (month / 12), month % 12, day)
137 def validate(self, cr, uid, ids, context={}):
138 return self.write(cr, uid, ids, {
144 def _amount_residual(self, cr, uid, ids, name, args, context={}):
146 l.asset_id as id, SUM(abs(l.debit-l.credit)) AS amount
150 l.asset_id IN %s GROUP BY l.asset_id """, (tuple(ids),))
151 res=dict(cr.fetchall())
152 for asset in self.browse(cr, uid, ids, context):
153 res[asset.id] = asset.purchase_value - res.get(asset.id, 0.0) - asset.salvage_value
155 res.setdefault(id, 0.0)
159 'period_id': fields.many2one('account.period', 'First Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
160 'account_move_line_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
162 'name': fields.char('Asset', size=64, required=True, select=1),
163 'code': fields.char('Reference ', size=16, select=1),
164 'purchase_value': fields.float('Gross value ', required=True, size=16, select=1),
165 'currency_id': fields.many2one('res.currency','Currency',required=True,size=5,select=1),
166 'company_id': fields.many2one('res.company', 'Company', required=True),
167 'note': fields.text('Note'),
168 'category_id': fields.many2one('account.asset.category', 'Asset category',required=True, change_default=True),
169 'localisation': fields.char('Localisation', size=32, select=2),
170 'parent_id': fields.many2one('account.asset.asset', 'Parent Asset'),
171 'child_ids': fields.one2many('account.asset.asset', 'parent_id', 'Children Assets'),
172 'purchase_date': fields.date('Purchase Date', required=True),
173 'state': fields.selection([('draft','Draft'),('open','Running'),('close','Close')], 'state', required=True),
174 'active': fields.boolean('Active', select=2),
175 'partner_id': fields.many2one('res.partner', 'Partner'),
177 '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) \
178 \nProgressive: Calculated on basis of Gross Value * Progressif Factor"),
179 'method_delay': fields.integer('During (interval)', readonly=True, states={'draft':[('readonly',False)]}, help="Calculates Depreciation within specified interval"),
180 'method_period': fields.integer('Depre. all (period)', readonly=True, states={'draft':[('readonly',False)]}),
181 'method_end': fields.date('Ending date'),
182 'method_progress_factor': fields.float('Progressif Factor', readonly=True, states={'draft':[('readonly',False)]}),
183 'value_residual': fields.function(_amount_residual, method=True, digits_compute=dp.get_precision('Account'), string='Residual Value'),
184 '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 every 12 months"),
185 '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'),
186 'history_ids': fields.one2many('account.asset.history', 'asset_id', 'History', readonly=True),
187 'depreciation_line_ids': fields.one2many('account.asset.depreciation.line', 'asset_id', 'Depreciation Lines', readonly=True,),
188 '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."),
191 'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'account.asset.code'),
192 'purchase_date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d'),
193 'active': lambda obj, cr, uid, context: True,
194 'state': lambda obj, cr, uid, context: 'draft',
195 'period_id': _get_period,
196 'method': lambda obj, cr, uid, context: 'linear',
197 'method_delay': lambda obj, cr, uid, context: 5,
198 'method_time': lambda obj, cr, uid, context: 'delay',
199 'method_period': lambda obj, cr, uid, context: 12,
200 'method_progress_factor': lambda obj, cr, uid, context: 0.3,
201 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
202 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.asset',context=context),
205 def onchange_category_id(self, cr, uid, ids, category_id, context=None):
207 asset_categ_obj = self.pool.get('account.asset.category')
209 category_obj = asset_categ_obj.browse(cr, uid, category_id, context=context)
211 'method': category_obj.method,
212 'method_delay': category_obj.method_delay,
213 'method_time': category_obj.method_time,
214 'method_period': category_obj.method_period,
215 'method_progress_factor': category_obj.method_progress_factor,
216 'prorata': category_obj.prorata,
220 def copy(self, cr, uid, id, default=None, context=None):
225 default.update({'depreciation_line_ids': [], 'state': 'draft'})
226 return super(account_asset_asset, self).copy(cr, uid, id, default, context=context)
228 def _compute_period(self, cr, uid, property, context={}):
229 if (len(property.entry_asset_ids or [])/2)>=property.method_delay:
231 if len(property.entry_asset_ids):
232 cp = property.entry_asset_ids[-1].period_id
233 cpid = self.pool.get('account.period').next(cr, uid, cp, property.method_period, context)
234 current_period = self.pool.get('account.period').browse(cr, uid, cpid, context)
236 current_period = property.asset_id.period_id
237 return current_period
239 def _compute_move(self, cr, uid, property, period, context={}):
240 #FIXME: fucntion not working OK
243 for move in property.asset_id.entry_ids:
244 total += move.debit-move.credit
245 for move in property.entry_asset_ids:
246 if move.account_id == property.account_asset_ids:
248 total += -move.credit
249 periods = (len(property.entry_asset_ids)/2) - property.method_delay
254 if property.method == 'linear':
255 amount = total / periods
257 amount = total * property.method_progress_factor
259 move_id = self.pool.get('account.move').create(cr, uid, {
260 'journal_id': property.journal_id.id,
261 'period_id': period.id,
262 'name': property.name or property.asset_id.name,
263 'ref': property.asset_id.code
266 id = self.pool.get('account.move.line').create(cr, uid, {
267 'name': property.name or property.asset_id.name,
269 'account_id': property.account_asset_id.id,
270 'debit': amount>0 and amount or 0.0,
271 'credit': amount<0 and -amount or 0.0,
272 'ref': property.asset_id.code,
273 'period_id': period.id,
274 'journal_id': property.journal_id.id,
275 'partner_id': property.asset_id.partner_id.id,
276 'date': time.strftime('%Y-%m-%d'),
278 id2 = self.pool.get('account.move.line').create(cr, uid, {
279 'name': property.name or property.asset_id.name,
281 'account_id': property.account_actif_id.id,
282 'credit': amount>0 and amount or 0.0,
283 'debit': amount<0 and -amount or 0.0,
284 'ref': property.asset_id.code,
285 'period_id': period.id,
286 'journal_id': property.journal_id.id,
287 'partner_id': property.asset_id.partner_id.id,
288 'date': time.strftime('%Y-%m-%d'),
291 self.pool.get('account.asset.asset').write(cr, uid, [property.id], {
292 'entry_asset_ids': [(4, id2, False),(4,id,False)]
294 if property.method_delay - (len(property.entry_asset_ids)/2)<=1:
295 #self.pool.get('account.asset.property')._close(cr, uid, property, context)
299 def _compute_entries(self, cr, uid, asset, period_id, context={}):
300 #FIXME: function not working CHECK all res
302 date_start = self.pool.get('account.period').browse(cr, uid, period_id, context).date_start
303 for property in asset.property_ids:
304 if property.state=='open':
305 period = self._compute_period(cr, uid, property, context)
306 if period and (period.date_start<=date_start):
307 result += self._compute_move(cr, uid, property, period, context)
309 account_asset_asset()
311 class account_asset_depreciation_line(osv.osv):
312 _name = 'account.asset.depreciation.line'
313 _description = 'Asset depreciation line'
315 def _get_move_check(self, cr, uid, ids, name, args, context=None):
317 for line in self.browse(cr, uid, ids, context=context):
318 res[line.id] = bool(line.move_id)
322 'name': fields.char('Depreciation Name', size=64, required=True, select=1),
323 'sequence': fields.integer('Sequence of the depreciation', required=True),
324 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
325 'amount': fields.float('Depreciation Amount', required=True),
326 'remaining_value': fields.float('Amount to Depreciate', required=True),
327 'depreciated_value': fields.float('Amount Already Depreciated', required=True),
328 'depreciation_date': fields.char('Depreciation Date', size=64, select=1),
329 'move_id': fields.many2one('account.move', 'Depreciation Entry'),
330 'move_check': fields.function(_get_move_check, method=True, type='boolean', string='Posted', store=True)
333 def create_move(self, cr, uid,ids, context=None):
336 asset_obj = self.pool.get('account.asset.asset')
337 period_obj = self.pool.get('account.period')
338 move_obj = self.pool.get('account.move')
339 move_line_obj = self.pool.get('account.move.line')
340 currency_obj = self.pool.get('res.currency')
341 for line in self.browse(cr, uid, ids, context=context):
342 depreciation_date = time.strftime('%Y-%m-%d')
343 period_ids = period_obj.find(cr, uid, depreciation_date, context=context)
344 company_currency = line.asset_id.company_id.currency_id.id
345 current_currency = line.asset_id.currency_id.id
346 context.update({'date': depreciation_date})
347 amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.amount, context=context)
348 sign = line.asset_id.category_id.journal_id.type = 'purchase' and 1 or -1
351 'date': depreciation_date,
353 'period_id': period_ids and period_ids[0] or False,
354 'journal_id': line.asset_id.category_id.journal_id.id,
356 move_id = move_obj.create(cr, uid, move_vals, context=context)
357 move_line_obj.create(cr, uid, {
361 'account_id': line.asset_id.category_id.account_depreciation_id.id,
364 'period_id': period_ids and period_ids[0] or False,
365 'journal_id': line.asset_id.category_id.journal_id.id,
366 'partner_id': line.asset_id.partner_id.id,
367 'currency_id': company_currency <> current_currency and current_currency or False,
368 'amount_currency': company_currency <> current_currency and - sign * line.amount or 0.0,
369 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
370 'date': depreciation_date,
372 move_line_obj.create(cr, uid, {
376 'account_id': line.asset_id.category_id.account_expense_depreciation_id.id,
379 'period_id': period_ids and period_ids[0] or False,
380 'journal_id': line.asset_id.category_id.journal_id.id,
381 'partner_id': line.asset_id.partner_id.id,
382 'currency_id': company_currency <> current_currency and current_currency or False,
383 'amount_currency': company_currency <> current_currency and sign * line.amount or 0.0,
384 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
385 'date': depreciation_date,
386 'asset_id': line.asset_id.id
388 self.write(cr, uid, line.id, {'move_id': move_id}, context=context)
391 account_asset_depreciation_line()
393 #class account_asset_property(osv.osv):
394 # def _amount_total(self, cr, uid, ids, name, args, context={}):
395 # id_set=",".join(map(str,ids))
396 # cr.execute("""SELECT l.asset_id,abs(SUM(l.debit-l.credit)) AS amount FROM
397 # account_asset_property p
399 # account_move_line l on (p.asset_id=l.asset_id)
400 # WHERE p.id IN ("""+id_set+") GROUP BY l.asset_id ")
401 # res=dict(cr.fetchall())
403 # res.setdefault(id, 0.0)
406 # def _close(self, cr, uid, property, context={}):
407 # if property.state<>'close':
408 # self.pool.get('account.asset.property').write(cr, uid, [property.id], {
411 # property.state='close'
412 # ok = property.asset_id.state=='open'
413 # for prop in property.asset_id.property_ids:
414 # ok = ok and prop.state=='close'
415 # self.pool.get('account.asset.asset').write(cr, uid, [property.asset_id.id], {
420 # _name = 'account.asset.property'
421 # _description = 'Asset property'
423 # 'name': fields.char('Method name', size=64, select=1),
424 # 'type': fields.selection([('direct','Direct'),('indirect','Indirect')], 'Depr. method type', select=2, required=True),
425 # 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
426 # 'account_asset_id': fields.many2one('account.account', 'Asset account', required=True),
427 # 'account_actif_id': fields.many2one('account.account', 'Depreciation account', required=True),
428 # 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
429 # 'journal_analytic_id': fields.many2one('account.analytic.journal', 'Analytic journal'),
430 # 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
432 # 'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
433 # 'method_delay': fields.integer('During', readonly=True, states={'draft':[('readonly',False)]}),
434 # 'method_period': fields.integer('Depre. all', readonly=True, states={'draft':[('readonly',False)]}),
435 # 'method_end': fields.date('Ending date'),
437 # 'date': fields.date('Date created'),
438 # #'test': fields.one2many('account.pre', 'asset_id', readonly=True, states={'draft':[('readonly',False)]}),
439 # 'entry_asset_ids': fields.many2many('account.move.line', 'account_move_asset_entry_rel', 'asset_property_id', 'move_id', 'Asset Entries'),
440 # 'board_ids': fields.one2many('account.asset.board', 'asset_id', 'Asset board'),
442 # 'value_total': fields.function(_amount_total, method=True, digits=(16,2),string='Gross value'),
443 # 'state': fields.selection([('draft','Draft'), ('open','Open'), ('close','Close')], 'State', required=True),
444 # 'history_ids': fields.one2many('account.asset.property.history', 'asset_property_id', 'History', readonly=True)
445 ## 'parent_id': fields.many2one('account.asset.asset', 'Parent asset'),
446 ## 'partner_id': fields.many2one('res.partner', 'Partner'),
447 ## 'note': fields.text('Note'),
451 # 'type': lambda obj, cr, uid, context: 'direct',
452 # 'state': lambda obj, cr, uid, context: 'draft',
453 # 'method': lambda obj, cr, uid, context: 'linear',
454 # 'method_time': lambda obj, cr, uid, context: 'delay',
455 # 'method_progress_factor': lambda obj, cr, uid, context: 0.3,
456 # 'method_delay': lambda obj, cr, uid, context: 5,
457 # 'method_period': lambda obj, cr, uid, context: 12,
458 # 'date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d')
460 #account_asset_property()
462 class account_move_line(osv.osv):
463 _inherit = 'account.move.line'
465 'asset_id': fields.many2one('account.asset.asset', 'Asset'),
466 'entry_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
471 class account_asset_history(osv.osv):
472 _name = 'account.asset.history'
473 _description = 'Asset history'
475 'name': fields.char('History name', size=64, select=1),
476 'user_id': fields.many2one('res.users', 'User', required=True),
477 'date': fields.date('Date', required=True),
478 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
479 'method_delay': fields.integer('Number of interval'),
480 'method_period': fields.integer('Period per interval'),
481 'method_end': fields.date('Ending date'),
482 'note': fields.text('Note'),
485 'date': lambda *args: time.strftime('%Y-%m-%d'),
486 'user_id': lambda self,cr, uid,ctx: uid
488 account_asset_history()
490 class account_asset_board(osv.osv):
491 _name = 'account.asset.board'
492 _description = 'Asset board'
494 'name': fields.char('Asset name', size=64, required=True, select=1),
495 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True, select=1),
496 'value_gross': fields.float('Gross value', required=True, select=1),
497 'value_asset': fields.float('Asset Value', required=True, select=1),
498 'value_asset_cumul': fields.float('Cumul. value', required=True, select=1),
499 'value_net': fields.float('Net value', required=True, select=1),
505 create or replace view account_asset_board as (
508 min(l.id) as asset_id,
511 0.0 as value_asset_cumul,
516 l.state <> 'draft' and
519 account_asset_board()
521 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: