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