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 ##############################################################################
24 from osv import fields, osv
25 from tools.translate import _
26 import decimal_precision as dp
28 class account_analytic_account(osv.osv):
29 _name = 'account.analytic.account'
30 _inherit = ['mail.thread']
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/Contract Name', size=128, required=True),
157 'complete_name': fields.function(_complete_name_calc, type='char', string='Full Account Name'),
158 'code': fields.char('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', required=True,
160 help="If you select the View Type, it means you won\'t allow to create journal entries using that account.\n"\
161 "The type 'Analytic account' stands for usual accounts that you only want to use in accounting.\n"\
162 "If you select Contract or Project, it offers you the possibility to manage the validity and the invoicing options for this account.\n"\
163 "The special type 'Template of Project' allows you to define a template with default data that you can reuse easily."),
164 'template_id': fields.many2one('account.analytic.account', 'Template of Contract'),
165 'description': fields.text('Description'),
166 'parent_id': fields.many2one('account.analytic.account', 'Parent Analytic Account', select=2),
167 'child_ids': fields.one2many('account.analytic.account', 'parent_id', 'Child Accounts'),
168 'child_complete_ids': fields.function(_child_compute, relation='account.analytic.account', string="Account Hierarchy", type='many2many'),
169 'line_ids': fields.one2many('account.analytic.line', 'account_id', 'Analytic Entries'),
170 'balance': fields.function(_debit_credit_bal_qtty, type='float', string='Balance', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
171 'debit': fields.function(_debit_credit_bal_qtty, type='float', string='Debit', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
172 'credit': fields.function(_debit_credit_bal_qtty, type='float', string='Credit', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
173 'quantity': fields.function(_debit_credit_bal_qtty, type='float', string='Quantity', multi='debit_credit_bal_qtty'),
174 'quantity_max': fields.float('Prepaid Units', help='Sets the higher limit of time to work on the contract.'),
175 'partner_id': fields.many2one('res.partner', 'Customer'),
176 'user_id': fields.many2one('res.users', 'Project Manager'),
177 'manager_id': fields.many2one('res.users', 'Account Manager'),
178 'date_start': fields.date('Date Start'),
179 'date': fields.date('Date End', select=True),
180 '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.
181 'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'), ('cancelled', 'Cancelled'),('pending','To Renew'),('close','Closed')], 'Status', required=True,),
182 'currency_id': fields.function(_currency, fnct_inv=_set_company_currency,
184 'res.company': (_get_analytic_account, ['currency_id'], 10),
185 }, string='Currency', type='many2one', relation='res.currency'),
188 def on_change_template(self, cr, uid, ids, template_id, context=None):
192 template = self.browse(cr, uid, template_id, context=context)
193 res['value']['date_start'] = template.date_start
194 res['value']['date'] = template.date
195 res['value']['quantity_max'] = template.quantity_max
196 res['value']['description'] = template.description
199 def on_change_partner_id(self, cr, uid, ids,partner_id, name, context={}):
202 partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
204 res['manager_id'] = partner.user_id.id
206 res['name'] = _('Contract: ') + partner.name
207 return {'value': res}
209 def _default_company(self, cr, uid, context=None):
210 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
212 return user.company_id.id
213 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
215 def _get_default_currency(self, cr, uid, context=None):
216 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
217 return user.company_id.currency_id.id
221 'company_id': _default_company,
222 'code' : lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'account.analytic.account'),
224 'user_id': lambda self, cr, uid, ctx: uid,
225 'partner_id': lambda self, cr, uid, ctx: ctx.get('partner_id', False),
226 'date_start': lambda *a: time.strftime('%Y-%m-%d'),
227 'currency_id': _get_default_currency,
230 def check_recursion(self, cr, uid, ids, context=None, parent=None):
231 return super(account_analytic_account, self)._check_recursion(cr, uid, ids, context=context, parent=parent)
235 (check_recursion, 'Error! You cannot create recursive analytic accounts.', ['parent_id']),
238 def copy(self, cr, uid, id, default=None, context=None):
241 default['code'] = False
242 default['line_ids'] = []
243 return super(account_analytic_account, self).copy(cr, uid, id, default, context=context)
245 def on_change_company(self, cr, uid, id, company_id):
248 currency = self.pool.get('res.company').read(cr, uid, [company_id], ['currency_id'])[0]['currency_id']
249 return {'value': {'currency_id': currency}}
251 def on_change_parent(self, cr, uid, id, parent_id):
254 parent = self.read(cr, uid, [parent_id], ['partner_id','code'])[0]
255 if parent['partner_id']:
256 partner = parent['partner_id'][0]
261 res['value']['partner_id'] = partner
264 def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
269 if context.get('current_model') == 'project.project':
270 cr.execute("select analytic_account_id from project_project")
271 project_ids = [x[0] for x in cr.fetchall()]
272 return self.name_get(cr, uid, project_ids, context=context)
274 account = self.search(cr, uid, [('code', '=', name)] + args, limit=limit, context=context)
276 names=map(lambda i : i.strip(),name.split('/'))
277 for i in range(len(names)):
278 dom=[('name', operator, names[i])]
280 dom+=[('id','child_of',account)]
281 account = self.search(cr, uid, dom, limit=limit, context=context)
284 newacc = self.search(cr, uid, [('parent_id', 'in', newacc)], limit=limit, context=context)
287 account = self.search(cr, uid, [('id', 'in', account)] + args, limit=limit, context=context)
289 account = self.search(cr, uid, args, limit=limit, context=context)
290 return self.name_get(cr, uid, account, context=context)
292 def create(self, cr, uid, vals, context=None):
293 contract = super(account_analytic_account, self).create(cr, uid, vals, context=context)
295 self.create_send_note(cr, uid, [contract], context=context)
298 def create_send_note(self, cr, uid, ids, context=None):
299 for obj in self.browse(cr, uid, ids, context=context):
300 self.message_post(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), context=context)
302 account_analytic_account()
305 class account_analytic_line(osv.osv):
306 _name = 'account.analytic.line'
307 _description = 'Analytic Line'
310 'name': fields.char('Description', size=256, required=True),
311 'date': fields.date('Date', required=True, select=True),
312 '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')),
313 'unit_amount': fields.float('Quantity', help='Specifies the amount of quantity to count.'),
314 'account_id': fields.many2one('account.analytic.account', 'Analytic Account', required=True, ondelete='cascade', select=True, domain=[('type','<>','view')]),
315 'user_id': fields.many2one('res.users', 'User'),
316 'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
320 'date': lambda *a: time.strftime('%Y-%m-%d'),
321 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=c),
327 def _check_no_view(self, cr, uid, ids, context=None):
328 analytic_lines = self.browse(cr, uid, ids, context=context)
329 for line in analytic_lines:
330 if line.account_id.type == 'view':
335 (_check_no_view, 'You cannot create analytic line on view account.', ['account_id']),
338 account_analytic_line()
340 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: