1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
23 from lxml import etree
25 from osv import fields, osv
26 from tools.translate import _
27 import decimal_precision as dp
29 class account_analytic_account(osv.osv):
30 _name = 'account.analytic.account'
31 _description = 'Analytic Account'
33 def _compute_level_tree(self, cr, uid, ids, child_ids, res, field_names, context=None):
34 currency_obj = self.pool.get('res.currency')
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]
45 for account in self.browse(cr, uid, ids, context=context):
46 if account.id not in child_ids:
48 recres[account.id] = recursive_computation(account)
51 def _debit_credit_bal_qtty(self, cr, uid, ids, fields, arg, context=None):
55 child_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
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']]
75 CASE WHEN l.amount > 0
81 CASE WHEN l.amount < 0
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)
91 """ + where_date + """
92 GROUP BY a.id""", where_clause_args)
93 for row in cr.dictfetchall():
96 res[row['id']][field] = row[field]
97 return self._compute_level_tree(cr, uid, ids, child_ids, res, fields, context)
99 def name_get(self, cr, uid, ids, context=None):
100 if isinstance(ids, (int, long)):
105 for account in self.browse(cr, uid, ids, context=context):
109 data.insert(0, acc.name)
111 data = ' / '.join(data)
112 res.append((account.id, data))
115 def _complete_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
116 res = self.name_get(cr, uid, ids)
119 def _child_compute(self, cr, uid, ids, name, arg, context=None):
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'])
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')
133 for company in company_obj.browse(cr, uid, ids, context=context):
134 accounts += analytic_obj.search(cr, uid, [('company_id', '=', company.id)])
137 def _set_company_currency(self, cr, uid, ids, name, value, arg, context=None):
138 if isinstance(ids, (int, long)):
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, ))
146 def _currency(self, cr, uid, ids, field_name, arg, context=None):
148 for rec in self.browse(cr, uid, ids, context=context):
150 result[rec.id] = rec.company_id.currency_id.id
152 result[rec.id] = rec.currency_id.id
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,
178 'res.company': (_get_analytic_account, ['currency_id'], 10),
179 }, string='Currency', type='many2one', relation='res.currency'),
182 def on_change_partner_id(self, cr, uid, ids,partner_id, name, context={}):
185 part = self.pool.get('res.partner').browse(cr, uid, partner_id,context=context)
187 res['name'] = part.name
188 if part.user_id:res['user_id'] = part.user_id.id
189 return {'value': res}
191 def _default_company(self, cr, uid, context=None):
192 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
194 return user.company_id.id
195 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
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
203 'company_id': _default_company,
204 'code' : lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'account.analytic.account'),
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,
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)
217 (check_recursion, 'Error! You can not create recursive analytic accounts.', ['parent_id']),
220 def copy(self, cr, uid, id, default=None, context=None):
223 default['code'] = False
224 default['line_ids'] = []
225 return super(account_analytic_account, self).copy(cr, uid, id, default, context=context)
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':
234 node.set('string', 'Contract/Project Name')
235 if context.get('default_type') == 'template':
237 node.set('string', 'Template/Project Name')
238 res['arch'] = etree.tostring(doc)
241 def on_change_company(self, cr, uid, id, company_id):
244 currency = self.pool.get('res.company').read(cr, uid, [company_id], ['currency_id'])[0]['currency_id']
245 return {'value': {'currency_id': currency}}
247 def on_change_parent(self, cr, uid, id, parent_id):
250 parent = self.read(cr, uid, [parent_id], ['partner_id','code'])[0]
251 if parent['partner_id']:
252 partner = parent['partner_id'][0]
257 res['value']['partner_id'] = partner
260 def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
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)
270 account = self.search(cr, uid, [('code', '=', name)] + args, limit=limit, context=context)
272 names=map(lambda i : i.strip(),name.split('/'))
273 for i in range(len(names)):
274 dom=[('name', operator, names[i])]
276 dom+=[('id','child_of',account)]
277 account = self.search(cr, uid, dom, limit=limit, context=context)
280 newacc = self.search(cr, uid, [('parent_id', 'in', newacc)], limit=limit, context=context)
283 account = self.search(cr, uid, [('id', 'in', account)] + args, limit=limit, context=context)
285 account = self.search(cr, uid, args, limit=limit, context=context)
286 return self.name_get(cr, uid, account, context=context)
288 account_analytic_account()
291 class account_analytic_line(osv.osv):
292 _name = 'account.analytic.line'
293 _description = 'Analytic Line'
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),
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),
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':
321 (_check_no_view, 'You can not create analytic line on view account.', ['account_id']),
324 account_analytic_line()
326 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: