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