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