[IMP] account_asset: Improved tooltip. Corrected digits for residual value.
[odoo/odoo.git] / addons / account_asset / account_asset.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 from osv import osv, fields
23 import time
24 from datetime import datetime
25 import decimal_precision as dp
26
27 class account_asset_category(osv.osv):
28     _name = 'account.asset.category'
29     _description = 'Asset category'
30
31     _columns = {
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     }
42
43     _defaults = {
44         'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.category', context=context),
45     }
46
47 account_asset_category()
48
49 #class one2many_mod_asset(fields.one2many):
50 #
51 #    def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
52 #        prinasset_property_id        if context is None:
53 #            context = {}
54 #        if not values:
55 #            values = {}
56 #        res = {}
57 #        for id in ids:
58 #            res[id] = []
59 #        #compute depreciation board
60 #        depreciation_line_ids = obj.pool.get('account.asset.asset').compute_depreciation_board(cr, user, ids, context=context)
61 #        for key, value in depreciation_line_ids.items():
62 #            #write values on asset
63 #            obj.pool.get(self._obj).write(cr, user, key, {'depreciation_line_ids': [6,0,value]})
64 #        return depreciation_line_ids
65
66 class account_asset_asset(osv.osv):
67     _name = 'account.asset.asset'
68     _description = 'Asset'
69
70     def _get_period(self, cr, uid, context={}):
71         periods = self.pool.get('account.period').find(cr, uid)
72         if periods:
73             return periods[0]
74         else:
75             return False
76
77     def _get_last_depreciation_date(self, cr, uid, ids, context=None):
78         """
79         @param id: ids of a account.asset.asset objects
80         @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
81         """
82         cr.execute("""
83             SELECT a.id as id, COALESCE(MAX(l.date),a.purchase_date) AS date
84             FROM account_asset_asset a
85             LEFT JOIN account_move_line l ON (l.asset_id = a.id)
86             WHERE a.id IN %s
87             GROUP BY a.id, a.purchase_date """, (tuple(ids),))
88         return dict(cr.fetchall())
89
90     def compute_depreciation_board(self, cr, uid,ids, context=None):
91         depreciation_lin_obj = self.pool.get('account.asset.depreciation.line')
92         for asset in self.browse(cr, uid, ids, context=context):
93             old_depreciation_line_ids = depreciation_lin_obj.search(cr, uid, [('asset_id', '=', asset.id), ('move_id', '=', False)])
94             if old_depreciation_line_ids:
95                 depreciation_lin_obj.unlink(cr, uid, old_depreciation_line_ids, context=context)
96
97             undone_dotation_number = asset.method_delay - len(asset.account_move_line_ids)
98             salvage_amount = residual_amount = asset.value_residual - asset.salvage_value
99             depreciation_date = datetime.strptime(self._get_last_depreciation_date(cr, uid, [asset.id], context)[asset.id], '%Y-%m-%d')
100             day = depreciation_date.day
101             month = depreciation_date.month
102             year = depreciation_date.year
103             for i in range(1,undone_dotation_number+1):
104                 if i == undone_dotation_number + 1:
105                     amount = residual_amount
106                 else:
107                     if asset.method == 'linear':
108                         amount = salvage_amount / undone_dotation_number
109                     else:
110                         amount = residual_amount * asset.method_progress_factor
111                 residual_amount -= amount
112                 vals = {
113                      'amount': amount,
114                      'asset_id': asset.id,
115                      'sequence':i,
116                      'name': str(asset.id) +'/'+ str(i),
117                      'remaining_value': residual_amount,
118                      'depreciated_value': salvage_amount - residual_amount,
119                      'depreciation_date': depreciation_date.strftime('%Y-%m-%d'),
120                 }
121                 self.pool.get('account.asset.depreciation.line').create(cr, uid, vals)
122                 month += asset.method_period
123                 depreciation_date = datetime(year + (month / 12), month % 12, day)
124         return True
125
126     def validate(self, cr, uid, ids, context={}):
127         return self.write(cr, uid, ids, {
128             'state':'normal'
129         }, context)
130
131     
132
133     def _amount_residual(self, cr, uid, ids, name, args, context={}):
134         cr.execute("""SELECT
135                 l.asset_id as id, SUM(abs(l.debit-l.credit)) AS amount
136             FROM
137                 account_move_line l
138             WHERE
139                 l.asset_id IN %s GROUP BY l.asset_id """, (tuple(ids),))
140         res=dict(cr.fetchall())
141         for asset in self.browse(cr, uid, ids, context):
142             res[asset.id] = asset.purchase_value - res.get(asset.id, 0.0)
143         for id in ids:
144             res.setdefault(id, 0.0)
145         return res
146
147     _columns = {
148         'period_id': fields.many2one('account.period', 'First Period', required=True, readonly=True, states={'draft':[('readonly',False)]}),
149         'account_move_line_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
150
151         'name': fields.char('Asset', size=64, required=True, select=1),
152         'code': fields.char('Reference ', size=16, select=1),
153         'purchase_value': fields.float('Gross value ', required=True, size=16, select=1),
154         'currency_id': fields.many2one('res.currency','Currency',required=True,size=5,select=1),
155         'company_id': fields.many2one('res.company', 'Company', required=True),
156         'note': fields.text('Note'),
157         'category_id': fields.many2one('account.asset.category', 'Asset category',required=True, change_default=True),
158         'localisation': fields.char('Localisation', size=32, select=2),
159         'parent_id': fields.many2one('account.asset.asset', 'Parent Asset'),
160         'child_ids': fields.one2many('account.asset.asset', 'parent_id', 'Children Assets'),
161         'purchase_date': fields.date('Purchase Date', required=True),
162         'state': fields.selection([('view','View'),('draft','Draft'),('normal','Normal'),('close','Close')], 'state', required=True),
163         'active': fields.boolean('Active', select=2),
164         'partner_id': fields.many2one('res.partner', 'Partner'),
165
166         'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
167         'method_delay': fields.integer('During (interval)', readonly=True, states={'draft':[('readonly',False)]}),
168         'method_period': fields.integer('Depre. all (period)', readonly=True, states={'draft':[('readonly',False)]}),
169         'method_end': fields.date('Ending date'),
170         'method_progress_factor': fields.float('Progressif Factor', readonly=True, states={'draft':[('readonly',False)]}),
171         'value_residual': fields.function(_amount_residual, method=True, digits_compute=dp.get_precision('Account'), string='Residual Value'),
172         'method_time': fields.selection([('delay','Delay'),('end','Ending Period')], 'Time Method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
173         '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'),
174         'history_ids': fields.one2many('account.asset.history', 'asset_id', 'History', readonly=True),
175         'depreciation_line_ids': fields.one2many('account.asset.depreciation.line', 'asset_id', 'Depreciation Lines', readonly=True,),
176         '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."),
177     }
178     _defaults = {
179         'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'account.asset.code'),
180         'purchase_date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d'),
181         'active': lambda obj, cr, uid, context: True,
182         'state': lambda obj, cr, uid, context: 'draft',
183         'period_id': _get_period,
184         'method': lambda obj, cr, uid, context: 'linear',
185         'method_delay': lambda obj, cr, uid, context: 5,
186         'method_time': lambda obj, cr, uid, context: 'delay',
187         'method_period': lambda obj, cr, uid, context: 12,
188         'method_progress_factor': lambda obj, cr, uid, context: 0.3,
189         'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
190         'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.asset',context=context),
191     }
192
193
194     def _compute_period(self, cr, uid, property, context={}):
195         if (len(property.entry_asset_ids or [])/2)>=property.method_delay:
196             return False
197         if len(property.entry_asset_ids):
198             cp = property.entry_asset_ids[-1].period_id
199             cpid = self.pool.get('account.period').next(cr, uid, cp, property.method_period, context)
200             current_period = self.pool.get('account.period').browse(cr, uid, cpid, context)
201         else:
202             current_period = property.asset_id.period_id
203         return current_period
204
205     def _compute_move(self, cr, uid, property, period, context={}):
206         #FIXME: fucntion not working OK
207         result = []
208         total = 0.0
209         for move in property.asset_id.entry_ids:
210             total += move.debit-move.credit
211         for move in property.entry_asset_ids:
212             if move.account_id == property.account_asset_ids:
213                 total += move.debit
214                 total += -move.credit
215         periods = (len(property.entry_asset_ids)/2) - property.method_delay
216
217         if periods==1:
218             amount = total
219         else:
220             if property.method == 'linear':
221                 amount = total / periods
222             else:
223                 amount = total * property.method_progress_factor
224
225         move_id = self.pool.get('account.move').create(cr, uid, {
226             'journal_id': property.journal_id.id,
227             'period_id': period.id,
228             'name': property.name or property.asset_id.name,
229             'ref': property.asset_id.code
230         })
231         result = [move_id]
232         id = self.pool.get('account.move.line').create(cr, uid, {
233             'name': property.name or property.asset_id.name,
234             'move_id': move_id,
235             'account_id': property.account_asset_id.id,
236             'debit': amount>0 and amount or 0.0,
237             'credit': amount<0 and -amount or 0.0,
238             'ref': property.asset_id.code,
239             'period_id': period.id,
240             'journal_id': property.journal_id.id,
241             'partner_id': property.asset_id.partner_id.id,
242             'date': time.strftime('%Y-%m-%d'),
243         })
244         id2 = self.pool.get('account.move.line').create(cr, uid, {
245             'name': property.name or property.asset_id.name,
246             'move_id': move_id,
247             'account_id': property.account_actif_id.id,
248             'credit': amount>0 and amount or 0.0,
249             'debit': amount<0 and -amount or 0.0,
250             'ref': property.asset_id.code,
251             'period_id': period.id,
252             'journal_id': property.journal_id.id,
253             'partner_id': property.asset_id.partner_id.id,
254             'date': time.strftime('%Y-%m-%d'),
255         })
256     #
257         self.pool.get('account.asset.asset').write(cr, uid, [property.id], {
258             'entry_asset_ids': [(4, id2, False),(4,id,False)]
259         })
260         if property.method_delay - (len(property.entry_asset_ids)/2)<=1:
261             #self.pool.get('account.asset.property')._close(cr, uid, property, context)
262             return result
263         return result
264
265     def _compute_entries(self, cr, uid, asset, period_id, context={}):
266         #FIXME: function not working CHECK all res
267         result = []
268         date_start = self.pool.get('account.period').browse(cr, uid, period_id, context).date_start
269         for property in asset.property_ids:
270             if property.state=='open':
271                 period = self._compute_period(cr, uid, property, context)
272                 if period and (period.date_start<=date_start):
273                     result += self._compute_move(cr, uid, property, period, context)
274         return result
275 account_asset_asset()
276
277 class account_asset_depreciation_line(osv.osv):
278     _name = 'account.asset.depreciation.line'
279     _description = 'Asset depreciation line'
280
281     def _get_move_check(self, cr, uid, ids, name, args, context=None):
282         res = {}
283         for line in self.browse(cr, uid, ids, context=context):
284             res[line.id] = bool(line.move_id)
285         return res
286
287     _columns = {
288         'name': fields.char('Depreciation Name', size=64, required=True, select=1),
289         'sequence': fields.integer('Sequence of the depreciation', required=True),
290         'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
291         'amount': fields.float('Depreciation Amount', required=True),
292         'remaining_value': fields.float('Amount to Depreciate', required=True),
293         'depreciated_value': fields.float('Amount Already Depreciated', required=True),
294         'depreciation_date': fields.char('Depreciation Date', size=64, select=1),
295         'move_id': fields.many2one('account.move', 'Depreciation Entry'),
296         'move_check': fields.function(_get_move_check, method=True, type='boolean', string='Move Included', store=True)
297     }
298
299     def create_move(self, cr, uid,ids, context=None):
300         if context is None:
301             context = {}
302         asset_obj = self.pool.get('account.asset.asset')
303         period_obj = self.pool.get('account.period')
304         move_obj = self.pool.get('account.move')
305         move_line_obj = self.pool.get('account.move.line')
306         currency_obj = self.pool.get('res.currency')
307         for line in self.browse(cr, uid, ids, context=context):
308             depreciation_date = time.strftime('%Y-%m-%d')
309             period_ids = period_obj.find(cr, uid, depreciation_date, context=context)
310             company_currency = line.asset_id.company_id.currency_id.id
311             current_currency = line.asset_id.currency_id.id
312             context.update({'date': depreciation_date})
313             amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.amount, context=context)
314             sign = line.asset_id.category_id.journal_id.type = 'purchase' and 1 or -1
315             move_vals = {
316                 'name': line.name,
317                 'date': depreciation_date,
318                 'ref': line.name,
319                 'period_id': period_ids and period_ids[0] or False,
320                 'journal_id': line.asset_id.category_id.journal_id.id,
321                 }
322             move_id = move_obj.create(cr, uid, move_vals, context=context)
323             move_line_obj.create(cr, uid, {
324                 'name': line.name,
325                 'ref': line.name,
326                 'move_id': move_id,
327                 'account_id': line.asset_id.category_id.account_depreciation_id.id,
328                 'debit': 0.0,
329                 'credit': amount,
330                 'period_id': period_ids and period_ids[0] or False,
331                 'journal_id': line.asset_id.category_id.journal_id.id,
332                 'partner_id': line.asset_id.partner_id.id,
333                 'currency_id': company_currency <> current_currency and  current_currency or False,
334                 'amount_currency': company_currency <> current_currency and - sign * line.amount or 0.0,
335                 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
336                 'date': depreciation_date,
337             })
338             move_line_obj.create(cr, uid, {
339                 'name': line.name,
340                 'ref': line.name,
341                 'move_id': move_id,
342                 'account_id': line.asset_id.category_id.account_expense_depreciation_id.id,
343                 'credit': 0.0,
344                 'debit': amount,
345                 'period_id': period_ids and period_ids[0] or False,
346                 'journal_id': line.asset_id.category_id.journal_id.id,
347                 'partner_id': line.asset_id.partner_id.id,
348                 'currency_id': company_currency <> current_currency and  current_currency or False,
349                 'amount_currency': company_currency <> current_currency and sign * line.amount or 0.0,
350                 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
351                 'date': depreciation_date,
352             })
353             self.write(cr, uid, line.id, {'move_id': move_id}, context=context)
354         return True
355
356 account_asset_depreciation_line()
357
358 #class account_asset_property(osv.osv):
359 #    def _amount_total(self, cr, uid, ids, name, args, context={}):
360 #        id_set=",".join(map(str,ids))
361 #        cr.execute("""SELECT l.asset_id,abs(SUM(l.debit-l.credit)) AS amount FROM
362 #                account_asset_property p
363 #            left join
364 #                account_move_line l on (p.asset_id=l.asset_id)
365 #            WHERE p.id IN ("""+id_set+") GROUP BY l.asset_id ")
366 #        res=dict(cr.fetchall())
367 #        for id in ids:
368 #            res.setdefault(id, 0.0)
369 #        return res
370 #
371 #    def _close(self, cr, uid, property, context={}):
372 #        if property.state<>'close':
373 #            self.pool.get('account.asset.property').write(cr, uid, [property.id], {
374 #                'state': 'close'
375 #            })
376 #            property.state='close'
377 #        ok = property.asset_id.state=='open'
378 #        for prop in property.asset_id.property_ids:
379 #            ok = ok and prop.state=='close'
380 #        self.pool.get('account.asset.asset').write(cr, uid, [property.asset_id.id], {
381 #            'state': 'close'
382 #        }, context)
383 #        return True
384 #
385 #    _name = 'account.asset.property'
386 #    _description = 'Asset property'
387 #    _columns = {
388 #        'name': fields.char('Method name', size=64, select=1),
389 #        'type': fields.selection([('direct','Direct'),('indirect','Indirect')], 'Depr. method type', select=2, required=True),
390 #        'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
391 #        'account_asset_id': fields.many2one('account.account', 'Asset account', required=True),
392 #        'account_actif_id': fields.many2one('account.account', 'Depreciation account', required=True),
393 #        'journal_id': fields.many2one('account.journal', 'Journal', required=True),
394 #        'journal_analytic_id': fields.many2one('account.analytic.journal', 'Analytic journal'),
395 #        'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
396 #
397 #        'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
398 #        'method_delay': fields.integer('During', readonly=True, states={'draft':[('readonly',False)]}),
399 #        'method_period': fields.integer('Depre. all', readonly=True, states={'draft':[('readonly',False)]}),
400 #        'method_end': fields.date('Ending date'),
401 #
402 #        'date': fields.date('Date created'),
403 #    #'test': fields.one2many('account.pre', 'asset_id',  readonly=True, states={'draft':[('readonly',False)]}),
404 #        'entry_asset_ids': fields.many2many('account.move.line', 'account_move_asset_entry_rel', 'asset_property_id', 'move_id', 'Asset Entries'),
405 #        'board_ids': fields.one2many('account.asset.board', 'asset_id', 'Asset board'),
406 #
407 #        'value_total': fields.function(_amount_total, method=True, digits=(16,2),string='Gross value'),
408 #        'state': fields.selection([('draft','Draft'), ('open','Open'), ('close','Close')], 'State', required=True),
409 #        'history_ids': fields.one2many('account.asset.property.history', 'asset_property_id', 'History', readonly=True)
410 ##    'parent_id': fields.many2one('account.asset.asset', 'Parent asset'),
411 ##    'partner_id': fields.many2one('res.partner', 'Partner'),
412 ##    'note': fields.text('Note'),
413 #
414 #    }
415 #    _defaults = {
416 #        'type': lambda obj, cr, uid, context: 'direct',
417 #        'state': lambda obj, cr, uid, context: 'draft',
418 #        'method': lambda obj, cr, uid, context: 'linear',
419 #        'method_time': lambda obj, cr, uid, context: 'delay',
420 #        'method_progress_factor': lambda obj, cr, uid, context: 0.3,
421 #        'method_delay': lambda obj, cr, uid, context: 5,
422 #        'method_period': lambda obj, cr, uid, context: 12,
423 #        'date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d')
424 #    }
425 #account_asset_property()
426
427 class account_move_line(osv.osv):
428     _inherit = 'account.move.line'
429     _columns = {
430         'asset_id': fields.many2one('account.asset.asset', 'Asset'),
431         'entry_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
432
433     }
434 account_move_line()
435
436 class account_asset_history(osv.osv):
437     _name = 'account.asset.history'
438     _description = 'Asset history'
439     _columns = {
440         'name': fields.char('History name', size=64, select=1),
441         'user_id': fields.many2one('res.users', 'User', required=True),
442         'date': fields.date('Date', required=True),
443         'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
444         'method_delay': fields.integer('Number of interval'),
445         'method_period': fields.integer('Period per interval'),
446         'method_end': fields.date('Ending date'),
447         'note': fields.text('Note'),
448     }
449     _defaults = {
450         'date': lambda *args: time.strftime('%Y-%m-%d'),
451         'user_id': lambda self,cr, uid,ctx: uid
452     }
453 account_asset_history()
454
455 class account_asset_board(osv.osv):
456     _name = 'account.asset.board'
457     _description = 'Asset board'
458     _columns = {
459         'name': fields.char('Asset name', size=64, required=True, select=1),
460         'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True, select=1),
461         'value_gross': fields.float('Gross value', required=True, select=1),
462         'value_asset': fields.float('Asset Value', required=True, select=1),
463         'value_asset_cumul': fields.float('Cumul. value', required=True, select=1),
464         'value_net': fields.float('Net value', required=True, select=1),
465
466     }
467     _auto = False
468     def init(self, cr):
469         cr.execute("""
470             create or replace view account_asset_board as (
471                 select
472                     min(l.id) as id,
473                     min(l.id) as asset_id,
474                     0.0 as value_gross,
475                     0.0 as value_asset,
476                     0.0 as value_asset_cumul,
477                     0.0 as value_net
478                 from
479                     account_move_line l
480                 where
481                     l.state <> 'draft' and
482                     l.asset_id=3
483             )""")
484 account_asset_board()
485
486 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: