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