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