[IMP]event_form
[odoo/odoo.git] / addons / analytic / analytic.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 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 import time
23
24 from osv import fields, osv
25 from tools.translate import _
26 import decimal_precision as dp
27
28 class account_analytic_account(osv.osv):
29     _name = 'account.analytic.account'
30     _description = 'Analytic Account'
31
32     def _compute_level_tree(self, cr, uid, ids, child_ids, res, field_names, context=None):
33         currency_obj = self.pool.get('res.currency')
34         recres = {}
35         def recursive_computation(account):
36             result2 = res[account.id].copy()
37             for son in account.child_ids:
38                 result = recursive_computation(son)
39                 for field in field_names:
40                     if (account.currency_id.id != son.currency_id.id) and (field!='quantity'):
41                         result[field] = currency_obj.compute(cr, uid, son.currency_id.id, account.currency_id.id, result[field], context=context)
42                     result2[field] += result[field]
43             return result2
44         for account in self.browse(cr, uid, ids, context=context):
45             if account.id not in child_ids:
46                 continue
47             recres[account.id] = recursive_computation(account)
48         return recres
49
50     def _debit_credit_bal_qtty(self, cr, uid, ids, fields, arg, context=None):
51         res = {}
52         if context is None:
53             context = {}
54         child_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
55         for i in child_ids:
56             res[i] =  {}
57             for n in fields:
58                 res[i][n] = 0.0
59
60         if not child_ids:
61             return res
62
63         where_date = ''
64         where_clause_args = [tuple(child_ids)]
65         if context.get('from_date', False):
66             where_date += " AND l.date >= %s"
67             where_clause_args  += [context['from_date']]
68         if context.get('to_date', False):
69             where_date += " AND l.date <= %s"
70             where_clause_args += [context['to_date']]
71         cr.execute("""
72               SELECT a.id,
73                      sum(
74                          CASE WHEN l.amount > 0
75                          THEN l.amount
76                          ELSE 0.0
77                          END
78                           ) as debit,
79                      sum(
80                          CASE WHEN l.amount < 0
81                          THEN -l.amount
82                          ELSE 0.0
83                          END
84                           ) as credit,
85                      COALESCE(SUM(l.amount),0) AS balance,
86                      COALESCE(SUM(l.unit_amount),0) AS quantity
87               FROM account_analytic_account a
88                   LEFT JOIN account_analytic_line l ON (a.id = l.account_id)
89               WHERE a.id IN %s
90               """ + where_date + """
91               GROUP BY a.id""", where_clause_args)
92         for row in cr.dictfetchall():
93             res[row['id']] = {}
94             for field in fields:
95                 res[row['id']][field] = row[field]
96         return self._compute_level_tree(cr, uid, ids, child_ids, res, fields, context)
97
98     def name_get(self, cr, uid, ids, context=None):
99         if isinstance(ids, (int, long)):
100             ids=[ids]
101         if not ids:
102             return []
103         res = []
104         for account in self.browse(cr, uid, ids, context=context):
105             data = []
106             acc = account
107             while acc:
108                 data.insert(0, acc.name)
109                 acc = acc.parent_id
110             data = ' / '.join(data)
111             res.append((account.id, data))
112         return res
113
114     def _complete_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
115         res = self.name_get(cr, uid, ids)
116         return dict(res)
117
118     def _child_compute(self, cr, uid, ids, name, arg, context=None):
119         result = {}
120         if context is None:
121             context = {}
122
123         for account in self.browse(cr, uid, ids, context=context):
124             result[account.id] = map(lambda x: x.id, [child for child in account.child_ids if child.state != 'template'])
125
126         return result
127
128     def _get_analytic_account(self, cr, uid, ids, context=None):
129         company_obj = self.pool.get('res.company')
130         analytic_obj = self.pool.get('account.analytic.account')
131         accounts = []
132         for company in company_obj.browse(cr, uid, ids, context=context):
133             accounts += analytic_obj.search(cr, uid, [('company_id', '=', company.id)])
134         return accounts
135
136     def _set_company_currency(self, cr, uid, ids, name, value, arg, context=None):
137         if isinstance(ids, (int, long)):
138             ids=[ids]
139         for account in self.browse(cr, uid, ids, context=context):
140             if account.company_id:
141                 if account.company_id.currency_id.id != value:
142                     raise osv.except_osv(_('Error !'), _("If you set a company, the currency selected has to be the same as it's currency. \nYou can remove the company belonging, and thus change the currency, only on analytic account of type 'view'. This can be really usefull for consolidation purposes of several companies charts with different currencies, for example."))
143         return cr.execute("""update account_analytic_account set currency_id=%s where id=%s""", (value, account.id, ))
144
145     def _currency(self, cr, uid, ids, field_name, arg, context=None):
146         result = {}
147         for rec in self.browse(cr, uid, ids, context=context):
148             if rec.company_id:
149                 result[rec.id] = rec.company_id.currency_id.id
150             else:
151                 result[rec.id] = rec.currency_id.id
152         return result
153
154     _columns = {
155         'name': fields.char('Account Name', size=128, required=True),
156         'complete_name': fields.function(_complete_name_calc, type='char', string='Full Account Name'),
157         'code': fields.char('Code/Reference', size=24, select=True),
158         'type': fields.selection([('view','View'), ('normal','Normal')], 'Account Type', help='If you select the View Type, it means you won\'t allow to create journal entries using that account.'),
159         'description': fields.text('Description'),
160         'parent_id': fields.many2one('account.analytic.account', 'Parent Analytic Account', select=2),
161         'child_ids': fields.one2many('account.analytic.account', 'parent_id', 'Child Accounts'),
162         'child_complete_ids': fields.function(_child_compute, relation='account.analytic.account', string="Account Hierarchy", type='many2many'),
163         'line_ids': fields.one2many('account.analytic.line', 'account_id', 'Analytic Entries'),
164         'balance': fields.function(_debit_credit_bal_qtty, type='float', string='Balance', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
165         'debit': fields.function(_debit_credit_bal_qtty, type='float', string='Debit', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
166         'credit': fields.function(_debit_credit_bal_qtty, type='float', string='Credit', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
167         'quantity': fields.function(_debit_credit_bal_qtty, type='float', string='Quantity', multi='debit_credit_bal_qtty'),
168         'quantity_max': fields.float('Maximum Time', help='Sets the higher limit of time to work on the contract.'),
169         'partner_id': fields.many2one('res.partner', 'Partner'),
170         'user_id': fields.many2one('res.users', 'Account Manager'),
171         'date_start': fields.date('Date Start'),
172         'date': fields.date('Date End', select=True),
173         'company_id': fields.many2one('res.company', 'Company', required=False), #not required because we want to allow different companies to use the same chart of account, except for leaf accounts.
174         'state': fields.selection([('template', 'Template'),('draft','New'),('open','Open'), ('cancelled', 'Cancelled'),('pending','Pending'),('close','Closed')], 'Status', required=True,
175                                   help='* When an account is created its in \'Draft\' state.\
176                                   \n* If any associated partner is there, it can be in \'Open\' state.\
177                                   \n* If any pending balance is there it can be in \'Pending\'. \
178                                   \n* And finally when all the transactions are over, it can be in \'Close\' state. \
179                                   \n* The project can be in either if the states \'Template\' and \'Running\'.\n If it is template then we can make projects based on the template projects. If its in \'Running\' state it is a normal project.\
180                                  \n If it is to be reviewed then the state is \'Pending\'.\n When the project is completed the state is set to \'Done\'.'),
181         'currency_id': fields.function(_currency, fnct_inv=_set_company_currency,
182             store = {
183                 'res.company': (_get_analytic_account, ['currency_id'], 10),
184             }, string='Currency', type='many2one', relation='res.currency'),
185     }
186
187     def _default_company(self, cr, uid, context=None):
188         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
189         if user.company_id:
190             return user.company_id.id
191         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
192
193     def _get_default_currency(self, cr, uid, context=None):
194         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
195         return user.company_id.currency_id.id
196
197     _defaults = {
198         'type': 'normal',
199         'company_id': _default_company,
200         'state': 'open',
201         'user_id': lambda self, cr, uid, ctx: uid,
202         'partner_id': lambda self, cr, uid, ctx: ctx.get('partner_id', False),
203         'date_start': lambda *a: time.strftime('%Y-%m-%d'),
204         'currency_id': _get_default_currency,
205     }
206
207     def check_recursion(self, cr, uid, ids, context=None, parent=None):
208         return super(account_analytic_account, self)._check_recursion(cr, uid, ids, context=context, parent=parent)
209
210     _order = 'name asc'
211     _constraints = [
212         (check_recursion, 'Error! You can not create recursive analytic accounts.', ['parent_id']),
213     ]
214
215     def copy(self, cr, uid, id, default=None, context=None):
216         if not default:
217             default = {}
218         default['code'] = False
219         default['line_ids'] = []
220         return super(account_analytic_account, self).copy(cr, uid, id, default, context=context)
221
222     def on_change_company(self, cr, uid, id, company_id):
223         if not company_id:
224             return {}
225         currency = self.pool.get('res.company').read(cr, uid, [company_id], ['currency_id'])[0]['currency_id']
226         return {'value': {'currency_id': currency}}
227
228     def on_change_parent(self, cr, uid, id, parent_id):
229         if not parent_id:
230             return {}
231         parent = self.read(cr, uid, [parent_id], ['partner_id','code'])[0]
232         if parent['partner_id']:
233             partner = parent['partner_id'][0]
234         else:
235             partner = False
236         res = {'value': {}}
237         if partner:
238             res['value']['partner_id'] = partner
239         return res
240
241     def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
242         if not args:
243             args=[]
244         if context is None:
245             context={}
246         if context.get('current_model') == 'project.project':
247             cr.execute("select analytic_account_id from project_project")
248             project_ids = [x[0] for x in cr.fetchall()]
249             return self.name_get(cr, uid, project_ids, context=context)
250         if name:
251             account = self.search(cr, uid, [('code', '=', name)] + args, limit=limit, context=context)
252             if not account:
253                 names=map(lambda i : i.strip(),name.split('/'))
254                 for i in range(len(names)):
255                     dom=[('name', operator, names[i])]
256                     if i>0:
257                         dom+=[('id','child_of',account)]
258                     account = self.search(cr, uid, dom, limit=limit, context=context)
259                 newacc = account
260                 while newacc:
261                     newacc = self.search(cr, uid, [('parent_id', 'in', newacc)], limit=limit, context=context)
262                     account += newacc
263                 if args:
264                     account = self.search(cr, uid, [('id', 'in', account)] + args, limit=limit, context=context)
265         else:
266             account = self.search(cr, uid, args, limit=limit, context=context)
267         return self.name_get(cr, uid, account, context=context)
268
269 account_analytic_account()
270
271
272 class account_analytic_line(osv.osv):
273     _name = 'account.analytic.line'
274     _description = 'Analytic Line'
275
276     _columns = {
277         'name': fields.char('Description', size=256, required=True),
278         'date': fields.date('Date', required=True, select=True),
279         'amount': fields.float('Amount', required=True, help='Calculated by multiplying the quantity and the price given in the Product\'s cost price. Always expressed in the company main currency.', digits_compute=dp.get_precision('Account')),
280         'unit_amount': fields.float('Quantity', help='Specifies the amount of quantity to count.'),
281         'account_id': fields.many2one('account.analytic.account', 'Analytic Account', required=True, ondelete='cascade', select=True, domain=[('type','<>','view')]),
282         'user_id': fields.many2one('res.users', 'User'),
283         'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
284
285     }
286     _defaults = {
287         'date': lambda *a: time.strftime('%Y-%m-%d'),
288         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=c),
289         'amount': 0.00
290     }
291
292     _order = 'date desc'
293     
294     def _check_no_view(self, cr, uid, ids, context=None):
295         analytic_lines = self.browse(cr, uid, ids, context=context)
296         for line in analytic_lines:
297             if line.account_id.type == 'view':
298                 return False
299         return True
300     
301     _constraints = [
302         (_check_no_view, 'You can not create analytic line on view account.', ['account_id']),
303     ]    
304
305 account_analytic_line()
306
307 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: