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