@@ -91,7 +91,7 @@
|
||||
<separator string="Supplier Debit" colspan="2"/>
|
||||
<field name="debit" select="2"/>
|
||||
</group>
|
||||
<field colspan="4" context="address=address" name="bank_ids" nolabel="1" select="2">
|
||||
<field colspan="4" context="{'address': address}" name="bank_ids" nolabel="1" select="2">
|
||||
<form string="Bank account">
|
||||
<field name="state" select="2"/>
|
||||
<newline/>
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
- |
|
||||
Tax Scenario:
|
||||
In order to check that the accounting entries and tax engine works
|
||||
efficiently with all kind of price accuracies we run this test scenario.
|
||||
-
|
||||
Given the price accuracy for the account module is configured with two
|
||||
digits
|
||||
-
|
||||
!record {model: decimal.precision, id: product.decimal_account}:
|
||||
digits: 2
|
||||
-
|
||||
And we have a tax defined
|
||||
-
|
||||
!record {model: account.tax, id: tax196}:
|
||||
name: Tax 19.6%
|
||||
amount: &tax 0.196
|
||||
-
|
||||
And we define an invoice with one invoice line with a tax of *tax
|
||||
-
|
||||
!record {model: account.invoice, id: invoice1}: &invoice
|
||||
name: Precision Test
|
||||
type: out_invoice
|
||||
partner_id: base.xxx
|
||||
address_contact_id:
|
||||
address_invoice_id:
|
||||
account_id:
|
||||
invoice_lines:
|
||||
- product_qty: &qty 11.11
|
||||
product_uom: product.product_unit
|
||||
price_unit: &price 12.34
|
||||
-
|
||||
When I press on the CREATE button on the invoice to open it.
|
||||
-
|
||||
# to be completed
|
||||
-
|
||||
Then I should see an account entry linked to this invoice.
|
||||
-
|
||||
!record {model: account.invoice, id: invoice1}:
|
||||
test: bool(move_id)
|
||||
-
|
||||
# This is not working, find a way to do that in YAML, *qty is not
|
||||
# interpreted because it's not a node.
|
||||
And this account entry must have a credit equals to 163.70€ which is
|
||||
equal to *qty x *price x (1 + *tax)
|
||||
-
|
||||
!record {model: account.invoice, id: invoice1}:
|
||||
test: abs(move_id.credit - 163.70) < 0.000001
|
||||
-
|
||||
And this account entry must have a credit equals to the debit
|
||||
-
|
||||
!record {model: account.invoice, id: invoice1}:
|
||||
test: abs(move_id.debit - move_id.credit) < 0.000001
|
||||
-
|
||||
Then, I change the precision of numbers to 3 digits.
|
||||
-
|
||||
!record {model: decimal.precision, id: product.decimal_account}:
|
||||
digits: 3
|
||||
-
|
||||
And I create another invoice with the same content.
|
||||
-
|
||||
!record {model: account.invoice, id: invoice2}:
|
||||
*invoice
|
||||
-
|
||||
to be completed, etc.
|
||||
@@ -0,0 +1,92 @@
|
||||
- |
|
||||
Tax Scenario:
|
||||
In order to check that the accounting entries and tax engine works
|
||||
efficiently with all kind of price accuracies we run this test scenario.
|
||||
-
|
||||
Given the price accuracy for the account module is configured with two
|
||||
digits
|
||||
-
|
||||
!record {model: decimal.precision, id: product.decimal_account}:
|
||||
digits: 2
|
||||
-
|
||||
And we have a tax defined
|
||||
-
|
||||
!record {model: account.tax, id: tax196}:
|
||||
name: Tax 19.6%
|
||||
amount: &tax 0.196
|
||||
account_collected_id: account.a_recv
|
||||
account_paid_id: account.a_recv
|
||||
-
|
||||
And we define an invoice with one invoice line with a tax of *tax
|
||||
-
|
||||
!record {model: account.invoice, id: invoice1}: &invoice
|
||||
name: Precision Test
|
||||
type: out_invoice
|
||||
partner_id: base.res_partner_2
|
||||
address_invoice_id: base.res_partner_address_1
|
||||
account_id: account.a_recv
|
||||
date_invoice: !eval time.strftime('%Y-%m-%d')
|
||||
invoice_line:
|
||||
- product_id: product.product_product_pc1
|
||||
quantity: &qty 11.11
|
||||
account_id: account.a_sale
|
||||
uos_id: product.product_uom_unit
|
||||
price_unit: &price 12.34
|
||||
name: test
|
||||
invoice_line_tax_id:
|
||||
- tax196
|
||||
-
|
||||
When I press on the CREATE button on the invoice to open it.
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
self.action_move_create(cr, uid, [ref("invoice1")])
|
||||
-
|
||||
Then I should see an account entry linked to this invoice.
|
||||
-
|
||||
!assert {model: account.invoice, id: invoice1}:
|
||||
test:
|
||||
- bool(move_id)
|
||||
-
|
||||
# This is not working, find a way to do that in YAML, *qty is not
|
||||
# interpreted because it's not a node.
|
||||
And this account entry must have a credit equals to 163.70€ which is
|
||||
equal to *qty x *price x (1 + *tax)
|
||||
-
|
||||
!assert {model: account.invoice, id: invoice1}:
|
||||
test:
|
||||
- abs(sum(x.credit for x in move_id.line_id) - 163.97) < 0.000001
|
||||
-
|
||||
And this account entry must have a credit equals to the debit
|
||||
-
|
||||
!assert {model: account.invoice, id: invoice1}:
|
||||
test:
|
||||
- abs(sum(x.credit - x.debit for x in move_id.line_id)) < 0.000001
|
||||
-
|
||||
Then, I change the precision of numbers to 3 digits.
|
||||
-
|
||||
!record {model: decimal.precision, id: product.decimal_account}:
|
||||
digits: 3
|
||||
-
|
||||
And I create another invoice with the same content.
|
||||
-
|
||||
!record {model: account.invoice, id: invoice2}:
|
||||
*invoice
|
||||
-
|
||||
When I press on the CREATE button on the invoice to open it.
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
self.action_move_create(cr, uid, [ref("invoice2")])
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
invoice = self.browse(cr, uid, ref("invoice2"))
|
||||
log(sum(x.credit for x in invoice.move_id.line_id))
|
||||
assert (sum(x.credit for x in invoice.move_id.line_id) - 163.968) < 0.000001, "Pas bon !!!"
|
||||
-
|
||||
!assert {model: account.invoice, id: invoice2}:
|
||||
test:
|
||||
- abs(sum(x.credit for x in move_id.line_id) - 163.968) < 0.000001
|
||||
-
|
||||
!assert {model: account.invoice, id: invoice2}:
|
||||
test:
|
||||
- abs(sum(x.credit - x.debit for x in move_id.line_id)) < 0.000001
|
||||
|
||||
+4
-6
@@ -295,9 +295,8 @@ class crm_case(osv.osv):
|
||||
section = (case.section_id.id or False)
|
||||
if section in s:
|
||||
st = case.stage_id.id or False
|
||||
if st in s[section]:
|
||||
stage_value = self.pool.get('crm.case.stage').read(cr, uid,s[section][st] , ['probability'], context)
|
||||
self.write(cr, uid, [case.id], {'stage_id': s[section][st],'probability':stage_value['probability']})
|
||||
if st in s[section]:
|
||||
self.write(cr, uid, [case.id], {'stage_id': s[section][st]})
|
||||
return True
|
||||
|
||||
def get_stage_dict(self, cr, uid, ids, context={}):
|
||||
@@ -318,9 +317,8 @@ class crm_case(osv.osv):
|
||||
if section in s:
|
||||
st = case.stage_id.id or False
|
||||
s[section] = dict([(v, k) for (k, v) in s[section].iteritems()])
|
||||
if st in s[section]:
|
||||
stage_value = self.pool.get('crm.case.stage').read(cr, uid,s[section][st] ,['probability'], context)
|
||||
self.write(cr, uid, [case.id], {'stage_id': s[section][st],'probability':stage_value['probability']})
|
||||
if st in s[section]:
|
||||
self.write(cr, uid, [case.id], {'stage_id': s[section][st]})
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<separator string="Links" colspan="4" col="3"/>
|
||||
<field name="partner_id"/>
|
||||
<button
|
||||
name="%(action_crm_lead2partner_create)d"
|
||||
name="%(wizard_crm_lead_partner_create)d"
|
||||
icon="gtk-index" type="action"
|
||||
string="Create"
|
||||
attrs="{'invisible':[('partner_id','!=',False)]}"/>
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- <wizard-->
|
||||
<!-- id="wizard_crm_lead_partner_create"-->
|
||||
<!-- keyword="client_action_multi"-->
|
||||
<!-- model="crm.lead"-->
|
||||
<!-- name="crm.lead.partner_create"-->
|
||||
<!-- multi="True"-->
|
||||
<!-- string="Create Partner"/>-->
|
||||
<wizard
|
||||
id="wizard_crm_lead_partner_create"
|
||||
keyword="client_action_multi"
|
||||
model="crm.lead"
|
||||
name="crm.lead.partner_create"
|
||||
multi="True"
|
||||
string="Create Partner"/>
|
||||
|
||||
<wizard
|
||||
id="wizard_crm_lead_opportunity_set"
|
||||
|
||||
@@ -61,6 +61,20 @@ class crm_opportunity(osv.osv):
|
||||
return {'value':{}}
|
||||
return {'value':{'probability':stage.probability}}
|
||||
|
||||
def stage_next(self, cr, uid, ids, context={}):
|
||||
res = super(crm_opportunity, self).stage_next(cr, uid, ids, context=context)
|
||||
for case in self.browse(cr, uid, ids, context):
|
||||
if case.stage_id and case.stage_id.on_change:
|
||||
self.write(cr, uid, [case.id], {'probability': case.stage_id.probability})
|
||||
return res
|
||||
|
||||
def stage_previous(self, cr, uid, ids, context={}):
|
||||
res = super(crm_opportunity, self).stage_previous(cr, uid, ids, context=context)
|
||||
for case in self.browse(cr, uid, ids, context):
|
||||
if case.stage_id and case.stage_id.on_change:
|
||||
self.write(cr, uid, [case.id], {'probability': case.stage_id.probability})
|
||||
return res
|
||||
|
||||
_defaults = {
|
||||
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.opportunity', context=c),
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
############################################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
|
||||
# Copyright (C) 2008-2009 AJM Technologies S.A. (<http://www.ajm.lu). All Rights Reserved
|
||||
# $Id$
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
############################################################################################
|
||||
############################################################################################
|
||||
|
||||
from osv import osv, fields
|
||||
import netsvc
|
||||
import time
|
||||
import tools
|
||||
import mx.DateTime
|
||||
from tools import config
|
||||
from tools.translate import _
|
||||
import tools
|
||||
|
||||
class crm_phonecall2phonecall(osv.osv_memory):
|
||||
_name = 'crm.phonecall2phonecall'
|
||||
_description = 'Phonecall To Phonecall'
|
||||
|
||||
def action_cancel(self, cr, uid, ids, context=None):
|
||||
return {'type':'ir.actions.act_window_close'}
|
||||
|
||||
def action_apply(self, cr, uid, ids, context=None):
|
||||
this = self.browse(cr, uid, ids)[0]
|
||||
values={}
|
||||
record_id = context and context.get('record_id', False) or False
|
||||
if record_id:
|
||||
for case in self.pool.get('crm.phonecall').browse(cr, uid, [record_id], context=context):
|
||||
values['name']=this.name
|
||||
values['user_id']=this.user_id and this.user_id.id
|
||||
values['categ_id']=case.categ_id and case.categ_id.id or False
|
||||
values['section_id']=case.section_id and case.section_id.id or False,
|
||||
values['description']=case.description or ''
|
||||
values['partner_id']=case.partner_id.id
|
||||
values['partner_address_id']=case.partner_address_id.id
|
||||
values['partner_mobile']=case.partner_mobile or False
|
||||
values['priority']=case.priority
|
||||
values['partner_phone']=case.partner_phone or False
|
||||
values['date']=this.date
|
||||
phonecall_proxy = self.pool.get('crm.phonecall')
|
||||
phonecall_id = phonecall_proxy.create(cr, uid, values, context=context)
|
||||
value = {
|
||||
'name': _('Phone Call'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'crm.phonecall',
|
||||
'view_id': False,
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_id': phonecall_id
|
||||
}
|
||||
return value
|
||||
|
||||
_columns = {
|
||||
'name' : fields.char('Call summary', size=64, required=True, select=1),
|
||||
'user_id' : fields.many2one('res.users',"Assign To"),
|
||||
'date': fields.datetime('Date'),
|
||||
'section_id':fields.many2one('crm.case.section','Sales Team'),
|
||||
|
||||
}
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
record_id = context and context.get('record_id', False) or False
|
||||
res = super(crm_phonecall2phonecall, self).default_get(cr, uid, fields, context=context)
|
||||
if record_id:
|
||||
phonecall_id = self.pool.get('crm.phonecall').browse(cr, uid, record_id, context=context)
|
||||
res['name']=phonecall_id.name
|
||||
res['user_id']=phonecall_id.user_id and phonecall_id.user_id.id or False
|
||||
res['date']=phonecall_id.date
|
||||
res['section_id']=phonecall_id.section_id and phonecall_id.section_id.id or False
|
||||
return res
|
||||
crm_phonecall2phonecall()
|
||||
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="phonecall_to_phonecall_view">
|
||||
<field name="name">crm.phonecall2phonecall.form</field>
|
||||
<field name="model">crm.phonecall2phonecall</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Schedule Other Call" col="2">
|
||||
<field name="name"/>
|
||||
<field name="user_id" />
|
||||
<field name="date" string="Planned Date"/>
|
||||
<field name="section_id"/>
|
||||
<group colspan="2" col="2" >
|
||||
<button name="action_cancel" string="_Cancel" icon="gtk-cancel" special="cancel" />
|
||||
<button name="action_apply" type="object" string="_Apply" icon="gtk-apply" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.actions.act_window" id="phonecall_to_phonecall_act">
|
||||
<field name="name">Schedule Other Call</field>
|
||||
<field name="res_model">crm.phonecall2phonecall</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="phonecall_to_phonecall_view"/>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'record_id' : active_id}</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -1,143 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from mx.DateTime import now
|
||||
|
||||
from osv import osv, fields
|
||||
import netsvc
|
||||
import ir
|
||||
import pooler
|
||||
from tools.translate import _
|
||||
|
||||
class crm_lead2partner(osv.osv_memory):
|
||||
_name = 'crm.lead2partner'
|
||||
_description = 'Lead to Partner'
|
||||
|
||||
_columns = {
|
||||
'action': fields.selection([('exist','Link to an existing partner'),('create','Create a new partner')],'Action',required=True),
|
||||
'partner_id': fields.many2one('res.partner','Partner'),
|
||||
}
|
||||
_defaults = {
|
||||
'action': lambda *a:'exist',
|
||||
}
|
||||
|
||||
def create_partner(self, cr, uid, ids, context):
|
||||
view_obj = self.pool.get('ir.ui.view')
|
||||
view_id = view_obj.search(cr,uid,[('model','=','crm.lead2partner'),('name','=','crm.lead2partner.view')])
|
||||
return {
|
||||
'view_mode': 'form',
|
||||
'view_type': 'form',
|
||||
'view_id': view_id or False,
|
||||
'res_model': 'crm.lead2partner',
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
def selectPartner(self, cr, uid, ids, context):
|
||||
case_obj = self.pool.get('crm.lead')
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
contact_obj = self.pool.get('res.partner.address')
|
||||
rec_ids = context and context.get('record_ids',False)
|
||||
value={}
|
||||
for case in case_obj.browse(cr, uid, rec_ids):
|
||||
if case.partner_id:
|
||||
raise osv.except_osv(_('Warning !'),
|
||||
_('A partner is already defined on this lead.'))
|
||||
|
||||
partner_ids = partner_obj.search(cr, uid, [('name', '=', case.partner_name or case.name)])
|
||||
if not partner_ids and case.email_from:
|
||||
address_ids = contact_obj.search(cr, uid, [('email', '=', case.email_from)])
|
||||
if address_ids:
|
||||
addresses = contact_obj.browse(cr, uid, address_ids)
|
||||
partner_ids = addresses and [addresses[0].partner_id.id] or False
|
||||
|
||||
partner_id = partner_ids and partner_ids[0] or False
|
||||
if not partner_id:
|
||||
value = self._make_partner(cr, uid, ids, context)
|
||||
return value
|
||||
|
||||
def _create_partner(self, cr, uid, ids, context):
|
||||
case_obj = self.pool.get('crm.lead')
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
contact_obj = self.pool.get('res.partner.address')
|
||||
datas = self.browse(cr, uid, ids)[0]
|
||||
partner_ids = []
|
||||
partner_id = False
|
||||
contact_id = False
|
||||
rec_ids = context and context.get('record_ids',False)
|
||||
for case in case_obj.browse(cr, uid, rec_ids):
|
||||
if datas.action == 'create':
|
||||
partner_id = partner_obj.create(cr, uid, {
|
||||
'name': case.partner_name or case.name,
|
||||
'user_id': case.user_id.id,
|
||||
'comment': case.description,
|
||||
})
|
||||
contact_id = contact_obj.create(cr, uid, {
|
||||
'partner_id': partner_id,
|
||||
'name': case.name,
|
||||
'phone': case.phone,
|
||||
'mobile': case.mobile,
|
||||
'email': case.email_from,
|
||||
'fax': case.fax,
|
||||
'title': case.title,
|
||||
'function': case.function and case.function.id or False,
|
||||
'street': case.street,
|
||||
'street2': case.street2,
|
||||
'zip': case.zip,
|
||||
'city': case.city,
|
||||
'country_id': case.country_id and case.country_id.id or False,
|
||||
'state_id': case.state_id and case.state_id.id or False,
|
||||
})
|
||||
|
||||
else:
|
||||
if datas.partner_id:
|
||||
partner = partner_obj.browse(cr,uid,datas.partner_id.id)
|
||||
partner_id = partner.id
|
||||
contact_id = partner.address and partner.address[0].id
|
||||
|
||||
partner_ids.append(partner_id)
|
||||
vals = {}
|
||||
if partner_id:
|
||||
vals.update({'partner_id': partner_id})
|
||||
if contact_id:
|
||||
vals.update({'partner_address_id': contact_id})
|
||||
case_obj.write(cr, uid, [case.id], vals)
|
||||
return partner_ids
|
||||
|
||||
def _make_partner(self, cr, uid, ids, context):
|
||||
partner_ids = self._create_partner(cr, uid, ids, context)
|
||||
mod_obj = self.pool.get('ir.model.data')
|
||||
result = mod_obj._get_id(cr, uid, 'base', 'view_res_partner_filter')
|
||||
res = mod_obj.read(cr, uid, result, ['res_id'])
|
||||
value = {
|
||||
'domain': "[]",
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form,tree',
|
||||
'res_model': 'res.partner',
|
||||
'res_id': partner_ids and int(partner_ids[0]) or False,
|
||||
'view_id': False,
|
||||
'type': 'ir.actions.act_window',
|
||||
'search_view_id': res['res_id']
|
||||
}
|
||||
return value
|
||||
|
||||
crm_lead2partner()
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="view_crm_lead2partner_create" model="ir.ui.view">
|
||||
<field name="name">crm.lead2partner.view.create</field>
|
||||
<field name="model">crm.lead2partner</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Create a Partner">
|
||||
<label string="Are you sure you want to create a partner based on this lead ?" colspan="4"/>
|
||||
<label string="You may have to verify that this partner does not exist already." colspan="4"/>
|
||||
<group col="4" colspan="4">
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="create_partner" string="Create Partner" type="object" icon="gtk-ok"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_crm_lead2partner_create" model="ir.actions.act_window">
|
||||
<field name="name">Create a Partner</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">crm.lead2partner</field>
|
||||
<field name="view_id" ref="view_crm_lead2partner_create"/>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'record_ids' : active_ids}</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.values" id="values_crm_lead2partner_create">
|
||||
<field name="model_id" ref="crm.model_crm_lead" />
|
||||
<field name="object" eval="1" />
|
||||
<field name="name">Create Partner</field>
|
||||
<field name="key2">client_action_multi</field>
|
||||
<field name="value" eval="'ir.actions.act_window,' + str(ref('action_crm_lead2partner_create'))" />
|
||||
<field name="key">action</field>
|
||||
<field name="model">crm.lead</field>
|
||||
</record>
|
||||
|
||||
<record id="view_crm_lead2partner" model="ir.ui.view">
|
||||
<field name="name">crm.lead2partner.view</field>
|
||||
<field name="model">crm.lead2partner</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Create a Partner">
|
||||
<field name="action"/>
|
||||
<group attrs="{'invisible':[('action','!=','exist')]}">
|
||||
<field name="partner_id"/>
|
||||
</group>
|
||||
<group col="4" colspan="4">
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="selectPartner" string="Continue" type="object" icon="gtk-ok"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_crm_lead2partner" model="ir.actions.act_window">
|
||||
<field name="name">Create a Partner</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">crm.lead2partner</field>
|
||||
<field name="view_id" ref="view_crm_lead2partner"/>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'record_ids' : active_ids}</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -338,7 +338,7 @@
|
||||
</record>
|
||||
<menuitem name="Reporting" id="menu_report_event" parent="menu_marketing_event_main" sequence="20"/>
|
||||
<!--<menuitem parent="menu_report_event" action="action_event_registration" id="menu_report_event_registration"/>-->
|
||||
<menuitem parent="menu_event_main" action="action_event_registration" id="menu_report_event_registration" />
|
||||
<menuitem parent="menu_report_event" action="action_event_registration" id="menu_report_event_registration" />
|
||||
<!-- end... -->
|
||||
|
||||
|
||||
|
||||
@@ -482,7 +482,7 @@ class hr_attendance(osv.osv):
|
||||
res[line_id] = False
|
||||
return res
|
||||
|
||||
def _sheet_search(self, cursor, user, obj, name, args):
|
||||
def _sheet_search(self, cursor, user, obj, name, args, context={}):
|
||||
if not len(args):
|
||||
return []
|
||||
sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
|
||||
@@ -525,9 +525,11 @@ class hr_attendance(osv.osv):
|
||||
LEFT JOIN (hr_attendance a \
|
||||
LEFT JOIN hr_employee e \
|
||||
ON (a.employee_id = e.id)) \
|
||||
LEFT JOIN resource_resource r \
|
||||
ON (e.resource_id = r.id) \
|
||||
ON (s.date_to >= date_trunc(\'day\',a.name) \
|
||||
AND s.date_from <= a.name \
|
||||
AND s.user_id = e.user_id) ' + \
|
||||
AND s.user_id = r.user_id) ' + \
|
||||
qu1, qu2)
|
||||
res = cursor.fetchall()
|
||||
if not len(res):
|
||||
|
||||
@@ -214,6 +214,11 @@
|
||||
help="My Departments Timesheet"/>
|
||||
</field>
|
||||
</group>
|
||||
<newline/>
|
||||
<group expand="1" string="Group By..." colspan="4" col="20">
|
||||
<filter string="Users" icon="terp-project" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Department" icon="terp-project" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import html_view
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name" : "Html View",
|
||||
"version" : "1.1",
|
||||
"author" : "Tiny",
|
||||
"category" : "Generic Modules/Inventory Control",
|
||||
"depends" : ['base'],
|
||||
"init_xml" : ['html_view.xml'],
|
||||
"demo_xml" : [],
|
||||
"description": """
|
||||
This is the test module which shows html tag supports in normal xml form view.
|
||||
""",
|
||||
'update_xml': ['html_view.xml'],
|
||||
'installable': True,
|
||||
'active': False,
|
||||
'certificate': '',
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
from osv import osv, fields
|
||||
|
||||
class html_view(osv.osv):
|
||||
_name = 'html.view'
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=128, required=True, select=True),
|
||||
'comp_id': fields.many2one('res.company', 'Company', select=1),
|
||||
'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'),
|
||||
}
|
||||
|
||||
html_view()
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="html_view_form" model="ir.ui.view">
|
||||
<field name="name">htmlform</field>
|
||||
<field name="model">html.view</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Html Example">
|
||||
<html>
|
||||
<h2>This is Test Html Page </h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Name :
|
||||
</td>
|
||||
<td>
|
||||
<field name="name"/>
|
||||
</td>
|
||||
<td>
|
||||
Company :
|
||||
</td>
|
||||
<td>
|
||||
<field name="comp_id"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<field name="bank_ids"/>
|
||||
</html>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_html_view_form" model="ir.actions.act_window">
|
||||
<field name="name">Html Test</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">html.view</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
<menuitem action="action_html_view_form" id="html_form" parent="base.menu_address_book" sequence="20"/>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -26,7 +26,7 @@
|
||||
"author" : "Tiny",
|
||||
"website" : "http://www.openerp.com",
|
||||
"category" : "Generic Modules/Projects & Services",
|
||||
"depends" : ["project","resource"],
|
||||
"depends" : ["project", "resource"],
|
||||
"description": """Long Term Project management module that track planning, scheduling, resources allocation.
|
||||
""",
|
||||
"init_xml" : [],
|
||||
@@ -35,7 +35,7 @@
|
||||
#"security/ir.model.access.csv",
|
||||
"project_wizard.xml" ,
|
||||
"project_view.xml",
|
||||
"project_phase_workflow.xml"
|
||||
"project_workflow.xml"
|
||||
],
|
||||
'installable': True,
|
||||
'active': False,
|
||||
|
||||
+134
-133
@@ -30,25 +30,21 @@ class project_phase(osv.osv):
|
||||
_name = "project.phase"
|
||||
_description = "Project Phase"
|
||||
|
||||
|
||||
def _check_recursion(self,cr,uid,ids):
|
||||
def _check_recursion(self, cr, uid, ids):
|
||||
obj_self = self.browse(cr, uid, ids[0])
|
||||
prev_ids = obj_self.previous_phase_ids
|
||||
next_ids = obj_self.next_phase_ids
|
||||
#it should nither be in prev_ids nor in next_ids
|
||||
# it should nither be in prev_ids nor in next_ids
|
||||
if (obj_self in prev_ids) or (obj_self in next_ids):
|
||||
return False
|
||||
ids = [id for id in prev_ids if id in next_ids]
|
||||
|
||||
#both prev_ids and next_ids must be unique
|
||||
# both prev_ids and next_ids must be unique
|
||||
if ids:
|
||||
return False
|
||||
#unrelated project
|
||||
|
||||
# unrelated project
|
||||
prev_ids = [rec.id for rec in prev_ids]
|
||||
next_ids = [rec.id for rec in next_ids]
|
||||
|
||||
#iter prev_ids
|
||||
# iter prev_ids
|
||||
while prev_ids:
|
||||
cr.execute('select distinct prv_phase_id from project_phase_rel where next_phase_id in ('+','.join(map(str, prev_ids))+')')
|
||||
prv_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
|
||||
@@ -58,8 +54,7 @@ class project_phase(osv.osv):
|
||||
if ids:
|
||||
return False
|
||||
prev_ids = prv_phase_ids
|
||||
|
||||
#iter next_ids
|
||||
# iter next_ids
|
||||
while next_ids:
|
||||
cr.execute('select distinct next_phase_id from project_phase_rel where prv_phase_id in ('+','.join(map(str, next_ids))+')')
|
||||
next_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
|
||||
@@ -72,21 +67,21 @@ class project_phase(osv.osv):
|
||||
return True
|
||||
|
||||
def _check_dates(self, cr, uid, ids):
|
||||
phase = self.read(cr, uid, ids[0],['date_start','date_end'])
|
||||
phase = self.read(cr, uid, ids[0], ['date_start', 'date_end'])
|
||||
if phase['date_start'] and phase['date_end']:
|
||||
if phase['date_start'] > phase['date_end']:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _check_constraint_start(self, cr, uid, ids):
|
||||
phase = self.read(cr, uid, ids[0],['date_start','constraint_date_start'])
|
||||
phase = self.read(cr, uid, ids[0], ['date_start', 'constraint_date_start'])
|
||||
if phase['date_start'] and phase['constraint_date_start']:
|
||||
if phase['date_start'] < phase['constraint_date_start']:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _check_constraint_end(self, cr, uid, ids):
|
||||
phase = self.read(cr, uid, ids[0],['date_end','constraint_date_end'])
|
||||
phase = self.read(cr, uid, ids[0], ['date_end', 'constraint_date_end'])
|
||||
if phase['date_end'] and phase['constraint_date_end']:
|
||||
if phase['date_end'] > phase['constraint_date_end']:
|
||||
return False
|
||||
@@ -102,74 +97,76 @@ class project_phase(osv.osv):
|
||||
'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases'),
|
||||
'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases'),
|
||||
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of phases."),
|
||||
'duration': fields.float('Duration',required=True),
|
||||
'product_uom': fields.many2one('product.uom', 'Duration UoM',required=True, help="UoM (Unit of Measure) is the unit of measurement for Duration"),
|
||||
'duration': fields.float('Duration', required=True),
|
||||
'product_uom': fields.many2one('product.uom', 'Duration UoM', required=True, help="UoM (Unit of Measure) is the unit of measurement for Duration"),
|
||||
'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks"),
|
||||
'resource_ids': fields.one2many('project.resource.allocation', 'phase_id', "Project Resources"),
|
||||
'responsible_id':fields.many2one('res.users', 'Responsible'),
|
||||
'state': fields.selection([('draft', 'Draft'),('open', 'In Progress'),('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True,
|
||||
'state': fields.selection([('draft', 'Draft'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True,
|
||||
help='If the phase is created the state \'Draft\'.\n If the phase is started, the state becomes \'In Progress\'.\n If review is needed the phase is in \'Pending\' state.\
|
||||
\n If the phase is over, the states is set to \'Done\'.')
|
||||
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'responsible_id': lambda obj,cr,uid,context: uid,
|
||||
'date_start': lambda *a: time.strftime('%Y-%m-%d'),
|
||||
'state': lambda *a: 'draft',
|
||||
'sequence': lambda *a: 10,
|
||||
}
|
||||
|
||||
_order = "name"
|
||||
_constraints = [
|
||||
(_check_recursion,'Error ! Loops In Phases Not Allowed',['next_phase_ids','previous_phase_ids']),
|
||||
(_check_recursion,'Error ! Loops In Phases Not Allowed',['next_phase_ids', 'previous_phase_ids']),
|
||||
(_check_dates, 'Error! Phase start-date must be lower then Phase end-date.', ['date_start', 'date_end']),
|
||||
(_check_constraint_start, 'Error! Phase must start-after Constraint Start Date.', ['date_start', 'constraint_date_start']),
|
||||
(_check_constraint_end, 'Error! Phase must end-before Constraint End Date.', ['date_end', 'constraint_date_end']),
|
||||
|
||||
]
|
||||
|
||||
def onchange_project(self, cr, uid, ids, project):
|
||||
result = {}
|
||||
if project:
|
||||
project_pool = self.pool.get('project.project')
|
||||
project_id = project_pool.browse(cr, uid, project)
|
||||
project_obj = self.pool.get('project.project')
|
||||
project_id = project_obj.browse(cr, uid, project)
|
||||
if project_id.date_start:
|
||||
result['date_start'] = mx.DateTime.strptime(project_id.date_start, "%Y-%m-%d").strftime('%Y-%m-%d %H:%M:%S')
|
||||
return {'value' : result}
|
||||
return {'value' : {'date_start' : []}}
|
||||
return {'value': result}
|
||||
return {'value': {'date_start': []}}
|
||||
|
||||
def constraint_date_start(self, cr, uid, phase, date_end, context=None):
|
||||
# Recursive call for all previous phases if change in date_start < older time
|
||||
|
||||
resource_cal_pool = self.pool.get('resource.calendar')
|
||||
uom_pool = self.pool.get('product.uom')
|
||||
resource_pool = self.pool.get('resource.resource')
|
||||
def _check_date_start(self, cr, uid, phase, date_end, context=None):
|
||||
"""
|
||||
Check And Compute date_end of phase if change in date_start < older time.
|
||||
"""
|
||||
resource_calendar_obj = self.pool.get('resource.calendar')
|
||||
uom_obj = self.pool.get('product.uom')
|
||||
resource_obj = self.pool.get('resource.resource')
|
||||
calendar_id = phase.project_id.resource_calendar_id.id
|
||||
resource_id = resource_pool.search(cr, uid, [('user_id','=',phase.responsible_id.id)])
|
||||
resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)])
|
||||
if resource_id:
|
||||
calendar_id = resource_pool.browse(cr, uid, resource_id[0]).calendar_id.id
|
||||
default_uom_id = uom_pool.search(cr, uid, [('name','=','Hour')])[0]
|
||||
avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
|
||||
work_time = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, date_end, avg_hours or 0.0, resource_id or False)
|
||||
dt_start = work_time[0][0].strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.write(cr, uid, [phase.id], {'date_start' : dt_start, 'date_end' : date_end.strftime('%Y-%m-%d %H:%M:%S')})
|
||||
cal_id = resource_obj.browse(cr, uid, resource_id[0]).calendar_id.id
|
||||
if cal_id:
|
||||
calendar_id = cal_id
|
||||
default_uom_id = uom_obj.search(cr, uid, [('name', '=', 'Hour')])[0]
|
||||
avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
|
||||
work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id or False, date_end, avg_hours or 0.0, resource_id or False)
|
||||
dt_start = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.write(cr, uid, [phase.id], {'date_start': dt_start, 'date_end': date_end.strftime('%Y-%m-%d %H:%M:%S')})
|
||||
|
||||
def constraint_date_end(self, cr, uid, phase, date_start, context=None):
|
||||
# Recursive call for all next phases if change in date_end > older time
|
||||
|
||||
resource_cal_pool = self.pool.get('resource.calendar')
|
||||
uom_pool = self.pool.get('product.uom')
|
||||
resource_pool = self.pool.get('resource.resource')
|
||||
def _check_date_end(self, cr, uid, phase, date_start, context=None):
|
||||
"""
|
||||
Check And Compute date_end of phase if change in date_end > older time.
|
||||
"""
|
||||
resource_calendar_obj = self.pool.get('resource.calendar')
|
||||
uom_obj = self.pool.get('product.uom')
|
||||
resource_obj = self.pool.get('resource.resource')
|
||||
calendar_id = phase.project_id.resource_calendar_id.id
|
||||
resource_id = resource_pool.search(cr, uid, [('user_id','=',phase.responsible_id.id)])
|
||||
resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)])
|
||||
if resource_id:
|
||||
calendar_id = resource_pool.browse(cr, uid, resource_id[0]).calendar_id.id
|
||||
default_uom_id = uom_pool.search(cr, uid, [('name','=','Hour')])[0]
|
||||
avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
|
||||
work_time = resource_cal_pool.interval_get(cr, uid, calendar_id or False, date_start, avg_hours or 0.0, resource_id or False)
|
||||
dt_end = work_time[-1][1].strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.write(cr, uid, [phase.id], {'date_start' : date_start.strftime('%Y-%m-%d %H:%M:%S'), 'date_end' : dt_end})
|
||||
cal_id = resource_obj.browse(cr, uid, resource_id[0]).calendar_id.id
|
||||
if cal_id:
|
||||
calendar_id = cal_id
|
||||
default_uom_id = uom_obj.search(cr, uid, [('name', '=', 'Hour')])[0]
|
||||
avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
|
||||
work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id or False, date_start, avg_hours or 0.0, resource_id or False)
|
||||
dt_end = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.write(cr, uid, [phase.id], {'date_start': date_start.strftime('%Y-%m-%d %H:%M:%S'), 'date_end': dt_end})
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
|
||||
@@ -179,62 +176,62 @@ class project_phase(osv.osv):
|
||||
if context.get('scheduler',False):
|
||||
return super(project_phase, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
# if the phase is performed by a resource then its calendar and efficiency also taken
|
||||
# otherwise the project's working calendar considered
|
||||
# Consider calendar and efficiency if the phase is performed by a resource
|
||||
# otherwise consider the project's working calendar
|
||||
phase = self.browse(cr, uid, ids[0])
|
||||
resource_cal_pool = self.pool.get('resource.calendar')
|
||||
resource_pool = self.pool.get('resource.resource')
|
||||
uom_pool = self.pool.get('product.uom')
|
||||
resource_calendar_obj = self.pool.get('resource.calendar')
|
||||
resource_obj = self.pool.get('resource.resource')
|
||||
uom_obj = self.pool.get('product.uom')
|
||||
resource_id = False
|
||||
calendar_id = phase.project_id.resource_calendar_id.id
|
||||
resource_id = resource_pool.search(cr, uid, [('user_id','=',phase.responsible_id.id)])
|
||||
resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)])
|
||||
if resource_id:
|
||||
calendar_id = resource_pool.browse(cr, uid, resource_id[0]).calendar_id.id
|
||||
default_uom_id = uom_pool.search(cr, uid, [('name','=','Hour')])[0]
|
||||
avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
|
||||
|
||||
# write method changes the date_start and date_end
|
||||
#for previous and next phases respectively based on valid condition
|
||||
cal_id = resource_obj.browse(cr, uid, resource_id[0]).calendar_id.id
|
||||
if cal_id:
|
||||
calendar_id = cal_id
|
||||
default_uom_id = uom_obj.search(cr, uid, [('name', '=', 'Hour')])[0]
|
||||
avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
|
||||
|
||||
# Change the date_start and date_end
|
||||
# for previous and next phases respectively based on valid condition
|
||||
if vals.get('date_start'):
|
||||
if vals['date_start'] < phase.date_start:
|
||||
dt_start = mx.DateTime.strptime(vals['date_start'],'%Y-%m-%d %H:%M:%S')
|
||||
work_times = resource_cal_pool.interval_get(cr, uid, calendar_id or False, dt_start, avg_hours or 0.0, resource_id or False)
|
||||
work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id or False, dt_start, avg_hours or 0.0, resource_id or False)
|
||||
vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
|
||||
super(project_phase, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
for prv_phase in phase.previous_phase_ids:
|
||||
self.constraint_date_start(cr, uid, prv_phase, dt_start)
|
||||
|
||||
self._check_date_start(cr, uid, prv_phase, dt_start)
|
||||
if vals.get('date_end'):
|
||||
if vals['date_end'] > phase.date_end:
|
||||
dt_end = mx.DateTime.strptime(vals['date_end'],'%Y-%m-%d %H:%M:%S')
|
||||
work_times = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, dt_end, avg_hours or 0.0, resource_id or False)
|
||||
work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id or False, dt_end, avg_hours or 0.0, resource_id or False)
|
||||
vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S')
|
||||
super(project_phase, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
for next_phase in phase.next_phase_ids:
|
||||
self.constraint_date_end(cr, uid, next_phase, dt_end)
|
||||
self._check_date_end(cr, uid, next_phase, dt_end)
|
||||
|
||||
return super(project_phase, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
def phase_draft(self, cr, uid, ids,*args):
|
||||
def set_draft(self, cr, uid, ids, *args):
|
||||
self.write(cr, uid, ids, {'state': 'draft'})
|
||||
return True
|
||||
|
||||
def phase_start(self, cr, uid, ids,*args):
|
||||
def set_open(self, cr, uid, ids, *args):
|
||||
self.write(cr, uid, ids, {'state': 'open'})
|
||||
return True
|
||||
|
||||
def phase_pending(self, cr, uid, ids,*args):
|
||||
def set_pending(self, cr, uid, ids,*args):
|
||||
self.write(cr, uid, ids, {'state': 'pending'})
|
||||
return True
|
||||
|
||||
def phase_cancel(self, cr, uid, ids,*args):
|
||||
def set_cancel(self, cr, uid, ids, *args):
|
||||
self.write(cr, uid, ids, {'state': 'cancelled'})
|
||||
return True
|
||||
|
||||
def phase_done(self, cr, uid, ids,*args):
|
||||
def set_done(self, cr, uid, ids, *args):
|
||||
self.write(cr, uid, ids, {'state': 'done'})
|
||||
return True
|
||||
|
||||
@@ -246,7 +243,7 @@ class project_resource_allocation(osv.osv):
|
||||
_rec_name = 'resource_id'
|
||||
_columns = {
|
||||
'resource_id': fields.many2one('resource.resource', 'Resource', required=True),
|
||||
'phase_id': fields.many2one('project.phase', 'Project Phase',required=True),
|
||||
'phase_id': fields.many2one('project.phase', 'Project Phase', required=True),
|
||||
'useability': fields.float('Useability', help="Useability of this ressource for this project phase in percentage (=50%)"),
|
||||
}
|
||||
_defaults = {
|
||||
@@ -257,7 +254,7 @@ project_resource_allocation()
|
||||
|
||||
class project(osv.osv):
|
||||
_inherit = "project.project"
|
||||
|
||||
_description = "Project"
|
||||
_columns = {
|
||||
'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"),
|
||||
'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report"),
|
||||
@@ -267,7 +264,7 @@ project()
|
||||
|
||||
class task(osv.osv):
|
||||
_inherit = "project.task"
|
||||
|
||||
_description = "Task"
|
||||
_columns = {
|
||||
'phase_id': fields.many2one('project.phase', 'Project Phase'),
|
||||
'occupation_rate': fields.float('Occupation Rate', help='The occupation rate fields indicates how much of his time a user is working on a task. A 100% occupation rate means the user works full time on the tasks. The ending date of a task is computed like this: Starting Date + Duration / Occupation Rate.'),
|
||||
@@ -280,53 +277,58 @@ class task(osv.osv):
|
||||
def onchange_planned(self, cr, uid, ids, project, user_id=False, planned=0.0, effective=0.0, date_start=None, occupation_rate=0.0):
|
||||
result = {}
|
||||
if date_start:
|
||||
resource_pool = self.pool.get('resource.resource')
|
||||
resource_obj = self.pool.get('resource.resource')
|
||||
project_pool = self.pool.get('project.project')
|
||||
hrs = float(planned / float(occupation_rate))
|
||||
calendar_id = project_pool.browse(cr, uid, project).resource_calendar_id.id
|
||||
resource_calendar = self.pool.get('resource.calendar')
|
||||
dt_start = mx.DateTime.strptime(date_start, '%Y-%m-%d %H:%M:%S')
|
||||
resource_id = resource_pool.search(cr, uid, [('user_id','=',user_id)])
|
||||
resource_id = resource_obj.search(cr, uid, [('user_id','=',user_id)])
|
||||
if resource_id:
|
||||
resource_obj = resource_pool.browse(cr, uid, resource_id)[0]
|
||||
hrs = planned / (float(occupation_rate) * resource_obj.time_efficiency)
|
||||
calendar_id = resource_obj.calendar_id.id
|
||||
else:
|
||||
hrs = float(planned / float(occupation_rate))
|
||||
calendar_id = project_pool.browse(cr, uid, project).resource_calendar_id .id
|
||||
resource = resource_obj.browse(cr, uid, resource_id)[0]
|
||||
hrs = planned / (float(occupation_rate) * resource.time_efficiency)
|
||||
if resource.calendar_id.id:
|
||||
calendar_id = resource.calendar_id.id
|
||||
work_times = resource_calendar.interval_get(cr, uid, calendar_id or False, dt_start, hrs or 0.0, resource_id or False)
|
||||
result['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
|
||||
result['remaining_hours'] = planned - effective
|
||||
return {'value' : result}
|
||||
|
||||
def constraint_date_start(self, cr, uid, task, date_end, context=None):
|
||||
# Recursive call for all previous tasks if change in date_start < older time
|
||||
|
||||
resource_cal_pool = self.pool.get('resource.calendar')
|
||||
resource_pool = self.pool.get('resource.resource')
|
||||
def _check_date_start(self, cr, uid, task, date_end, context=None):
|
||||
"""
|
||||
Check And Compute date_end of task if change in date_start < older time.
|
||||
"""
|
||||
resource_calendar_obj = self.pool.get('resource.calendar')
|
||||
resource_obj = self.pool.get('resource.resource')
|
||||
calendar_id = task.project_id.resource_calendar_id.id
|
||||
hours = task.remaining_hours / task.occupation_rate
|
||||
resource_id = resource_pool.search(cr, uid, [('user_id','=',task.user_id.id)])
|
||||
hours = task.planned_hours / task.occupation_rate
|
||||
resource_id = resource_obj.search(cr, uid, [('user_id', '=', task.user_id.id)])
|
||||
if resource_id:
|
||||
resource_obj = resource_pool.browse(cr, uid, resource_id[0])
|
||||
calendar_id = resource_obj.calendar_id.id
|
||||
hours = task.planned_hours / (float(task.occupation_rate) * resource_obj.time_efficiency)
|
||||
work_time = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, date_end, hours or 0.0, resource_id or False)
|
||||
dt_start = work_time[0][0].strftime('%Y-%m-%d %H:%M:%S')
|
||||
resource = resource_obj.browse(cr, uid, resource_id[0])
|
||||
if resource.calendar_id.id:
|
||||
calendar_id = resource.calendar_id.id
|
||||
hours = task.planned_hours / (float(task.occupation_rate) * resource.time_efficiency)
|
||||
work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id or False, date_end, hours or 0.0, resource_id or False)
|
||||
dt_start = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.write(cr, uid, [task.id], {'date_start' : dt_start,'date_end' : date_end.strftime('%Y-%m-%d %H:%M:%S')})
|
||||
|
||||
def constraint_date_end(self, cr, uid, task, date_start, context=None):
|
||||
# Recursive call for all next tasks if change in date_end > older time
|
||||
def _check_date_end(self, cr, uid, task, date_start, context=None):
|
||||
"""
|
||||
Check And Compute date_end of task if change in date_end > older time.
|
||||
"""
|
||||
|
||||
resource_cal_pool = self.pool.get('resource.calendar')
|
||||
resource_pool = self.pool.get('resource.resource')
|
||||
resource_calendar_obj = self.pool.get('resource.calendar')
|
||||
resource_obj = self.pool.get('resource.resource')
|
||||
calendar_id = task.project_id.resource_calendar_id.id
|
||||
hours = task.remaining_hours / task.occupation_rate
|
||||
resource_id = resource_pool.search(cr,uid,[('user_id','=',task.user_id.id)])
|
||||
hours = task.planned_hours / task.occupation_rate
|
||||
resource_id = resource_obj.search(cr,uid,[('user_id', '=', task.user_id.id)])
|
||||
if resource_id:
|
||||
resource_obj = resource_pool.browse(cr, uid, resource_id[0])
|
||||
calendar_id = resource_obj.calendar_id.id
|
||||
hours = task.planned_hours / (float(task.occupation_rate) * resource_obj.time_efficiency)
|
||||
work_time = resource_cal_pool.interval_get(cr, uid, calendar_id or False, date_start, hours or 0.0, resource_id or False)
|
||||
dt_end = work_time[-1][1].strftime('%Y-%m-%d %H:%M:%S')
|
||||
resource = resource_obj.browse(cr, uid, resource_id[0])
|
||||
if resource.calendar_id.id:
|
||||
calendar_id = resource.calendar_id.id
|
||||
hours = task.planned_hours / (float(task.occupation_rate) * resource.time_efficiency)
|
||||
work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id or False, date_start, hours or 0.0, resource_id or False)
|
||||
dt_end = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.write(cr, uid, [task.id], {'date_start' : date_start.strftime('%Y-%m-%d %H:%M:%S'),'date_end' : dt_end})
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
@@ -337,44 +339,43 @@ class task(osv.osv):
|
||||
if context.get('scheduler',False):
|
||||
return super(task, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
# if the task is performed by a resource then its calendar and efficiency also taken
|
||||
# otherwise the project's working calendar considered
|
||||
# Consider calendar and efficiency if the task is performed by a resource
|
||||
# otherwise consider the project's working calendar
|
||||
task_id = ids
|
||||
if isinstance(ids, list):
|
||||
task_id = ids[0]
|
||||
tasks = self.browse(cr, uid, task_id, context=context)
|
||||
resource_cal_pool = self.pool.get('resource.calendar')
|
||||
resource_pool = self.pool.get('resource.resource')
|
||||
calendar_id = tasks.project_id.resource_calendar_id.id
|
||||
hrs = tasks.remaining_hours / tasks.occupation_rate
|
||||
resource_id = resource_pool.search(cr, uid, [('user_id','=',tasks.user_id.id)])
|
||||
task_rec = self.browse(cr, uid, task_id, context=context)
|
||||
resource_calendar_obj = self.pool.get('resource.calendar')
|
||||
resource_obj = self.pool.get('resource.resource')
|
||||
calendar_id = task_rec.project_id.resource_calendar_id.id
|
||||
hrs = task_rec.planned_hours / task_rec.occupation_rate
|
||||
resource_id = resource_obj.search(cr, uid, [('user_id', '=', task_rec.user_id.id)])
|
||||
if resource_id:
|
||||
resource_obj = resource_pool.browse(cr, uid, resource_id[0])
|
||||
calendar_id = resource_obj.calendar_id.id
|
||||
hrs = tasks.planned_hours / (float(tasks.occupation_rate) * resource_obj.time_efficiency)
|
||||
resource = resource_obj.browse(cr, uid, resource_id[0])
|
||||
if resource.calendar_id.id:
|
||||
calendar_id = resource.calendar_id.id
|
||||
hrs = task_rec.planned_hours / (float(task_rec.occupation_rate) * resource.time_efficiency)
|
||||
|
||||
# write method changes the date_start and date_end
|
||||
# for previous and next tasks respectively based on valid condition
|
||||
# Change the date_start and date_end
|
||||
# for previous and next tasks respectively based on valid condition
|
||||
if vals.get('date_start'):
|
||||
if vals['date_start'] < tasks.date_start:
|
||||
dt_start = mx.DateTime.strptime(vals['date_start'],'%Y-%m-%d %H:%M:%S')
|
||||
work_times = resource_cal_pool.interval_get(cr, uid, calendar_id or False, dt_start, hrs or 0.0, resource_id or False)
|
||||
if vals['date_start'] < task_rec.date_start:
|
||||
dt_start = mx.DateTime.strptime(vals['date_start'], '%Y-%m-%d %H:%M:%S')
|
||||
work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id or False, dt_start, hrs or 0.0, resource_id or False)
|
||||
vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
|
||||
super(task, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
for prv_task in tasks.parent_ids:
|
||||
self.constraint_date_start(cr, uid, prv_task, dt_start)
|
||||
|
||||
for prv_task in task_rec.parent_ids:
|
||||
self._check_date_start(cr, uid, prv_task, dt_start)
|
||||
if vals.get('date_end'):
|
||||
if vals['date_end'] > tasks.date_end:
|
||||
dt_end = mx.DateTime.strptime(vals['date_end'],'%Y-%m-%d %H:%M:%S')
|
||||
hrs = tasks.remaining_hours / tasks.occupation_rate
|
||||
work_times = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, dt_end, hrs or 0.0, resource_id or False)
|
||||
if vals['date_end'] > task_rec.date_end:
|
||||
dt_end = mx.DateTime.strptime(vals['date_end'], '%Y-%m-%d %H:%M:%S')
|
||||
work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id or False, dt_end, hrs or 0.0, resource_id or False)
|
||||
vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S')
|
||||
super(task, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
for next_task in tasks.child_ids:
|
||||
self.constraint_date_end(cr, uid, next_task, dt_end)
|
||||
for next_task in task_rec.child_ids:
|
||||
self._check_date_end(cr, uid, next_task, dt_end)
|
||||
|
||||
return super(task, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
|
||||
@@ -201,6 +201,7 @@
|
||||
</record>
|
||||
|
||||
<!-- Resources -->
|
||||
|
||||
<record id="resource_resource_analyst0" model="resource.resource">
|
||||
<field eval="1.0" name="time_efficiency"/>
|
||||
<field name="user_id" ref="res_users_useranalyst0"/>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
# ------------------------------------------------------
|
||||
# Project Resource Allocation
|
||||
# ------------------------------------------------------
|
||||
|
||||
<record id="view_project_resource_allocation_form" model="ir.ui.view">
|
||||
<field name="name">project.resource.allocation.form</field>
|
||||
<field name="model">project.resource.allocation</field>
|
||||
@@ -13,7 +18,7 @@
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="view_project_resource_allocation_list" model="ir.ui.view">
|
||||
<field name="name">project.resource.allocation.list</field>
|
||||
<field name="model">project.resource.allocation</field>
|
||||
@@ -26,7 +31,11 @@
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# Project Phase
|
||||
# ------------------------------------------------------
|
||||
|
||||
<record id="view_project_phase_form" model="ir.ui.view">
|
||||
<field name="name">project.phase.form</field>
|
||||
<field name="model">project.phase</field>
|
||||
@@ -57,11 +66,11 @@
|
||||
</field>
|
||||
<group col="12" colspan="4">
|
||||
<field name="state" select="1"/>
|
||||
<button string="Draft" name="phase_draft" states="open"/>
|
||||
<button string="Start Phase" name="phase_start" states="pending,draft"/>
|
||||
<button string="Cancel" name="phase_cancel" states="draft,open,pending"/>
|
||||
<button string="Done" name="phase_done" states="draft,pending,open"/>
|
||||
<button string="Pending" name="phase_pending" states="open"/>
|
||||
<button string="Draft" name="set_draft" states="open" icon="gtk-indent"/>
|
||||
<button string="Start Phase" name="set_open" states="pending,draft" icon="gtk-execute"/>
|
||||
<button string="Cancel" name="set_cancel" states="draft,open,pending" icon="gtk-cancel"/>
|
||||
<button string="Done" name="set_done" states="draft,pending,open" icon="gtk-jump-to"/>
|
||||
<button string="Pending" name="set_pending" states="open" icon="gtk-media-pause"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Other Info">
|
||||
@@ -128,7 +137,7 @@
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="view_project_phase_list" model="ir.ui.view">
|
||||
<field name="name">project.phase.list</field>
|
||||
<field name="model">project.phase</field>
|
||||
@@ -185,7 +194,7 @@
|
||||
<field name="project_id" widget="selection"/>
|
||||
<field name="responsible_id">
|
||||
<filter domain="[('responsible_id','=',uid)]" help="My Phase" icon="gtk-execute" default="1"/>
|
||||
</field>
|
||||
</field>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
</group>
|
||||
@@ -194,7 +203,7 @@
|
||||
<filter string="Project" icon="terp-project" domain="[]" context="{'group_by':'project_id'}"/>
|
||||
<filter string="User" icon="terp-project" domain="[]" context="{'group_by':'responsible_id'}"/>
|
||||
<filter string="Start Date" icon="terp-project" domain="[]" context="{'group_by':'date_start'}"/>
|
||||
</group>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
@@ -207,6 +216,10 @@
|
||||
<field name="search_view_id" ref="view_project_phase_search"/>
|
||||
</record>
|
||||
|
||||
# ------------------------------------------------------
|
||||
# Project
|
||||
# ------------------------------------------------------
|
||||
|
||||
<record id="view_phase_project_form1" model="ir.ui.view">
|
||||
<field name="name">phase.project.form1</field>
|
||||
<field name="model">project.project</field>
|
||||
@@ -220,6 +233,10 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
# ------------------------------------------------------
|
||||
# Project Task
|
||||
# ------------------------------------------------------
|
||||
|
||||
<record id="view_project_phase_task_form1" model="ir.ui.view">
|
||||
<field name="name">project.phase.task.form1</field>
|
||||
<field name="model">project.task</field>
|
||||
@@ -262,9 +279,11 @@
|
||||
view_mode="tree,form"
|
||||
view_type="form" />
|
||||
|
||||
<menuitem action="act_project_phase" id="menu_project_phase" parent="project.menu_project_management" sequence="4"/>
|
||||
# ------------------------------------------------------
|
||||
# Menu Items
|
||||
# ------------------------------------------------------
|
||||
|
||||
<!-- Menu for Resource for Long Term Project -->
|
||||
<menuitem action="act_project_phase" id="menu_project_phase" parent="project.menu_project_management" sequence="4"/>
|
||||
<menuitem icon="terp-project" id="base.menu_main_pm" name="Project Management" sequence="1"/>
|
||||
<menuitem id="menu_pm_resources_project1" name="Resources" parent="base.menu_main_pm" sequence="2"/>
|
||||
<menuitem action="resource.action_resource_resource_tree" id="menu_view_resource" parent="menu_pm_resources_project1" sequence="1"/>
|
||||
|
||||
+28
-24
@@ -10,95 +10,99 @@
|
||||
<record id="act_draft" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_phase"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">phase_draft</field>
|
||||
<field name="name">set_draft</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">phase_draft()</field>
|
||||
<field name="action">set_draft()</field>
|
||||
</record>
|
||||
|
||||
<record id="act_start_phase" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_phase"/>
|
||||
<field name="name">phase_start</field>
|
||||
<field name="name">set_open</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">phase_start()</field>
|
||||
|
||||
<field name="action">set_open()</field>
|
||||
</record>
|
||||
|
||||
<record id="act_cancel_phase" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_phase"/>
|
||||
<field name="name">phase_cancel</field>
|
||||
<field name="name">set_cancel</field>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">phase_cancel()</field>
|
||||
|
||||
<field name="action">set_cancel()</field>
|
||||
</record>
|
||||
|
||||
<record id="act_phase_pending" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_phase"/>
|
||||
<field name="name">phase_pending</field>
|
||||
<field name="name">set_pending</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">phase_pending()</field>
|
||||
<field name="action">set_pending()</field>
|
||||
</record>
|
||||
|
||||
<record id="act_phase_done" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_phase"/>
|
||||
<field name="name">phase_done</field>
|
||||
<field name="name">set_done</field>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">phase_done()</field>
|
||||
<field name="action">set_done()</field>
|
||||
</record>
|
||||
|
||||
<record id="t0" model="workflow.transition">
|
||||
<field name="act_from" ref="act_draft"/>
|
||||
<field name="act_to" ref="act_start_phase"/>
|
||||
<field name="signal">phase_start</field>
|
||||
<field name="signal">set_open</field>
|
||||
</record>
|
||||
|
||||
<record id="t1" model="workflow.transition">
|
||||
<field name="act_from" ref="act_draft"/>
|
||||
<field name="act_to" ref="act_cancel_phase"/>
|
||||
<field name="signal">phase_cancel</field>
|
||||
<field name="signal">set_cancel</field>
|
||||
</record>
|
||||
|
||||
<record id="t2" model="workflow.transition">
|
||||
<field name="act_from" ref="act_draft"/>
|
||||
<field name="act_to" ref="act_phase_done"/>
|
||||
<field name="signal">phase_done</field>
|
||||
<field name="signal">set_done</field>
|
||||
</record>
|
||||
|
||||
<record id="t3" model="workflow.transition">
|
||||
<field name="act_from" ref="act_start_phase"/>
|
||||
<field name="act_to" ref="act_phase_pending"/>
|
||||
<field name="signal">phase_pending</field>
|
||||
<field name="signal">set_pending</field>
|
||||
</record>
|
||||
|
||||
<record id="t4" model="workflow.transition">
|
||||
<field name="act_from" ref="act_phase_pending"/>
|
||||
<field name="act_to" ref="act_cancel_phase"/>
|
||||
<field name="signal">phase_cancel</field>
|
||||
<field name="signal">set_cancel</field>
|
||||
</record>
|
||||
|
||||
<record id="t5" model="workflow.transition">
|
||||
<field name="act_from" ref="act_phase_pending"/>
|
||||
<field name="act_to" ref="act_draft"/>
|
||||
<field name="signal">phase_draft</field>
|
||||
<field name="signal">set_draft</field>
|
||||
</record>
|
||||
|
||||
<record id="t6" model="workflow.transition">
|
||||
<field name="act_from" ref="act_start_phase"/>
|
||||
<field name="act_to" ref="act_cancel_phase"/>
|
||||
<field name="signal">phase_cancel</field>
|
||||
<field name="act_from" ref="act_phase_pending"/>
|
||||
<field name="act_to" ref="act_start_phase"/>
|
||||
<field name="signal">set_open</field>
|
||||
</record>
|
||||
|
||||
<record id="t7" model="workflow.transition">
|
||||
<field name="act_from" ref="act_start_phase"/>
|
||||
<field name="act_to" ref="act_phase_done"/>
|
||||
<field name="signal">phase_done</field>
|
||||
<field name="act_to" ref="act_cancel_phase"/>
|
||||
<field name="signal">set_cancel</field>
|
||||
</record>
|
||||
|
||||
<record id="t8" model="workflow.transition">
|
||||
<field name="act_from" ref="act_start_phase"/>
|
||||
<field name="act_to" ref="act_phase_done"/>
|
||||
<field name="signal">set_done</field>
|
||||
</record>
|
||||
|
||||
<record id="t9" model="workflow.transition">
|
||||
<field name="act_from" ref="act_start_phase"/>
|
||||
<field name="act_to" ref="act_draft"/>
|
||||
<field name="signal">phase_draft</field>
|
||||
<field name="signal">set_draft</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -22,7 +22,7 @@
|
||||
import compute_phases_date
|
||||
import compute_tasks_date
|
||||
import schedule_tasks
|
||||
import project_resource
|
||||
import working_calendar
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
||||
@@ -23,9 +23,11 @@ import wizard
|
||||
import pooler
|
||||
from tools.translate import _
|
||||
import datetime
|
||||
|
||||
from resource.faces import *
|
||||
from new import classobj
|
||||
import project_resource as proj
|
||||
|
||||
import working_calendar as wkcal
|
||||
|
||||
compute_form = """<?xml version="1.0" ?>
|
||||
<form string="Compute Scheduling of Phases">
|
||||
@@ -35,42 +37,54 @@ compute_form = """<?xml version="1.0" ?>
|
||||
</form>"""
|
||||
|
||||
compute_fields = {
|
||||
'project_id': {'string':'Project', 'type':'many2one', 'relation': 'project.project'},
|
||||
'project_id': {'string':'Project', 'type':'many2one', 'relation':'project.project'},
|
||||
|
||||
}
|
||||
|
||||
success_msg = """<?xml version="1.0" ?>
|
||||
<form string="Compute Scheduling of Phases">
|
||||
<label string="Phase Scheduling completed successfully."/>
|
||||
</form>"""
|
||||
class wizard_compute_phases(wizard.interface):
|
||||
def _phase_schedule(self, cr, uid, phase, start_date, calendar_id=False, context={}):
|
||||
|
||||
"""Schedule phase with the start date till all the next phases are completed.
|
||||
|
||||
Arguements: start_dsate -- start date for the phase
|
||||
calendar_id -- working calendar of the project
|
||||
|
||||
"""
|
||||
|
||||
def phase_schedule(cr, uid, phase, start_date, calendar_id=False):
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
phase_pool = pool.get('project.phase')
|
||||
resource_pool = pool.get('resource.resource')
|
||||
uom_pool = pool.get('product.uom')
|
||||
wktime_cal = []
|
||||
resource_cal = False
|
||||
phase_resource = False
|
||||
phase_obj = pool.get('project.phase')
|
||||
resource_obj = pool.get('resource.resource')
|
||||
uom_obj = pool.get('product.uom')
|
||||
phase_resource_obj = False
|
||||
if phase:
|
||||
resource_id = resource_pool.search(cr, uid, [('user_id','=',phase.responsible_id.id)])
|
||||
leaves = []
|
||||
time_efficiency = 1.0
|
||||
resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)])
|
||||
if resource_id:
|
||||
resource_obj = resource_pool.browse(cr, uid, resource_id)[0]
|
||||
leaves = proj.leaves_resource(cr, uid, calendar_id or False , resource_id, resource_obj.calendar_id.id)
|
||||
phase_resource = classobj(str(resource_obj.name), (Resource,), {'__doc__' : resource_obj.name, '__name__' : resource_obj.name, 'vacation' : tuple(leaves), 'efficiency' : resource_obj.time_efficiency})
|
||||
default_uom_id = uom_pool.search(cr, uid, [('name','=','Hour')])[0]
|
||||
avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
|
||||
# Create a new resource object with
|
||||
# all the attributes of the Resource Class
|
||||
resource = resource_obj.browse(cr, uid, resource_id, context=context)[0]
|
||||
time_efficiency = resource.time_efficiency
|
||||
leaves = wkcal.compute_leaves(cr, uid, calendar_id or False , resource_id, resource.calendar_id.id)
|
||||
phase_resource_obj = classobj(str(phase.responsible_id.name), (Resource,),
|
||||
{'__doc__': phase.responsible_id.name,
|
||||
'__name__': phase.responsible_id.name,
|
||||
'vacation': tuple(leaves),
|
||||
'efficiency': time_efficiency
|
||||
})
|
||||
default_uom_id = uom_obj.search(cr, uid, [('name','=','Hour')])[0]
|
||||
avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
|
||||
duration = str(avg_hours) + 'H'
|
||||
|
||||
# Creating a new project for each phase
|
||||
# Create a new project for each phase
|
||||
def Project():
|
||||
start = start_date
|
||||
minimum_time_unit = 1
|
||||
resource = phase_resource
|
||||
# If project has working calendar else the default one would be considered
|
||||
resource = phase_resource_obj
|
||||
# If project has working calendar then that
|
||||
# else the default one would be considered
|
||||
if calendar_id:
|
||||
working_days = proj.compute_working_calendar(cr, uid, calendar_id)
|
||||
vacation = tuple(proj.leaves_resource(cr, uid, calendar_id))
|
||||
working_days = wkcal.compute_working_calendar(cr, uid, calendar_id)
|
||||
vacation = tuple(wkcal.compute_leaves(cr, uid, calendar_id))
|
||||
|
||||
def phase():
|
||||
effort = duration
|
||||
@@ -78,7 +92,8 @@ def phase_schedule(cr, uid, phase, start_date, calendar_id=False):
|
||||
project = BalancedProject(Project)
|
||||
s_date = project.phase.start.to_datetime()
|
||||
e_date = project.phase.end.to_datetime()
|
||||
# According to constraints on date start and date end on phase recalculation done
|
||||
# Recalculate date_start and date_end
|
||||
# according to constraints on date start and date end on phase
|
||||
if phase.constraint_date_start and str(s_date) < phase.constraint_date_start:
|
||||
start_date = datetime.datetime.strptime(phase.constraint_date_start, '%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
@@ -89,54 +104,59 @@ def phase_schedule(cr, uid, phase, start_date, calendar_id=False):
|
||||
else:
|
||||
end_date = e_date
|
||||
date_start = end_date
|
||||
|
||||
# Writing the dates back
|
||||
phase_pool.write(cr, uid, [phase.id], {'date_start' :start_date.strftime('%Y-%m-%d %H:%M:%S'), 'date_end' :end_date.strftime('%Y-%m-%d %H:%M:%S')}, context={'scheduler' :True})
|
||||
|
||||
# Recursive calling the next phases till all the phases are scheduled
|
||||
# Write the calculated dates back
|
||||
phase_obj.write(cr, uid, [phase.id], {'date_start': start_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'date_end': end_date.strftime('%Y-%m-%d %H:%M:%S')},
|
||||
context={'scheduler': True
|
||||
})
|
||||
# Recursive call till all the next phases scheduled
|
||||
for phase in phase.next_phase_ids:
|
||||
if phase.state in ['draft','open','pending']:
|
||||
phase_schedule(cr, uid, phase, date_start, phase.project_id.resource_calendar_id.id or False)
|
||||
self._phase_schedule(cr, uid, phase, date_start, phase.project_id.resource_calendar_id.id or False)
|
||||
else:
|
||||
continue
|
||||
|
||||
#
|
||||
class wizard_compute_phases(wizard.interface):
|
||||
|
||||
def _compute_date(self, cr, uid, data, context):
|
||||
"""
|
||||
Compute the phases for scheduling.
|
||||
"""
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
project_pool = pool.get('project.project')
|
||||
phase_pool = pool.get('project.phase')
|
||||
|
||||
# if project mentioned
|
||||
if data['form']['project_id']:
|
||||
project_id = project_pool.browse(cr, uid, data['form']['project_id'])
|
||||
phase_ids = phase_pool.search(cr, uid, [('project_id','=',project_id.id), ('state','in',['draft','open','pending']), ('previous_phase_ids','=',False)])
|
||||
|
||||
# else all the draft,open,pending states phases taken
|
||||
else:
|
||||
phase_ids = phase_pool.search(cr, uid,[('state','in',['draft','open','pending']), ('previous_phase_ids','=',False)])
|
||||
|
||||
project_obj = pool.get('project.project')
|
||||
phase_obj = pool.get('project.phase')
|
||||
if data['form']['project_id']: # If project mentioned find its phases
|
||||
project_id = project_obj.browse(cr, uid, data['form']['project_id'], context=context)
|
||||
phase_ids = phase_obj.search(cr, uid, [('project_id', '=', project_id.id),
|
||||
('state', 'in', ['draft', 'open', 'pending']),
|
||||
('previous_phase_ids', '=', False)
|
||||
])
|
||||
else: # Else take all the draft,open,pending states phases
|
||||
phase_ids = phase_obj.search(cr, uid,[('state', 'in', ['draft', 'open', 'pending']),
|
||||
('previous_phase_ids', '=', False)
|
||||
])
|
||||
phase_ids.sort()
|
||||
phase_objs = phase_pool.browse(cr, uid, phase_ids)
|
||||
phase_objs = phase_obj.browse(cr, uid, phase_ids, context=context)
|
||||
for phase in phase_objs:
|
||||
start_date = phase.project_id.date_start
|
||||
if not phase.project_id.date_start:
|
||||
start_date = datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
start_dt = datetime.datetime.strftime((datetime.datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d %H:%M")
|
||||
calendar_id = phase.project_id.resource_calendar_id.id
|
||||
phase_schedule(cr, uid, phase, start_dt, calendar_id or False)
|
||||
self._phase_schedule(cr, uid, phase, start_dt, calendar_id or False)
|
||||
return {}
|
||||
|
||||
def phases_open_list(self, cr, uid, data, context):
|
||||
def _open_phases_list(self, cr, uid, data, context):
|
||||
"""
|
||||
Return the scheduled phases list.
|
||||
"""
|
||||
mod_obj = pooler.get_pool(cr.dbname).get('ir.model.data')
|
||||
act_obj = pooler.get_pool(cr.dbname).get('ir.actions.act_window')
|
||||
result = mod_obj._get_id(cr, uid, 'project_long_term', 'act_project_phase')
|
||||
id = mod_obj.read(cr, uid, [result], ['res_id'])[0]['res_id']
|
||||
result = act_obj.read(cr, uid, [id], context=context)[0]
|
||||
if data['form']['project_id']:
|
||||
result['domain'] = [('project_id', '=', data['form']['project_id'])]
|
||||
result['domain'] = [('state', 'not in', ['cancelled','done'])]
|
||||
if data['form']['project_id']:
|
||||
result['domain'] = [('project_id', '=', data['form']['project_id']),
|
||||
('state', 'not in', ['cancelled','done'])]
|
||||
return result
|
||||
|
||||
states = {
|
||||
@@ -150,11 +170,8 @@ class wizard_compute_phases(wizard.interface):
|
||||
},
|
||||
'compute': {
|
||||
'actions': [_compute_date],
|
||||
'result': {'type': 'action', 'action':phases_open_list, 'state':'end'},
|
||||
'result': {'type': 'action', 'action':_open_phases_list, 'state':'end'},
|
||||
},
|
||||
}
|
||||
|
||||
wizard_compute_phases('wizard.compute.phases')
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -23,10 +23,12 @@ import wizard
|
||||
import pooler
|
||||
from tools.translate import _
|
||||
import datetime
|
||||
|
||||
from resource.faces import *
|
||||
from new import classobj
|
||||
import operator
|
||||
import project_resource as proj
|
||||
|
||||
import working_calendar as wkcal
|
||||
|
||||
compute_form = """<?xml version="1.0" ?>
|
||||
<form string="Compute Scheduling of Tasks">
|
||||
@@ -39,44 +41,51 @@ success_msg = """<?xml version="1.0" ?>
|
||||
</form>"""
|
||||
|
||||
compute_fields = {
|
||||
'project_id': {'string':'Project', 'type':'many2one', 'relation': 'project.project', 'required':'True'},
|
||||
'project_id': {'string':'Project', 'type':'many2one', 'relation':'project.project', 'required':'True'},
|
||||
}
|
||||
|
||||
class wizard_compute_tasks(wizard.interface):
|
||||
|
||||
def _compute_date(self, cr, uid, data, context):
|
||||
"""
|
||||
Schedule the tasks according to resource available and priority.
|
||||
"""
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
project_pool = pool.get('project.project')
|
||||
project_obj = pool.get('project.project')
|
||||
task_pool = pool.get('project.task')
|
||||
resource_pool = pool.get('resource.resource')
|
||||
user_pool = pool.get('res.users')
|
||||
resource_obj = pool.get('resource.resource')
|
||||
user_obj = pool.get('res.users')
|
||||
project_id = data['form']['project_id']
|
||||
project = project_pool.browse(cr, uid, project_id)
|
||||
task_ids = task_pool.search(cr, uid, [('project_id','=',project_id), ('state','in',['draft','open','pending'])])
|
||||
project = project_obj.browse(cr, uid, project_id, context=context)
|
||||
task_ids = task_pool.search(cr, uid, [('project_id', '=', project_id),
|
||||
('state', 'in', ['draft', 'open', 'pending'])
|
||||
])
|
||||
if task_ids:
|
||||
wktime_cal = []
|
||||
task_ids.sort()
|
||||
task_obj = task_pool.browse(cr, uid, task_ids)
|
||||
task_objs = task_pool.browse(cr, uid, task_ids, context=context)
|
||||
calendar_id = project.resource_calendar_id.id
|
||||
start_date = project.date_start
|
||||
if not project.date_start:
|
||||
start_date = datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
date_start = datetime.datetime.strftime(datetime.datetime.strptime(start_date, "%Y-%m-%d"), "%Y-%m-%d %H:%M")
|
||||
|
||||
# To create resources which are the Project Members
|
||||
resource_objs = []
|
||||
for resource in project.members:
|
||||
# Create Resource Class objects which are the Project Members
|
||||
resources = []
|
||||
for user in project.members:
|
||||
leaves = []
|
||||
resource_id = resource_pool.search(cr, uid, [('user_id','=',resource.id)])
|
||||
time_efficiency = 1.0
|
||||
resource_id = resource_obj.search(cr, uid, [('user_id', '=', user.id)])
|
||||
if resource_id:
|
||||
resource_obj = resource_pool.browse(cr, uid, resource_id)[0]
|
||||
leaves = proj.leaves_resource(cr, uid, calendar_id or False , resource_id, resource_obj.calendar_id.id)
|
||||
resource_objs.append(classobj(str(resource.name), (Resource,), {'__doc__' : resource.name, '__name__' : resource.name, 'vacation' : tuple(leaves), 'efficiency' : resource_obj.time_efficiency}))
|
||||
|
||||
priority_dict = {'0' :1000, '1' :800, '2' :500, '3' :300,'4' :100}
|
||||
|
||||
# To create dynamic no of tasks with the resource specified
|
||||
def tasks_resource(j, eff, priorty=500, obj=None):
|
||||
resource = resource_obj.browse(cr, uid, resource_id, context=context)[0]
|
||||
leaves = wkcal.compute_leaves(cr, uid, calendar_id or False , resource_id, resource.calendar_id.id)
|
||||
time_efficiency = resource.time_efficiency
|
||||
resources.append(classobj(str(user.name), (Resource,), {'__doc__': user.name,
|
||||
'__name__': user.name,
|
||||
'vacation': tuple(leaves),
|
||||
'efficiency': time_efficiency
|
||||
}))
|
||||
priority_dict = {'0': 1000, '1': 800, '2': 500, '3': 300,'4': 100}
|
||||
# Create dynamic no of tasks with the resource specified
|
||||
def create_tasks(j, eff, priorty=500, obj=None):
|
||||
def task():
|
||||
"""
|
||||
task is a dynamic method!
|
||||
@@ -89,43 +98,47 @@ class wizard_compute_tasks(wizard.interface):
|
||||
task.__name__ = "task%d" %j
|
||||
return task
|
||||
|
||||
# Creating the project with all the tasks and resources
|
||||
# Create a 'Faces' project with all the tasks and resources
|
||||
def Project():
|
||||
title = project.name
|
||||
start = date_start
|
||||
resource = reduce(operator.or_, resource_objs)
|
||||
try:
|
||||
resource = reduce(operator.or_, resources)
|
||||
except:
|
||||
raise wizard.except_wizard(_('MemberError'), _('Project must have members assigned !'))
|
||||
minimum_time_unit = 1
|
||||
|
||||
# If project has calendar
|
||||
if calendar_id:
|
||||
working_days = proj.compute_working_calendar(cr, uid, calendar_id)
|
||||
vacation = tuple(proj.leaves_resource(cr, uid, calendar_id))
|
||||
|
||||
# Dynamic Creation of tasks
|
||||
if calendar_id: # If project has calendar
|
||||
working_days = wkcal.compute_working_calendar(cr, uid, calendar_id)
|
||||
vacation = tuple(wkcal.compute_leaves(cr, uid, calendar_id))
|
||||
# Dynamic Creation of tasks
|
||||
i = 0
|
||||
for each_task in task_obj:
|
||||
for each_task in task_objs:
|
||||
hours = str(each_task.planned_hours / each_task.occupation_rate)+ 'H'
|
||||
if each_task.priority in priority_dict.keys():
|
||||
priorty = priority_dict[each_task.priority]
|
||||
if each_task.user_id:
|
||||
for resource_object in resource_objs:
|
||||
if resource_object.__name__ == each_task.user_id.name:
|
||||
task = tasks_resource(i, hours, priorty, resource_object)
|
||||
for resource in resources:
|
||||
if resource.__name__ == each_task.user_id.name:
|
||||
task = create_tasks(i, hours, priorty, resource)
|
||||
else:
|
||||
task = tasks_resource(i, hours, priorty)
|
||||
task = create_tasks(i, hours, priorty)
|
||||
i += 1
|
||||
|
||||
# Writing back the dates
|
||||
project = BalancedProject(Project)
|
||||
loop_no = 0
|
||||
# Write back the computed dates
|
||||
for t in project:
|
||||
s_date = t.start.to_datetime()
|
||||
e_date = t.end.to_datetime()
|
||||
if loop_no == 0:
|
||||
project_pool.write(cr, uid, [project_id], {'date' : e_date})
|
||||
project_obj.write(cr, uid, [project_id], {'date' : e_date})
|
||||
else:
|
||||
user_id = user_pool.search(cr, uid, [('name','=',t.booked_resource[0].__name__)])
|
||||
task_pool.write(cr, uid, [task_obj[loop_no-1].id], {'date_start' :s_date.strftime('%Y-%m-%d %H:%M:%S'), 'date_deadline' :e_date.strftime('%Y-%m-%d %H:%M:%S'), 'user_id' :user_id[0]}, context={'scheduler' :True})
|
||||
user_id = user_obj.search(cr, uid, [('name', '=', t.booked_resource[0].__name__)])
|
||||
task_pool.write(cr, uid, [task_objs[loop_no-1].id], {'date_start': s_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'date_deadline': e_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'user_id': user_id[0]},
|
||||
context={'scheduler': True
|
||||
})
|
||||
loop_no +=1
|
||||
return {}
|
||||
|
||||
@@ -137,14 +150,10 @@ class wizard_compute_tasks(wizard.interface):
|
||||
('compute', 'Compute')
|
||||
]},
|
||||
},
|
||||
|
||||
|
||||
'compute': {
|
||||
'actions': [_compute_date],
|
||||
'result': {'type':'form','arch':success_msg,'fields':{}, 'state':[('end', 'Ok')]},
|
||||
}
|
||||
}
|
||||
wizard_compute_tasks('wizard.compute.tasks')
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import pooler
|
||||
import datetime
|
||||
|
||||
def timeformat_convert(cr, uid, time_string, context={}):
|
||||
# Function to convert input time string:: 8.5 to output time string 8:30
|
||||
|
||||
split_list = str(time_string).split('.')
|
||||
hour_part = split_list[0]
|
||||
mins_part = split_list[1]
|
||||
round_mins = int(round(float(mins_part) * 60,-2))
|
||||
converted_string = hour_part + ':' + str(round_mins)[0:2]
|
||||
return converted_string
|
||||
|
||||
def leaves_resource(cr, uid, calendar_id, resource_id=False, resource_calendar=False):
|
||||
# To get the leaves for the resource_ids working on phase
|
||||
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
resource_leaves_pool = pool.get('resource.calendar.leaves')
|
||||
leaves = []
|
||||
if resource_id:
|
||||
resource_leave_ids = resource_leaves_pool.search(cr, uid, ['|', ('calendar_id','=',calendar_id), ('calendar_id','=',resource_calendar), ('resource_id','=',resource_id)])
|
||||
else:
|
||||
resource_leave_ids = resource_leaves_pool.search(cr, uid, [('calendar_id','=',calendar_id), ('resource_id','=',False)])
|
||||
res_leaves = resource_leaves_pool.read(cr, uid, resource_leave_ids, ['date_from', 'date_to'])
|
||||
for leave in range(len(res_leaves)):
|
||||
dt_start = datetime.datetime.strptime(res_leaves[leave]['date_from'], '%Y-%m-%d %H:%M:%S')
|
||||
dt_end = datetime.datetime.strptime(res_leaves[leave]['date_to'], '%Y-%m-%d %H:%M:%S')
|
||||
no = dt_end - dt_start
|
||||
[leaves.append((dt_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))]
|
||||
leaves.sort()
|
||||
return leaves
|
||||
|
||||
def compute_working_calendar(cr, uid, calendar_id):
|
||||
# To change the format of working calendar to bring it into 'faces' format
|
||||
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
resource_week_pool = pool.get('resource.calendar.week')
|
||||
time_range = "8:00-8:00"
|
||||
non_working = ""
|
||||
wk = {"0":"mon","1":"tue","2":"wed","3":"thu","4":"fri","5":"sat","6":"sun"}
|
||||
wk_days = {}
|
||||
wk_time = {}
|
||||
wktime_list = []
|
||||
wktime_cal = []
|
||||
week_ids = resource_week_pool.search(cr, uid, [('calendar_id','=',calendar_id)])
|
||||
week_obj = resource_week_pool.read(cr, uid, week_ids, ['dayofweek', 'hour_from', 'hour_to'])
|
||||
|
||||
# Converting time formats into appropriate format required
|
||||
# and creating a list like [('mon', '8:00-12:00'), ('mon', '13:00-18:00')]
|
||||
for week in week_obj:
|
||||
res_str = ""
|
||||
if wk.has_key(week['dayofweek']):
|
||||
day = wk[week['dayofweek']]
|
||||
wk_days[week['dayofweek']] = wk[week['dayofweek']]
|
||||
|
||||
hour_from_str = timeformat_convert(cr, uid, week['hour_from'])
|
||||
hour_to_str = timeformat_convert(cr, uid, week['hour_to'])
|
||||
res_str = hour_from_str + '-' + hour_to_str
|
||||
wktime_list.append((day, res_str))
|
||||
|
||||
# Converting it to format like [('mon', '8:00-12:00', '13:00-18:00')]
|
||||
for item in wktime_list:
|
||||
if wk_time.has_key(item[0]):
|
||||
wk_time[item[0]].append(item[1])
|
||||
else:
|
||||
wk_time[item[0]] = [item[0]]
|
||||
wk_time[item[0]].append(item[1])
|
||||
|
||||
for k,v in wk_time.items():
|
||||
wktime_cal.append(tuple(v))
|
||||
|
||||
# For non working days adding [('tue,wed,fri,sat,sun', '8:00-8:00')]
|
||||
for k,v in wk_days.items():
|
||||
if wk.has_key(k):
|
||||
wk.pop(k)
|
||||
for v in wk.itervalues():
|
||||
non_working += v + ','
|
||||
if non_working:
|
||||
wktime_cal.append((non_working[:-1], time_range))
|
||||
|
||||
return wktime_cal
|
||||
@@ -23,45 +23,53 @@ import wizard
|
||||
import pooler
|
||||
from tools.translate import _
|
||||
import datetime
|
||||
|
||||
from resource.faces import *
|
||||
from new import classobj
|
||||
import operator
|
||||
import time
|
||||
import project_resource as proj
|
||||
|
||||
import working_calendar as wkcal
|
||||
|
||||
success_msg = """<?xml version="1.0" ?>
|
||||
<form string="Compute Scheduling of Tasks">
|
||||
<label string="Task Scheduling completed successfully."/>
|
||||
</form>"""
|
||||
|
||||
def resource_list(cr, uid, phase):
|
||||
# To create resources which are the Project Members
|
||||
class wizard_schedule_task(wizard.interface):
|
||||
|
||||
def _create_resources(self, cr, uid, phase, context={}):
|
||||
"""
|
||||
Return a list of Resource Class objects for the resources allocated to the phase.
|
||||
"""
|
||||
resource_objs = []
|
||||
for resource in phase.resource_ids:
|
||||
res = resource.resource_id
|
||||
leaves = []
|
||||
resource_eff = res.time_efficiency
|
||||
resource_cal = res.calendar_id.id
|
||||
wktime_cal = proj.compute_working_calendar(cr, uid, resource_cal)
|
||||
leaves = proj.leaves_resource(cr, uid, phase.project_id.resource_calendar_id.id or False , res.id, resource_cal)
|
||||
resource_objs.append(classobj(str(res.user_id.name), (Resource,), {'__doc__' : res.user_id.name, '__name__' : res.user_id.name, 'vacation' : tuple(leaves), 'efficiency' : resource_eff}))
|
||||
if resource_cal:
|
||||
leaves = wkcal.compute_leaves(cr, uid, phase.project_id.resource_calendar_id.id or False, res.id, resource_cal)
|
||||
resource_objs.append(classobj(str(res.user_id.name), (Resource,),
|
||||
{'__doc__': res.user_id.name,
|
||||
'__name__': res.user_id.name,
|
||||
'vacation': tuple(leaves),
|
||||
'efficiency': resource_eff
|
||||
}))
|
||||
return resource_objs
|
||||
|
||||
class wizard_schedule_task(wizard.interface):
|
||||
|
||||
def _compute_date(self, cr, uid, data, context):
|
||||
"""
|
||||
Schedule the tasks according to resource available and priority.
|
||||
"""
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
phase_pool = pool.get('project.phase')
|
||||
task_pool = pool.get('project.task')
|
||||
user_pool = pool.get('res.users')
|
||||
phase = phase_pool.browse(cr, uid, data['id'])
|
||||
task_ids = map(lambda x : x.id, (filter(lambda x : x.state in ['open','draft','pending'] , phase.task_ids)))
|
||||
|
||||
phase_obj = pool.get('project.phase')
|
||||
task_obj = pool.get('project.task')
|
||||
user_obj = pool.get('res.users')
|
||||
phase = phase_obj.browse(cr, uid, data['id'], context=context)
|
||||
task_ids = map(lambda x : x.id, (filter(lambda x : x.state in ['open', 'draft', 'pending'] , phase.task_ids)))
|
||||
if task_ids:
|
||||
task_ids.sort()
|
||||
tasks = task_pool.browse(cr, uid, task_ids)
|
||||
wktime_cal = []
|
||||
tasks = task_obj.browse(cr, uid, task_ids, context=context)
|
||||
start_date = str(phase.date_start)[:-9]
|
||||
if not phase.date_start:
|
||||
if not phase.project_id.date_start:
|
||||
@@ -70,11 +78,10 @@ class wizard_schedule_task(wizard.interface):
|
||||
start_date = phase.project_id.date_start
|
||||
date_start = datetime.datetime.strftime(datetime.datetime.strptime(start_date, "%Y-%m-%d"), "%Y-%m-%d %H:%M")
|
||||
calendar_id = phase.project_id.resource_calendar_id.id
|
||||
resource_objs = resource_list(cr, uid, phase)
|
||||
priority_dict = {'0' :1000,'1' :800,'2' :500,'3' :300,'4' :100}
|
||||
|
||||
# To create dynamic no of tasks with the resource specified
|
||||
def tasks_resource(j, eff, priorty=500, obj=False):
|
||||
resources = self._create_resources(cr, uid, phase)
|
||||
priority_dict = {'0': 1000, '1': 800, '2': 500, '3': 300, '4': 100}
|
||||
# Create dynamic no of tasks with the resource specified
|
||||
def create_tasks(j, eff, priorty=500, obj=False):
|
||||
def task():
|
||||
"""
|
||||
task is a dynamic method!
|
||||
@@ -87,44 +94,47 @@ class wizard_schedule_task(wizard.interface):
|
||||
task.__name__ = "task%d" %j
|
||||
return task
|
||||
|
||||
# Creating the project with all the tasks and resources
|
||||
# Create a 'Faces' project with all the tasks and resources
|
||||
def Project():
|
||||
title = "Test Project"
|
||||
title = "Project"
|
||||
start = date_start
|
||||
resource = reduce(operator.or_, resource_objs)
|
||||
try:
|
||||
resource = reduce(operator.or_, resources)
|
||||
except:
|
||||
raise wizard.except_wizard(_('ResourceError'), _('Phase must have resources assigned !'))
|
||||
minimum_time_unit = 1
|
||||
|
||||
# If project has calendar
|
||||
if calendar_id:
|
||||
working_days = proj.compute_working_calendar(cr, uid, calendar_id)
|
||||
vacation = tuple(proj.leaves_resource(cr, uid, calendar_id))
|
||||
|
||||
# Dynamic Creation of tasks
|
||||
if calendar_id: # If project has working calendar
|
||||
working_days = wkcal.compute_working_calendar(cr, uid, calendar_id)
|
||||
vacation = tuple(wkcal.compute_leaves(cr, uid, calendar_id))
|
||||
# Dynamic creation of tasks
|
||||
i = 0
|
||||
for each_task in tasks:
|
||||
hours = str(each_task.planned_hours / each_task.occupation_rate)+ 'H'
|
||||
if each_task.priority in priority_dict.keys():
|
||||
priorty = priority_dict[each_task.priority]
|
||||
if each_task.user_id:
|
||||
for resource_object in resource_objs:
|
||||
if resource_object.__name__ == each_task.user_id.name:
|
||||
task = tasks_resource(i, hours, priorty, resource_object)
|
||||
for resource in resources:
|
||||
if resource.__name__ == each_task.user_id.name:
|
||||
task = create_tasks(i, hours, priorty, resource)
|
||||
else:
|
||||
task = tasks_resource(i, hours, priorty)
|
||||
task = create_tasks(i, hours, priorty)
|
||||
i += 1
|
||||
|
||||
# Writing back the dates
|
||||
project = BalancedProject(Project)
|
||||
loop_no = 0
|
||||
# Write back the computed dates
|
||||
for t in project:
|
||||
s_date = t.start.to_datetime()
|
||||
e_date = t.end.to_datetime()
|
||||
if loop_no > 0:
|
||||
user_id = user_pool.search(cr, uid, [('name','=',t.booked_resource[0].__name__)])
|
||||
task_pool.write(cr, uid, [tasks[loop_no-1].id], {'date_start' :s_date.strftime('%Y-%m-%d %H:%M:%S'), 'date_deadline' :e_date.strftime('%Y-%m-%d %H:%M:%S'), 'user_id' :user_id[0]}, context={'scheduler' :True})
|
||||
user_id = user_obj.search(cr, uid, [('name', '=', t.booked_resource[0].__name__)])
|
||||
task_obj.write(cr, uid, [tasks[loop_no-1].id], {'date_start': s_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'date_deadline': e_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'user_id': user_id[0]},
|
||||
context={'scheduler': True
|
||||
})
|
||||
loop_no +=1
|
||||
return {}
|
||||
|
||||
states = {
|
||||
'init': {
|
||||
'actions': [_compute_date],
|
||||
@@ -132,6 +142,4 @@ class wizard_schedule_task(wizard.interface):
|
||||
}
|
||||
}
|
||||
wizard_schedule_task('phase.schedule.tasks')
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import pooler
|
||||
import datetime
|
||||
|
||||
def convert_timeformat(cr, uid, time_string, context={}):
|
||||
""" Convert input time string: 8.5 to output time string 8:30."""
|
||||
|
||||
split_list = str(time_string).split('.')
|
||||
hour_part = split_list[0]
|
||||
mins_part = split_list[1]
|
||||
round_mins = int(round(float(mins_part) * 60,-2))
|
||||
converted_string = hour_part + ':' + str(round_mins)[0:2]
|
||||
return converted_string
|
||||
|
||||
def compute_leaves(cr, uid, calendar_id, resource_id=False, resource_calendar=False):
|
||||
|
||||
"""Compute the leaves from the working calendar of the resource.
|
||||
|
||||
Arguements: calendar_id -- working calendar of the project
|
||||
resource_id -- resource working on phase/task
|
||||
resource_calendar -- working calendar of the resource
|
||||
|
||||
"""
|
||||
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
resource_calendar_leaves_obj = pool.get('resource.calendar.leaves')
|
||||
leave_list = []
|
||||
if resource_id:
|
||||
leave_ids = resource_calendar_leaves_obj.search(cr, uid, ['|', ('calendar_id', '=', calendar_id),
|
||||
('calendar_id', '=', resource_calendar),
|
||||
('resource_id', '=', resource_id)
|
||||
])
|
||||
else:
|
||||
leave_ids = resource_calendar_leaves_obj.search(cr, uid, [('calendar_id', '=', calendar_id),
|
||||
('resource_id', '=', False)
|
||||
])
|
||||
leaves = resource_calendar_leaves_obj.read(cr, uid, leave_ids, ['date_from', 'date_to'])
|
||||
for i in range(len(leaves)):
|
||||
dt_start = datetime.datetime.strptime(leaves[i]['date_from'], '%Y-%m-%d %H:%M:%S')
|
||||
dt_end = datetime.datetime.strptime(leaves[i]['date_to'], '%Y-%m-%d %H:%M:%S')
|
||||
no = dt_end - dt_start
|
||||
[leave_list.append((dt_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))]
|
||||
leave_list.sort()
|
||||
return leave_list
|
||||
|
||||
def compute_working_calendar(cr, uid, calendar_id):
|
||||
|
||||
"""Change the format of working calendar from 'Openerp' format to bring it into 'Faces' format.
|
||||
|
||||
Arguement: calendar_id -- working calendar of the project
|
||||
|
||||
"""
|
||||
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
resource_week_obj = pool.get('resource.calendar.week')
|
||||
time_range = "8:00-8:00"
|
||||
non_working = ""
|
||||
week_days = {"0": "mon", "1": "tue", "2": "wed","3": "thu", "4": "fri", "5": "sat", "6": "sun"}
|
||||
wk_days = {}
|
||||
wk_time = {}
|
||||
wktime_list = []
|
||||
wktime_cal = []
|
||||
week_ids = resource_week_obj.search(cr, uid, [('calendar_id', '=', calendar_id)])
|
||||
weeks = resource_week_obj.read(cr, uid, week_ids, ['dayofweek', 'hour_from', 'hour_to'])
|
||||
# Convert time formats into appropriate format required
|
||||
# and create a list like [('mon', '8:00-12:00'), ('mon', '13:00-18:00')]
|
||||
for week in weeks:
|
||||
res_str = ""
|
||||
if week_days.has_key(week['dayofweek']):
|
||||
day = week_days[week['dayofweek']]
|
||||
wk_days[week['dayofweek']] = week_days[week['dayofweek']]
|
||||
hour_from_str = convert_timeformat(cr, uid, week['hour_from'])
|
||||
hour_to_str = convert_timeformat(cr, uid, week['hour_to'])
|
||||
res_str = hour_from_str + '-' + hour_to_str
|
||||
wktime_list.append((day, res_str))
|
||||
# Convert into format like [('mon', '8:00-12:00', '13:00-18:00')]
|
||||
for item in wktime_list:
|
||||
if wk_time.has_key(item[0]):
|
||||
wk_time[item[0]].append(item[1])
|
||||
else:
|
||||
wk_time[item[0]] = [item[0]]
|
||||
wk_time[item[0]].append(item[1])
|
||||
for k,v in wk_time.items():
|
||||
wktime_cal.append(tuple(v))
|
||||
# Add for the non-working days like: [('sat, sun', '8:00-8:00')]
|
||||
for k,v in wk_days.items():
|
||||
if week_days.has_key(k):
|
||||
week_days.pop(k)
|
||||
for v in week_days.itervalues():
|
||||
non_working += v + ','
|
||||
if non_working:
|
||||
wktime_cal.append((non_working[:-1], time_range))
|
||||
return wktime_cal
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
@@ -19,7 +19,6 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import task_burndown
|
||||
import sprint_burndown
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -50,9 +50,6 @@ class report_tasks(report_int):
|
||||
|
||||
cr.execute('select id,date_start,date_stop from scrum_sprint where id=%s', (datas['id'],))
|
||||
for (id,date_start,date_stop) in cr.fetchall():
|
||||
date_to_int = lambda x: int(x.ticks())
|
||||
int_to_date = lambda x: '/a60{}'+DateTime.localtime(x).strftime('%d/%m/%Y')
|
||||
|
||||
cr.execute('select id from project_task where product_backlog_id in(select id from scrum_product_backlog where sprint_id=%s)', (id,))
|
||||
|
||||
ids = map(lambda x: x[0], cr.fetchall())
|
||||
@@ -71,7 +68,9 @@ class report_tasks(report_int):
|
||||
if (not result) or result[-1]<>res:
|
||||
result.append(res)
|
||||
return result
|
||||
|
||||
|
||||
guideline__data=[(datas[0][0],max_hour), (datas[-1][0],0)]
|
||||
|
||||
ar = area.T(x_grid_style=line_style.gray50_dash1,
|
||||
x_axis=axis.X(label="Date", format=int_to_date),
|
||||
y_axis=axis.Y(label="Burndown Chart - Planned Hours"),
|
||||
@@ -80,7 +79,14 @@ class report_tasks(report_int):
|
||||
y_range = (0,max_hour),
|
||||
legend = None,
|
||||
size = (680,450))
|
||||
ar.add_plot(line_plot.T(data=datas))
|
||||
ar.add_plot(line_plot.T(data=guideline__data, line_style=line_style.red))
|
||||
ar.add_plot(line_plot.T(data=datas, line_style=line_style.green))
|
||||
|
||||
entr1 = pychart.legend.Entry(label="guideline", line_style=line_style.red)
|
||||
entr2 = pychart.legend.Entry(label="burndownchart",line_style=line_style.green)
|
||||
legend = pychart.legend.T(nr_rows=2, inter_row_sep=5)
|
||||
legend.draw(ar,[entr1,entr2],canv)
|
||||
|
||||
ar.draw(canv)
|
||||
canv.close()
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<report auto="False" model="project.task" name="project.tasks.burndown" string="Burndown Chart"/>
|
||||
|
||||
<data>
|
||||
<report auto="False" model="scrum.sprint" name="scrum.sprint.burndown" string="Burndown Chart"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
Reference in New Issue
Block a user