[Merge]
[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 import decimal_precision as dp
26
27 class account_analytic_account(osv.osv):
28     _name = 'account.analytic.account'
29     _description = 'Analytic Account'
30
31     def _compute_level_tree(self, cr, uid, ids, child_ids, res, field_names, context=None):
32         def recursive_computation(account_id, res):
33             account = self.browse(cr, uid, account_id)
34             for son in account.child_ids:
35                 res = recursive_computation(son.id, res)
36                 for field in field_names:
37                     res[account.id][field] += res[son.id][field]
38             return res
39         for account in self.browse(cr, uid, ids, context=context):
40             if account.id not in child_ids:
41                 continue
42             res = recursive_computation(account.id, res)
43         return res
44
45     def _debit_credit_bal_qtty(self, cr, uid, ids, name, arg, context=None):
46         res = {}
47         if context is None:
48             context = {}
49         child_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
50         for i in child_ids:
51             res[i] =  {}
52             for n in name:
53                 res[i][n] = 0.0
54
55         if not child_ids:
56             return res
57
58         where_date = ''
59         where_clause_args = [tuple(child_ids)]  
60         if context.get('from_date', False):
61             where_date += " AND l.date >= %s"
62             where_clause_args  += [context['from_date']]
63         if context.get('to_date', False):
64             where_date += " AND l.date <= %s"
65             where_clause_args += [context['to_date']]
66         cr.execute("""
67               SELECT a.id,
68                      sum(
69                          CASE WHEN l.amount > 0
70                          THEN l.amount 
71                          ELSE 0.0
72                          END
73                           ) as debit,
74                      sum(
75                          CASE WHEN l.amount < 0
76                          THEN -l.amount
77                          ELSE 0.0 
78                          END
79                           ) as credit,
80                      COALESCE(SUM(l.amount),0) AS balance,
81                      COALESCE(SUM(l.unit_amount),0) AS quantity
82               FROM account_analytic_account a 
83                   LEFT JOIN account_analytic_line l ON (a.id = l.account_id) 
84               WHERE a.id IN %s
85               """ + where_date + """
86               GROUP BY a.id""", where_clause_args)
87         for ac_id, debit, credit, balance, quantity in cr.fetchall():
88             res[ac_id] = {'debit': debit, 'credit': credit, 'balance': balance, 'quantity': quantity}
89         return self._compute_level_tree(cr, uid, ids, child_ids, res, ['debit', 'credit', 'balance', 'quantity'], context)
90
91     def name_get(self, cr, uid, ids, context=None):
92         if not ids:
93             return []
94         res = []
95         for account in self.browse(cr, uid, ids, context=context):
96             data = []
97             acc = account
98             while acc:
99                 data.insert(0, acc.name)
100                 acc = acc.parent_id
101             data = ' / '.join(data)
102             res.append((account.id, data))
103         return res
104
105     def _complete_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
106         res = self.name_get(cr, uid, ids)
107         return dict(res)
108
109     _columns = {
110         'name': fields.char('Account Name', size=128, required=True),
111         'complete_name': fields.function(_complete_name_calc, method=True, type='char', string='Full Account Name'),
112         'code': fields.char('Account Code', size=24),
113         '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.'),
114         'description': fields.text('Description'),
115         'parent_id': fields.many2one('account.analytic.account', 'Parent Analytic Account', select=2),
116         'child_ids': fields.one2many('account.analytic.account', 'parent_id', 'Child Accounts'),
117         'line_ids': fields.one2many('account.analytic.line', 'account_id', 'Analytic Entries'),
118         'balance': fields.function(_debit_credit_bal_qtty, method=True, type='float', string='Balance', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
119         'debit': fields.function(_debit_credit_bal_qtty, method=True, type='float', string='Debit', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
120         'credit': fields.function(_debit_credit_bal_qtty, method=True, type='float', string='Credit', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
121         'quantity': fields.function(_debit_credit_bal_qtty, method=True, type='float', string='Quantity', multi='debit_credit_bal_qtty'),
122         'quantity_max': fields.float('Maximum Quantity', help='Sets the higher limit of quantity of hours.'),
123         'partner_id': fields.many2one('res.partner', 'Partner'),
124         'contact_id': fields.many2one('res.partner.address', 'Contact'),
125         'user_id': fields.many2one('res.users', 'Account Manager'),
126         'date_start': fields.date('Date Start'),
127         'date': fields.date('Date End'),
128         'company_id': fields.many2one('res.company', 'Company', required=True),
129         'state': fields.selection([('draft','Draft'),('open','Open'), ('pending','Pending'),('cancelled', 'Cancelled'),('close','Closed'),('template', 'Template')], 'State', required=True,
130                                   help='* When an account is created its in \'Draft\' state.\
131                                   \n* If any associated partner is there, it can be in \'Open\' state.\
132                                   \n* If any pending balance is there it can be in \'Pending\'. \
133                                   \n* And finally when all the transactions are over, it can be in \'Close\' state. \
134                                   \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.\
135                                  \n If it is to be reviewed then the state is \'Pending\'.\n When the project is completed the state is set to \'Done\'.'),
136        'currency_id': fields.related('company_id', 'currency_id', type='many2one', relation='res.currency', string='Account currency', store=True, readonly=True),
137     }
138
139     def _default_company(self, cr, uid, context=None):
140         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
141         if user.company_id:
142             return user.company_id.id
143         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
144
145     _defaults = {
146         'type': 'normal',
147         'company_id': _default_company,
148         'state': 'open',
149         'user_id': lambda self, cr, uid, ctx: uid,
150         'partner_id': lambda self, cr, uid, ctx: ctx.get('partner_id', False),
151         'contact_id': lambda self, cr, uid, ctx: ctx.get('contact_id', False),
152         'date_start': time.strftime('%Y-%m-%d')
153     }
154
155     def check_recursion(self, cr, uid, ids, parent=None):
156         return super(account_analytic_account, self).check_recursion(cr, uid, ids, parent=parent)
157
158     _order = 'date_start desc,parent_id desc,code'
159     _constraints = [
160         (check_recursion, 'Error! You can not create recursive analytic accounts.', ['parent_id'])
161     ]
162
163     def copy(self, cr, uid, id, default=None, context=None):
164         if not default:
165             default = {}
166         default['code'] = False
167         default['line_ids'] = []
168         return super(account_analytic_account, self).copy(cr, uid, id, default, context=context)
169
170     def on_change_parent(self, cr, uid, id, parent_id):
171         if not parent_id:
172             return {}
173         parent = self.read(cr, uid, [parent_id], ['partner_id','code'])[0]
174         if parent['partner_id']:
175             partner = parent['partner_id'][0]
176         else:
177             partner = False
178         res = {'value': {}}
179         if partner:
180             res['value']['partner_id'] = partner
181         return res
182
183     def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
184         if not args:
185             args=[]
186         if not context:
187             context={}
188         account = self.search(cr, uid, [('code', '=', name)]+args, limit=limit, context=context)
189         if not account:
190             account = self.search(cr, uid, [('name', 'ilike', '%%%s%%' % name)]+args, limit=limit, context=context)
191             newacc = account
192             while newacc:
193                 newacc = self.search(cr, uid, [('parent_id', 'in', newacc)]+args, limit=limit, context=context)
194                 account+=newacc
195         return self.name_get(cr, uid, account, context=context)
196
197 account_analytic_account()
198
199
200 class account_analytic_line(osv.osv):
201     _name = 'account.analytic.line'
202     _description = 'Analytic Line'
203
204     _columns = {
205         'name': fields.char('Description', size=256, required=True),
206         'date': fields.date('Date', required=True, select=1),
207         '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')),
208         'unit_amount': fields.float('Quantity', help='Specifies the amount of quantity to count.'),
209         'account_id': fields.many2one('account.analytic.account', 'Analytic Account', required=True, ondelete='cascade', select=True),
210         'user_id': fields.many2one('res.users', 'User'),
211         'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
212
213     }
214     _defaults = {
215         'date': time.strftime('%Y-%m-%d'),
216         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=c),
217         'amount': 0.00
218     }
219
220     _order = 'date desc'
221
222 account_analytic_line()
223
224 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: