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 _inherit = ['mail.thread']
32 _description = 'Analytic Account'
34 def _compute_level_tree(self, cr, uid, ids, child_ids, res, field_names, context=None):
35 currency_obj = self.pool.get('res.currency')
37 def recursive_computation(account):
38 result2 = res[account.id].copy()
39 for son in account.child_ids:
40 result = recursive_computation(son)
41 for field in field_names:
42 if (account.currency_id.id != son.currency_id.id) and (field!='quantity'):
43 result[field] = currency_obj.compute(cr, uid, son.currency_id.id, account.currency_id.id, result[field], context=context)
44 result2[field] += result[field]
46 for account in self.browse(cr, uid, ids, context=context):
47 if account.id not in child_ids:
49 recres[account.id] = recursive_computation(account)
52 def _debit_credit_bal_qtty(self, cr, uid, ids, fields, arg, context=None):
56 child_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
66 where_clause_args = [tuple(child_ids)]
67 if context.get('from_date', False):
68 where_date += " AND l.date >= %s"
69 where_clause_args += [context['from_date']]
70 if context.get('to_date', False):
71 where_date += " AND l.date <= %s"
72 where_clause_args += [context['to_date']]
76 CASE WHEN l.amount > 0
82 CASE WHEN l.amount < 0
87 COALESCE(SUM(l.amount),0) AS balance,
88 COALESCE(SUM(l.unit_amount),0) AS quantity
89 FROM account_analytic_account a
90 LEFT JOIN account_analytic_line l ON (a.id = l.account_id)
92 """ + where_date + """
93 GROUP BY a.id""", where_clause_args)
94 for row in cr.dictfetchall():
97 res[row['id']][field] = row[field]
98 return self._compute_level_tree(cr, uid, ids, child_ids, res, fields, context)
100 def name_get(self, cr, uid, ids, context=None):
101 if isinstance(ids, (int, long)):
106 for account in self.browse(cr, uid, ids, context=context):
110 data.insert(0, acc.name)
112 data = ' / '.join(data)
113 res.append((account.id, data))
116 def _complete_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
117 res = self.name_get(cr, uid, ids)
120 def _child_compute(self, cr, uid, ids, name, arg, context=None):
125 for account in self.browse(cr, uid, ids, context=context):
126 result[account.id] = map(lambda x: x.id, [child for child in account.child_ids if child.state != 'template'])
130 def _get_analytic_account(self, cr, uid, ids, context=None):
131 company_obj = self.pool.get('res.company')
132 analytic_obj = self.pool.get('account.analytic.account')
134 for company in company_obj.browse(cr, uid, ids, context=context):
135 accounts += analytic_obj.search(cr, uid, [('company_id', '=', company.id)])
138 def _set_company_currency(self, cr, uid, ids, name, value, arg, context=None):
139 if isinstance(ids, (int, long)):
141 for account in self.browse(cr, uid, ids, context=context):
142 if account.company_id:
143 if account.company_id.currency_id.id != value:
144 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."))
145 return cr.execute("""update account_analytic_account set currency_id=%s where id=%s""", (value, account.id, ))
147 def _currency(self, cr, uid, ids, field_name, arg, context=None):
149 for rec in self.browse(cr, uid, ids, context=context):
151 result[rec.id] = rec.company_id.currency_id.id
153 result[rec.id] = rec.currency_id.id
157 'name': fields.char('Account Name', size=128, required=True),
158 'complete_name': fields.function(_complete_name_calc, type='char', string='Full Account Name'),
159 'code': fields.char('Code/Reference', size=24, select=True),
160 '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.'),
161 'description': fields.text('Description'),
162 'parent_id': fields.many2one('account.analytic.account', 'Parent Analytic Account', select=2),
163 'child_ids': fields.one2many('account.analytic.account', 'parent_id', 'Child Accounts'),
164 'child_complete_ids': fields.function(_child_compute, relation='account.analytic.account', string="Account Hierarchy", type='many2many'),
165 'line_ids': fields.one2many('account.analytic.line', 'account_id', 'Analytic Entries'),
166 'balance': fields.function(_debit_credit_bal_qtty, type='float', string='Balance', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
167 'debit': fields.function(_debit_credit_bal_qtty, type='float', string='Debit', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
168 'credit': fields.function(_debit_credit_bal_qtty, type='float', string='Credit', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
169 'quantity': fields.function(_debit_credit_bal_qtty, type='float', string='Quantity', multi='debit_credit_bal_qtty'),
170 'quantity_max': fields.float('Maximum Time', help='Sets the higher limit of time to work on the contract.'),
171 'partner_id': fields.many2one('res.partner', 'Customer'),
172 'user_id': fields.many2one('res.users', 'Project Manager'),
173 'manager_id': fields.many2one('res.users', 'Account Manager'),
174 'date_start': fields.date('Date Start'),
175 'date': fields.date('Date End', select=True),
176 '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.
177 'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'), ('cancelled', 'Cancelled'),('pending','To Renew'),('close','Closed')], 'Status', required=True,),
178 'currency_id': fields.function(_currency, fnct_inv=_set_company_currency,
180 'res.company': (_get_analytic_account, ['currency_id'], 10),
181 }, string='Currency', type='many2one', relation='res.currency'),
184 def on_change_partner_id(self, cr, uid, ids,partner_id, name, context={}):
187 part = self.pool.get('res.partner').browse(cr, uid, partner_id,context=context)
188 if part.user_id:res['manager_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 if context is None:context = {}
230 res = super(account_analytic_account, self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
232 doc = etree.XML(res['arch'])
234 if view_type == 'form':node = doc.xpath("//field[@name='name']")
235 if view_type == 'tree':node = doc.xpath("//field[@name='complete_name']")
238 if context.get('default_type') == 'contract':
239 curr_node.set('string', 'Contract/Project Name')
240 curr_node.set('placeholder', 'Contract or Project Name')
241 elif context.get('default_type') == 'template':
242 curr_node.set('string', 'Template Name')
243 curr_node.set('placeholder', 'Template Name')
245 curr_node.set('string', 'Account Name')
246 curr_node.set('placeholder', 'Account Name')
247 res['arch'] = etree.tostring(doc)
250 def on_change_company(self, cr, uid, id, company_id):
253 currency = self.pool.get('res.company').read(cr, uid, [company_id], ['currency_id'])[0]['currency_id']
254 return {'value': {'currency_id': currency}}
256 def on_change_parent(self, cr, uid, id, parent_id):
259 parent = self.read(cr, uid, [parent_id], ['partner_id','code'])[0]
260 if parent['partner_id']:
261 partner = parent['partner_id'][0]
266 res['value']['partner_id'] = partner
269 def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
274 if context.get('current_model') == 'project.project':
275 cr.execute("select analytic_account_id from project_project")
276 project_ids = [x[0] for x in cr.fetchall()]
277 return self.name_get(cr, uid, project_ids, context=context)
279 account = self.search(cr, uid, [('code', '=', name)] + args, limit=limit, context=context)
281 names=map(lambda i : i.strip(),name.split('/'))
282 for i in range(len(names)):
283 dom=[('name', operator, names[i])]
285 dom+=[('id','child_of',account)]
286 account = self.search(cr, uid, dom, limit=limit, context=context)
289 newacc = self.search(cr, uid, [('parent_id', 'in', newacc)], limit=limit, context=context)
292 account = self.search(cr, uid, [('id', 'in', account)] + args, limit=limit, context=context)
294 account = self.search(cr, uid, args, limit=limit, context=context)
295 return self.name_get(cr, uid, account, context=context)
297 account_analytic_account()
300 class account_analytic_line(osv.osv):
301 _name = 'account.analytic.line'
302 _description = 'Analytic Line'
305 'name': fields.char('Description', size=256, required=True),
306 'date': fields.date('Date', required=True, select=True),
307 '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')),
308 'unit_amount': fields.float('Quantity', help='Specifies the amount of quantity to count.'),
309 'account_id': fields.many2one('account.analytic.account', 'Analytic Account', required=True, ondelete='cascade', select=True, domain=[('type','<>','view')]),
310 'user_id': fields.many2one('res.users', 'User'),
311 'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
315 'date': lambda *a: time.strftime('%Y-%m-%d'),
316 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=c),
322 def _check_no_view(self, cr, uid, ids, context=None):
323 analytic_lines = self.browse(cr, uid, ids, context=context)
324 for line in analytic_lines:
325 if line.account_id.type == 'view':
330 (_check_no_view, 'You can not create analytic line on view account.', ['account_id']),
333 account_analytic_line()
335 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: