[MERGE] Sync with upstream (trunk)
bzr revid: tde@openerp.com-20140211141925-p3sziwkon7jlsq5r
@@ -21,16 +21,15 @@
|
||||
|
||||
import logging
|
||||
|
||||
from openerp import tools
|
||||
from openerp.modules.module import get_module_resource
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp import tools
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class hr_employee_category(osv.osv):
|
||||
class hr_employee_category(osv.Model):
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
if not ids:
|
||||
@@ -73,9 +72,9 @@ class hr_employee_category(osv.osv):
|
||||
]
|
||||
|
||||
|
||||
class hr_job(osv.osv):
|
||||
class hr_job(osv.Model):
|
||||
|
||||
def _no_of_employee(self, cr, uid, ids, name, args, context=None):
|
||||
def _get_nbr_employees(self, cr, uid, ids, name, args, context=None):
|
||||
res = {}
|
||||
for job in self.browse(cr, uid, ids, context=context):
|
||||
nb_employees = len(job.employee_ids or [])
|
||||
@@ -93,59 +92,81 @@ class hr_job(osv.osv):
|
||||
return res
|
||||
|
||||
_name = "hr.job"
|
||||
_description = "Job Description"
|
||||
_inherit = ['mail.thread']
|
||||
_description = "Job Position"
|
||||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||
_columns = {
|
||||
'name': fields.char('Job Name', size=128, required=True, select=True),
|
||||
'expected_employees': fields.function(_no_of_employee, string='Total Forecasted Employees',
|
||||
'expected_employees': fields.function(_get_nbr_employees, string='Total Forecasted Employees',
|
||||
help='Expected number of employees for this job position after new recruitment.',
|
||||
store = {
|
||||
'hr.job': (lambda self,cr,uid,ids,c=None: ids, ['no_of_recruitment'], 10),
|
||||
'hr.employee': (_get_job_position, ['job_id'], 10),
|
||||
}, type='integer',
|
||||
multi='no_of_employee'),
|
||||
'no_of_employee': fields.function(_no_of_employee, string="Current Number of Employees",
|
||||
multi='_get_nbr_employees'),
|
||||
'no_of_employee': fields.function(_get_nbr_employees, string="Current Number of Employees",
|
||||
help='Number of employees currently occupying this job position.',
|
||||
store = {
|
||||
'hr.employee': (_get_job_position, ['job_id'], 10),
|
||||
}, type='integer',
|
||||
multi='no_of_employee'),
|
||||
'no_of_recruitment': fields.integer('Expected in Recruitment', help='Number of new employees you expect to recruit.'),
|
||||
multi='_get_nbr_employees'),
|
||||
'no_of_recruitment': fields.integer('Expected New Employees', help='Number of new employees you expect to recruit.'),
|
||||
'no_of_hired_employee': fields.integer('Hired Employees', help='Number of hired employees for this job position during recruitment phase.'),
|
||||
'employee_ids': fields.one2many('hr.employee', 'job_id', 'Employees', groups='base.group_user'),
|
||||
'description': fields.text('Job Description'),
|
||||
'requirements': fields.text('Requirements'),
|
||||
'department_id': fields.many2one('hr.department', 'Department'),
|
||||
'company_id': fields.many2one('res.company', 'Company'),
|
||||
'state': fields.selection([('open', 'No Recruitment'), ('recruit', 'Recruitement in Progress')], 'Status', readonly=True, required=True,
|
||||
help="By default 'In position', set it to 'In Recruitment' if recruitment process is going on for this job position."),
|
||||
'state': fields.selection([('open', 'Recruitment Closed'), ('recruit', 'Recruitment in Progress')],
|
||||
string='Status', readonly=True, required=True,
|
||||
track_visibility='always',
|
||||
help="By default 'Closed', set it to 'In Recruitment' if recruitment process is going on for this job position."),
|
||||
'write_date': fields.datetime('Update Date', readonly=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.job', context=c),
|
||||
'no_of_recruitment': 0,
|
||||
'company_id': lambda self, cr, uid, ctx=None: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.job', context=ctx),
|
||||
'state': 'open',
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
('name_company_uniq', 'unique(name, company_id, department_id)', 'The name of the job position must be unique per department in company!'),
|
||||
('hired_employee_check', "CHECK ( no_of_hired_employee <= no_of_recruitment )", "Number of hired employee must be less than expected number of employee in recruitment."),
|
||||
]
|
||||
|
||||
|
||||
def on_change_expected_employee(self, cr, uid, ids, no_of_recruitment, no_of_employee, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
return {'value': {'expected_employees': no_of_recruitment + no_of_employee}}
|
||||
|
||||
def job_recruitement(self, cr, uid, ids, *args):
|
||||
for job in self.browse(cr, uid, ids):
|
||||
def set_recruit(self, cr, uid, ids, context=None):
|
||||
for job in self.browse(cr, uid, ids, context=context):
|
||||
no_of_recruitment = job.no_of_recruitment == 0 and 1 or job.no_of_recruitment
|
||||
self.write(cr, uid, [job.id], {'state': 'recruit', 'no_of_recruitment': no_of_recruitment})
|
||||
self.write(cr, uid, [job.id], {'state': 'recruit', 'no_of_recruitment': no_of_recruitment}, context=context)
|
||||
return True
|
||||
|
||||
def job_open(self, cr, uid, ids, *args):
|
||||
self.write(cr, uid, ids, {'state': 'open', 'no_of_recruitment': 0})
|
||||
def set_open(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {
|
||||
'state': 'open',
|
||||
'no_of_recruitment': 0,
|
||||
'no_of_hired_employee': 0
|
||||
}, context=context)
|
||||
return True
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
default.update({
|
||||
'employee_ids': [],
|
||||
'no_of_recruitment': 0,
|
||||
'no_of_hired_employee': 0,
|
||||
})
|
||||
if 'name' in default:
|
||||
job = self.browse(cr, uid, id, context=context)
|
||||
default['name'] = _("%s (copy)") % (job.name)
|
||||
return super(hr_job, self).copy(cr, uid, id, default=default, context=context)
|
||||
|
||||
# ----------------------------------------
|
||||
# Compatibility methods
|
||||
# ----------------------------------------
|
||||
_no_of_employee = _get_nbr_employees # v7 compatibility
|
||||
job_open = set_open # v7 compatibility
|
||||
job_recruitment = set_recruit # v7 compatibility
|
||||
|
||||
|
||||
class hr_employee(osv.osv):
|
||||
_name = "hr.employee"
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</group>
|
||||
<group string="Position">
|
||||
<field name="department_id" on_change="onchange_department_id(department_id)"/>
|
||||
<field name="job_id" options='{"no_open": True}' domain="[('state','!=','old')]" context="{'form_view_ref': 'hr.view_hr_job_employee_form'}"/>
|
||||
<field name="job_id"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="coach_id"/>
|
||||
</group>
|
||||
@@ -333,8 +333,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Job" version="7.0">
|
||||
<header>
|
||||
<button name="job_recruitement" string="Launch Recruitement" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="job_open" string="Stop Recruitment" states="recruit" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="set_recruit" string="Launch Recruitment" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="set_open" string="Stop Recruitment" states="recruit" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="recruit,open"/>
|
||||
</header>
|
||||
<sheet>
|
||||
@@ -342,20 +342,20 @@
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name" class="oe_inline"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group name="job_data">
|
||||
<field name="no_of_employee" groups="base.group_user"/>
|
||||
<field name="no_of_recruitment" on_change="on_change_expected_employee(no_of_recruitment,no_of_employee)"/>
|
||||
<field name="expected_employees" groups="base.group_user"/>
|
||||
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
|
||||
<field name="department_id"/>
|
||||
</group>
|
||||
<div class="oe_right" name="buttons"/>
|
||||
<group name="employee_data">
|
||||
<field name="department_id" class="oe_inline"/>
|
||||
<label for="no_of_employee"/>no_of_recruitment
|
||||
<div>
|
||||
<field name="no_of_employee" class="oe_inline"/>
|
||||
<p><field name="no_of_recruitment" groups="base.group_user" colspan="0" class="oe_inline" style="padding-top: 1px"/> new employee(s) expected</p>
|
||||
</div>
|
||||
</group>
|
||||
<div>
|
||||
<div attrs="{'invisible': [('state', '!=', 'recruit')]}">
|
||||
<label for="description"/>
|
||||
<field name="description"/>
|
||||
</div>
|
||||
<div>
|
||||
<div attrs="{'invisible': [('state', '!=', 'recruit')]}">
|
||||
<label for="requirements"/>
|
||||
<field name="requirements"/>
|
||||
</div>
|
||||
@@ -378,6 +378,7 @@
|
||||
<field name="no_of_employee"/>
|
||||
<field name="no_of_recruitment"/>
|
||||
<field name="expected_employees"/>
|
||||
<field name="no_of_hired_employee"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -389,34 +390,18 @@
|
||||
<field name="arch" type="xml">
|
||||
<search string="Jobs">
|
||||
<field name="name" string="Job"/>
|
||||
<filter icon="terp-camera_test" domain="[('state','=','open')]" string="In Position" help="In Position"/>
|
||||
<filter icon="terp-personal+" domain="[('state','=','recruit')]" string="In Recruitment" help="In Recruitment"/>
|
||||
<filter domain="[('state','=','open')]" string="In Position"/>
|
||||
<filter domain="[('state','=','recruit')]" string="In Recruitment" name="in_recruitment"/>
|
||||
<field name="department_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Department" icon="terp-personal+" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Company" icon="terp-go-home" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
<filter string="Department" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_job_employee_form" model="ir.ui.view">
|
||||
<field name="name">hr.job.employee.form</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Job" version="7.0">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="department_id"/>
|
||||
</group>
|
||||
<label for="description"/>
|
||||
<field name="description"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_hr_job">
|
||||
<field name="name">Job Positions</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
@@ -441,7 +426,6 @@
|
||||
</record>
|
||||
|
||||
<menuitem name="Recruitment" id="base.menu_crm_case_job_req_main" parent="menu_hr_root" groups="base.group_hr_user"/>
|
||||
<menuitem parent="hr.menu_hr_configuration" id="menu_hr_job" action="action_hr_job" sequence="6"/>
|
||||
|
||||
<!-- hr.department -->
|
||||
<record id="view_department_form" model="ir.ui.view">
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<group name="recruitment_grp">
|
||||
<label for="id" string="Talent Management"/>
|
||||
<div name="recruitment">
|
||||
<div>
|
||||
<div name="hr_recruitment">
|
||||
<field name="module_hr_recruitment" class="oe_inline"/>
|
||||
<label for="module_hr_recruitment"/>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
name: HR Officer
|
||||
login: hro
|
||||
password: hro
|
||||
email: hro@example.com
|
||||
-
|
||||
I added groups for HR Officer.
|
||||
-
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
- state == 'open'
|
||||
- no_of_recruitment == 0
|
||||
-
|
||||
Now, Recruitement is started so I start recruitement of Job Postion of "Developer" Profile.
|
||||
Now, Recruitment is started so I start recruitment of Job Postion of "Developer" Profile.
|
||||
-
|
||||
!python {model: hr.job}: |
|
||||
self.job_recruitement(cr, uid, [ref('job_developer')])
|
||||
self.job_recruitment(cr, uid, [ref('job_developer')])
|
||||
-
|
||||
I check 'state' and number of 'Expected in Recruitment' after initiating the recruitment
|
||||
-
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id" on_change="onchange_employee_id(employee_id)"/>
|
||||
<field name="job_id" context="{'form_view_ref': 'hr.view_hr_job_employee_form'}"/>
|
||||
<field name="job_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="type_id"/>
|
||||
|
||||
@@ -37,13 +37,14 @@ You can define the different phases of interviews and easily rate the applicant
|
||||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'images': ['images/hr_recruitment_analysis.jpeg','images/hr_recruitment_applicants.jpeg'],
|
||||
'images': ['images/hr_recruitment_analysis.jpeg','images/hr_recruitment_applicants.jpeg','static/src/img/down1.png'],
|
||||
'depends': [
|
||||
'decimal_precision',
|
||||
'hr',
|
||||
'survey',
|
||||
'calendar',
|
||||
'fetchmail',
|
||||
'web_kanban_gauge',
|
||||
],
|
||||
'data': [
|
||||
'wizard/hr_recruitment_create_partner_job_view.xml',
|
||||
@@ -58,7 +59,11 @@ You can define the different phases of interviews and easily rate the applicant
|
||||
'hr_recruitment_data.xml',
|
||||
],
|
||||
'demo': ['hr_recruitment_demo.xml'],
|
||||
'js': [
|
||||
'static/src/js/job_position.js',
|
||||
],
|
||||
'test': ['test/recruitment_process.yml'],
|
||||
'css':['static/src/css/job_position.css'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': True,
|
||||
|
||||
@@ -19,12 +19,10 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import tools
|
||||
|
||||
from datetime import datetime
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import html2plaintext
|
||||
|
||||
|
||||
AVAILABLE_PRIORITIES = [
|
||||
('', ''),
|
||||
@@ -246,7 +244,8 @@ class hr_applicant(osv.Model):
|
||||
if job_id:
|
||||
job_record = self.pool.get('hr.job').browse(cr, uid, job_id, context=context)
|
||||
department_id = job_record and job_record.department_id and job_record.department_id.id or False
|
||||
return {'value': {'department_id': department_id}}
|
||||
user_id = job_record and job_record.user_id and job_record.user_id.id or False
|
||||
return {'value': {'department_id': department_id, 'user_id': user_id}}
|
||||
|
||||
def onchange_department_id(self, cr, uid, ids, department_id=False, stage_id=False, context=None):
|
||||
if not stage_id:
|
||||
@@ -331,19 +330,12 @@ class hr_applicant(osv.Model):
|
||||
value = self.pool.get("survey").action_print_survey(cr, uid, ids, context=context)
|
||||
return value
|
||||
|
||||
def action_get_attachment_tree_view(self, cr, uid, ids, context):
|
||||
domain = ['&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', ids)]
|
||||
return {
|
||||
'name': _('Attachments'),
|
||||
'domain': domain,
|
||||
'res_model': 'ir.attachment',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_id': False,
|
||||
'view_mode': 'tree,form',
|
||||
'view_type': 'form',
|
||||
'limit': 80,
|
||||
'context': "{'default_res_model': '%s'}" % (self._name)
|
||||
}
|
||||
def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
|
||||
model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
|
||||
action = self.pool.get(model).read(cr, uid, action_id, context=context)
|
||||
action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
|
||||
action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', ids)])
|
||||
return action
|
||||
|
||||
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
|
||||
recipients = super(hr_applicant, self).message_get_suggested_recipients(cr, uid, ids, context=context)
|
||||
@@ -364,7 +356,7 @@ class hr_applicant(osv.Model):
|
||||
val = msg.get('from').split('<')[0]
|
||||
defaults = {
|
||||
'name': msg.get('subject') or _("No Subject"),
|
||||
'partner_name':val,
|
||||
'partner_name': val,
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'user_id': False,
|
||||
@@ -378,13 +370,20 @@ class hr_applicant(osv.Model):
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
context['mail_create_nolog'] = True
|
||||
if vals.get('department_id') and not context.get('default_department_id'):
|
||||
context['default_department_id'] = vals.get('department_id')
|
||||
|
||||
if vals.get('job_id') or context.get('default_job_id'):
|
||||
job_id = vals.get('job_id') or context.get('default_job_id')
|
||||
vals.update(self.onchange_job(cr, uid, [], job_id, context=context)['value'])
|
||||
obj_id = super(hr_applicant, self).create(cr, uid, vals, context=context)
|
||||
applicant = self.browse(cr, uid, obj_id, context=context)
|
||||
if applicant.job_id:
|
||||
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)
|
||||
name = applicant.partner_name if applicant.partner_name else applicant.name
|
||||
self.pool['hr.job'].message_post(
|
||||
cr, uid, [applicant.job_id.id],
|
||||
body=_('New application from %s') % name,
|
||||
subtype="hr_recruitment.mt_job_applicant_new", context=context)
|
||||
return obj_id
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
@@ -404,6 +403,15 @@ class hr_applicant(osv.Model):
|
||||
else:
|
||||
res = super(hr_applicant, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
# post processing: if job changed, post a message on the job
|
||||
if vals.get('job_id'):
|
||||
for applicant in self.browse(cr, uid, ids, context=None):
|
||||
name = applicant.partner_name if applicant.partner_name else applicant.name
|
||||
self.pool['hr.job'].message_post(
|
||||
cr, uid, [vals['job_id']],
|
||||
body=_('New application from %s') % name,
|
||||
subtype="hr_recruitment.mt_job_applicant_new", context=context)
|
||||
|
||||
# post processing: if stage changed, post a message in the chatter
|
||||
if vals.get('stage_id'):
|
||||
stage = self.pool['hr.recruitment.stage'].browse(cr, uid, vals['stage_id'], context=context)
|
||||
@@ -444,7 +452,7 @@ class hr_applicant(osv.Model):
|
||||
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})
|
||||
applicant.job_id.write({'no_of_hired_employee': applicant.job_id.no_of_hired_employee + 1}, context=context)
|
||||
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,
|
||||
@@ -454,6 +462,10 @@ class hr_applicant(osv.Model):
|
||||
'work_phone': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.phone or False,
|
||||
})
|
||||
self.write(cr, uid, [applicant.id], {'emp_id': emp_id}, context=context)
|
||||
self.pool['hr.job'].message_post(
|
||||
cr, uid, [applicant.job_id.id],
|
||||
body=_('New Employee %s Hired') % applicant.partner_name if applicant.partner_name else applicant.name,
|
||||
subtype="hr_recruitment.mt_job_applicant_hired", context=context)
|
||||
else:
|
||||
raise osv.except_osv(_('Warning!'), _('You must define an Applied Job and a Contact Name for this applicant.'))
|
||||
|
||||
@@ -490,16 +502,37 @@ class hr_job(osv.osv):
|
||||
_inherit = "hr.job"
|
||||
_name = "hr.job"
|
||||
_inherits = {'mail.alias': 'alias_id'}
|
||||
|
||||
def _get_attached_docs(self, cr, uid, ids, field_name, arg, context=None):
|
||||
res = {}
|
||||
attachment_obj = self.pool.get('ir.attachment')
|
||||
for job_id in ids:
|
||||
applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', '=', job_id)], context=context)
|
||||
res[job_id] = attachment_obj.search(
|
||||
cr, uid, [
|
||||
'|',
|
||||
'&', ('res_model', '=', 'hr.job'), ('res_id', '=', job_id),
|
||||
'&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)
|
||||
], context=context)
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'survey_id': fields.many2one('survey', 'Interview Form', help="Choose an interview form for this job position and you will be able to print/answer this interview from all applicants who apply for this job"),
|
||||
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True,
|
||||
help="Email alias for this job position. New emails will automatically "
|
||||
"create new applicants for this job position."),
|
||||
'address_id': fields.many2one('res.partner', 'Job Location', help="Address where employees are working"),
|
||||
'application_ids': fields.one2many('hr.applicant', 'job_id', 'Applications'),
|
||||
'manager_id': fields.related('department_id', 'manager_id', type='many2one', string='Department Manager', relation='hr.employee', readonly=True, store=True),
|
||||
'document_ids': fields.function(_get_attached_docs, type='one2many', relation='ir.attachment', string='Applications'),
|
||||
'user_id': fields.many2one('res.users', 'Recruitment Responsible', track_visibility='onchange'),
|
||||
'color': fields.integer('Color Index'),
|
||||
}
|
||||
|
||||
def _address_get(self, cr, uid, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
return user.company_id.partner_id.id
|
||||
|
||||
_defaults = {
|
||||
'address_id': _address_get
|
||||
}
|
||||
@@ -541,6 +574,18 @@ class hr_job(osv.osv):
|
||||
'nodestroy': True,
|
||||
}
|
||||
|
||||
def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
|
||||
#open attachments of job and related applicantions.
|
||||
model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
|
||||
action = self.pool.get(model).read(cr, uid, action_id, context=context)
|
||||
applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', 'in', ids)], context=context)
|
||||
action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
|
||||
action['domain'] = str(['|', '&', ('res_model', '=', 'hr.job'), ('res_id', 'in', ids), '&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)])
|
||||
return action
|
||||
|
||||
def action_set_no_of_recruitment(self, cr, uid, id, value, context=None):
|
||||
return self.write(cr, uid, [id], {'no_of_recruitment': value}, context=context)
|
||||
|
||||
|
||||
class applicant_category(osv.osv):
|
||||
""" Category of applicant """
|
||||
|
||||
@@ -495,13 +495,9 @@
|
||||
<field name="alias_name">jobs</field>
|
||||
<field name="alias_model_id" ref="model_hr_applicant"/>
|
||||
<field name="alias_user_id" ref="base.user_root"/>
|
||||
<field name="alias_parent_model_id" ref="model_hr_job"/>
|
||||
</record>
|
||||
|
||||
<!-- Job-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_job_new_applicant" model="mail.message.subtype">
|
||||
<field name="name">New Applicant</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
</record>
|
||||
<!-- Applicant-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_applicant_new" model="mail.message.subtype">
|
||||
<field name="name">New Applicant</field>
|
||||
@@ -515,12 +511,35 @@
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Stage changed</field>
|
||||
</record>
|
||||
<record id="mt_applicant_employee" model="mail.message.subtype">
|
||||
<record id="mt_applicant_hired" 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>
|
||||
<!-- Job-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_job_applicant_new" model="mail.message.subtype">
|
||||
<field name="name">Applicant Created</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="parent_id" eval="ref('mt_applicant_new')"/>
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
<record id="mt_job_applicant_stage_changed" model="mail.message.subtype">
|
||||
<field name="name">Applicant Stage Changed</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="parent_id" eval="ref('mt_applicant_stage_changed')"/>
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
<record id="mt_job_applicant_hired" model="mail.message.subtype">
|
||||
<field name="name">Applicant Hired</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="parent_id" eval="ref('mt_applicant_hired')"/>
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
|
||||
<!-- Applicant Categories(Tag) -->
|
||||
<record id="tag_applicant_reserve" model="hr.applicant_category">
|
||||
<field name="name">Reserve</field>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
######################## JOB OPPORTUNITIES (menu) ###########################
|
||||
<record model="ir.actions.act_window" id="crm_case_categ0_act_job">
|
||||
<field name="name">Applications</field>
|
||||
@@ -9,16 +8,15 @@
|
||||
<field name="view_mode">kanban,tree,form,graph,calendar</field>
|
||||
<field name="view_id" eval="False"/>
|
||||
<field name="search_view_id" ref="view_crm_case_jobs_filter"/>
|
||||
<field name="context">{'empty_list_help_model': 'hr.job'}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a new job applicant.
|
||||
<p>
|
||||
OpenERP helps you track applicants in the recruitment
|
||||
process and follow up all operations: meetings, interviews, etc.
|
||||
</p><p>
|
||||
OpenERP helps you track applicants in the recruitment process
|
||||
and follow up all operations: meetings, interviews, etc.
|
||||
Candidates and their cv's are automatically created when they
|
||||
apply for a job. If you install the document management modules,
|
||||
all resumes are indexed automatically, so that you can easily
|
||||
search through their content in the recruitment menu.
|
||||
Applicants and their attached CV are created automatically when an email is sent.
|
||||
If you install the document management modules, all resumes are indexed automatically,
|
||||
so that you can easily search through their content.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
@@ -58,15 +56,10 @@
|
||||
sequence="1"/>
|
||||
|
||||
<!-- ALL JOBS REQUESTS -->
|
||||
|
||||
<menuitem parent="base.menu_crm_case_job_req_main" id="hr.menu_hr_job_position" action="action_hr_job" sequence="1"/>
|
||||
<menuitem
|
||||
name="Applications"
|
||||
parent="base.menu_crm_case_job_req_main"
|
||||
id="menu_crm_case_categ0_act_job" action="crm_case_categ0_act_job" sequence="1"/>
|
||||
|
||||
|
||||
<menuitem parent="hr.menu_hr_configuration" id="hr.menu_hr_job" action="hr.action_hr_job" sequence="2"/>
|
||||
|
||||
|
||||
id="menu_crm_case_categ0_act_job" action="crm_case_categ0_act_job" sequence="2"/>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Jobs -->
|
||||
<!-- Applicants -->
|
||||
<record model="ir.ui.view" id="crm_case_tree_view_job">
|
||||
<field name="name">Applicants</field>
|
||||
<field name="model">hr.applicant</field>
|
||||
@@ -91,10 +91,10 @@
|
||||
<label for="partner_name" class="oe_edit_only"/>
|
||||
<h2 style="display: inline-block;">
|
||||
<field name="partner_name" class="oe_inline"/>
|
||||
<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)]}"/>
|
||||
</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>
|
||||
@@ -307,46 +307,190 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- HR Job -->
|
||||
<record model="ir.actions.act_window" id="action_hr_job_applications">
|
||||
<field name="name">Applications</field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="view_mode">kanban,tree,form,graph,calendar</field>
|
||||
<field name="context">{'search_default_job_id': [active_id], 'default_job_id': active_id, 'empty_list_help_model': 'hr.job'}</field>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
OpenERP helps you track applicants in the recruitment
|
||||
process and follow up all operations: meetings, interviews, etc.
|
||||
</p><p>
|
||||
Applicants and their attached CV are created automatically when an email is sent.
|
||||
If you install the document management modules, all resumes are indexed automatically,
|
||||
so that you can easily search through their content.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Jobs -->
|
||||
<record id="view_job_filter_recruitment" model="ir.ui.view">
|
||||
<field name="name">Job</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="inherit_id" ref="hr.view_job_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="department_id" positon="after">
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_job_survey" model="ir.ui.view">
|
||||
<field name="name">hr.job.form1</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="inherit_id" ref="hr.view_hr_job_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<group name="job_data" position="inside">
|
||||
<group name="employee_data" position="inside">
|
||||
<label for="survey_id" groups="base.group_user"/>
|
||||
<div groups="base.group_user">
|
||||
<field name="survey_id" class="oe_inline" domain="[('type','=','Human Resources')]"/>
|
||||
<button string="Print Interview" name="action_print_survey" type="object" attrs="{'invisible':[('survey_id','=',False)]}" class="oe_inline oe_link"/>
|
||||
</div>
|
||||
<label for="address_id"/>
|
||||
<div>
|
||||
<field name="address_id" context="{'show_address': 1}"/>
|
||||
<span class="oe_grey">(empty = remote work)</span>
|
||||
</div>
|
||||
</group>
|
||||
<field name="expected_employees" position="after">
|
||||
<label for="survey_id" groups="base.group_user"/>
|
||||
<div groups="base.group_user">
|
||||
<field name="survey_id" class="oe_inline" domain="[('type','=','Human Resources')]"/>
|
||||
<button class="oe_inline"
|
||||
string="Interview"
|
||||
name="action_print_survey" type="object"
|
||||
attrs="{'invisible':[('survey_id','=',False)]}"/>
|
||||
</div>
|
||||
</field>
|
||||
<xpath expr="//group[@name='job_data']" position="after">
|
||||
<group name="group_alias"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<label for="alias_name" string="Email Alias"/>
|
||||
<div name="alias_def">
|
||||
<xpath expr="//field[@name='department_id']" position="after">
|
||||
<label for="alias_name" string="Specific Email Address" attrs="{'invisible': [('alias_domain', '=', False)]}" help ="Define a specific contact address for this job position. If you keep it empty, the default email address will be used which is in human resources settings"/>
|
||||
<div name="alias_def" attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<field name="alias_id" class="oe_read_only oe_inline"
|
||||
string="Email Alias" required="0"/>
|
||||
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
|
||||
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<field name="alias_contact" class="oe_inline" string="Accept Emails From"/>
|
||||
</group>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='department_id']" position="after">
|
||||
<field name="user_id" class="oe_inline"/>
|
||||
</xpath>
|
||||
<div name="buttons" position="inside">
|
||||
<button string="Applications" name="%(action_hr_job_applications)d" context="{'default_user_id': user_id}" type="action"/>
|
||||
<button string="Documents" name="action_get_attachment_tree_view" type="object"/>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_job_kanban" model="ir.ui.view">
|
||||
<field name="name">hr.job.kanban</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban version="7.0" class="oe_background_grey">
|
||||
<field name="name"/>
|
||||
<field name="department_id"/>
|
||||
<field name="no_of_recruitment"/>
|
||||
<field name="color"/>
|
||||
<field name="application_ids"/>
|
||||
<field name="document_ids"/>
|
||||
<field name="no_of_hired_employee"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="survey_id"/>
|
||||
<field name="state"/>
|
||||
<field name="user_id"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_job oe_kanban_card oe_kanban_global_click">
|
||||
<div class="oe_dropdown_toggle oe_dropdown_kanban oe_custom">
|
||||
<span class="oe_e">í</span>
|
||||
<ul class="oe_dropdown_menu">
|
||||
<t t-if="widget.view.is_action_enabled('edit')">
|
||||
<li><a type="edit">Edit...</a></li>
|
||||
</t>
|
||||
<t t-if="widget.view.is_action_enabled('delete')">
|
||||
<li><a type="delete">Delete</a></li>
|
||||
</t>
|
||||
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class = "oe_kanban_content">
|
||||
<t t-if="record.user_id.raw_value">
|
||||
<img t-att-src="kanban_image('res.users', 'image_medium', record.user_id.raw_value[0])" t-att-title="record.user_id.value" class="oe_kanban_avatar oe_job_avatar"/>
|
||||
</t>
|
||||
<t t-if="record.user_id.raw_value === false">
|
||||
<img t-att-src='_s + "/base/static/src/img/avatar.png"' class="oe_kanban_avatar oe_job_avatar"/>
|
||||
</t>
|
||||
<div class="oe_job_detail">
|
||||
<div class="oe_job oe_name oe_kanban_ellipsis">
|
||||
<field name="name"/>
|
||||
</div>
|
||||
<div class="oe_job oe_department oe_kanban_ellipsis">
|
||||
<field name="department_id"/>
|
||||
<span t-if="record.manager_id.value" class="oe_manager_name">
|
||||
(<t t-esc="record.manager_id.value"/>)
|
||||
</span>
|
||||
</div>
|
||||
<div class="oe_job_alias oe_kanban_ellipsis" t-if=" record.alias_id.value and record.state.raw_value == 'recruit'">
|
||||
<span class="oe_e">%%</span><small><field name="alias_id"/></small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<t t-if="record.state.raw_value == 'recruit'">
|
||||
<div class="oe_applications">
|
||||
<a name="%(action_hr_job_applications)d" type="action">
|
||||
<span t-if="record.application_ids.raw_value.length gt 1"><t t-esc="record.application_ids.raw_value.length"/> Applications</span>
|
||||
<span t-if="record.application_ids.raw_value.length lt 2"><t t-esc="record.application_ids.raw_value.length"/> Application</span>
|
||||
</a>
|
||||
<br/>
|
||||
<a t-if="record.document_ids.raw_value.length gt 0" name="action_get_attachment_tree_view" type="object">
|
||||
<span t-if="record.document_ids.raw_value.length gt 1"><t t-esc="record.document_ids.raw_value.length"/> Documents</span>
|
||||
<span t-if="record.document_ids.raw_value.length lt 2"><t t-esc="record.document_ids.raw_value.length"/> Document</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oe_job_justgage">
|
||||
<field state="recruit" name="no_of_hired_employee" widget="gauge"
|
||||
style="width:160px; height: 120px;"
|
||||
options="{
|
||||
'max_field': 'no_of_recruitment',
|
||||
'label': 'Hired Employees',
|
||||
'on_change': 'action_set_no_of_recruitment',
|
||||
'on_click_label': 'employee(s) to recruit',
|
||||
'force_set': False,
|
||||
'gauge_value_field': 'no_of_recruitment',
|
||||
}">
|
||||
Hired Employees
|
||||
</field>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="record.state.raw_value == 'open'">
|
||||
<div class="oe_start_recruitment">
|
||||
<p><b>click here</b>, To start the recruitment</p>
|
||||
<img src="/hr_recruitment/static/src/img/down1.png"/>
|
||||
</div>
|
||||
</t>
|
||||
<div class="oe_launch_recruitment">
|
||||
<a t-if="record.state.raw_value == 'open'" data-name="job_recruitment" data-type="object" class="oe_kanban_action">Launch Recruitment</a>
|
||||
<a t-if="record.state.raw_value == 'recruit'" data-name="job_open" data-type="object" class="oe_kanban_action">Recruitment Done</a>
|
||||
<a t-if="record.survey_id.raw_value"> | </a>
|
||||
<a t-if="record.survey_id.raw_value" data-name="action_print_survey" data-type="object" class="oe_kanban_action">Print Interview</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- hr related job position menu action -->
|
||||
<record model="ir.actions.act_window" id="action_hr_job">
|
||||
<field name="name">Job Positions</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="context">{'search_default_in_recruitment': 1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click here to create a new job or remove the filter on "In Recruitment" to recruit for an on hold job.
|
||||
</p>
|
||||
<p>
|
||||
Define job position profile and manage recruitment in a context of a particular job: print interview survey, define number of expected new employees, and manage its recruitment pipe
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Stage Tree View -->
|
||||
<record model="ir.ui.view" id="hr_recruitment_stage_tree">
|
||||
<field name="name">hr.recruitment.stage.tree</field>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
|
||||
# Copyright (C) 2004-Today OpenERP S.A. (<http://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,11 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
class hr_applicant_settings(osv.osv_memory):
|
||||
|
||||
class hr_applicant_settings(osv.TransientModel):
|
||||
_name = 'hr.config.settings'
|
||||
_inherit = ['hr.config.settings', 'fetchmail.config.settings']
|
||||
|
||||
@@ -32,6 +34,44 @@ class hr_applicant_settings(osv.osv_memory):
|
||||
'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account',
|
||||
fetchmail_model='hr.applicant', fetchmail_name='Incoming HR Applications',
|
||||
help='Allow applicants to send their job application to an email address (jobs@mycompany.com), '
|
||||
'and create automatically application documents in the system.'),
|
||||
'and create automatically application documents in the system.',
|
||||
deprecated='Will be removed with OpenERP v8, not applicable anymore. Use aliases instead.'),
|
||||
'alias_prefix': fields.char('Default Alias Name for Jobs'),
|
||||
'alias_domain': fields.char('Alias Domain'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'alias_domain': lambda self, cr, uid, context: self.pool['mail.alias']._get_alias_domain(cr, SUPERUSER_ID, [1], None, None)[1],
|
||||
}
|
||||
|
||||
def _find_default_job_alias_id(self, cr, uid, context=None):
|
||||
alias_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'hr_recruitment.mail_alias_jobs')
|
||||
if not alias_id:
|
||||
alias_ids = self.pool['mail.alias'].search(
|
||||
cr, uid, [
|
||||
('alias_model_id.model', '=', 'hr.applicant'),
|
||||
('alias_force_thread_id', '=', 0),
|
||||
('alias_parent_model_id.model', '=', 'hr.job'),
|
||||
('alias_parent_thread_id', '=', 0),
|
||||
('alias_defaults', '=', '{}')
|
||||
], context=context)
|
||||
alias_id = alias_ids and alias_ids[0] or False
|
||||
return alias_id
|
||||
|
||||
def get_default_alias_prefix(self, cr, uid, ids, context=None):
|
||||
alias_name = False
|
||||
alias_id = self._find_default_job_alias_id(cr, uid, context=context)
|
||||
if alias_id:
|
||||
alias_name = self.pool['mail.alias'].browse(cr, uid, alias_id, context=context).alias_name
|
||||
return {'alias_prefix': alias_name}
|
||||
|
||||
def set_default_alias_prefix(self, cr, uid, ids, context=None):
|
||||
mail_alias = self.pool.get('mail.alias')
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
alias_id = self._find_default_job_alias_id(cr, uid, context=context)
|
||||
if not alias_id:
|
||||
create_ctx = dict(context, alias_model_name='hr.applicant', alias_parent_model_name='hr.job')
|
||||
alias_id = self.pool['mail.alias'].create(cr, uid, {'alias_name': record.alias_prefix}, context=create_ctx)
|
||||
else:
|
||||
mail_alias.write(cr, uid, alias_id, {'alias_name': record.alias_prefix}, context=context)
|
||||
return True
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
<label for="module_document"/>
|
||||
</div>
|
||||
</div>
|
||||
<xpath expr="//div[@name='hr_recruitment']" position="after">
|
||||
<div attrs="{'invisible': ['|',('module_hr_recruitment','=',False),('alias_domain', '=', False)]}">
|
||||
<label string="Default job email address"/>
|
||||
<field name="alias_prefix" class="oe_inline" attrs="{'required': [('alias_domain', '!=', False)]}"/>
|
||||
@
|
||||
<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
job_position.css: job_position.sass
|
||||
sass --trace -t expanded job_position.sass job_position.css
|
||||
@@ -0,0 +1,86 @@
|
||||
.openerp .oe_kanban_job{
|
||||
width: 355px;
|
||||
min-height: 165px !important;
|
||||
}
|
||||
.openerp .oe_job_alias{
|
||||
margin: 3px;
|
||||
}
|
||||
.openerp .oe_job_detail{
|
||||
height: 70px;
|
||||
width: 308px
|
||||
}
|
||||
.openerp .oe_job_alias .oe_e {
|
||||
font-size: 30px;
|
||||
line-height: 6px;
|
||||
vertical-align: top;
|
||||
margin-right: 3px;
|
||||
color: white;
|
||||
text-shadow: 0px 0px 2px black;
|
||||
float: left;
|
||||
}
|
||||
.openerp .oe_job {
|
||||
font-size: 112%;
|
||||
position: inline;
|
||||
margin: 3px 3px;
|
||||
color: #4c4c4c;
|
||||
height: 16px;
|
||||
}
|
||||
.openerp img.oe_job_avatar {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: 295px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
.openerp .oe_launch_recruitment{
|
||||
float: left;
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
left: 10px;
|
||||
}
|
||||
.openerp div.oe_applications {
|
||||
position: absolute;;
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.openerp .oe_applications > a > span:hover{
|
||||
margin: 4px 0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.openerp .oe_name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.openerp .oe_manager_name {
|
||||
width: 135px;
|
||||
font-size: 11px;
|
||||
color: gray;
|
||||
}
|
||||
.openerp .oe_job_justgage {
|
||||
float: right;
|
||||
margin-top: -40px;
|
||||
margin-right: -58px;
|
||||
width:200px;
|
||||
height:130px;
|
||||
}
|
||||
.openerp .oe_department {
|
||||
width: 350px;
|
||||
}
|
||||
.openerp .oe_start_recruitment {
|
||||
padding-top: 10px;
|
||||
}
|
||||
.openerp .oe_start_recruitment p {
|
||||
font-size: 14px;
|
||||
color: gray;
|
||||
padding-left: 50px;
|
||||
}
|
||||
.openerp .oe_start_recruitment img {
|
||||
margin-top: -22px;
|
||||
width: 32px;
|
||||
height: 34px;
|
||||
float: left;
|
||||
padding-left: 12px;
|
||||
}
|
||||
.openerp .oe_job_messages{
|
||||
margin-top: 40px !important;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
.openerp
|
||||
.oe_kanban_job
|
||||
width: 355px
|
||||
min-height: 165px !important
|
||||
.oe_job_alias
|
||||
margin: 3px
|
||||
.oe_job_detail
|
||||
height: 70px
|
||||
width: 308px
|
||||
.oe_job_alias .oe_e
|
||||
font-size: 30px
|
||||
line-height: 6px
|
||||
vertical-align: top
|
||||
margin-right: 3px
|
||||
color: white
|
||||
text-shadow: 0px 0px 2px black
|
||||
float: left
|
||||
.oe_job
|
||||
font-size: 112%
|
||||
position: inline
|
||||
margin: 3px 3px
|
||||
color: #4c4c4c
|
||||
height: 16px
|
||||
img.oe_job_avatar
|
||||
position: absolute
|
||||
width: 24px
|
||||
height: 24px
|
||||
margin-left: 295px
|
||||
margin-top: -5px
|
||||
.oe_launch_recruitment
|
||||
float: left
|
||||
position: absolute
|
||||
bottom: 3px
|
||||
left: 10px
|
||||
div.oe_applications
|
||||
position: absolute
|
||||
margin-top: 16px
|
||||
font-size: 14px
|
||||
.oe_applications > a > span:hover
|
||||
margin: 4px 0
|
||||
text-decoration: underline
|
||||
.oe_name
|
||||
font-size: 14px
|
||||
font-weight: bold
|
||||
.oe_manager_name
|
||||
width: 135px
|
||||
font-size: 11px
|
||||
color: gray
|
||||
.oe_job_justgage
|
||||
float: right
|
||||
margin-top: -40px
|
||||
margin-right: -58px
|
||||
width: 200px
|
||||
height: 130px
|
||||
.oe_department
|
||||
width: 350px
|
||||
.oe_start_recruitment
|
||||
padding-top: 10px
|
||||
p
|
||||
font-size: 14px
|
||||
color: gray
|
||||
padding-left: 50px
|
||||
img
|
||||
margin-top: -22px
|
||||
width: 32px
|
||||
height: 34px
|
||||
float: left
|
||||
padding-left: 12px
|
||||
.oe_job_messages
|
||||
margin-top: 40px !important
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,13 @@
|
||||
openerp.hr_recruitment = function (openerp) {
|
||||
|
||||
openerp.web_kanban.KanbanRecord.include({
|
||||
on_card_clicked: function() {
|
||||
if (this.view.dataset.model === 'hr.job') {
|
||||
this.$('.oe_applications a').first().click();
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import commands
|
||||
import logging
|
||||
import simplejson
|
||||
import os
|
||||
@@ -11,7 +12,7 @@ import math
|
||||
import md5
|
||||
import openerp.addons.hw_proxy.controllers.main as hw_proxy
|
||||
import subprocess
|
||||
from threading import Thread
|
||||
from threading import Thread, Lock
|
||||
from Queue import Queue, Empty
|
||||
|
||||
try:
|
||||
@@ -39,6 +40,7 @@ class EscposDriver(Thread):
|
||||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.queue = Queue()
|
||||
self.lock = Lock()
|
||||
self.status = {'status':'connecting', 'messages':[]}
|
||||
|
||||
def connected_usb_devices(self):
|
||||
@@ -47,6 +49,13 @@ class EscposDriver(Thread):
|
||||
if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None:
|
||||
connected.append(device)
|
||||
return connected
|
||||
|
||||
def lockedstart(self):
|
||||
self.lock.acquire()
|
||||
if not self.isAlive():
|
||||
self.daemon = True
|
||||
self.start()
|
||||
self.lock.release()
|
||||
|
||||
def get_escpos_printer(self):
|
||||
try:
|
||||
@@ -86,7 +95,6 @@ class EscposDriver(Thread):
|
||||
_logger.warning('ESC/POS Device Disconnected: '+message)
|
||||
|
||||
def run(self):
|
||||
self.queue = Queue()
|
||||
while True:
|
||||
try:
|
||||
timestamp, task, data = self.queue.get(True)
|
||||
@@ -105,6 +113,8 @@ class EscposDriver(Thread):
|
||||
elif task == 'cashbox':
|
||||
if timestamp >= time.time() - 12:
|
||||
self.open_cashbox(printer)
|
||||
elif task == 'printstatus':
|
||||
self.print_status(printer)
|
||||
elif task == 'status':
|
||||
pass
|
||||
|
||||
@@ -113,10 +123,31 @@ class EscposDriver(Thread):
|
||||
_logger.error(e);
|
||||
|
||||
def push_task(self,task, data = None):
|
||||
if not self.isAlive():
|
||||
self.start()
|
||||
self.lockedstart()
|
||||
self.queue.put((time.time(),task,data))
|
||||
|
||||
def print_status(self,eprint):
|
||||
localips = ['0.0.0.0','127.0.0.1','127.0.1.1']
|
||||
ips = [ c.split(':')[1].split(' ')[0] for c in commands.getoutput("/sbin/ifconfig").split('\n') if 'inet addr' in c ]
|
||||
ips = [ ip for ip in ips if ip not in localips ]
|
||||
eprint.text('\n\n')
|
||||
eprint.set(align='center',type='b',height=2,width=2)
|
||||
eprint.text('PosBox Status\n')
|
||||
eprint.text('\n')
|
||||
eprint.set(align='center')
|
||||
|
||||
if len(ips) == 0:
|
||||
eprint.text('ERROR: Could not connect to LAN\n\nPlease check that the PosBox is correc-\ntly connected with a network cable,\n that the LAN is setup with DHCP, and\nthat network addresses are available')
|
||||
elif len(ips) == 1:
|
||||
eprint.text('IP Address\n'+ips[0]+'\n')
|
||||
else:
|
||||
eprint.text('IP Addresses\n')
|
||||
for ip in ips:
|
||||
eprint.text(ip+'\n')
|
||||
|
||||
eprint.text('\n\n')
|
||||
eprint.cut()
|
||||
|
||||
def print_receipt_body(self,eprint,receipt):
|
||||
|
||||
def check(string):
|
||||
@@ -134,7 +165,6 @@ class EscposDriver(Thread):
|
||||
else:
|
||||
return str(amount)
|
||||
|
||||
|
||||
def printline(left, right='', width=40, ratio=0.5, indent=0):
|
||||
lwidth = int(width * ratio)
|
||||
rwidth = width - lwidth
|
||||
@@ -157,6 +187,7 @@ class EscposDriver(Thread):
|
||||
|
||||
# Receipt Header
|
||||
if receipt['company']['logo']:
|
||||
eprint.set(align='center')
|
||||
eprint.print_base64_image(receipt['company']['logo'])
|
||||
eprint.text('\n')
|
||||
else:
|
||||
@@ -245,6 +276,8 @@ class EscposDriver(Thread):
|
||||
driver = EscposDriver()
|
||||
|
||||
hw_proxy.drivers['escpos'] = driver
|
||||
|
||||
driver.push_task('printstatus')
|
||||
|
||||
class EscposProxy(hw_proxy.Proxy):
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
import time
|
||||
from os import listdir
|
||||
from os.path import join
|
||||
from threading import Thread
|
||||
from threading import Thread, Lock
|
||||
from select import select
|
||||
from Queue import Queue, Empty
|
||||
|
||||
@@ -26,6 +26,7 @@ except ImportError:
|
||||
class Scanner(Thread):
|
||||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.lock = Lock()
|
||||
self.status = {'status':'connecting', 'messages':[]}
|
||||
self.input_dir = '/dev/input/by-id/'
|
||||
self.barcodes = Queue()
|
||||
@@ -86,6 +87,12 @@ class Scanner(Thread):
|
||||
57:(" "," "),
|
||||
}
|
||||
|
||||
def lockedstart(self):
|
||||
self.lock.acquire()
|
||||
if not self.isAlive():
|
||||
self.start()
|
||||
self.lock.release()
|
||||
|
||||
def set_status(self, status, message = None):
|
||||
if status == self.status['status']:
|
||||
if message != None and message != self.status['messages'][-1]:
|
||||
@@ -102,8 +109,6 @@ class Scanner(Thread):
|
||||
elif status == 'disconnected' and message:
|
||||
_logger.warning('Disconnected Barcode Scanner: '+message)
|
||||
|
||||
|
||||
|
||||
def get_device(self):
|
||||
try:
|
||||
if not evdev:
|
||||
@@ -135,6 +140,8 @@ class Scanner(Thread):
|
||||
busy reading another barcode
|
||||
"""
|
||||
|
||||
self.lockedstart()
|
||||
|
||||
while True:
|
||||
try:
|
||||
timestamp, barcode = self.barcodes.get(True, 5)
|
||||
@@ -144,8 +151,7 @@ class Scanner(Thread):
|
||||
return ''
|
||||
|
||||
def get_status(self):
|
||||
if not s.isAlive():
|
||||
s.start()
|
||||
self.lockedstart()
|
||||
return self.status
|
||||
|
||||
def run(self):
|
||||
@@ -209,7 +215,6 @@ hw_proxy.drivers['scanner'] = s
|
||||
class ScannerDriver(hw_proxy.Proxy):
|
||||
@http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
|
||||
def scanner(self):
|
||||
if not s.isAlive():
|
||||
s.start()
|
||||
return s.get_barcode()
|
||||
|
||||
|
||||
|
||||
@@ -154,6 +154,12 @@ class mail_alias(osv.Model):
|
||||
sequence = (sequence + 1) if sequence else 2
|
||||
return new_name
|
||||
|
||||
def _clean_and_make_unique(self, cr, uid, name, context=None):
|
||||
# when an alias name appears to already be an email, we keep the local part only
|
||||
name = remove_accents(name).lower().split('@')[0]
|
||||
name = re.sub(r'[^\w+.]+', '-', name)
|
||||
return self._find_unique(cr, uid, name, context=context)
|
||||
|
||||
def migrate_to_alias(self, cr, child_model_name, child_table_name, child_model_auto_init_fct,
|
||||
alias_model_name, alias_id_column, alias_key, alias_prefix='', alias_force_key='', alias_defaults={},
|
||||
alias_generate_name=False, context=None):
|
||||
@@ -199,7 +205,7 @@ class mail_alias(osv.Model):
|
||||
alias_vals['alias_parent_thread_id'] = obj_data['id']
|
||||
alias_create_ctx = dict(context, alias_model_name=alias_model_name, alias_parent_model_name=child_model_name)
|
||||
alias_id = mail_alias.create(cr, SUPERUSER_ID, alias_vals, context=alias_create_ctx)
|
||||
child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id})
|
||||
child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id}, context={'mail_notrack': True})
|
||||
_logger.info('Mail alias created for %s %s (id %s)', child_model_name, obj_data[alias_key], obj_data['id'])
|
||||
|
||||
# Finally attempt to reinstate the missing constraint
|
||||
@@ -227,11 +233,7 @@ class mail_alias(osv.Model):
|
||||
model_name = context.get('alias_model_name')
|
||||
parent_model_name = context.get('alias_parent_model_name')
|
||||
if vals.get('alias_name'):
|
||||
# when an alias name appears to already be an email, we keep the local part only
|
||||
alias_name = remove_accents(vals['alias_name']).lower().split('@')[0]
|
||||
alias_name = re.sub(r'[^\w+.]+', '-', alias_name)
|
||||
alias_name = self._find_unique(cr, uid, alias_name, context=context)
|
||||
vals['alias_name'] = alias_name
|
||||
vals['alias_name'] = self._clean_and_make_unique(cr, uid, vals.get('alias_name'), context=context)
|
||||
if model_name:
|
||||
model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', model_name)], context=context)[0]
|
||||
vals['alias_model_id'] = model_id
|
||||
@@ -240,6 +242,12 @@ class mail_alias(osv.Model):
|
||||
vals['alias_parent_model_id'] = model_id
|
||||
return super(mail_alias, self).create(cr, uid, vals, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
""""give uniqe alias name if given alias name is allready assigned"""
|
||||
if vals.get('alias_name'):
|
||||
vals['alias_name'] = self._clean_and_make_unique(cr, uid, vals.get('alias_name'), context=context)
|
||||
return super(mail_alias, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
def open_document(self, cr, uid, ids, context=None):
|
||||
alias = self.browse(cr, uid, ids, context=context)[0]
|
||||
if not alias.alias_model_id or not alias.alias_force_thread_id:
|
||||
|
||||
@@ -113,9 +113,9 @@ class mail_thread(osv.AbstractModel):
|
||||
object_id.alias_id.alias_model_id.model == self._name and \
|
||||
object_id.alias_id.alias_force_thread_id == 0:
|
||||
alias = object_id.alias_id
|
||||
elif catchall_domain and model: # no specific res_id given -> generic help message, take an example alias (i.e. alias of some section_id)
|
||||
if not alias and catchall_domain and model: # no res_id or res_id not linked to an alias -> generic help message, take a generic alias of the model
|
||||
alias_obj = self.pool.get('mail.alias')
|
||||
alias_ids = alias_obj.search(cr, uid, [("alias_parent_model_id.model", "=", model), ("alias_name", "!=", False), ('alias_force_thread_id', '=', False)], context=context, order='id ASC')
|
||||
alias_ids = alias_obj.search(cr, uid, [("alias_parent_model_id.model", "=", model), ("alias_name", "!=", False), ('alias_force_thread_id', '=', False), ('alias_parent_thread_id', '=', False)], context=context, order='id ASC')
|
||||
if alias_ids and len(alias_ids) == 1:
|
||||
alias = alias_obj.browse(cr, uid, alias_ids[0], context=context)
|
||||
|
||||
@@ -384,7 +384,10 @@ class mail_thread(osv.AbstractModel):
|
||||
track_ctx = dict(context)
|
||||
if 'lang' not in track_ctx:
|
||||
track_ctx['lang'] = self.pool.get('res.users').browse(cr, uid, uid, context=context).lang
|
||||
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
|
||||
if not context.get('mail_notrack'):
|
||||
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
|
||||
else:
|
||||
tracked_fields = []
|
||||
if tracked_fields:
|
||||
records = self.browse(cr, uid, ids, context=track_ctx)
|
||||
initial_values = dict((this.id, dict((key, getattr(this, key)) for key in tracked_fields.keys())) for this in records)
|
||||
|
||||
@@ -162,25 +162,37 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
||||
},
|
||||
|
||||
// find a proxy and connects to it. for options see find_proxy
|
||||
// - force_ip : only try to connect to the specified ip.
|
||||
// - port: what port to listen to (default 8069)
|
||||
// - progress(fac) : callback for search progress ( fac in [0,1] )
|
||||
autoconnect: function(options){
|
||||
var self = this;
|
||||
this.set_connection_status('connecting',{});
|
||||
var found_url = new $.Deferred();
|
||||
var success = new $.Deferred();
|
||||
this.find_proxy(options)
|
||||
.then(function(proxies){
|
||||
if(proxies.length > 0){
|
||||
self.connect(proxies[0])
|
||||
.then(function(){
|
||||
success.resolve();
|
||||
},function(){
|
||||
self.set_connection_status('disconnected');
|
||||
success.reject();
|
||||
});
|
||||
}else{
|
||||
self.set_connection_status('disconnected');
|
||||
success.reject();
|
||||
}
|
||||
|
||||
if ( options.force_ip ){
|
||||
// if the ip is forced by server config, bailout on fail
|
||||
found_url = this.try_hard_to_connect(options.force_ip, options)
|
||||
}else if( localStorage['hw_proxy_url'] ){
|
||||
// try harder when we remember a good proxy url
|
||||
found_url = this.try_hard_to_connect(localStorage['hw_proxy_url'], options)
|
||||
.then(null,function(){
|
||||
return self.find_proxy(options);
|
||||
});
|
||||
}else{
|
||||
// just find something quick
|
||||
found_url = this.find_proxy(options);
|
||||
}
|
||||
|
||||
success = found_url.then(function(url){
|
||||
return self.connect(url);
|
||||
});
|
||||
|
||||
success.fail(function(){
|
||||
self.set_connection_status('disconnected');
|
||||
});
|
||||
|
||||
return success;
|
||||
},
|
||||
|
||||
@@ -217,10 +229,50 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
||||
}
|
||||
},
|
||||
|
||||
// returns as a deferred a list of valid hosts urls that can be used as proxy.
|
||||
// try several time to connect to a known proxy url
|
||||
try_hard_to_connect: function(url,options){
|
||||
options = options || {};
|
||||
var port = ':' + (options.port || '8069');
|
||||
|
||||
this.set_connection_status('connecting');
|
||||
|
||||
if(url.indexOf('//') < 0){
|
||||
url = 'http://'+url;
|
||||
}
|
||||
|
||||
if(url.indexOf(':',5) < 0){
|
||||
url = url+port;
|
||||
}
|
||||
|
||||
// try real hard to connect to url, with a 1sec timeout and up to 'retries' retries
|
||||
function try_real_hard_to_connect(url, retries, done){
|
||||
|
||||
done = done || new $.Deferred();
|
||||
|
||||
var c = $.ajax({
|
||||
url: url + '/hw_proxy/hello',
|
||||
method: 'GET',
|
||||
timeout: 1000,
|
||||
})
|
||||
.done(function(){
|
||||
done.resolve(url);
|
||||
})
|
||||
.fail(function(){
|
||||
if(retries > 0){
|
||||
try_real_hard_to_connect(url,retries-1,done);
|
||||
}else{
|
||||
done.reject();
|
||||
}
|
||||
});
|
||||
return done;
|
||||
}
|
||||
|
||||
return try_real_hard_to_connect(url,3);
|
||||
},
|
||||
|
||||
// returns as a deferred a valid host url that can be used as proxy.
|
||||
// options:
|
||||
// - port: what port to listen to (default 8069)
|
||||
// - force_ip : limit the search to the specified ip
|
||||
// - progress(fac) : callback for search progress ( fac in [0,1] )
|
||||
find_proxy: function(options){
|
||||
options = options || {};
|
||||
@@ -228,36 +280,17 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
||||
var port = ':' + (options.port || '8069');
|
||||
var urls = [];
|
||||
var found = false;
|
||||
var proxies = [];
|
||||
var done = new $.Deferred();
|
||||
var parallel = 8;
|
||||
var done = new $.Deferred(); // will be resolved with the proxies valid urls
|
||||
var threads = [];
|
||||
var progress = 0;
|
||||
|
||||
this.set_connection_status('connecting');
|
||||
|
||||
if(options.force_ip){
|
||||
var url = options.force_ip;
|
||||
if(url.indexOf('//') < 0){
|
||||
url = 'http://'+url;
|
||||
}
|
||||
if(url.indexOf(':',5) < 0){
|
||||
url = url+port;
|
||||
}
|
||||
urls.push(url);
|
||||
}else{
|
||||
if(localStorage['hw_proxy_url']){
|
||||
urls.push(localStorage['hw_proxy_url']);
|
||||
}
|
||||
|
||||
urls.push('http://localhost'+port);
|
||||
|
||||
for(var i = 0; i < 256; i++){
|
||||
urls.push('http://192.168.0.'+i+port);
|
||||
urls.push('http://192.168.1.'+i+port);
|
||||
urls.push('http://192.168.2.'+i+port);
|
||||
urls.push('http://10.0.0.'+i+port);
|
||||
}
|
||||
urls.push('http://localhost'+port);
|
||||
for(var i = 0; i < 256; i++){
|
||||
urls.push('http://192.168.0.'+i+port);
|
||||
urls.push('http://192.168.1.'+i+port);
|
||||
urls.push('http://10.0.0.'+i+port);
|
||||
}
|
||||
|
||||
var prog_inc = 1/urls.length;
|
||||
@@ -269,40 +302,39 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
||||
}
|
||||
}
|
||||
|
||||
function thread(url,done){
|
||||
if(!url){
|
||||
function thread(done){
|
||||
var url = urls.shift();
|
||||
|
||||
done = done || new $.Deferred();
|
||||
|
||||
if( !url || found || !self.searching_for_proxy ){
|
||||
done.resolve();
|
||||
return done;
|
||||
}
|
||||
|
||||
var c = $.ajax({
|
||||
url: url + '/hw_proxy/hello',
|
||||
method: 'GET',
|
||||
timeout: 300,
|
||||
timeout: 400,
|
||||
}).done(function(){
|
||||
found = true;
|
||||
update_progress();
|
||||
proxies.push(url);
|
||||
done.resolve(url);
|
||||
})
|
||||
.fail(function(){
|
||||
update_progress();
|
||||
var next_url = urls.shift();
|
||||
if(found ||! self.searching_for_proxy || !next_url){
|
||||
done.resolve();
|
||||
}else{
|
||||
thread(next_url,done);
|
||||
}
|
||||
thread(done);
|
||||
});
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
this.searching_for_proxy = true;
|
||||
|
||||
for(var i = 0; i < Math.min(parallel,urls.length); i++){
|
||||
threads.push(thread(urls.shift(),new $.Deferred()));
|
||||
for(var i = 0, len = Math.min(parallel,urls.length); i < len; i++){
|
||||
threads.push(thread());
|
||||
}
|
||||
|
||||
var done = new $.Deferred();
|
||||
|
||||
$.when.apply($,threads).then(function(){
|
||||
var urls = [];
|
||||
for(var i = 0; i < arguments.length; i++){
|
||||
@@ -310,7 +342,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
||||
urls.push(arguments[i]);
|
||||
}
|
||||
}
|
||||
done.resolve(urls);
|
||||
done.resolve(urls[0]);
|
||||
});
|
||||
|
||||
return done;
|
||||
|
||||
@@ -258,8 +258,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
||||
var total = order ? order.getTotalTaxIncluded() : 0;
|
||||
var taxes = order ? total - order.getTotalTaxExcluded() : 0;
|
||||
|
||||
this.el.querySelector('.summary .total > .value').innerText = this.format_currency(total);
|
||||
this.el.querySelector('.summary .total .subentry .value').innerText = this.format_currency(taxes);
|
||||
this.el.querySelector('.summary .total > .value').textContent = this.format_currency(total);
|
||||
this.el.querySelector('.summary .total .subentry .value').textContent = this.format_currency(taxes);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -635,9 +635,9 @@
|
||||
Shop: <t t-esc="widget.pos.shop.name"/><br />
|
||||
<br />
|
||||
<t t-if="widget.pos.config.receipt_header">
|
||||
<pre>
|
||||
<div style='text-align:center'>
|
||||
<t t-esc="widget.pos.config.receipt_header" />
|
||||
</pre>
|
||||
</div>
|
||||
<br />
|
||||
</t>
|
||||
<table>
|
||||
@@ -711,9 +711,9 @@
|
||||
</table>
|
||||
<t t-if="widget.pos.config.receipt_footer">
|
||||
<br />
|
||||
<pre>
|
||||
<div style='text-align:center'>
|
||||
<t t-esc="widget.pos.config.receipt_footer" />
|
||||
</pre>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -301,7 +301,7 @@
|
||||
<field name="monthly_invoiced" widget="gauge" style="width:160px; height: 120px; cursor: pointer;"
|
||||
options="{'max_field': 'invoiced_target'}">Invoiced</field>
|
||||
<field name="invoiced_forecast" widget="gauge" style="width:160px; height: 120px; cursor: pointer;"
|
||||
options="{'max_field': 'invoiced_target', 'action_change': 'action_forecast'}">Forecast</field>
|
||||
options="{'max_field': 'invoiced_target', 'on_change': 'action_forecast'}">Forecast</field>
|
||||
</div>
|
||||
<div class="oe_center oe_salesteams_help" style="color:#bbbbbb;" t-if="!record.invoiced_target.raw_value">
|
||||
<br/>Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance.
|
||||
|
||||
@@ -133,8 +133,9 @@ class view(osv.osv):
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
qcontext = context.copy()
|
||||
qcontext = dict(editable=False)
|
||||
qcontext.update(
|
||||
context.copy(),
|
||||
website=request.website,
|
||||
url_for=website.url_for,
|
||||
slug=website.slug,
|
||||
|
||||
@@ -399,7 +399,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
|
||||
}
|
||||
|
||||
.oe_green {
|
||||
background-color: #51d466;
|
||||
background-color: #169c78;
|
||||
color: white;
|
||||
}
|
||||
.oe_green .text-muted {
|
||||
@@ -407,7 +407,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
|
||||
}
|
||||
|
||||
.oe_blue_light {
|
||||
background-color: #4791d2;
|
||||
background-color: #41b6ab;
|
||||
color: white;
|
||||
}
|
||||
.oe_blue_light .text-muted {
|
||||
@@ -420,7 +420,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
|
||||
}
|
||||
|
||||
.oe_orange {
|
||||
background-color: #e67e22;
|
||||
background-color: #f05442;
|
||||
color: white;
|
||||
}
|
||||
.oe_orange .text-muted {
|
||||
@@ -436,7 +436,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
|
||||
}
|
||||
|
||||
.oe_red {
|
||||
background-color: #f75353;
|
||||
background-color: #9c1b31;
|
||||
color: white;
|
||||
}
|
||||
.oe_red .text-muted {
|
||||
|
||||
@@ -328,13 +328,13 @@ div.carousel[data-snippet-id="slider"]
|
||||
color: white
|
||||
|
||||
.oe_green
|
||||
background-color: #51d466
|
||||
background-color: #169C78
|
||||
color: white
|
||||
.text-muted
|
||||
color: #ddd
|
||||
|
||||
.oe_blue_light
|
||||
background-color: #4791d2
|
||||
background-color: #41b6ab
|
||||
color: white
|
||||
.text-muted
|
||||
color: #ddd
|
||||
@@ -344,7 +344,7 @@ div.carousel[data-snippet-id="slider"]
|
||||
color: white
|
||||
|
||||
.oe_orange
|
||||
background-color: #e67e22
|
||||
background-color: #f05442
|
||||
color: white
|
||||
.text-muted
|
||||
color: #ddd
|
||||
@@ -356,7 +356,7 @@ div.carousel[data-snippet-id="slider"]
|
||||
color: #ddd
|
||||
|
||||
.oe_red
|
||||
background-color: #f75353
|
||||
background-color: #9C1b31
|
||||
color: white
|
||||
.text-muted
|
||||
color: #ddd
|
||||
|
||||
|
Before Width: | Height: | Size: 370 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 392 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 825 KiB After Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 584 KiB After Width: | Height: | Size: 223 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 348 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 732 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 27 KiB |
@@ -638,7 +638,7 @@
|
||||
image_dialog(editor, new CKEDITOR.dom.element(previous));
|
||||
$image_button.hide();
|
||||
previous = null;
|
||||
});
|
||||
}, 'btn-sm');
|
||||
|
||||
// previous is the state of the button-trigger: it's the
|
||||
// currently-ish hovered element which can trigger a button showing.
|
||||
|
||||
@@ -71,8 +71,8 @@
|
||||
{
|
||||
snippet: 'three-columns',
|
||||
placement: 'bottom',
|
||||
title: _t("Drag & Drop a Block"),
|
||||
content: _t("Drag the <em>'3 Columns'</em> block and drop it below the banner."),
|
||||
title: _t("Drag & Drop This Block"),
|
||||
content: _t("Drag the <em>'Three Columns'</em> block and drop it below the banner."),
|
||||
popover: { fixed: true },
|
||||
},
|
||||
{
|
||||
@@ -98,17 +98,18 @@
|
||||
popover: { fixed: true },
|
||||
},
|
||||
{
|
||||
element: 'button[data-dismiss=modal]',
|
||||
element: '.modal:has(#mobile-viewport) button[data-dismiss=modal]',
|
||||
placement: 'right',
|
||||
title: _t("Close Mobile Preview"),
|
||||
content: _t("Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog."),
|
||||
},
|
||||
{
|
||||
title: "Congratulation",
|
||||
element: 'a[id=content-menu-button]',
|
||||
waitNot: '.modal',
|
||||
element: '#content-menu-button',
|
||||
placement: 'bottom',
|
||||
content: _t("This tour is finished. You can continue discovering features with the <em>'Content'</em> menu."),
|
||||
popover: { fixed: true, next: _t("Close Tutorial") },
|
||||
title: _t("Add new pages and menus"),
|
||||
content: _t("The 'Content' menu allows you to add pages or add the top menu."),
|
||||
popover: { next: _t("Close Tutorial") },
|
||||
},
|
||||
];
|
||||
return this._super();
|
||||
|
||||
@@ -77,7 +77,11 @@ website.Tour = openerp.Class.extend({
|
||||
|
||||
website.Tour.busy = true;
|
||||
|
||||
this.localStorage.setItem("tour-"+this.id+"-test-automatic", automatic);
|
||||
if (automatic) {
|
||||
this.localStorage.setItem("tour-"+this.id+"-test-automatic", true);
|
||||
} else {
|
||||
this.localStorage.removeItem("tour-"+this.id+"-test-automatic");
|
||||
}
|
||||
this.automatic = automatic;
|
||||
|
||||
if (this.path) {
|
||||
|
||||
@@ -22,6 +22,6 @@
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="website.tour_popover_title">
|
||||
<t t-esc="title"/><button title="Close Tutorial" type="button" class="close" data-role="end">×</button>
|
||||
<t t-esc="title"/><button title="End This Tutorial" type="button" class="close" data-role="end">×</button>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -907,7 +907,7 @@
|
||||
<li data-class='oe_dark'><a>Darken</a></li>
|
||||
<li data-class='oe_green'><a>Green</a></li>
|
||||
<li data-class='oe_red'><a>Red</a></li>
|
||||
<li data-class='oe_blue_light'><a>Light Blue</a></li>
|
||||
<li data-class='oe_blue_light'><a>Turquoise</a></li>
|
||||
<li data-class='oe_blue'><a>Dark Blue</a></li>
|
||||
<li data-class='oe_orange'><a>Orange</a></li>
|
||||
<li data-class='oe_purple'><a>Purple</a></li>
|
||||
|
||||
@@ -224,7 +224,7 @@
|
||||
<xpath expr="//body" position="attributes">
|
||||
<attribute name="style">padding-top: 51px;</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//body/*[1]" position="before">
|
||||
<xpath expr="//body" position="inside">
|
||||
<div id="website-top-navbar-placeholder" class="navbar navbar-inverse navbar-fixed-top hidden-xs">
|
||||
<div class="navbar-header">
|
||||
<form class="navbar-form navbar-left">
|
||||
|
||||
@@ -51,8 +51,8 @@
|
||||
"widget": "monetary",
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/>
|
||||
<meta itemprop="price" t-att-content="ticket.price"/>
|
||||
<meta itemprop="priceCurrency" t-att-content="website.pricelist_id.currency_id.name"/>
|
||||
<span itemprop="price" style="display:none;" t-esc="ticket.price"/>
|
||||
<span itemprop="priceCurrency" style="display:none;" t-esc="website.pricelist_id.currency_id.name"/>
|
||||
</t>
|
||||
<t t-if="not ticket.price and not editable">
|
||||
<span>Free</span>
|
||||
|
||||
@@ -81,8 +81,8 @@
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'>
|
||||
</span>
|
||||
<meta itemprop="price" t-att-content="product.product_variant_ids[0].price"/>
|
||||
<meta itemprop="priceCurrency" t-att-content="website.pricelist_id.currency_id.name"/>
|
||||
<span itemprop="price" style="display:none;" t-esc="product.product_variant_ids[0].price"/>
|
||||
<span itemprop="priceCurrency" style="display:none;" t-esc="website.pricelist_id.currency_id.name"/>
|
||||
</b>
|
||||
</div>
|
||||
</section>
|
||||
@@ -303,7 +303,7 @@
|
||||
<span itemprop="image" t-field="product.image" t-field-options='{"widget": "image", "class": "product_detail_img"}'/>
|
||||
</div><div class="col-sm-5 col-md-5 col-lg-4 col-lg-offset-1">
|
||||
<h1 itemprop="name" t-field="product.name">Product Name</h1>
|
||||
<meta itemprop="url" t-attf-content="/shop/product/{{ slug(product) }}"/>
|
||||
<span itemprop="url" style="display:none;" t-esc="'/shop/product/%s' % slug(product)"/>
|
||||
<form action="/shop/add_cart/" class="js_add_cart_json" method="POST">
|
||||
<input type="hidden" t-if="len(product.product_variant_ids) == 1" name="product_id" t-att-value="product.product_variant_ids[0].id"/>
|
||||
<t t-if="len(product.product_variant_ids) > 1">
|
||||
@@ -335,8 +335,8 @@
|
||||
"widget": "monetary",
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/>
|
||||
<meta itemprop="price" t-att-content="product.product_variant_ids[0].price"/>
|
||||
<meta itemprop="priceCurrency" t-att-content="website.pricelist_id.currency_id.name"/>
|
||||
<span itemprop="price" style="display:none;" t-esc="product.product_variant_ids[0].price"/>
|
||||
<span itemprop="priceCurrency" style="display:none;" t-esc="website.pricelist_id.currency_id.name"/>
|
||||
</h4>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-lg mt8">Add to Cart</button>
|
||||
|
||||