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