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