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