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