[REF] hr_recruitment stage management cleaning

Using stages and states is not easy to manage. Indeed those two fields mess with the same basic concept. This task remove the state on hr.applicant model, leaving only stages.

Main features:
- removed base_stage inheritance on hr.applicant model
- removed state field on hr.applicant model
- removed state field on hr.recruitment.stage model
- removed date field on hr.applicant model, not used anywhere; other date fields are considered as sufficient
- added date_last_stage_update on hr.applicant model, holding the date of the last stage change, allowing reporting and analysis on time spend in stages
- date_open is set when assigning an user to the applicant; date_open now means assignation date

This allows to remove nearly all buttons on the form view. As the statusbar is clickable, the only remaining action is to create an employee from the applicant, which is a link button in the form view.

Subtypes have been updated :
- new applicant: stage_id.sequence=1
- other: stage change

Misc:
- [IMP] views accordingly;
- [IMP] reports accordingly, added date_last_stage_update in reports;
- [IMP] subtypes: New, Stage Changed and Hired are kept;
- [REM] removed some tests because they are not applicable anymore;
- [DOC] doc directory + changelog + stage explanation;
- [REM] removed hired.employee wizard

bzr revid: tde@openerp.com-20130709115904-o9ac29fesu3lreol
This commit is contained in:
Thibault Delavallée 2013-07-09 13:59:04 +02:00
commit 9e2c08219d
14 changed files with 184 additions and 312 deletions

View File

@ -47,7 +47,6 @@ You can define the different phases of interviews and easily rate the applicant
'fetchmail',
],
'data': [
'wizard/hr_recruitment_employee_hired.xml',
'wizard/hr_recruitment_create_partner_job_view.xml',
'hr_recruitment_view.xml',
'hr_recruitment_menu.xml',

View File

@ -22,7 +22,7 @@
<field name="res_model">hr.applicant</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('state','in',('draft','open'))]</field>
<field name="domain">[('stage_id.fold', '!=', True)]</field>
<field name="view_id" ref="view_applicants_status_tree"/>
</record>

View File

@ -0,0 +1,16 @@
.. _changelog:
Changelog
=========
`trunk (saas-2)`
----------------
- Stage/state update
- ``hr.applicant``: removed inheritance from ``base_stage`` class and removed
``state`` field. Added ``date_last_stage_update`` field holding last stage_id
modification. Removed ``date`` field not used anywhere. Updated reports.
- ``hr.recruitment.stage``: removed ``state`` field.
- Removed ``hired.employee`` wizard.

View File

