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