[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         if context is None:
294             context = {}
295         asset_obj = self.pool.get('account.asset.asset')
296         period_obj = self.pool.get('account.period')
297         move_obj = self.pool.get('account.move')
298         move_line_obj = self.pool.get('account.move.line')
299         currency_obj = self.pool.get('res.currency')
300         for line in self.browse(cr, uid, ids, context=context):
301             depreciation_date = asset_obj._get_last_depreciation_date(cr, uid, [line.asset_id.id], context=context)[line.asset_id.id]
302             period_ids = period_obj.find(cr, uid, depreciation_date, context=context)
303             company_currency = line.asset_id.company_id.currency_id.id
304             current_currency = line.asset_id.currency_id.id
305             context.update({'date': depreciation_date})
306             amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.amount, context=context)
307             sign = line.asset_id.category_id.journal_id.type = 'purchase' and 1 or -1
308             move_vals = {
309                 'name': line.name,
310                 'date': depreciation_date,
311                 'ref': line.name,
312                 'period_id': period_ids and period_ids[0] or False,
313                 'journal_id': line.asset_id.category_id.journal_id.id,
314                 }
315             move_id = move_obj.create(cr, uid, move_vals, context=context)
316             move_line_obj.create(cr, uid, {
317                 'name': line.name,
318                 'ref': line.name,
319                 'move_id': move_id,
320                 'account_id': line.asset_id.category_id.account_depreciation_id.id,
321                 'debit': 0.0,
322                 'credit': amount,
323                 'period_id': period_ids and period_ids[0] or False,
324                 'journal_id': line.asset_id.category_id.journal_id.id,
325                 'partner_id': line.asset_id.partner_id.id,
326                 'currency_id': company_currency <> current_currency and  current_currency or False,
327                 'amount_currency': company_currency <> current_currency and - sign * line.amount or 0.0,
328                 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
329                 'date': depreciation_date,
330             })
331             move_line_obj.create(cr, uid, {
332                 'name': line.name,
333                 'ref': line.name,
334                 'move_id': move_id,
335                 'account_id': line.asset_id.category_id.account_expense_depreciation_id.id,
336                 'credit': 0.0,
337                 'debit': amount,
338                 'period_id': period_ids and period_ids[0] or False,
339                 'journal_id': line.asset_id.category_id.journal_id.id,
340                 'partner_id': line.asset_id.partner_id.id,
341                 'currency_id': company_currency <> current_currency and  current_currency or False,
342                 'amount_currency': company_currency <> current_currency and sign * line.amount or 0.0,
343                 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
344                 'date': depreciation_date,
345             })
346             self.write(cr, uid, line.id, {'move_id': move_id}, context=context)
347         return True
348
349 account_asset_depreciation_line()
350
351 #class account_asset_property(osv.osv):
352 #    def _amount_total(self, cr, uid, ids, name, args, context={}):
353 #        id_set=",".join(map(str,ids))
354 #        cr.execute("""SELECT l.asset_id,abs(SUM(l.debit-l.credit)) AS amount FROM
355 #                account_asset_property p
356 #            left join
357 #                account_move_line l on (p.asset_id=l.asset_id)
358 #            WHERE p.id IN ("""+id_set+") GROUP BY l.asset_id ")
359 #        res=dict(cr.fetchall())
360 #        for id in ids:
361 #            res.setdefault(id, 0.0)
362 #        return res
363 #
364 #    def _close(self, cr, uid, property, context={}):
365 #        if property.state<>'close':
366 #            self.pool.get('account.asset.property').write(cr, uid, [property.id], {
367 #                'state': 'close'
368 #            })
369 #            property.state='close'
370 #        ok = property.asset_id.state=='open'
371 #        for prop in property.asset_id.property_ids:
372 #            ok = ok and prop.state=='close'
373 #        self.pool.get('account.asset.asset').write(cr, uid, [property.asset_id.id], {
374 #            'state': 'close'
375 #        }, context)
376 #        return True
377 #
378 #    _name = 'account.asset.property'
379 #    _description = 'Asset property'
380 #    _columns = {
381 #        'name': fields.char('Method name', size=64, select=1),
382 #        'type': fields.selection([('direct','Direct'),('indirect','Indirect')], 'Depr. method type', select=2, required=True),
383 #        'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
384 #        'account_asset_id': fields.many2one('account.account', 'Asset account', required=True),
385 #        'account_actif_id': fields.many2one('account.account', 'Depreciation account', required=True),
386 #        'journal_id': fields.many2one('account.journal', 'Journal', required=True),
387 #        'journal_analytic_id': fields.many2one('account.analytic.journal', 'Analytic journal'),
388 #        'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
389 #
390 #        'method': fields.selection([('linear','Linear'),('progressif','Progressive')], 'Computation method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
391 #        'method_delay': fields.integer('During', readonly=True, states={'draft':[('readonly',False)]}),
392 #        'method_period': fields.integer('Depre. all', readonly=True, states={'draft':[('readonly',False)]}),
393 #        'method_end': fields.date('Ending date'),
394 #
395 #        'date': fields.date('Date created'),
396 #    #'test': fields.one2many('account.pre', 'asset_id',  readonly=True, states={'draft':[('readonly',False)]}),
397 #        'entry_asset_ids': fields.many2many('account.move.line', 'account_move_asset_entry_rel', 'asset_property_id', 'move_id', 'Asset Entries'),
398 #        'board_ids': fields.one2many('account.asset.board', 'asset_id', 'Asset board'),
399 #
400 #        'value_total': fields.function(_amount_total, method=True, digits=(16,2),string='Gross value'),
401 #        'state': fields.selection([('draft','Draft'), ('open','Open'), ('close','Close')], 'State', required=True),
402 #        'history_ids': fields.one2many('account.asset.property.history', 'asset_property_id', 'History', readonly=True)
403 ##    'parent_id': fields.many2one('account.asset.asset', 'Parent asset'),
404 ##    'partner_id': fields.many2one('res.partner', 'Partner'),
405 ##    'note': fields.text('Note'),
406 #
407 #    }
408 #    _defaults = {
409 #        'type': lambda obj, cr, uid, context: 'direct',
410 #        'state': lambda obj, cr, uid, context: 'draft',
411 #        'method': lambda obj, cr, uid, context: 'linear',
412 #        'method_time': lambda obj, cr, uid, context: 'delay',
413 #        'method_progress_factor': lambda obj, cr, uid, context: 0.3,
414 #        'method_delay': lambda obj, cr, uid, context: 5,
415 #        'method_period': lambda obj, cr, uid, context: 12,
416 #        'date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d')
417 #    }
418 #account_asset_property()
419
420 class account_move_line(osv.osv):
421     _inherit = 'account.move.line'
422     _columns = {
423         'asset_id': fields.many2one('account.asset.asset', 'Asset'),
424         'entry_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
425
426     }
427 account_move_line()
428
429 class account_asset_history(osv.osv):
430     _name = 'account.asset.history'
431     _description = 'Asset history'
432     _columns = {
433         'name': fields.char('History name', size=64, select=1),
434         'user_id': fields.many2one('res.users', 'User', required=True),
435         'date': fields.date('Date', required=True),
436         'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
437         'method_delay': fields.integer('Number of interval'),
438         'method_period': fields.integer('Period per interval'),
439         'method_end': fields.date('Ending date'),
440         'note': fields.text('Note'),
441     }
442     _defaults = {
443         'date': lambda *args: time.strftime('%Y-%m-%d'),
444         'user_id': lambda self,cr, uid,ctx: uid
445     }
446 account_asset_history()
447
448 class account_asset_board(osv.osv):
449     _name = 'account.asset.board'
450     _description = 'Asset board'
451     _columns = {
452         'name': fields.char('Asset name', size=64, required=True, select=1),
453         'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True, select=1),
454         'value_gross': fields.float('Gross value', required=True, select=1),
455         'value_asset': fields.float('Asset Value', required=True, select=1),
456         'value_asset_cumul': fields.float('Cumul. value', required=True, select=1),
457         'value_net': fields.float('Net value', required=True, select=1),
458
459     }
460     _auto = False
461     def init(self, cr):
462         cr.execute("""
463             create or replace view account_asset_board as (
464                 select
465                     min(l.id) as id,
466                     min(l.id) as asset_id,
467                     0.0 as value_gross,
468                     0.0 as value_asset,
469                     0.0 as value_asset_cumul,
470                     0.0 as value_net
471                 from
472                     account_move_line l
473                 where
474                     l.state <> 'draft' and
475                     l.asset_id=3
476             )""")
477 account_asset_board()
478
479 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: