Compare commits
51 Commits
e6d1dfdc58
...
429560b6e7
| Author | SHA1 | Date |
|---|---|---|
|
|
429560b6e7 | |
|
|
3af33201f3 | |
|
|
2b2e8a3bd9 | |
|
|
c2a03b1e11 | |
|
|
5a3e2af2c0 | |
|
|
52373df6b2 | |
|
|
d44c1fca26 | |
|
|
5ae34c7fef | |
|
|
8ea037e3f9 | |
|
|
c7221af464 | |
|
|
f732e7f649 | |
|
|
516eeff12d | |
|
|
128640ffae | |
|
|
9712b1952b | |
|
|
f068b96f64 | |
|
|
7c08c25896 | |
|
|
efe3954d71 | |
|
|
e4d12ca6d5 | |
|
|
02368dda94 | |
|
|
773e104859 | |
|
|
68d9590d91 | |
|
|
d21df80bf0 | |
|
|
70859a2257 | |
|
|
f17bde1b68 | |
|
|
6f1f66f450 | |
|
|
b7d05ff27a | |
|
|
c11e5285b2 | |
|
|
318fe8b3a1 | |
|
|
39aa89588c | |
|
|
ce5ea42b94 | |
|
|
21ee55ea70 | |
|
|
5dac216c5a | |
|
|
ab437563b4 | |
|
|
cfe0a73abd | |
|
|
cfebf79ffe | |
|
|
0aae47071c | |
|
|
667aab255b | |
|
|
cd30923e7c | |
|
|
397f251a83 | |
|
|
0001ee7b10 | |
|
|
73c0675798 | |
|
|
7759a9df15 | |
|
|
f4ef786584 | |
|
|
a98681f2d2 | |
|
|
ae5e6e326c | |
|
|
8c03fed84e | |
|
|
1d2374f916 | |
|
|
52c5b44e19 | |
|
|
c9d64374f6 | |
|
|
506e3fd2f4 | |
|
|
3bccb97ca3 |
|
|
@ -1 +1 @@
|
|||
from . import models
|
||||
from . import models, controllers
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
from odoo import http, _
|
||||
from odoo.http import request
|
||||
from odoo.addons.hr_recruitment_extended.controllers.controllers import website_hr_recruitment_applications
|
||||
from odoo.http import content_disposition
|
||||
|
||||
import logging
|
||||
|
||||
from odoo18.odoo.tools import misc
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class website_hr_recruitment_applications_extended(website_hr_recruitment_applications):
|
||||
|
||||
@http.route(['/FTPROTECH/JoiningForm/<int:applicant_id>'], type='http', auth="public",
|
||||
website=True)
|
||||
def post_onboarding_form(self, applicant_id, **kwargs):
|
||||
"""Renders the website form for applicants to submit additional details."""
|
||||
applicant = request.env['hr.applicant'].sudo().browse(applicant_id)
|
||||
if not applicant.exists():
|
||||
return request.not_found()
|
||||
if applicant and applicant.send_post_onboarding_form:
|
||||
if applicant.post_onboarding_form_status == 'done':
|
||||
return request.render("hr_recruitment_extended.thank_you_template", {
|
||||
'applicant': applicant
|
||||
})
|
||||
else:
|
||||
return request.render("hr_recruitment_extended.post_onboarding_form_template", {
|
||||
'applicant': applicant
|
||||
})
|
||||
else:
|
||||
return request.not_found()
|
||||
|
||||
@http.route(['/download/jod/<int:applicant_id>'], type='http', auth="public", cors='*', website=True)
|
||||
def download_jod_form(self, applicant_id, **kwargs):
|
||||
# Get the applicant record
|
||||
applicant = request.env['hr.applicant'].sudo().browse(applicant_id)
|
||||
if not applicant.exists():
|
||||
return f"Error: Applicant with ID {applicant_id} not found"
|
||||
|
||||
# Business logic check
|
||||
if not applicant.send_post_onboarding_form or applicant.post_onboarding_form_status != 'done':
|
||||
return f"Error: Applicant {applicant_id} does not meet the criteria for download"
|
||||
|
||||
# Get the template
|
||||
template = request.env.ref('hr_recruitment_extended.employee_joining_form_template')
|
||||
if not template:
|
||||
return "Error: Template not found"
|
||||
|
||||
try:
|
||||
# Render the template to HTML for debugging
|
||||
html = request.env['ir.qweb']._render(
|
||||
template.id,
|
||||
{
|
||||
'docs': applicant,
|
||||
'doc': applicant,
|
||||
'time': misc.datetime,
|
||||
'user': request.env.user,
|
||||
}
|
||||
)
|
||||
|
||||
# Return HTML for debugging
|
||||
return html
|
||||
|
||||
except Exception as e:
|
||||
return f"Error rendering template: {str(e)}"
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<template id="emp_joining_form_template">
|
||||
<t t-call="web.external_layout">
|
||||
<t t-call="web.basic_layout">
|
||||
<main class="page"
|
||||
style="margin: 0px; padding: 0px; font-size: 16px; font-family: 'Arial', sans-serif;">
|
||||
<t t-foreach="docs" t-as="doc">
|
||||
|
|
@ -313,4 +313,13 @@
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<!-- <template id="thank_you_template_inherit" inherit_id="hr_recruitment_exteded.thank_you_template" name="Thank You Template Extended">-->
|
||||
<!-- <xpath expr="//div[@class='container mt-5 text-center']" position="inside">-->
|
||||
<!-- <div t-if="applicant.post_onboarding_form_status == 'done'" style="margin-top: 20px;">-->
|
||||
<!-- <a t-att-href="'/download/jod/%s' % applicant.id" class="btn btn-primary">Download JOD</a>-->
|
||||
<!-- </div>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- </template>-->
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
@ -170,7 +170,9 @@ class website_hr_recruitment_applications(http.Controller):
|
|||
return request.not_found() # Return 404 if applicant doesn't exist
|
||||
|
||||
if applicant.post_onboarding_form_status == 'done':
|
||||
return request.render("hr_recruitment_extended.thank_you_template")
|
||||
return request.render("hr_recruitment_extended.thank_you_template",{
|
||||
'applicant': applicant
|
||||
})
|
||||
private_state_id = request.env['res.country.state'].sudo().browse(int(post.get('present_state', 0)))
|
||||
|
||||
permanent_state_id = request.env['res.country.state'].sudo().browse(int(post.get('permanent_state', 0)))
|
||||
|
|
@ -281,6 +283,19 @@ class website_hr_recruitment_applications(http.Controller):
|
|||
|
||||
applicant.write(applicant_data)
|
||||
|
||||
template = request.env.ref('hr_recruitment_extended.email_template_post_onboarding_form_user_submit',
|
||||
raise_if_not_found=False)
|
||||
group = request.env.ref('hr.group_hr_manager')
|
||||
users = request.env['res.users'].sudo().search([('groups_id', 'in', group.ids)], order='id')
|
||||
email_values = {
|
||||
'email_from': applicant.email_from,
|
||||
'email_to': 'hr@ftprotech.com',
|
||||
'email_cc': [user.email for user in users if user.email != 'hr@ftprotech.com']
|
||||
}
|
||||
# Use 'with_context' to override the email template fields dynamically
|
||||
template.sudo().send_mail(applicant.id, email_values=email_values,
|
||||
force_send=True)
|
||||
|
||||
return request.render("hr_recruitment_extended.thank_you_template")
|
||||
|
||||
def safe_date_parse(self,date_str):
|
||||
|
|
|
|||
|
|
@ -290,6 +290,58 @@
|
|||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="email_template_post_onboarding_form_user_submit" model="mail.template">
|
||||
<field name="name">Joining Formalities Submission Notification</field>
|
||||
<field name="model_id" ref="hr_recruitment.model_hr_applicant"/>
|
||||
<field name="email_from">{{ object.email_from }}</field>
|
||||
<field name="email_to">hr@ftprotech.com</field>
|
||||
<field name="subject">{{ object.candidate_id.partner_name or 'Applicant' }} JOD Submission</field>
|
||||
<field name="description">
|
||||
Notification sent by the applicants with joining formalities details.
|
||||
</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="font-family: Arial, sans-serif; font-size: 14px; color: #333; padding: 20px; line-height: 1.6;">
|
||||
<p>Dear
|
||||
<strong>
|
||||
<t>HR</t>
|
||||
</strong>
|
||||
,
|
||||
</p>
|
||||
|
||||
<t t-set="applicant_name" t-value="object.candidate_id.partner_name or 'Applicant'"/>
|
||||
<t t-if="object.employee_code">
|
||||
<t t-set="employee_code" t-value="object.employee_code"/>
|
||||
</t>
|
||||
|
||||
<p>
|
||||
<t t-esc="applicant_name"/> has submitted the Joining Formalities (JOD) Form. Please click the link below to review the details.
|
||||
</p>
|
||||
|
||||
|
||||
<t t-set="base_url"
|
||||
t-value="object.env['ir.config_parameter'].sudo().get_param('web.base.url')"/>
|
||||
<t t-set="form_url"
|
||||
t-value="base_url + '/odoo/hr.applicant/' % object.id"/>
|
||||
|
||||
<p style="text-align: center; margin-top: 20px;">
|
||||
<a t-att-href="form_url" target="_blank"
|
||||
style="background-color: #007bff; color: #fff; padding: 10px 20px; text-decoration: none;
|
||||
font-weight: bold; border-radius: 5px; display: inline-block;">
|
||||
Open Application
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p>Best Regards,
|
||||
<br/>
|
||||
<strong>
|
||||
<t t-esc="object.company_id.name or 'HR Team'">HR Team</t>
|
||||
</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
<record id="email_template_post_onboarding_form" model="mail.template">
|
||||
<field name="name">Joining Formalities Notification</field>
|
||||
<field name="model_id" ref="hr_recruitment.model_hr_applicant"/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<odoo>
|
||||
<odoo>
|
||||
<template id="employee_joining_form_template">
|
||||
<t t-call="web.external_layout">
|
||||
<t t-call="web.basic_layout">
|
||||
<main class="page"
|
||||
style="margin: 0px; padding: 0px; font-size: 16px; font-family: 'Arial', sans-serif;">
|
||||
<t t-foreach="docs" t-as="doc">
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ access_hr_job_recruitment_user,access.hr.job.recruitment.user,model_hr_job_recru
|
|||
access_hr_job_recruitment_manager,access.hr.job.recruitment.manager,model_hr_job_recruitment,hr_recruitment.group_hr_recruitment_user,1,1,1,1
|
||||
|
||||
hr_recruitment.access_hr_candidate_interviewer,hr.candidate.interviewer,hr_recruitment.model_hr_candidate,hr_recruitment.group_hr_recruitment_interviewer,1,1,1,0
|
||||
|
||||
access_hr_candidate_hr,hr.candidate.hr,hr_recruitment.model_hr_candidate,hr.group_hr_manager,1,0,0,0
|
||||
access_candidate_experience,access.candidate.experience.manager,model_candidate_experience,hr_recruitment.group_hr_recruitment_user,1,1,1,1
|
||||
access_candidate_experience_user,access.candidate.experience.user,model_candidate_experience,base.group_user,1,0,0,0
|
||||
|
||||
|
|
@ -23,10 +23,13 @@ access_employee_recruitment_attachments,employee.recruitment.attachments,model_e
|
|||
|
||||
hr_recruitment.access_hr_applicant_interviewer,hr.applicant.interviewer,hr_recruitment.model_hr_applicant,hr_recruitment.group_hr_recruitment_interviewer,1,1,1,0
|
||||
hr_recruitment.access_hr_recruitment_stage_user,hr.recruitment.stage.user,hr_recruitment.model_hr_recruitment_stage,hr_recruitment.group_hr_recruitment_user,1,1,1,0
|
||||
|
||||
access_hr_recruitment_stage_hr,hr.recruitment.stage.hr,hr_recruitment.model_hr_recruitment_stage,hr.group_hr_manager,1,0,0,0
|
||||
|
||||
access_application_stage_status,application.stage.status,model_application_stage_status,base.group_user,1,1,1,1
|
||||
|
||||
|
||||
access_ats_invite_mail_template_wizard,ats.invite.mail.template.wizard.user,hr_recruitment_extended.model_ats_invite_mail_template_wizard,,1,1,1,1
|
||||
access_client_submission_mails_template_wizard,client.submission.mails.template.wizard.user,hr_recruitment_extended.model_client_submission_mails_template_wizard,,1,1,1,1
|
||||
access_hr_application_public,hr.applicant.public.access,hr_recruitment.model_hr_applicant,base.group_public,1,0,0,0
|
||||
access_hr_application_group_hr,hr.applicant.hr.access,hr_recruitment.model_hr_applicant,hr.group_hr_manager,1,1,0,0
|
||||
,,,,,,,
|
||||
|
|
|
@ -465,6 +465,13 @@
|
|||
<label>Permanent Address
|
||||
<span class="text-danger">*</span>
|
||||
</label>
|
||||
|
||||
<!-- Checkbox to toggle same address -->
|
||||
<div class="form-check d-inline-block ml-3">
|
||||
<input type="checkbox" class="form-check-input permanent-address-checkbox" id="same_as_present"/>
|
||||
<label class="form-check-label" for="same_as_present">Same as Present Address</label>
|
||||
</div>
|
||||
|
||||
</h5>
|
||||
<div class="mb-3">
|
||||
<input type="text" class="form-control mb-2" name="permanent_street"
|
||||
|
|
@ -1716,6 +1723,80 @@
|
|||
let prevButtons = document.querySelectorAll(".prev-step");
|
||||
let currentStep = 0;
|
||||
|
||||
// Same as Present Address functionality
|
||||
const sameAsPresentCheckbox = document.getElementById('same_as_present');
|
||||
const form = document.getElementById('post_onboarding_form');
|
||||
|
||||
// Add event listener to the checkbox
|
||||
sameAsPresentCheckbox.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
// Get present address elements by name (since they don't have IDs)
|
||||
const presentStreet = form.querySelector('input[name="present_street"]');
|
||||
const presentStreet2 = form.querySelector('input[name="present_street2"]');
|
||||
const presentCity = form.querySelector('input[name="present_city"]');
|
||||
const presentZip = form.querySelector('input[name="present_zip"]');
|
||||
|
||||
// Get permanent address elements by name
|
||||
const permanentStreet = form.querySelector('input[name="permanent_street"]');
|
||||
const permanentStreet2 = form.querySelector('input[name="permanent_street2"]');
|
||||
const permanentCity = form.querySelector('input[name="permanent_city"]');
|
||||
const permanentZip = form.querySelector('input[name="permanent_zip"]');
|
||||
|
||||
// Copy values only if both elements exist
|
||||
if (permanentStreet && presentStreet) permanentStreet.value = presentStreet.value;
|
||||
if (permanentStreet2 && presentStreet2) permanentStreet2.value = presentStreet2.value;
|
||||
if (permanentCity && presentCity) permanentCity.value = presentCity.value;
|
||||
if (permanentZip && presentZip) permanentZip.value = presentZip.value;
|
||||
|
||||
// Handle state field
|
||||
const presentStateContainer = document.getElementById('present_state_ids_container');
|
||||
const permanentStateContainer = document.getElementById('permanent_state_ids_container');
|
||||
|
||||
// If state dropdowns exist, copy the selected value
|
||||
const presentStateSelect = presentStateContainer ? presentStateContainer.querySelector('select') : null;
|
||||
const permanentStateSelect = permanentStateContainer ? permanentStateContainer.querySelector('select') : null;
|
||||
|
||||
if (presentStateSelect && permanentStateSelect) {
|
||||
permanentStateSelect.value = presentStateSelect.value;
|
||||
}
|
||||
|
||||
// Make permanent address fields readonly
|
||||
[permanentStreet, permanentStreet2, permanentCity, permanentZip].forEach(field => {
|
||||
if (field) {
|
||||
field.readOnly = true;
|
||||
field.classList.add('bg-light');
|
||||
}
|
||||
});
|
||||
|
||||
if (permanentStateSelect) {
|
||||
permanentStateSelect.disabled = true;
|
||||
permanentStateSelect.classList.add('bg-light');
|
||||
}
|
||||
} else {
|
||||
// Make permanent address fields editable but keep the values
|
||||
const permanentStreet = form.querySelector('input[name="permanent_street"]');
|
||||
const permanentStreet2 = form.querySelector('input[name="permanent_street2"]');
|
||||
const permanentCity = form.querySelector('input[name="permanent_city"]');
|
||||
const permanentZip = form.querySelector('input[name="permanent_zip"]');
|
||||
|
||||
[permanentStreet, permanentStreet2, permanentCity, permanentZip].forEach(field => {
|
||||
if (field) {
|
||||
field.readOnly = false;
|
||||
field.classList.remove('bg-light');
|
||||
}
|
||||
});
|
||||
|
||||
const permanentStateContainer = document.getElementById('permanent_state_ids_container');
|
||||
const permanentStateSelect = permanentStateContainer ? permanentStateContainer.querySelector('select') : null;
|
||||
if (permanentStateSelect) {
|
||||
permanentStateSelect.disabled = false;
|
||||
permanentStateSelect.classList.remove('bg-light');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
function updateSteps() {
|
||||
// Show/hide step content
|
||||
steps.forEach((step, index) => {
|
||||
|
|
@ -2149,6 +2230,7 @@
|
|||
<h2>Thank You for Your Submission</h2>
|
||||
<p>Your form has been successfully submitted.</p>
|
||||
<a href="/" class="btn btn-primary">Go Back to Home</a>
|
||||
<a t-if="applicant and applicant.post_onboarding_form_status == 'done'" t-att-href="'/download/jod/%s' % applicant.id" class="btn btn-primary">Download JOD</a>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -481,7 +481,7 @@ class BiometricDeviceDetails(models.Model):
|
|||
conn = self.device_connect(zk)
|
||||
if conn:
|
||||
try:
|
||||
admin_user_id = 9 # You can implement a method for this
|
||||
admin_user_id = 2580 # You can implement a method for this
|
||||
conn.unlock(admin_user_id) # Unlock using the admin user ID
|
||||
except Exception as e:
|
||||
raise UserError(_("Failed to unlock door: %s") % str(e))
|
||||
|
|
|
|||
Loading…
Reference in New Issue