@ -0,0 +1,22 @@
=====================
HR Recruitment DevDoc
=====================
Hr Recruitment module documentation
===================================
Documentation topics
''''''''''''''''''''
.. toctree::
:maxdepth: 1
stage_status.rst
Changelog
'''''''''
.. toctree::
:maxdepth: 1
changelog.rst

View File

@ -0,0 +1,54 @@
.. _stage_status:
Stage and Status
================
.. versionchanged:: 8.0 saas-2 state/stage cleaning
Stage
+++++
This revision removed the concept of state on hr.applicant objects. The ``state``
field has been totally removed and replaced by stages, using ``stage_id``. The
following models are impacted:
- ``hr.applicant`` now use only stages. However a convention still exists about
'New' stage. An applicant is consdered as ``new`` when it has the following
properties:
- ``stage_id and stage_id.sequence = 1``
- ``hr.recruitment.stage`` do not have any ``state`` field anymore.
- ``hr.recruitment.report`` do not have any ``state`` field anymore.
By default a newly created applicant be in a new stage. It means that it will
fetch the stage having ``sequence = 1``. Stage mangement is done using the
kanban view or the clikable statusbar. It is not done using buttons anymore.
Employee creation is still feasible directly from a link button in the form view.
Stage analysis
++++++++++++++
Stage analysis can be performed using the newly introduced ``date_last_stage_update``
datetime field. This field is updated everytime ``stage_id`` is updated.
``hr.recruitment.report`` model also uses the ``date_last_stage_update`` field.
This allows to group and analyse the time spend in the various stages.
Open / Assignation date
+++++++++++++++++++++++
The ``date_open`` field meaning has been updated. It is now set when the ``user_id``
(responsible) is set. It is therefore the assignation date.
Subtypes
++++++++
The following subtypes are triggered on ``hr.applicant``:
- ``mt_applicant_new``: new applicants. Condition: ``obj.stage_id and obj.stage_id.sequence == 1``
- ``mt_applicant_stage_changed``: stage changed. Condition: ``obj.stage_id and obj.stage_id.sequence != 1``
The following subtype is trigerred on ``hr.job``:
- ``mt_job_new_applicant``: new applicant in the job.

View File

@ -19,23 +19,13 @@
#
##############################################################################
import time
from openerp import tools
from openerp.addons.base_status.base_stage import base_stage
from datetime import datetime
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.tools import html2plaintext
AVAILABLE_STATES = [
('draft', 'New'),
('cancel', 'Refused'),
('open', 'In Progress'),
('pending', 'Pending'),
('done', 'Hired')
]
AVAILABLE_PRIORITIES = [
('', ''),
('5', 'Not Good'),
@ -62,13 +52,11 @@ class hr_recruitment_stage(osv.osv):
'name': fields.char('Name', size=64, required=True, translate=True),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of stages."),
'department_id':fields.many2one('hr.department', 'Specific to a Department', help="Stages of the recruitment process may be different per department. If this stage is common to all departments, keep this field empty."),
'state': fields.selection(AVAILABLE_STATES, 'Status', required=True, help="The related status for the stage. The status of your document will automatically change according to the selected stage. Example, a stage is related to the status 'Close', when your document reach this stage, it will be automatically closed."),
'fold': fields.boolean('Hide in views if empty', help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."),
'requirements': fields.text('Requirements'),
}
_defaults = {
'sequence': 1,
'state': 'draft',
'fold': False,
}
@ -87,18 +75,15 @@ class hr_recruitment_degree(osv.osv):
('name_uniq', 'unique (name)', 'The name of the Degree of Recruitment must be unique!')
]
class hr_applicant(base_stage, osv.Model):
class hr_applicant(osv.Model):
_name = "hr.applicant"
_description = "Applicant"
_order = "id desc"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_track = {
'state': {
'hr_recruitment.mt_applicant_hired': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
'hr_recruitment.mt_applicant_refused': lambda self, cr, uid, obj, ctx=None: obj.state == 'cancel',
},
'stage_id': {
'hr_recruitment.mt_stage_changed': lambda self, cr, uid, obj, ctx=None: obj.state not in ['done', 'cancel'],
'hr_recruitment.mt_applicant_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1,
'hr_recruitment.mt_applicant_stage_changed': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence != 1,
},
}
@ -109,7 +94,7 @@ class hr_applicant(base_stage, osv.Model):
def _get_default_stage_id(self, cr, uid, context=None):
""" Gives default stage_id """
department_id = self._get_default_department_id(cr, uid, context=context)
return self.stage_find(cr, uid, [], department_id, [('state', '=', 'draft')], context=context)
return self.stage_find(cr, uid, [], department_id, [('sequence', '=', '1')], context=context)
def _resolve_department_id_from_context(self, cr, uid, context=None):
""" Returns ID of department based on the value of 'default_department_id'
@ -197,20 +182,12 @@ class hr_applicant(base_stage, osv.Model):
'write_date': fields.datetime('Update Date', readonly=True),
'stage_id': fields.many2one ('hr.recruitment.stage', 'Stage', track_visibility='onchange',
domain="['|', ('department_id', '=', department_id), ('department_id', '=', False)]"),
'state': fields.related('stage_id', 'state', type="selection", store=True,
selection=AVAILABLE_STATES, string="Status", readonly=True,
help='The status is set to \'Draft\', when a case is created.\
If the case is in progress the status is set to \'Open\'.\
When the case is over, the status is set to \'Done\'.\
If the case needs to be reviewed then the status is \
set to \'Pending\'.'),
'categ_ids': fields.many2many('hr.applicant_category', string='Tags'),
'company_id': fields.many2one('res.company', 'Company'),
'user_id': fields.many2one('res.users', 'Responsible', track_visibility='onchange'),
# Applicant Columns
'date_closed': fields.datetime('Closed', readonly=True, select=True),
'date_open': fields.datetime('Opened', readonly=True, select=True),
'date': fields.datetime('Date'),
'date_open': fields.datetime('Assigned', readonly=True, select=True),
'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
'date_action': fields.date('Next Action Date'),
'title_action': fields.char('Next Action', size=64),
'priority': fields.selection(AVAILABLE_PRIORITIES, 'Appreciation'),
@ -234,35 +211,35 @@ class hr_applicant(base_stage, osv.Model):
'day_close': fields.function(_compute_day, string='Days to Close', \
multi='day_close', type="float", store=True),
'color': fields.integer('Color Index'),
'emp_id': fields.many2one('hr.employee', 'employee'),
'emp_id': fields.many2one('hr.employee', string='Employee',
help='Employee linked to the applicant.'),
'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True),
}
_defaults = {
'active': lambda *a: 1,
'user_id': lambda s, cr, uid, c: uid,
'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c),
'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
'department_id': lambda s, cr, uid, c: s._get_default_department_id(cr, uid, c),
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.applicant', context=c),
'color': 0,
'date_last_stage_update': fields.datetime.now(),
}
_group_by_full = {
'stage_id': _read_group_stage_ids
}
def onchange_job(self, cr, uid, ids, job, context=None):
if job:
job_record = self.pool.get('hr.job').browse(cr, uid, job, context=context)
def onchange_job(self, cr, uid, ids, job_id=False, context=None):
if job_id:
job_record = self.pool.get('hr.job').browse(cr, uid, job_id, context=context)
if job_record and job_record.department_id:
return {'value': {'department_id': job_record.department_id.id}}
return {}
def onchange_department_id(self, cr, uid, ids, department_id=False, context=None):
obj_recru_stage = self.pool.get('hr.recruitment.stage')
stage_ids = obj_recru_stage.search(cr, uid, ['|',('department_id','=',department_id),('department_id','=',False)], context=context)
stage_id = stage_ids and stage_ids[0] or False
def onchange_department_id(self, cr, uid, ids, department_id=False, stage_id=False, context=None):
if not stage_id:
stage_id = self.stage_find(cr, uid, [], department_id, [('sequence', '=', '1')], context=context)
return {'value': {'stage_id': stage_id}}
def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
@ -412,21 +389,20 @@ class hr_applicant(base_stage, osv.Model):
self.pool.get('hr.job').message_post(cr, uid, [applicant.job_id.id], body=_('Applicant <b>created</b>'), subtype="hr_recruitment.mt_job_new_applicant", context=context)
return obj_id
def case_open(self, cr, uid, ids, context=None):
"""
open Request of the applicant for the hr_recruitment
"""
res = super(hr_applicant, self).case_open(cr, uid, ids, context)
date = self.read(cr, uid, ids, ['date_open'])[0]
if not date['date_open']:
self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S'),})
return res
def write(self, cr, uid, ids, vals, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
# stage change: update date_last_stage_update
if 'stage_id' in vals:
vals['date_last_stage_update'] = fields.datetime.now()
# user_id change: update date_start
if vals.get('user_id'):
vals['date_start'] = fields.datetime.now()
def case_close(self, cr, uid, ids, context=None):
res = super(hr_applicant, self).case_close(cr, uid, ids, context)
return res
return super(hr_applicant, self).write(cr, uid, ids, vals, context=context)
def case_close_with_emp(self, cr, uid, ids, context=None):
def create_employee_from_applicant(self, cr, uid, ids, context=None):
""" Create an hr.employee from the hr.applicants """
if context is None:
context = {}
hr_employee = self.pool.get('hr.employee')
@ -436,17 +412,16 @@ class hr_applicant(base_stage, osv.Model):
for applicant in self.browse(cr, uid, ids, context=context):
address_id = contact_name = False
if applicant.partner_id:
address_id = self.pool.get('res.partner').address_get(cr,uid,[applicant.partner_id.id],['contact'])['contact']
contact_name = self.pool.get('res.partner').name_get(cr,uid,[applicant.partner_id.id])[0][1]
address_id = self.pool.get('res.partner').address_get(cr, uid, [applicant.partner_id.id], ['contact'])['contact']
contact_name = self.pool.get('res.partner').name_get(cr, uid, [applicant.partner_id.id])[0][1]
if applicant.job_id and (applicant.partner_name or contact_name):
applicant.job_id.write({'no_of_recruitment': applicant.job_id.no_of_recruitment - 1})
emp_id = hr_employee.create(cr,uid,{'name': applicant.partner_name or contact_name,
emp_id = hr_employee.create(cr, uid, {'name': applicant.partner_name or contact_name,
'job_id': applicant.job_id.id,
'address_home_id': address_id,
'department_id': applicant.department_id.id
})
self.write(cr, uid, [applicant.id], {'emp_id': emp_id}, context=context)
self.case_close(cr, uid, [applicant.id], context)
else:
raise osv.except_osv(_('Warning!'), _('You must define an Applied Job and a Contact Name for this applicant.'))
@ -457,26 +432,6 @@ class hr_applicant(base_stage, osv.Model):
dict_act_window['view_mode'] = 'form,tree'
return dict_act_window
def case_cancel(self, cr, uid, ids, context=None):
"""Overrides cancel for crm_case for setting probability
"""
res = super(hr_applicant, self).case_cancel(cr, uid, ids, context)
self.write(cr, uid, ids, {'probability': 0.0})
return res
def case_pending(self, cr, uid, ids, context=None):
"""Marks case as pending"""
res = super(hr_applicant, self).case_pending(cr, uid, ids, context)
self.write(cr, uid, ids, {'probability': 0.0})
return res
def case_reset(self, cr, uid, ids, context=None):
"""Resets case as draft
"""
res = super(hr_applicant, self).case_reset(cr, uid, ids, context)
self.write(cr, uid, ids, {'date_open': False, 'date_closed': False})
return res
def set_priority(self, cr, uid, ids, priority, *args):
"""Set applicant priority
"""

View File

@ -51,34 +51,29 @@
<field name="name">Doctoral Degree</field>
<field name="sequence">4</field>
</record>
<record model="hr.recruitment.stage" id="stage_job1">
<field name="name">Initial Qualification</field>
<field name="state">draft</field>
<field name="sequence">1</field>
</record>
<record model="hr.recruitment.stage" id="stage_job2">
<field name="name">First Interview</field>
<field name="state">open</field>
<field name="sequence">2</field>
</record>
<record model="hr.recruitment.stage" id="stage_job3">
<field name="name">Second Interview</field>
<field name="state">open</field>
<field name="sequence">3</field>
</record>
<record model="hr.recruitment.stage" id="stage_job4">
<field name="name">Contract Proposed</field>
<field name="state">pending</field>
<field name="sequence">4</field>
</record>
<record model="hr.recruitment.stage" id="stage_job5">
<field name="name">Contract Signed</field>
<field name="state">done</field>
<field name="sequence">5</field>
</record>
<record model="hr.recruitment.stage" id="stage_job6">
<field name="name">Refused</field>
<field name="state">cancel</field>
<field name="sequence">6</field>
<field name="fold" eval="True"/>
</record>
@ -467,24 +462,24 @@
<field name="res_model">hr.job</field>
</record>
<!-- Applicant-related subtypes for messaging / Chatter -->
<record id="mt_stage_changed" model="mail.message.subtype">
<record id="mt_applicant_new" model="mail.message.subtype">
<field name="name">New Applicant</field>
<field name="res_model">hr.applicant</field>
<field name="default" eval="False"/>
<field name="description">Applicant created</field>
</record>
<record id="mt_applicant_stage_changed" model="mail.message.subtype">
<field name="name">Stage Changed</field>
<field name="res_model">hr.applicant</field>
<field name="default" eval="False"/>
<field name="description">Stage changed</field>
</record>
<record id="mt_applicant_hired" model="mail.message.subtype">
<record id="mt_applicant_employee" model="mail.message.subtype">
<field name="name">Applicant Hired</field>
<field name="res_model">hr.applicant</field>
<field name="default" eval="False"/>
<field name="description">Applicant hired</field>
</record>
<record id="mt_applicant_refused" model="mail.message.subtype">
<field name="name">Applicant Refused</field>
<field name="res_model">hr.applicant</field>
<field name="default" eval="False"/>
<field name="description">Applicant refused</field>
</record>
<!-- Applicant Categories(Tag) -->
<record id="tag_applicant_reserve" model="hr.applicant_category">
<field name="name">Reserve</field>

View File

@ -39,9 +39,10 @@
<field name="name">Applicants</field>
<field name="model">hr.applicant</field>
<field name="arch" type="xml">
<tree string="Applicants" fonts="bold:message_unread==True" colors="grey:state in ('cancel','done');blue:state=='pending'">
<tree string="Applicants" fonts="bold:message_unread==True">
<field name="message_unread" invisible="1"/>
<field name="create_date"/>
<field name="date_last_stage_update" invisible="1"/>
<field name="name" string="Subject"/>
<field name="partner_name"/>
<field name="email_from"/>
@ -58,7 +59,6 @@
<field name="availability" invisible="1"/>
<field name="department_id" invisible="context.get('invisible_department', True)"/>
<field name="user_id"/>
<field name="state" invisible="1"/>
</tree>
</field>
</record>
@ -69,13 +69,7 @@
<field name="arch" type="xml">
<form string="Jobs - Recruitment Form" version="7.0">
<header>
<button name="case_close_with_emp" string="Hire &amp; Create Employee" type="object"
class="oe_highlight"
attrs="{'invisible':['|', ('emp_id','!=',False), ('state','=','cancel')]}"/>
<button name="case_cancel" string="Refuse" type="object"
states="draft,open,pending" class="oe_highlight"/>
<field name="stage_id" widget="statusbar" clickable="True"/>
<field name="emp_id" invisible="1"/>
</header>
<sheet>
<div class="oe_right oe_button_box">
@ -93,9 +87,12 @@
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
<label for="partner_name" class="oe_edit_only"/>
<h2>
<h2 style="display: inline-block;">
<field name="partner_name" class="oe_inline"/>
</h2>
<button string="Create Employee" name="create_employee_from_applicant" type="object"
class="oe_link oe_inline" style="margin-left: 8px;"
attrs="{'invisible': [('emp_id', '!=', False)]}"/>
</div>
<group>
<group>
@ -115,7 +112,6 @@
<field name="title_action" class="oe_inline" placeholder="e.g. Call for interview"/>
</div>
<field name="priority"/>
<field name="state" invisible="1"/>
<field name="source_id"/>
<field name="reference"/>
</group>
@ -123,7 +119,7 @@
<field name="survey" invisible="1"/>
<field name="response" invisible="1"/>
<field name="job_id" on_change="onchange_job(job_id)"/>
<field name="department_id" on_change="onchange_department_id(department_id)"/>
<field name="department_id" on_change="onchange_department_id(department_id, stage_id)"/>
<label for="availability"/>
<div>
<field name="availability" class="oe_inline"/> <label string="Day(s)" class="oe_inline"/>
@ -142,6 +138,7 @@
<span class="oe_inline" attrs="{'invisible':[('salary_proposed_extra','=',False)]}"> + </span>
<field name="salary_proposed_extra" class="oe_inline" placeholder="Extra advantages..."/>
</div>
<field name="emp_id" readonly="1"/>
</group>
</group>
<group>
@ -165,7 +162,7 @@
<graph string="Cases By Stage and Estimates" type="bar" orientation="vertical">
<field name="job_id"/>
<field name="salary_expected" operator="+"/>
<field name="state" group="True"/>
<field name="stage_id" group="True"/>
</graph>
</field>
</record>
@ -174,23 +171,22 @@
<field name="name">Jobs - Recruitment Search</field>
<field name="model">hr.applicant</field>
<field name="arch" type="xml">
<search string="Search Jobs">
<search string="Search Applicants">
<field name="partner_name" filter_domain="['|','|',('name','ilike',self),('partner_name','ilike',self),('email_from','ilike',self)]" string="Subject / Applicant"/>
<filter string="Unassigned" domain="[('user_id', '=', False)]"/>
<filter string="My" domain="[('user_id', '=', uid)]"/>
<separator/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter string="New" domain="[('state','=','draft')]" help="All Initial Jobs"/>
<filter string="In Progress" domain="[('state','=','open')]" help="Open Jobs"/>
<filter string="Pending" domain="[('state','=','pending')]" help="Pending Jobs"/>
<separator/>
<filter string="Unassigned Recruitments" domain="[('user_id','=',False)]" help="Unassigned Recruitments"/>
<separator/>
<filter string="Next Actions" context="{'invisible_next_action':False, 'invisible_next_date':False}"
domain="[('date_action','&lt;&gt;',False)]" help="Filter and view on next actions and date"/>
<field name="job_id"/>
<field name="department_id"/>
<field name="user_id"/>
<field name="stage_id" domain="[]"/>
<separator/>
<field name="categ_ids"/>
<separator/>
<group expand="0" string="Group By...">
<filter string="Responsible" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Department" domain="[]" context="{'group_by':'department_id'}"/>
@ -201,6 +197,7 @@
<filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Source" domain="[]" context="{'group_by':'source_id'}"/>
<filter string="Creation Date" domain="[]" context="{'group_by':'create_date'}"/>
<filter string="Last Stage Update" context="{'group_by':'date_last_stage_update'}"/>
</group>
</search>
</field>
@ -343,7 +340,6 @@
<field name="sequence" invisible="1"/>
<field name="name"/>
<field name="department_id"/>
<field name="state"/>
</tree>
</field>
</record>
@ -361,7 +357,6 @@
<field name="department_id"/>
</group>
<group>
<field name="state"/>
<field name="sequence"/>
<field name="fold"/>
</group>

View File

@ -18,30 +18,24 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import tools
from openerp.osv import fields,osv
from openerp.osv import fields, osv
from .. import hr_recruitment
from openerp.addons.decimal_precision import decimal_precision as dp
AVAILABLE_STATES = [
('draft','New'),
('open','Open'),
('cancel', 'Refused'),
('done', 'Hired'),
('pending','Pending')
]
class hr_recruitment_report(osv.osv):
class hr_recruitment_report(osv.Model):
_name = "hr.recruitment.report"
_description = "Recruitments Statistics"
_auto = False
_rec_name = 'date'
_rec_name = 'date_create'
_order = 'date_create desc'
_columns = {
'user_id': fields.many2one('res.users', 'User', readonly=True),
'nbr': fields.integer('# of Applications', readonly=True),
'state': fields.selection(AVAILABLE_STATES, 'Status', size=16, readonly=True),
# TDE TODO: use MONTHS
'month':fields.selection([('01', 'January'), ('02', 'February'), \
('03', 'March'), ('04', 'April'),\
('05', 'May'), ('06', 'June'), \
@ -51,7 +45,8 @@ class hr_recruitment_report(osv.osv):
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'day': fields.char('Day', size=128, readonly=True),
'year': fields.char('Year', size=4, readonly=True),
'date': fields.date('Date', readonly=True),
'date_create': fields.date('Create Date', readonly=True),
'date_last_stage_update': fields.datetime('Last Stage Update', readonly=True),
'date_closed': fields.date('Closed', readonly=True),
'job_id': fields.many2one('hr.job', 'Applied Job',readonly=True),
'stage_id': fields.many2one ('hr.recruitment.stage', 'Stage'),
@ -67,19 +62,19 @@ class hr_recruitment_report(osv.osv):
'delay_close': fields.float('Avg. Delay to Close', digits=(16,2), readonly=True, group_operator="avg",
help="Number of Days to close the project issue"),
}
_order = 'date desc'
def init(self, cr):
tools.drop_view_if_exists(cr, 'hr_recruitment_report')
cr.execute("""
create or replace view hr_recruitment_report as (
select
min(s.id) as id,
date_trunc('day',s.create_date) as date,
date_trunc('day',s.create_date) as date_create,
date_trunc('day',s.date_closed) as date_closed,
date_trunc('day',s.date_last_stage_update) as date_last_stage_update,
to_char(s.create_date, 'YYYY') as year,
to_char(s.create_date, 'MM') as month,
to_char(s.create_date, 'YYYY-MM-DD') as day,
s.state,
s.partner_id,
s.company_id,
s.user_id,
@ -93,7 +88,7 @@ class hr_recruitment_report(osv.osv):
(sum(salary_proposed)/count(*)) as salary_prop_avg,
sum(salary_expected) as salary_exp,
(sum(salary_expected)/count(*)) as salary_exp_avg,
extract('epoch' from (s.date_closed-s.create_date))/(3600*24) as delay_close,
extract('epoch' from (s.date_closed-s.create_date))/(3600*24) as delay_close,
count(*) as nbr
from hr_applicant s
group by
@ -105,7 +100,7 @@ class hr_recruitment_report(osv.osv):
s.date_open,
s.create_date,
s.date_closed,
s.state,
s.date_last_stage_update,
s.partner_id,
s.company_id,
s.user_id,

View File

@ -6,7 +6,7 @@
<field name="model">hr.recruitment.report</field>
<field name="arch" type="xml">
<tree string="Recruitment Analysis" create="false">
<field name="date" invisible="1"/>
<field name="date_create" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="job_id"/>
<field name="stage_id" invisible="1" />
@ -14,10 +14,10 @@
<field name="type_id" invisible="1"/>
<field name="partner_id" invisible="1"/>
<field name="company_id" groups="base.group_multi_company" invisible="1"/>
<field name="state" invisible="1"/>
<field name="year" invisible="1"/>
<field name="day" invisible="1"/>
<field name="month" invisible="1"/>
<field name="date_last_stage_update" invisible="1"/>
<field name="nbr" sum="# of Applications"/>
<field name="available" sum="Available"/>
<field name="salary_exp" sum="Expected Salary"/>
@ -46,34 +46,33 @@
<field name="model">hr.recruitment.report</field>
<field name="arch" type="xml">
<search string="Recruitment Analysis">
<filter string="New" icon="terp-document-new" domain="[('state','=','draft')]" help = "Draft recruitment"/>
<filter string="In progress" icon="terp-camera_test" domain="[('state', '=' ,'open')]" help = "In progress recruitment"/>
<filter string="Pending" icon="terp-gtk-media-pause" domain="[('state','=','pending')]" help = "Pending recruitment"/>
<filter string="Hired" icon="terp-camera_test" domain="[('state','=','done')]" help = "Hired employees"/>
<separator/>
<filter icon="terp-personal" string="My Recruitment" help="My Recruitment" domain="[('user_id','=',uid)]"/>
<field name="job_id"/>
<field name="department_id"/>
<field name="user_id" string="Responsible"/>
<field name="user_id"/>
<filter string="New" domain="[('sequence', '=', 1)]"/>
<separator/>
<filter string="Unassigned" domain="[('user_id', '=', False)]"/>
<filter string="My" domain="[('user_id', '=', uid)]"/>
<separator/>
<group expand="0" string="Extended Filters...">
<!--field name="job_id"/-->
<field name="priority"/>
<field name="stage_id"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="date" string="Creation Date"/>
<field name="date_create"/>
<field name="date_closed"/>
</group>
<group expand="1" string="Group By ...">
<filter string="Responsible" name='User' icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Company" icon="terp-go-home" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}" />
<filter string="Jobs" name="job" icon="terp-gtk-select-all" domain="[]" context="{'group_by':'job_id'}"/>
<filter string="Department" name="department" icon="terp-personal+" domain="[]" context="{'group_by':'department_id'}"/>
<filter string="Degree" name="degree" icon="terp-gtk-select-all" domain="[]" context="{'group_by':'type_id'}"/>
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}" />
<filter string="Day" name = "day" icon="terp-go-today" domain="[]" context="{'group_by':'day'}" help="Creation Date"/>
<filter string="Month" icon="terp-go-month" domain="[]" context="{'group_by':'month'}" help="Creation Date"/>
<filter string="Year" icon="terp-go-year" domain="[]" context="{'group_by':'year'}" help="Creation Date"/>
<filter string="Responsible" name='User' context="{'group_by':'user_id'}"/>
<filter string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Partner" context="{'group_by':'partner_id'}" />
<filter string="Jobs" name="job" context="{'group_by':'job_id'}"/>
<filter string="Department" name="department" context="{'group_by':'department_id'}"/>
<filter string="Degree" name="degree" context="{'group_by':'type_id'}"/>
<filter string="Stage" context="{'group_by':'stage_id'}" />
<filter string="Last Stage Update" context="{'group_by':'date_last_stage_update'}" />
<filter string="Day" name="day" context="{'group_by':'day'}" help="Creation Date"/>
<filter string="Month" context="{'group_by':'month'}" help="Creation Date"/>
<filter string="Year" context="{'group_by':'year'}" help="Creation Date"/>
</group>
</search>
</field>

View File

@ -17,33 +17,8 @@
resume_ids = self.pool.get('ir.attachment').search(cr, uid, [('datas_fname','=','resume.pdf'),('res_model','=',self._name),('res_id','=',applicant.id)])
assert applicant.name == "Application for the post of Jr.application Programmer.", "Applicant name does not match."
assert applicant.stage_id.id == ref('hr_recruitment.stage_job1'), "Stage should be 'Initial qualification' and is '%s'." % (applicant.stage_id.name)
assert applicant.state == "draft", "Applicant state should be 'draft'."
assert applicant.stage_id.sequence == 1, "Applicant stage sequence should be 1."
assert len(resume_ids), "Resume is not attached."
-
I refuse the applicant (hr_case_programmer)
-
!python {model: hr.applicant}: |
self.case_cancel(cr, uid, [ref("hr_case_programmer")])
-
I check the details of the refused applicant.
-
!python {model: hr.applicant}: |
applicant = self.browse(cr, uid, ref("hr_case_programmer"), context=context)
assert applicant.stage_id.id == ref('hr_recruitment.stage_job6'), "Stage should be 'Refused' and is %s." % (applicant.stage_id.name)
assert applicant.state == 'cancel', "Applicant is not in 'cancel' state."
-
I reset and re-open the previously refused applicant.
-
!python {model: hr.applicant}: |
self.case_reset(cr, uid, [ref("hr_case_programmer")])
self.case_open(cr, uid, [ref("hr_case_programmer")])
-
I check the details of the re-opened applicant.
-
!python {model: hr.applicant}: |
applicant = self.browse(cr, uid, ref("hr_case_programmer"), context=context)
assert applicant.stage_id.id == ref('hr_recruitment.stage_job2'), "Stage should be 'First interview' and is '%s'." % (applicant.stage_id.name)
assert applicant.state == "open", "Applicant state should be 'open'."
-
I assign the Job position to the applicant
-
@ -54,21 +29,6 @@
-
!python {model: hr.applicant}: |
self.action_makeMeeting(cr, uid, [ref('hr_case_programmer')])
-
I check Initial Qualification of applicant.
-
!python {model: hr.applicant}: |
self.stage_next(cr, uid, [ref('hr_case_programmer')])
-
I schedule First Interview of applicant.
-
!python {model: hr.applicant}: |
self.stage_next(cr, uid, [ref('hr_case_programmer')])
-
On a successful First Interview of applicant, I schedule Second Interview.
-
!python {model: hr.applicant}: |
self.stage_next(cr, uid, [ref('hr_case_programmer')])
-
Applicant fillup the answer of the interview quetion.
-
@ -83,40 +43,8 @@
I print Applicant fill up the interview quetion
-
!python {model: hr.applicant}: |
self.action_print_survey(cr, uid, [ref('hr_case_programmer')])
-
On a successful Second Interview of applicant Contract is Proposed to applicant.
-
!python {model: hr.applicant}: |
self.stage_next(cr, uid, [ref('hr_case_programmer')])
self.stage_previous(cr, uid, [ref('hr_case_programmer')])
-
I Hired Applicant.
-
!python {model: hr.applicant}: |
self.case_close_with_emp(cr, uid, [ref('hr_case_programmer')])
-
I check that applicant is "Hired".
-
!assert {model: hr.applicant, id: hr_case_programmer, string: Applicant state is done}:
- state == 'done'
-
I do not give employment to the hired the applicant.
-
!python {model: hired.employee}: |
context.update({'active_model': 'hr.applicant', 'active_ids': [ref("hr_recruitment.hr_case_programmer")], 'active_id': ref("hr_recruitment.hr_case_programmer")})
emp_id = self.create(cr, uid, {}, context=context)
self.case_close(cr, uid, [emp_id], context=context)
-
Now I give employment to hired applicant .
-
!python {model: hr.applicant}: |
hired_emp_obj = self.pool.get('hired.employee')
self.case_reset(cr, uid, [ref("hr_case_programmer")])
self.case_open(cr, uid, [ref("hr_case_programmer")])
context.update({'active_model': 'hr.applicant', 'active_ids': [ref("hr_recruitment.hr_case_programmer")], 'active_id': ref("hr_recruitment.hr_case_programmer")})
emp_hr_id = hired_emp_obj.create(cr, uid, {}, context=context)
hired_emp_obj.case_close_with_emp(cr, uid, [emp_hr_id], context=context)
self.action_print_survey(cr, uid, [ref('hr_case_programmer')])
-
Now hired employee want to be a partner of company.
-

View File

@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2004-Today OpenERP (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -19,9 +19,7 @@
#
##############################################################################
import hr_recruitment_create_partner_job
import hr_recruitment_employee_hired
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,54 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
# $Id$
#
# 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 openerp.osv import fields, osv
from openerp.tools.translate import _
class hired_employee(osv.osv_memory):
_name = 'hired.employee'
_description = 'Create Employee'
def case_close(self, cr, uid, ids, context=None):
"""
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case's Ids
@param *args: Give Tuple Value
"""
if context is None:
context = {}
self.pool.get('hr.applicant').case_close(cr, uid,context.get('active_ids',[]))
return {}
def case_close_with_emp(self, cr, uid, ids, context=None):
"""
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case's Ids
"""
if context is None:
context = {}
return self.pool.get('hr.applicant').case_close_with_emp(cr, uid,context.get('active_ids', []))
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_hr_recruitment_hired_employee" model="ir.ui.view">
<field name="name">hr.recruitment.hired2employee.form</field>
<field name="model">hired.employee</field>
<field name="arch" type="xml">
<form string="Create New Employee" version="7.0">
<header>
<button name="case_close_with_emp" string="Yes" type="object" class="oe_highlight" />
<button name="case_close" string="No" type="object"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</header>
<label string="Would you like to create an employee ?"/>
</form>
</field>
</record>
<record id="action_hr_recruitment_hired_employee" model="ir.actions.act_window">
<field name="name">Create Employee</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hired.employee</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>