recritment bug fixes
This commit is contained in:
parent
7960d1926b
commit
a9a9f08ff9
|
|
@ -115,7 +115,6 @@ class CwfTimesheet(models.Model):
|
||||||
external_group_id = self.env.ref("hr_employee_extended.group_external_user")
|
external_group_id = self.env.ref("hr_employee_extended.group_external_user")
|
||||||
users = self.env["res.users"].search([("groups_id", "=", external_group_id.id)])
|
users = self.env["res.users"].search([("groups_id", "=", external_group_id.id)])
|
||||||
employees = self.env['hr.employee'].search([('user_id', 'in', users.ids),'|',('doj','=',False),('doj','>=', self.week_start_date)])
|
employees = self.env['hr.employee'].search([('user_id', 'in', users.ids),'|',('doj','=',False),('doj','>=', self.week_start_date)])
|
||||||
print(employees)
|
|
||||||
# Loop through each day of the week and create timesheet lines for each employee
|
# Loop through each day of the week and create timesheet lines for each employee
|
||||||
while current_date <= end_date:
|
while current_date <= end_date:
|
||||||
for employee in employees:
|
for employee in employees:
|
||||||
|
|
|
||||||
|
|
@ -756,9 +756,9 @@ class ITTaxStatementWizard(models.TransientModel):
|
||||||
if not self.employee_id or not self.contract_id or not self.period_id or not self.period_line:
|
if not self.employee_id or not self.contract_id or not self.period_id or not self.period_line:
|
||||||
raise ValidationError(_("Select employee, period, and period line before checking comparison."))
|
raise ValidationError(_("Select employee, period, and period line before checking comparison."))
|
||||||
|
|
||||||
values = self._get_tax_base_values(include_comparison=True)
|
values = self.sudo()._get_tax_base_values(include_comparison=True)
|
||||||
if not values['comparison_available']:
|
if not values['comparison_available']:
|
||||||
self._reset_regime_comparison()
|
self.sudo()._reset_regime_comparison()
|
||||||
raise ValidationError(_("Tax comparison is available only when both old and new regime slabs are configured."))
|
raise ValidationError(_("Tax comparison is available only when both old and new regime slabs are configured."))
|
||||||
|
|
||||||
self.write({
|
self.write({
|
||||||
|
|
@ -938,7 +938,7 @@ class ITTaxStatementWizard(models.TransientModel):
|
||||||
return {'data': data}
|
return {'data': data}
|
||||||
|
|
||||||
def action_generate_report(self):
|
def action_generate_report(self):
|
||||||
report_data = self._prepare_income_tax_data(include_comparison=False)
|
report_data = self.sudo()._prepare_income_tax_data(include_comparison=False)
|
||||||
|
|
||||||
return self.env.ref('employee_it_declaration.income_tax_statement_action_report').report_action(
|
return self.env.ref('employee_it_declaration.income_tax_statement_action_report').report_action(
|
||||||
self,
|
self,
|
||||||
|
|
@ -946,7 +946,7 @@ class ITTaxStatementWizard(models.TransientModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
def action_generate_comparison_report(self):
|
def action_generate_comparison_report(self):
|
||||||
report_data = self._prepare_income_tax_data(include_comparison=True)
|
report_data = self.sudo()._prepare_income_tax_data(include_comparison=True)
|
||||||
if not report_data['data']['comparison']['available']:
|
if not report_data['data']['comparison']['available']:
|
||||||
raise ValidationError(_("Tax comparison is available only when both old and new regime slabs are configured."))
|
raise ValidationError(_("Tax comparison is available only when both old and new regime slabs are configured."))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
<field name="currency_id" invisible="1"/>
|
<field name="currency_id" invisible="1"/>
|
||||||
<field name="is_general_tax_statement" invisible="1"/>
|
<field name="is_general_tax_statement" invisible="1"/>
|
||||||
<group>
|
<group>
|
||||||
<field name="is_hr_manager" invisible="0" force_save="1"/>
|
<field name="is_hr_manager" invisible="1" force_save="1"/>
|
||||||
<field name="employee_id" readonly="not is_hr_manager" options="{'no_edit': True, 'no_create': True}"/>
|
<field name="employee_id" readonly="not is_hr_manager" options="{'no_edit': True, 'no_create': True}"/>
|
||||||
<field name="contract_id" readonly="1" force_save="1" invisible="0"/>
|
<field name="contract_id" readonly="1" force_save="1" invisible="0"/>
|
||||||
</group>
|
</group>
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,6 @@ class LetoutHouseProperty(models.Model):
|
||||||
deduction = net_annual_value * 0.30
|
deduction = net_annual_value * 0.30
|
||||||
income_loss = net_annual_value - deduction - record.interest_paid_to
|
income_loss = net_annual_value - deduction - record.interest_paid_to
|
||||||
|
|
||||||
print(net_annual_value)
|
|
||||||
print(deduction)
|
|
||||||
print(income_loss)
|
|
||||||
record.net_annual_value = net_annual_value
|
record.net_annual_value = net_annual_value
|
||||||
record.deduction_for_repairs = round(deduction)
|
record.deduction_for_repairs = round(deduction)
|
||||||
record.income_loss = round(income_loss)
|
record.income_loss = round(income_loss)
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,6 @@ ORDER BY
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Error executing attendance report query: {str(e)}"
|
error_msg = f"Error executing attendance report query: {str(e)}"
|
||||||
print(error_msg)
|
|
||||||
raise UserError(
|
raise UserError(
|
||||||
_("An error occurred while generating the attendance report. Please check the logs for details."))
|
_("An error occurred while generating the attendance report. Please check the logs for details."))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ class HrApplicant(models.Model):
|
||||||
action = self.env.ref("hr_recruitment_auto_doc.action_hr_recruitment_auto_doc_wizard_applicant").read()[0]
|
action = self.env.ref("hr_recruitment_auto_doc.action_hr_recruitment_auto_doc_wizard_applicant").read()[0]
|
||||||
context = dict(self.env.context)
|
context = dict(self.env.context)
|
||||||
context['single_parser'] = True
|
context['single_parser'] = True
|
||||||
action["context"]['current_id'] = self.id
|
context['applicant_id'] = self.id
|
||||||
|
context['target_model'] = 'applicant'
|
||||||
if len(self) == 1 and self.hr_job_recruitment:
|
if len(self) == 1 and self.hr_job_recruitment:
|
||||||
context["default_job_recruitment_id"] = self.hr_job_recruitment.id
|
context["default_job_recruitment_id"] = self.hr_job_recruitment.id
|
||||||
action["context"] = context
|
action["context"] = context
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ class HrCandidate(models.Model):
|
||||||
action = self.env.ref("hr_recruitment_auto_doc.action_hr_recruitment_auto_doc_wizard_candidate").read()[0]
|
action = self.env.ref("hr_recruitment_auto_doc.action_hr_recruitment_auto_doc_wizard_candidate").read()[0]
|
||||||
action["context"] = dict(self.env.context)
|
action["context"] = dict(self.env.context)
|
||||||
action["context"]['single_parser'] = True
|
action["context"]['single_parser'] = True
|
||||||
action["context"]['current_id'] = self.id
|
action["context"]['target_model'] = 'candidate'
|
||||||
|
action["context"]['candidate_id'] = self.id
|
||||||
action["name"] = _("Parse Resumes")
|
action["name"] = _("Parse Resumes")
|
||||||
return action
|
return action
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ class HrJobRecruitment(models.Model):
|
||||||
def action_open_auto_doc_wizard(self):
|
def action_open_auto_doc_wizard(self):
|
||||||
action = self.env.ref("hr_recruitment_auto_doc.action_hr_recruitment_auto_doc_wizard_job_recruitment").read()[0]
|
action = self.env.ref("hr_recruitment_auto_doc.action_hr_recruitment_auto_doc_wizard_job_recruitment").read()[0]
|
||||||
context = dict(self.env.context)
|
context = dict(self.env.context)
|
||||||
|
context['target_model'] = 'job_recruitment'
|
||||||
if len(self) == 1:
|
if len(self) == 1:
|
||||||
context["default_job_recruitment_id"] = self.id
|
context["default_job_recruitment_id"] = self.id
|
||||||
action["context"] = context
|
action["context"] = context
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,11 @@
|
||||||
<field name="inherit_id" ref="hr_recruitment_extended.hr_applicant_view_form_inherit"/>
|
<field name="inherit_id" ref="hr_recruitment_extended.hr_applicant_view_form_inherit"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//header" position="inside">
|
<xpath expr="//header" position="inside">
|
||||||
<button name="action_open_auto_doc_wizard"
|
<!-- <button name="action_open_auto_doc_wizard"-->
|
||||||
string="Parse Resume"
|
<!-- string="Parse Resume"-->
|
||||||
type="object"
|
<!-- type="object"-->
|
||||||
class="btn-secondary"
|
<!-- class="btn-secondary"-->
|
||||||
groups="hr_recruitment.group_hr_recruitment_user"/>
|
<!-- groups="hr_recruitment.group_hr_recruitment_user"/>-->
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
||||||
|
|
@ -95,10 +95,21 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
|
||||||
res["job_recruitment_id"] = default_job_recruitment_id
|
res["job_recruitment_id"] = default_job_recruitment_id
|
||||||
elif len(active_ids) == 1:
|
elif len(active_ids) == 1:
|
||||||
res["job_recruitment_id"] = active_ids[0]
|
res["job_recruitment_id"] = active_ids[0]
|
||||||
|
if self.env.context.get("target_model"):
|
||||||
|
res["target_model"] = self.env.context.get("target_model")
|
||||||
|
if res["target_model"] == "candidate" and self.env.context.get("candidate_id"):
|
||||||
|
candidate = self.env["hr.candidate"].browse(int(self.env.context.get("candidate_id")))
|
||||||
|
|
||||||
|
if candidate and candidate.resume:
|
||||||
|
res.update({
|
||||||
|
"resume_file": candidate.resume,
|
||||||
|
"resume_filename": candidate.resume_name,
|
||||||
|
})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def action_parse_documents(self):
|
def action_parse_documents(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
self.line_ids.unlink()
|
||||||
self._sync_upload_lines()
|
self._sync_upload_lines()
|
||||||
if not self.line_ids:
|
if not self.line_ids:
|
||||||
raise UserError(_("Please add at least one document to parse."))
|
raise UserError(_("Please add at least one document to parse."))
|
||||||
|
|
@ -220,7 +231,8 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
|
||||||
"updated_count": updated,
|
"updated_count": updated,
|
||||||
"skipped_count": skipped,
|
"skipped_count": skipped,
|
||||||
"result_html": self._build_summary_html(summary_rows),
|
"result_html": self._build_summary_html(summary_rows),
|
||||||
"parsed_document": True
|
"parsed_document": True,
|
||||||
|
"create_updated_records": False,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -516,15 +528,24 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
|
||||||
|
|
||||||
def _find_or_create_candidate(self, line, parsed_data, parsed_payload):
|
def _find_or_create_candidate(self, line, parsed_data, parsed_payload):
|
||||||
candidate = self._find_existing_candidate(parsed_data)
|
candidate = self._find_existing_candidate(parsed_data)
|
||||||
|
existing_candidate = True
|
||||||
candidate_vals = self._prepare_candidate_vals(line, parsed_data, parsed_payload)
|
candidate_vals = self._prepare_candidate_vals(line, parsed_data, parsed_payload)
|
||||||
|
if self.env.context.get('candidate_id') and not candidate:
|
||||||
|
existing_candidate = False
|
||||||
|
candidate = self.env['hr.candidate'].sudo().browse(int(self.env.context.get('candidate_id')))
|
||||||
if candidate:
|
if candidate:
|
||||||
if self.update_existing_candidates:
|
if self.update_existing_candidates:
|
||||||
candidate.write(self._prepare_sparse_update_vals(candidate, candidate_vals))
|
candidate.write(self._prepare_sparse_update_vals(candidate, candidate_vals, False))
|
||||||
self._sync_candidate_skills(candidate, parsed_data.get("skills") or [])
|
self._sync_candidate_skills(candidate, parsed_data.get("skills") or [])
|
||||||
if self.target_model == "candidate":
|
if self.target_model == "candidate":
|
||||||
self._sync_candidate_resume_histories(candidate, parsed_data)
|
self._sync_candidate_resume_histories(candidate, parsed_data)
|
||||||
message = _("Matched existing candidate: %s") % candidate.display_name
|
if existing_candidate:
|
||||||
|
message = _("Matched existing candidate: %s") % candidate.display_name
|
||||||
|
if self.env.context.get('candidate_id'):
|
||||||
|
self.env['hr.candidate'].sudo().browse(int(self.env.context.get('candidate_id'))).unlink()
|
||||||
|
else:
|
||||||
|
candidate.write(self._prepare_sparse_update_vals(candidate, candidate_vals, True))
|
||||||
|
message = _("Details Updated Successfully: %s") % candidate.display_name
|
||||||
return candidate, "updated", message
|
return candidate, "updated", message
|
||||||
|
|
||||||
self._ensure_resume_creation_allowed(parsed_data, line.file_name)
|
self._ensure_resume_creation_allowed(parsed_data, line.file_name)
|
||||||
|
|
@ -586,7 +607,7 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
|
||||||
|
|
||||||
candidate = False
|
candidate = False
|
||||||
if email_value:
|
if email_value:
|
||||||
candidate = search_model.search(['|',('email_from','=', email_value),("email_normalized", "=", email_value)], limit=1)
|
candidate = search_model.search([('id','!=',self.env.context.get('candidate_id')),'|',('email_from','=', email_value),("email_normalized", "=", email_value)], limit=1)
|
||||||
if not candidate and phone_values:
|
if not candidate and phone_values:
|
||||||
candidate = search_model.search([
|
candidate = search_model.search([
|
||||||
"|","|",('partner_phone',"in",phone_values),
|
"|","|",('partner_phone',"in",phone_values),
|
||||||
|
|
@ -973,7 +994,7 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
|
||||||
if not skills:
|
if not skills:
|
||||||
raise ValidationError(_("No reliable skills were found in the resume. Record creation was skipped."))
|
raise ValidationError(_("No reliable skills were found in the resume. Record creation was skipped."))
|
||||||
|
|
||||||
def _prepare_sparse_update_vals(self, candidate, values):
|
def _prepare_sparse_update_vals(self, candidate, values, current_update:False):
|
||||||
update_vals = {}
|
update_vals = {}
|
||||||
field_map = {
|
field_map = {
|
||||||
"partner_name": not candidate.partner_name,
|
"partner_name": not candidate.partner_name,
|
||||||
|
|
@ -988,8 +1009,13 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
|
||||||
"resume_type": not getattr(candidate, "resume_type", False),
|
"resume_type": not getattr(candidate, "resume_type", False),
|
||||||
}
|
}
|
||||||
for field_name, can_update in field_map.items():
|
for field_name, can_update in field_map.items():
|
||||||
if can_update and values.get(field_name):
|
if can_update and values.get(field_name) and not current_update:
|
||||||
update_vals[field_name] = values[field_name]
|
update_vals[field_name] = values[field_name]
|
||||||
|
elif values.get(field_name) and current_update:
|
||||||
|
if field_name == 'partner_name':
|
||||||
|
update_vals[field_name] = values.get('partner_name')
|
||||||
|
else:
|
||||||
|
update_vals[field_name] = values[field_name]
|
||||||
return update_vals
|
return update_vals
|
||||||
|
|
||||||
def _sync_candidate_resume_histories(self, candidate, parsed_data):
|
def _sync_candidate_resume_histories(self, candidate, parsed_data):
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,18 @@ from odoo.tools.mimetypes import guess_mimetype, fix_filename_extension
|
||||||
class HRApplicant(models.Model):
|
class HRApplicant(models.Model):
|
||||||
_inherit = 'hr.applicant'
|
_inherit = 'hr.applicant'
|
||||||
_track_duration_field = 'recruitment_stage_id'
|
_track_duration_field = 'recruitment_stage_id'
|
||||||
|
_rec_names_search = ['candidate_id', 'candidate_id.candidate_sequence', 'candidate_id.partner_phone','partner_name','job_id.name','hr_job_recruitment.recruitment_sequence']
|
||||||
|
_sql_constraints = [
|
||||||
|
('name_applicant_job_id', 'unique(candidate_id, hr_job_recruitment)',
|
||||||
|
"Avoid creating Multiple applications for the same job")]
|
||||||
|
|
||||||
|
@api.constrains('candidate_id','hr_job_recruitment')
|
||||||
|
def _check_name_applicant_job_id(self):
|
||||||
|
for rec in self:
|
||||||
|
if rec.candidate_id and rec.hr_job_recruitment:
|
||||||
|
candidate_history = self.sudo().search([('id','!=',rec.id),('candidate_id','=',rec.candidate_id.id),('hr_job_recruitment','=',rec.hr_job_recruitment.id)])
|
||||||
|
if candidate_history:
|
||||||
|
raise ValidationError(_("Avoid creating Multiple applications for the same Job Position."))
|
||||||
|
|
||||||
hide_chatter_suggestion = fields.Boolean(string="Hide Chatter Suggestions", default=False, tracking=True)
|
hide_chatter_suggestion = fields.Boolean(string="Hide Chatter Suggestions", default=False, tracking=True)
|
||||||
primary_skill_match_percentage = fields.Float(
|
primary_skill_match_percentage = fields.Float(
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,23 @@ from odoo import models, fields, api, _
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class HRCandidate(models.Model):
|
||||||
|
_inherit = 'hr.candidate'
|
||||||
|
|
||||||
|
education_history = fields.Many2many('education.history', string='Education Details')
|
||||||
|
|
||||||
|
employer_history = fields.Many2many('employer.history', string='Education Details')
|
||||||
|
|
||||||
|
family_details = fields.Many2many('family.details', string='Family Details')
|
||||||
|
|
||||||
class HRApplicant(models.Model):
|
class HRApplicant(models.Model):
|
||||||
_inherit = 'hr.applicant'
|
_inherit = 'hr.applicant'
|
||||||
|
|
||||||
education_history = fields.One2many('education.history', 'applicant_id', string='Education Details')
|
education_history = fields.Many2many('education.history', related='candidate_id.education_history', readonly=False, string='Education Details')
|
||||||
|
|
||||||
employer_history = fields.One2many('employer.history', 'applicant_id', string='Education Details')
|
employer_history = fields.Many2many('employer.history', related='candidate_id.employer_history', readonly=False, string='Education Details')
|
||||||
|
|
||||||
family_details = fields.One2many('family.details', 'applicant_id', string='Family Details')
|
family_details = fields.Many2many('family.details', related='candidate_id.family_details', readonly=False, string='Family Details')
|
||||||
|
|
||||||
|
|
||||||
family_education_employer_details_status = fields.Selection([('pending', 'Pending'),
|
family_education_employer_details_status = fields.Selection([('pending', 'Pending'),
|
||||||
|
|
@ -27,15 +36,6 @@ class HRApplicant(models.Model):
|
||||||
raise ValidationError(_("No Data to Validate"))
|
raise ValidationError(_("No Data to Validate"))
|
||||||
|
|
||||||
|
|
||||||
class HRCandidate(models.Model):
|
|
||||||
_inherit = 'hr.candidate'
|
|
||||||
|
|
||||||
education_history = fields.One2many('education.history', 'candidate_id', string='Education Details')
|
|
||||||
|
|
||||||
employer_history = fields.One2many('employer.history', 'candidate_id', string='Education Details')
|
|
||||||
|
|
||||||
family_details = fields.One2many('family.details', 'candidate_id', string='Family Details')
|
|
||||||
|
|
||||||
|
|
||||||
class FamilyDetails(models.Model):
|
class FamilyDetails(models.Model):
|
||||||
_inherit = "family.details"
|
_inherit = "family.details"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ class HrCandidate(models.Model):
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
('unique_candidate_sequence', 'UNIQUE(candidate_sequence)', 'Candidate sequence must be unique!'),
|
('unique_candidate_sequence', 'UNIQUE(candidate_sequence)', 'Candidate sequence must be unique!'),
|
||||||
]
|
]
|
||||||
|
_rec_names_search = ['partner_name', 'candidate_sequence', 'partner_phone']
|
||||||
|
|
||||||
#personal Details
|
#personal Details
|
||||||
candidate_sequence = fields.Char(string='Candidate Sequence', readonly=False, default='/', copy=False)
|
candidate_sequence = fields.Char(string='Candidate Sequence', readonly=False, default='/', copy=False)
|
||||||
hide_chatter_suggestion = fields.Boolean(string="Hide Chatter Suggestions", default=False, tracking=True)
|
hide_chatter_suggestion = fields.Boolean(string="Hide Chatter Suggestions", default=False, tracking=True)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,12 @@ patch(RecruitmentFormController.prototype, {
|
||||||
this._matchPanelSearchTerm = "";
|
this._matchPanelSearchTerm = "";
|
||||||
this._matchPanelActiveTab = "candidates";
|
this._matchPanelActiveTab = "candidates";
|
||||||
this._matchPanelAddingCandidateId = null;
|
this._matchPanelAddingCandidateId = null;
|
||||||
|
this._primarySkillsExpanded = false;
|
||||||
|
this._secondarySkillsExpanded = false;
|
||||||
|
this._matchPanelScrollPosition = {
|
||||||
|
candidates: 0,
|
||||||
|
applicants: 0,
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => this._syncRecruitmentMatchPanel());
|
onMounted(() => this._syncRecruitmentMatchPanel());
|
||||||
onPatched(() => this._syncRecruitmentMatchPanel());
|
onPatched(() => this._syncRecruitmentMatchPanel());
|
||||||
|
|
@ -161,7 +167,7 @@ patch(RecruitmentFormController.prototype, {
|
||||||
|
|
||||||
_renderSkillTags(skillNames, className) {
|
_renderSkillTags(skillNames, className) {
|
||||||
if (!skillNames.length) {
|
if (!skillNames.length) {
|
||||||
return `<span class="o_hr_match_chip ${className}">${escapeHtml(_t("None"))}</span>`;
|
return `<span class="o_hr_match_chip ${className}">${escapeHtml(_t("No Skills Added"))}</span>`;
|
||||||
}
|
}
|
||||||
return skillNames
|
return skillNames
|
||||||
.map((skillName) => `<span class="o_hr_match_chip ${className}">${escapeHtml(skillName)}</span>`)
|
.map((skillName) => `<span class="o_hr_match_chip ${className}">${escapeHtml(skillName)}</span>`)
|
||||||
|
|
@ -288,6 +294,7 @@ patch(RecruitmentFormController.prototype, {
|
||||||
? _t("Applicants added to this recruitment will appear here with the same match insights.")
|
? _t("Applicants added to this recruitment will appear here with the same match insights.")
|
||||||
: _t("Try refreshing after updating the recruitment skills or candidate pool.")
|
: _t("Try refreshing after updating the recruitment skills or candidate pool.")
|
||||||
);
|
);
|
||||||
|
const previousScroll = this._matchPanelScrollPosition[this._matchPanelActiveTab] || 0;
|
||||||
|
|
||||||
panel.innerHTML = `
|
panel.innerHTML = `
|
||||||
<div class="o_hr_match_panel_backdropless">
|
<div class="o_hr_match_panel_backdropless">
|
||||||
|
|
@ -295,7 +302,7 @@ patch(RecruitmentFormController.prototype, {
|
||||||
<div>
|
<div>
|
||||||
<p>${escapeHtml(_t("Candidate Pool"))}</p>
|
<p>${escapeHtml(_t("Candidate Pool"))}</p>
|
||||||
<h3>${escapeHtml(payload.job_recruitment_name || _t("Recruitment Matches"))}</h3>
|
<h3>${escapeHtml(payload.job_recruitment_name || _t("Recruitment Matches"))}</h3>
|
||||||
<span>${escapeHtml(`${payload.candidate_count || 0} ${_t("pool candidates")} • ${payload.applicant_count || 0} ${_t("applicants")}`)}</span>
|
<span>${escapeHtml(`${payload.candidate_count || 0} ${_t(" pool candidates ")} • ${payload.applicant_count || 0} ${_t(" applicants")}`)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="o_hr_match_panel_actions">
|
<div class="o_hr_match_panel_actions">
|
||||||
<button type="button" class="btn btn-light o_hr_match_refresh">${escapeHtml(_t("Refresh"))}</button>
|
<button type="button" class="btn btn-light o_hr_match_refresh">${escapeHtml(_t("Refresh"))}</button>
|
||||||
|
|
@ -322,28 +329,88 @@ patch(RecruitmentFormController.prototype, {
|
||||||
</label>
|
</label>
|
||||||
</section>
|
</section>
|
||||||
<section class="o_hr_match_panel_skill_summary">
|
<section class="o_hr_match_panel_skill_summary">
|
||||||
<div>
|
|
||||||
<label>${escapeHtml(_t("Primary Skills"))}</label>
|
${
|
||||||
<div class="o_hr_match_chip_row">
|
payload.primary_skill_names?.length
|
||||||
${this._renderSkillTags(payload.primary_skill_names || [], "o_hr_match_chip_primary")}
|
? `
|
||||||
</div>
|
<div class="o_hr_match_skill_header"
|
||||||
</div>
|
data-toggle="primary">
|
||||||
<div>
|
<span>
|
||||||
<label>${escapeHtml(_t("Secondary Skills"))}</label>
|
${this._primarySkillsExpanded ? "▼" : "▶"}
|
||||||
<div class="o_hr_match_chip_row">
|
${escapeHtml(_t("Primary Skills"))}
|
||||||
${this._renderSkillTags(payload.secondary_skill_names || [], "o_hr_match_chip_secondary")}
|
</span>
|
||||||
</div>
|
|
||||||
</div>
|
<span class="o_hr_match_skill_count">
|
||||||
</section>
|
(${payload.primary_skill_names.length})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${
|
||||||
|
this._primarySkillsExpanded
|
||||||
|
? `
|
||||||
|
<div class="o_hr_match_chip_row">
|
||||||
|
${this._renderSkillTags(payload.primary_skill_names, "o_hr_match_chip_primary")}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
${
|
||||||
|
payload.secondary_skill_names?.length
|
||||||
|
? `
|
||||||
|
<div class="o_hr_match_skill_header"
|
||||||
|
data-toggle="secondary">
|
||||||
|
<span>
|
||||||
|
${this._secondarySkillsExpanded ? "▼" : "▶"}
|
||||||
|
${escapeHtml(_t("Secondary Skills"))}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="o_hr_match_skill_count">
|
||||||
|
(${payload.secondary_skill_names.length})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${
|
||||||
|
this._secondarySkillsExpanded
|
||||||
|
? `
|
||||||
|
<div class="o_hr_match_chip_row">
|
||||||
|
${this._renderSkillTags(payload.secondary_skill_names, "o_hr_match_chip_secondary")}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
</section>
|
||||||
<section class="o_hr_match_panel_body ${this._matchPanelLoading ? "o_hr_match_panel_body_loading" : ""}">
|
<section class="o_hr_match_panel_body ${this._matchPanelLoading ? "o_hr_match_panel_body_loading" : ""}">
|
||||||
${this._matchPanelLoading ? `<div class="o_hr_match_loading">${escapeHtml(_t("Refreshing matches..."))}</div>` : activeCards}
|
${this._matchPanelLoading ? `<div class="o_hr_match_loading">${escapeHtml(_t("Refreshing matches..."))}</div>` : activeCards}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
panel.classList.add("o_hr_match_panel_open");
|
panel.classList.add("o_hr_match_panel_open");
|
||||||
|
panel.querySelector('[data-toggle="primary"]')?.addEventListener("click", () => {
|
||||||
|
this._primarySkillsExpanded = !this._primarySkillsExpanded;
|
||||||
|
this._renderRecruitmentMatchPanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
panel.querySelector('[data-toggle="secondary"]')?.addEventListener("click", () => {
|
||||||
|
this._secondarySkillsExpanded = !this._secondarySkillsExpanded;
|
||||||
|
this._renderRecruitmentMatchPanel();
|
||||||
|
});
|
||||||
panel.querySelector(".o_hr_match_close")?.addEventListener("click", () => this._closeRecruitmentMatchPanel());
|
panel.querySelector(".o_hr_match_close")?.addEventListener("click", () => this._closeRecruitmentMatchPanel());
|
||||||
panel.querySelector(".o_hr_match_refresh")?.addEventListener("click", () => this._loadRecruitmentMatchPanelData());
|
panel.querySelector(".o_hr_match_refresh")?.addEventListener("click", () => {
|
||||||
|
this._matchPanelScrollPosition = {
|
||||||
|
candidates: 0,
|
||||||
|
applicants: 0,
|
||||||
|
};
|
||||||
|
this._loadRecruitmentMatchPanelData()
|
||||||
|
}
|
||||||
|
);
|
||||||
panel.querySelectorAll(".o_hr_match_tab").forEach((tabButton) => {
|
panel.querySelectorAll(".o_hr_match_tab").forEach((tabButton) => {
|
||||||
tabButton.addEventListener("click", () => {
|
tabButton.addEventListener("click", () => {
|
||||||
this._matchPanelActiveTab = tabButton.dataset.tab || "candidates";
|
this._matchPanelActiveTab = tabButton.dataset.tab || "candidates";
|
||||||
|
|
@ -401,7 +468,17 @@ patch(RecruitmentFormController.prototype, {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const body = panel.querySelector(".o_hr_match_panel_body");
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
// Restore previous scroll for this tab
|
||||||
|
body.scrollTop = previousScroll;
|
||||||
|
|
||||||
|
// Continuously save scroll position
|
||||||
|
body.addEventListener("scroll", () => {
|
||||||
|
this._matchPanelScrollPosition[this._matchPanelActiveTab] = body.scrollTop;
|
||||||
|
});
|
||||||
|
}
|
||||||
const button = this._getRecruitmentMatchPanelButton();
|
const button = this._getRecruitmentMatchPanelButton();
|
||||||
if (button) {
|
if (button) {
|
||||||
button.classList.add("o_hr_match_fab_hidden");
|
button.classList.add("o_hr_match_fab_hidden");
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.o_hr_match_panel_header span {
|
.o_hr_match_panel_header span {
|
||||||
|
|
@ -416,6 +417,44 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
.o_hr_match_collapsible {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_hr_match_collapsible_header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_hr_match_collapsible_header:hover {
|
||||||
|
background: #ececec;
|
||||||
|
}
|
||||||
|
.o_hr_match_skill_header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .08em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_hr_match_skill_header:hover {
|
||||||
|
color: var(--o-brand-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_hr_match_skill_count {
|
||||||
|
color: #64748b;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.o_hr_match_skill_split {
|
.o_hr_match_skill_split {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//list" position="attributes">
|
<xpath expr="//list" position="attributes">
|
||||||
<attribute name="decoration-muted">hold_state == 'hold'</attribute>
|
<attribute name="decoration-muted">hold_state == 'hold'</attribute>
|
||||||
|
<attribute name="default_order">id desc</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='message_needaction']" position="after">
|
<xpath expr="//field[@name='message_needaction']" position="after">
|
||||||
<field name="is_on_hold" column_invisible="1"/>
|
<field name="is_on_hold" column_invisible="1"/>
|
||||||
|
|
@ -16,7 +17,7 @@
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='stage_id']" position="after">
|
<xpath expr="//field[@name='stage_id']" position="after">
|
||||||
<field name="recruitment_stage_id"/>
|
<field name="recruitment_stage_id"/>
|
||||||
<field name="hold_state" widget="badge" decoration-muted="hold_state == 'hold'" optional="show" nolabel="1"/>
|
<field name="hold_state" string="Hold State" widget="badge" decoration-muted="hold_state == 'hold'" optional="show"/>
|
||||||
<field name="submitted_to_client" optional="show"/>
|
<field name="submitted_to_client" optional="show"/>
|
||||||
<field name="client_submission_date" optional="show"/>
|
<field name="client_submission_date" optional="show"/>
|
||||||
<field name="submitted_stage" optional="hide"/>
|
<field name="submitted_stage" optional="hide"/>
|
||||||
|
|
@ -289,6 +290,26 @@
|
||||||
<xpath expr="//field[@name='kanban_state']" position="attributes">
|
<xpath expr="//field[@name='kanban_state']" position="attributes">
|
||||||
<attribute name="invisible">is_on_hold</attribute>
|
<attribute name="invisible">is_on_hold</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
<xpath expr="//t[@t-name='card']/field[@name='categ_ids']" position="replace"/>
|
||||||
|
<xpath expr="//t[@t-name='card']/field[@name='applicant_properties']" position="replace"/>
|
||||||
|
<xpath expr="//t[@t-name='card']/field[@name='job_id']" position="replace">
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<field name="job_id" invisible="context.get('search_default_job_id', False)" on_change="1"/>
|
||||||
|
<field name="categ_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||||
|
<field name="applicant_properties" widget="properties"/>
|
||||||
|
<!-- <field name="partner_name" class="fw-bold fs-5"/>-->
|
||||||
|
</div>
|
||||||
|
<t t-if="record.candidate_image.raw_value">
|
||||||
|
<field name="candidate_image"
|
||||||
|
widget="image"
|
||||||
|
class="rounded-circle ms-2"
|
||||||
|
options="{'size': [48, 48]}"/>
|
||||||
|
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="hr_kanban_view_applicant_inherit" model="ir.ui.view">
|
<record id="hr_kanban_view_applicant_inherit" model="ir.ui.view">
|
||||||
|
|
@ -297,7 +318,7 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<kanban highlight_color="color" default_group_by="recruitment_stage_id"
|
<kanban highlight_color="color" default_group_by="recruitment_stage_id"
|
||||||
class="o_kanban_applicant o_search_matching_applicant"
|
class="o_kanban_applicant o_search_matching_applicant"
|
||||||
quick_create_view="hr_recruitment.quick_create_applicant_form" sample="1">
|
quick_create_view="hr_recruitment.quick_create_applicant_form" sample="1" default_order="id desc">
|
||||||
<field name="recruitment_stage_id" options='{"group_by_tooltip": {"requirements": "Requirements"}}'/>
|
<field name="recruitment_stage_id" options='{"group_by_tooltip": {"requirements": "Requirements"}}'/>
|
||||||
<field name="legend_normal"/>
|
<field name="legend_normal"/>
|
||||||
<field name="legend_blocked"/>
|
<field name="legend_blocked"/>
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
name="action_validate_personal_details"
|
name="action_validate_personal_details"
|
||||||
type="object"
|
type="object"
|
||||||
class="btn btn-danger"
|
class="btn btn-danger"
|
||||||
invisible="not employee_id">
|
invisible="employee_id">
|
||||||
<div>
|
<div>
|
||||||
Click here to save, Validate and Update into Employee Data
|
Click here to save, Validate and Update into Employee Data
|
||||||
<field name="personal_details_status"
|
<field name="personal_details_status"
|
||||||
|
|
|
||||||
|
|
@ -276,6 +276,16 @@
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="hr_candidate_view_tree_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.candidate.view.tree.inherit</field>
|
||||||
|
<field name="model">hr.candidate</field>
|
||||||
|
<field name="inherit_id" ref="hr_recruitment.hr_candidate_view_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//list" position="attributes">
|
||||||
|
<attribute name="default_order">id desc</attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
<record id="hr_candidate_view_kanban_inherit" model="ir.ui.view">
|
<record id="hr_candidate_view_kanban_inherit" model="ir.ui.view">
|
||||||
<field name="name">hr.candidate.view.kanban.inherit</field>
|
<field name="name">hr.candidate.view.kanban.inherit</field>
|
||||||
<field name="model">hr.candidate</field>
|
<field name="model">hr.candidate</field>
|
||||||
|
|
@ -284,14 +294,30 @@
|
||||||
<!-- Ensure applicant_ids is included in the kanban field list -->
|
<!-- Ensure applicant_ids is included in the kanban field list -->
|
||||||
<xpath expr="//kanban" position="inside">
|
<xpath expr="//kanban" position="inside">
|
||||||
<field name="applicant_ids" context="{'active_test': False}"/>
|
<field name="applicant_ids" context="{'active_test': False}"/>
|
||||||
|
<field name="candidate_image"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
<xpath expr="//kanban" position="attributes">
|
<xpath expr="//kanban" position="attributes">
|
||||||
<attribute name="context">{'active_test': False, 'kanban': True}</attribute>
|
<attribute name="context">{'active_test': False, 'kanban': True}</attribute>
|
||||||
|
<attribute name="default_order">id desc</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
<xpath expr="//field[@name='partner_name']" position="before">
|
<xpath expr="//field[@name='partner_name']" position="before">
|
||||||
<field t-if="record.candidate_sequence.raw_value" name="candidate_sequence" class="fw-bold fs-4"/>
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<field t-if="record.candidate_sequence.raw_value"
|
||||||
|
name="candidate_sequence"
|
||||||
|
class="fw-bold fs-4"/>
|
||||||
|
<!-- <field name="partner_name" class="fw-bold fs-5"/>-->
|
||||||
|
</div>
|
||||||
|
<t t-if="record.candidate_image.raw_value">
|
||||||
|
<field name="candidate_image"
|
||||||
|
widget="image"
|
||||||
|
class="rounded-circle ms-2"
|
||||||
|
options="{'size': [48, 48]}"/>
|
||||||
|
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
<xpath expr="//t[@t-name='card']" position="inside">
|
<xpath expr="//t[@t-name='card']" position="inside">
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,6 @@ class PostOnboardingAttachmentWizard(models.TransientModel):
|
||||||
lambda a: a.attachment_type == 'previous_employer').mapped('name')
|
lambda a: a.attachment_type == 'previous_employer').mapped('name')
|
||||||
other_docs = rec.req_attachment_ids.filtered(lambda a: a.attachment_type == 'others').mapped('name')
|
other_docs = rec.req_attachment_ids.filtered(lambda a: a.attachment_type == 'others').mapped('name')
|
||||||
|
|
||||||
print(request_upload_url)
|
|
||||||
email_context = {
|
email_context = {
|
||||||
'applicant_request_form_id': rec.request_form_id.id,
|
'applicant_request_form_id': rec.request_form_id.id,
|
||||||
'applicant_request_form_token': request_token,
|
'applicant_request_form_token': request_token,
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ class HrmsEmployeeDashboard(http.Controller):
|
||||||
calendar_attendances = self._get_attendances(employee, calendar_start, calendar_end)
|
calendar_attendances = self._get_attendances(employee, calendar_start, calendar_end)
|
||||||
calendar_leaves = self._get_leaves(employee, calendar_start, calendar_end)
|
calendar_leaves = self._get_leaves(employee, calendar_start, calendar_end)
|
||||||
calendar_public_holidays = self._get_public_holidays(employee, calendar_start, calendar_end)
|
calendar_public_holidays = self._get_public_holidays(employee, calendar_start, calendar_end)
|
||||||
print(self._expense_data(employee, range_start, range_end))
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"employee": self._employee_card(employee),
|
"employee": self._employee_card(employee),
|
||||||
|
|
@ -325,12 +324,6 @@ class HrmsEmployeeDashboard(http.Controller):
|
||||||
("date", "<=", date_to),
|
("date", "<=", date_to),
|
||||||
])
|
])
|
||||||
for expense in emp_expenses:
|
for expense in emp_expenses:
|
||||||
print(
|
|
||||||
"Expense: %s | Date: %s | Amount: %s",
|
|
||||||
expense.name,
|
|
||||||
expense.date,
|
|
||||||
expense.total_amount
|
|
||||||
)
|
|
||||||
totals[expense.date.strftime("%Y-%m")] += expense.total_amount or 0.0
|
totals[expense.date.strftime("%Y-%m")] += expense.total_amount or 0.0
|
||||||
state_totals[expense.state or "draft"] += expense.total_amount or 0.0
|
state_totals[expense.state or "draft"] += expense.total_amount or 0.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'data/mail_template.xml',
|
'data/mail_template.xml',
|
||||||
|
'views/stages.xml',
|
||||||
'views/offer_letter_views.xml',
|
'views/offer_letter_views.xml',
|
||||||
'views/hr_applicant_offer_views.xml',
|
'views/hr_applicant_offer_views.xml',
|
||||||
'views/offer_response_templates.xml',
|
'views/offer_response_templates.xml',
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from . import stages
|
||||||
from . import offer_letter
|
from . import offer_letter
|
||||||
from . import hr_applicant
|
from . import hr_applicant
|
||||||
from . import hr_candidate
|
from . import hr_candidate
|
||||||
|
|
@ -26,6 +26,8 @@ class HRApplicant(models.Model):
|
||||||
store=False,
|
store=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
request_offer_release = fields.Boolean(related='recruitment_stage_id.request_offer_release')
|
||||||
|
|
||||||
@api.depends('offer_letter_ids', 'offer_letter_ids.create_date', 'offer_letter_ids.state')
|
@api.depends('offer_letter_ids', 'offer_letter_ids.create_date', 'offer_letter_ids.state')
|
||||||
def _compute_current_offer_letter(self):
|
def _compute_current_offer_letter(self):
|
||||||
for applicant in self:
|
for applicant in self:
|
||||||
|
|
|
||||||
|
|
@ -174,12 +174,12 @@ class OfferLetter(models.Model):
|
||||||
first_day = today.replace(day=1)
|
first_day = today.replace(day=1)
|
||||||
last_day = today.replace(day=calendar.monthrange(today.year, today.month)[1])
|
last_day = today.replace(day=calendar.monthrange(today.year, today.month)[1])
|
||||||
|
|
||||||
payslip = self.env['hr.payslip'].new({
|
payslip = self.env['hr.payslip'].sudo().new({
|
||||||
'date_from': first_day,
|
'date_from': first_day,
|
||||||
'date_to': last_day,
|
'date_to': last_day,
|
||||||
})
|
})
|
||||||
|
|
||||||
contract = self.env['hr.contract'].new({
|
contract = self.env['hr.contract'].sudo().new({
|
||||||
'date_start': first_day,
|
'date_start': first_day,
|
||||||
'date_end': last_day,
|
'date_end': last_day,
|
||||||
'l10n_in_medical_insurance':self.mi,
|
'l10n_in_medical_insurance':self.mi,
|
||||||
|
|
@ -205,7 +205,7 @@ class OfferLetter(models.Model):
|
||||||
'inputs': {},
|
'inputs': {},
|
||||||
}
|
}
|
||||||
blacklisted_ids = set(self.env.context.get('prevent_payslip_computation_line_ids', []))
|
blacklisted_ids = set(self.env.context.get('prevent_payslip_computation_line_ids', []))
|
||||||
for rule in sorted(self.pay_struct_id.rule_ids, key=lambda r: r.sequence):
|
for rule in sorted(self.sudo().pay_struct_id.rule_ids, key=lambda r: r.sequence):
|
||||||
if rule.id in blacklisted_ids or not rule._satisfy_condition(localdict):
|
if rule.id in blacklisted_ids or not rule._satisfy_condition(localdict):
|
||||||
continue
|
continue
|
||||||
qty = 1.0
|
qty = 1.0
|
||||||
|
|
@ -223,15 +223,15 @@ class OfferLetter(models.Model):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise UserError(_("Error in rule %s: %s") % (rule.name, str(e)))
|
raise UserError(_("Error in rule %s: %s") % (rule.name, str(e)))
|
||||||
|
|
||||||
total = payslip._get_payslip_line_total(amount, qty, rate, rule)
|
total = payslip.sudo()._get_payslip_line_total(amount, qty, rate, rule)
|
||||||
rule_code = rule.code
|
rule_code = rule.code
|
||||||
previous_amount = localdict.get(rule.code, 0.0)
|
previous_amount = localdict.get(rule.code, 0.0)
|
||||||
category_code = rule.category_id.code
|
category_code = rule.category_id.code
|
||||||
tot_rule = payslip._get_payslip_line_total(amount, qty, rate, rule)
|
tot_rule = payslip.sudo()._get_payslip_line_total(amount, qty, rate, rule)
|
||||||
|
|
||||||
# Make sure _sum_salary_rule_category method exists
|
# Make sure _sum_salary_rule_category method exists
|
||||||
if hasattr(rule.category_id, '_sum_salary_rule_category'):
|
if hasattr(rule.category_id, '_sum_salary_rule_category'):
|
||||||
localdict = rule.category_id._sum_salary_rule_category(localdict, tot_rule - previous_amount)
|
localdict = rule.category_id.sudo()._sum_salary_rule_category(localdict, tot_rule - previous_amount)
|
||||||
|
|
||||||
localdict[rule_code] = total
|
localdict[rule_code] = total
|
||||||
rules_dict[rule_code] = rule
|
rules_dict[rule_code] = rule
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
|
||||||
|
class RecruitmentStage(models.Model):
|
||||||
|
_inherit = 'hr.recruitment.stage'
|
||||||
|
|
||||||
|
request_offer_release = fields.Boolean(string="Request Offer Release")
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
<xpath expr="//button[@name='action_share_applicant']" position="before">
|
<xpath expr="//button[@name='action_share_applicant']" position="before">
|
||||||
<button name="action_request_offer_release" string="Request Offer Release" type="object" class="btn-primary"
|
<button name="action_request_offer_release" string="Request Offer Release" type="object" class="btn-primary"
|
||||||
groups="hr_recruitment.group_hr_recruitment_user"
|
groups="hr_recruitment.group_hr_recruitment_user"
|
||||||
invisible="not id"/>
|
invisible="not id or not request_offer_release"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//notebook/page[@name='request_forms']" position="after">
|
<xpath expr="//notebook/page[@name='request_forms']" position="after">
|
||||||
<page string="Offer Letters" name="offer_letters">
|
<page string="Offer Letters" name="offer_letters">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record model="ir.ui.view" id="hr_recruitment_stage_offer_request_form_extended">
|
||||||
|
<field name="name">hr.recruitment.stage.form.offer.request.extended</field>
|
||||||
|
<field name="model">hr.recruitment.stage</field>
|
||||||
|
<field name="inherit_id" ref="hr_recruitment.hr_recruitment_stage_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='fold']" position="after">
|
||||||
|
<field name="request_offer_release"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -70,7 +70,7 @@ class OfferReleaseRequestWizard(models.TransientModel):
|
||||||
|
|
||||||
def _get_default_pay_structure(self, applicant):
|
def _get_default_pay_structure(self, applicant):
|
||||||
company = applicant.company_id or self.env.company
|
company = applicant.company_id or self.env.company
|
||||||
return self.env['hr.payroll.structure'].search([
|
return self.env['hr.payroll.structure'].sudo().search([
|
||||||
], limit=1)
|
], limit=1)
|
||||||
|
|
||||||
def _get_default_manager(self, applicant):
|
def _get_default_manager(self, applicant):
|
||||||
|
|
@ -104,7 +104,7 @@ class OfferReleaseRequestWizard(models.TransientModel):
|
||||||
f"<p><a href=\"{offer_url}\">Review Offer Letter Request</a></p>"
|
f"<p><a href=\"{offer_url}\">Review Offer Letter Request</a></p>"
|
||||||
)
|
)
|
||||||
|
|
||||||
mail = self.env['mail.mail'].create({
|
mail = self.env['mail.mail'].sudo().create({
|
||||||
'email_from': self.email_from,
|
'email_from': self.email_from,
|
||||||
'email_to': self.email_to,
|
'email_to': self.email_to,
|
||||||
'email_cc': self.email_cc,
|
'email_cc': self.email_cc,
|
||||||
|
|
@ -114,5 +114,5 @@ class OfferReleaseRequestWizard(models.TransientModel):
|
||||||
'model': 'offer.letter',
|
'model': 'offer.letter',
|
||||||
'res_id': offer_letter.id,
|
'res_id': offer_letter.id,
|
||||||
})
|
})
|
||||||
mail.send()
|
mail.sudo().send()
|
||||||
return {'type': 'ir.actions.act_window_close'}
|
return {'type': 'ir.actions.act_window_close'}
|
||||||
|
|
|
||||||
|
|
@ -20,19 +20,15 @@ class ProjectDashboardController(http.Controller):
|
||||||
|
|
||||||
# 1. Get project data
|
# 1. Get project data
|
||||||
project_data = self._get_project_data(project)
|
project_data = self._get_project_data(project)
|
||||||
print(project_data)
|
|
||||||
|
|
||||||
# 2. Get tasks data
|
# 2. Get tasks data
|
||||||
tasks_data = self._get_tasks_data(project)
|
tasks_data = self._get_tasks_data(project)
|
||||||
print(tasks_data)
|
|
||||||
|
|
||||||
# 3. Get employee performance
|
# 3. Get employee performance
|
||||||
employee_performance = self._get_employee_performance(project)
|
employee_performance = self._get_employee_performance(project)
|
||||||
print(employee_performance)
|
|
||||||
|
|
||||||
# 4. Get budget data
|
# 4. Get budget data
|
||||||
budget_data = self._get_budget_data(project)
|
budget_data = self._get_budget_data(project)
|
||||||
print(budget_data)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,8 @@
|
||||||
color: var(--body-color, #374151);
|
color: var(--body-color, #374151);
|
||||||
padding: 0.35rem 0.75rem;
|
padding: 0.35rem 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-link.active {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,12 @@ class ApplicantCandidate(models.Model):
|
||||||
string="Resume Type"
|
string="Resume Type"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
certificate_ids = fields.Many2many(
|
||||||
|
'applicant.certificate',
|
||||||
|
string='Certificates'
|
||||||
|
)
|
||||||
|
|
||||||
def action_open_resume_candidate(self):
|
def action_open_resume_candidate(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|
||||||
|
|
@ -152,9 +158,10 @@ class CandidateApplicant(models.Model):
|
||||||
string="Online Tests"
|
string="Online Tests"
|
||||||
)
|
)
|
||||||
|
|
||||||
certificate_ids = fields.One2many(
|
certificate_ids = fields.Many2many(
|
||||||
'applicant.certificate',
|
'applicant.certificate',
|
||||||
'applicant_id',
|
related='candidate_id.certificate_ids',
|
||||||
|
readonly=False,
|
||||||
string='Certificates'
|
string='Certificates'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,3 +52,24 @@
|
||||||
.job-name {
|
.job-name {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Make the email field occupy the available width */
|
||||||
|
.o_field_widget[name="email_from"] {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: none !important;
|
||||||
|
flex: 1 1 auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input inside the email field */
|
||||||
|
.o_field_widget[name="email_from"] input {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: none !important;
|
||||||
|
min-width: 350px !important; /* adjust as needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If rendered as a link instead of input */
|
||||||
|
.o_field_widget[name="email_from"] a {
|
||||||
|
white-space: nowrap !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
text-overflow: clip !important;
|
||||||
|
}
|
||||||
|
|
@ -105,6 +105,7 @@
|
||||||
|
|
||||||
<div class="candidate-name">
|
<div class="candidate-name">
|
||||||
<field name="candidate_id"
|
<field name="candidate_id"
|
||||||
|
options="{'no_quick_create': True}"
|
||||||
nolabel="1"
|
nolabel="1"
|
||||||
can_create="True"
|
can_create="True"
|
||||||
can_write="True"/>
|
can_write="True"/>
|
||||||
|
|
@ -268,7 +269,7 @@
|
||||||
<!-- EXPERIENCE TAB -->
|
<!-- EXPERIENCE TAB -->
|
||||||
<page string="Past Experience">
|
<page string="Past Experience">
|
||||||
|
|
||||||
<field name="employer_history" nolabel="1">
|
<field name="employer_history" nolabel="1" force_save="1">
|
||||||
|
|
||||||
<kanban create="1" class="o_kanban_mobile">
|
<kanban create="1" class="o_kanban_mobile">
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,232 @@
|
||||||
options="{'color_field':'color','no_create':True}"/>
|
options="{'color_field':'color','no_create':True}"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
|
<notebook>
|
||||||
|
|
||||||
|
<!-- EXPERIENCE TAB -->
|
||||||
|
<page string="Past Experience">
|
||||||
|
|
||||||
|
<field name="employer_history" nolabel="1" force_save="1">
|
||||||
|
|
||||||
|
<kanban create="1" class="o_kanban_mobile">
|
||||||
|
|
||||||
|
<templates>
|
||||||
|
|
||||||
|
<t t-name="card">
|
||||||
|
|
||||||
|
<div class="oe_kanban_global_click shadow-sm rounded-4 p-3 mb-3 bg-white border"
|
||||||
|
style="border-left:4px solid #4F46E5 !important;">
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
|
||||||
|
<div class="fw-bold fs-4 text-dark">
|
||||||
|
<field name="designation"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<i class="fa fa-building me-1 text-primary"/>
|
||||||
|
<strong>Company:</strong>
|
||||||
|
<field name="company_name"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2 text-muted">
|
||||||
|
<i class="fa fa-calendar me-1 text-dark"/>
|
||||||
|
<field name="date_of_joining"/>
|
||||||
|
-
|
||||||
|
|
||||||
|
<field name="last_working_day"/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 text-muted">
|
||||||
|
<i class="fa fa-money me-1 text-success"/>
|
||||||
|
<span class="fw-bold text-dark me-1">
|
||||||
|
CTC :
|
||||||
|
</span>
|
||||||
|
<field name="ctc"/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 p-3 rounded-3 border bg-light"
|
||||||
|
style="border-left:4px solid #875A7B !important;">
|
||||||
|
<div class="fw-bold mb-2 text-primary fs-6">
|
||||||
|
<i class="fa fa-file-text-o me-1"/>
|
||||||
|
Summary
|
||||||
|
</div>
|
||||||
|
<div style="font-size:13px; line-height:1.7;"
|
||||||
|
class="text-dark">
|
||||||
|
<field name="summary"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
</kanban>
|
||||||
|
|
||||||
|
<!-- FORM -->
|
||||||
|
<form string="Employer Details">
|
||||||
|
|
||||||
|
<sheet>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
<field name="company_name"/>
|
||||||
|
<field name="designation"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="date_of_joining"/>
|
||||||
|
<field name="last_working_day"/>
|
||||||
|
<field name="ctc"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="summary"
|
||||||
|
widget="html"/>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</sheet>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<!-- EDUCATION TAB -->
|
||||||
|
<page string="Education">
|
||||||
|
|
||||||
|
<field name="education_history" nolabel="1">
|
||||||
|
|
||||||
|
<kanban create="1" class="o_kanban_mobile">
|
||||||
|
|
||||||
|
<templates>
|
||||||
|
|
||||||
|
<t t-name="card">
|
||||||
|
|
||||||
|
<div class="oe_kanban_global_click shadow-sm rounded-4 p-3 mb-3 bg-white border"
|
||||||
|
style="border-left:4px solid #4F46E5 !important;">
|
||||||
|
|
||||||
|
<div class="fw-bold fs-5 text-dark">
|
||||||
|
<field name="name"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<i class="fa fa-university me-1 text-primary"/>
|
||||||
|
<strong>University:</strong>
|
||||||
|
<field name="university"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<strong>Education:</strong>
|
||||||
|
<span class="badge rounded-pill text-bg-light border px-3 py-2">
|
||||||
|
<field name="education_type"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2 text-muted">
|
||||||
|
<i class="fa fa-calendar me-1 text-dark"/>
|
||||||
|
|
||||||
|
|
||||||
|
<field name="start_year"/>
|
||||||
|
-
|
||||||
|
|
||||||
|
<field name="end_year"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 p-2 rounded-3 bg-light">
|
||||||
|
|
||||||
|
<span class="fw-bold text-dark">
|
||||||
|
Grade:
|
||||||
|
</span>
|
||||||
|
<field name="marks_or_grade"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
</kanban>
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="education_type"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="university"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="start_year"/>
|
||||||
|
<field name="end_year"/>
|
||||||
|
<field name="marks_or_grade"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Certificates">
|
||||||
|
|
||||||
|
<field name="certificate_ids" nolabel="1">
|
||||||
|
|
||||||
|
<kanban create="1" class="o_kanban_mobile">
|
||||||
|
|
||||||
|
<templates>
|
||||||
|
|
||||||
|
<t t-name="card">
|
||||||
|
|
||||||
|
<div class="oe_kanban_global_click shadow-sm rounded-4 p-3 mb-3 bg-white border"
|
||||||
|
style="border-left:4px solid #F97316 !important;">
|
||||||
|
|
||||||
|
<div class="fw-bold fs-5 text-dark">
|
||||||
|
|
||||||
|
<i class="fa fa-certificate text-warning me-2"/>
|
||||||
|
|
||||||
|
<field name="name"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
|
||||||
|
<field name="certificate_file"
|
||||||
|
filename="certificate_filename"
|
||||||
|
widget="binary"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</t>
|
||||||
|
|
||||||
|
</templates>
|
||||||
|
|
||||||
|
</kanban>
|
||||||
|
|
||||||
|
<!-- FORM -->
|
||||||
|
<form string="Certificate">
|
||||||
|
|
||||||
|
<sheet>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
|
||||||
|
<field name="name"/>
|
||||||
|
|
||||||
|
<field name="certificate_file"
|
||||||
|
filename="certificate_filename"/>
|
||||||
|
|
||||||
|
<field name="certificate_filename"
|
||||||
|
invisible="1"/>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</sheet>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
|
||||||
</page>
|
</page>
|
||||||
<page string="Resume" name="resume_page">
|
<page string="Resume" name="resume_page">
|
||||||
<group>
|
<group>
|
||||||
|
|
|
||||||
|
|
@ -956,7 +956,6 @@ class WebsiteJobHrRecruitment(WebsiteHrRecruitment):
|
||||||
))
|
))
|
||||||
|
|
||||||
error_message = 'An application already exists for %s Duplicates might be rejected. %s '%(value,recruiter_contact)
|
error_message = 'An application already exists for %s Duplicates might be rejected. %s '%(value,recruiter_contact)
|
||||||
print(error_message)
|
|
||||||
return {
|
return {
|
||||||
'message': _(error_message)
|
'message': _(error_message)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,4 @@ class Website(models.Model):
|
||||||
result = super()._search_get_details(search_type, order, options)
|
result = super()._search_get_details(search_type, order, options)
|
||||||
if search_type in ['job_requests', 'all']:
|
if search_type in ['job_requests', 'all']:
|
||||||
result.append(self.env['hr.job.recruitment']._search_get_detail(self, order, options))
|
result.append(self.env['hr.job.recruitment']._search_get_detail(self, order, options))
|
||||||
print(result)
|
|
||||||
print("hello result")
|
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue