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