Compare commits

..

51 Commits

Author SHA1 Message Date
administrator 429560b6e7 15-9-2025
Merge branch 'develop'
2025-09-15 12:10:19 +05:30
administrator 3af33201f3 Initial commit 2025-09-15 12:07:18 +05:30
administrator 2b2e8a3bd9 Initial commit 2025-09-15 12:07:18 +05:30
administrator c2a03b1e11 Initial commit 2025-09-15 12:07:18 +05:30
administrator 5a3e2af2c0 Initial commit 2025-09-15 12:07:18 +05:30
administrator 52373df6b2 Initial commit 2025-09-15 12:07:18 +05:30
administrator d44c1fca26 Initial commit 2025-09-15 12:07:18 +05:30
administrator 5ae34c7fef Initial commit 2025-09-15 12:07:18 +05:30
administrator 8ea037e3f9 Initial commit 2025-09-15 12:07:18 +05:30
administrator c7221af464 Initial commit 2025-09-15 12:07:18 +05:30
administrator f732e7f649 Initial commit 2025-09-15 12:07:17 +05:30
administrator 516eeff12d Initial commit 2025-09-15 12:07:17 +05:30
administrator 128640ffae Initial commit 2025-09-15 12:07:17 +05:30
administrator 9712b1952b Initial commit 2025-09-15 12:07:17 +05:30
administrator f068b96f64 Initial commit 2025-09-15 12:07:17 +05:30
administrator 7c08c25896 Initial commit 2025-09-15 12:07:17 +05:30
administrator efe3954d71 Initial commit 2025-09-15 12:07:17 +05:30
administrator e4d12ca6d5 Initial commit 2025-09-15 12:07:17 +05:30
administrator 02368dda94 Initial commit 2025-09-15 12:07:17 +05:30
administrator 773e104859 Initial commit 2025-09-15 12:07:17 +05:30
administrator 68d9590d91 Initial commit 2025-09-15 12:07:17 +05:30
administrator d21df80bf0 Initial commit 2025-09-15 12:07:17 +05:30
administrator 70859a2257 pull commit 2025-09-15 12:07:17 +05:30
administrator f17bde1b68 Initial commit 2025-09-15 12:07:17 +05:30
Pranay 6f1f66f450 TimeOff Fix 2025-09-15 12:07:17 +05:30
Pranay b7d05ff27a time-off FIX 2025-09-15 12:07:17 +05:30
Pranay c11e5285b2 Recruitment Changes 2025-09-15 12:07:17 +05:30
Pranay 318fe8b3a1 fix whatsapp 2025-09-15 12:07:17 +05:30
Pranay 39aa89588c update whatsapp code 2025-09-15 12:07:17 +05:30
administrator ce5ea42b94 Initial commit 2025-09-15 12:07:17 +05:30
administrator 21ee55ea70 Initial commit 2025-09-15 12:07:17 +05:30
administrator 5dac216c5a Initial commit 2025-09-15 12:07:16 +05:30
administrator ab437563b4 Initial commit 2025-09-15 12:07:16 +05:30
administrator cfe0a73abd Initial commit 2025-09-15 12:07:16 +05:30
administrator cfebf79ffe Initial commit 2025-09-15 12:07:16 +05:30
administrator 0aae47071c Initial commit 2025-09-15 12:07:16 +05:30
administrator 667aab255b Initial commit 2025-09-15 12:07:16 +05:30
administrator cd30923e7c Initial commit 2025-09-15 12:07:16 +05:30
administrator 397f251a83 Initial commit 2025-09-15 12:07:16 +05:30
administrator 0001ee7b10 Initial commit 2025-09-15 12:07:16 +05:30
administrator 73c0675798 Initial commit 2025-09-15 12:07:16 +05:30
administrator 7759a9df15 Initial commit 2025-09-15 12:07:16 +05:30
administrator f4ef786584 Initial commit 2025-09-15 12:07:16 +05:30
administrator a98681f2d2 Initial commit 2025-09-15 12:07:16 +05:30
administrator ae5e6e326c Initial commit 2025-09-15 12:07:16 +05:30
administrator 8c03fed84e Initial commit 2025-09-15 12:07:16 +05:30
administrator 1d2374f916 Initial commit 2025-09-15 12:07:16 +05:30
administrator 52c5b44e19 Initial commit 2025-09-15 12:07:16 +05:30
administrator c9d64374f6 Initial commit 2025-09-15 12:07:16 +05:30
pranay 506e3fd2f4 JOD-CHANGES 2025-09-15 12:02:54 +05:30
raman 3bccb97ca3 admin fix 2025-09-02 13:50:46 +05:30
9 changed files with 234 additions and 8 deletions

View File

@ -1 +1 @@
from . import models
from . import models, controllers

View File

@ -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)}"

View File

@ -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>

View File

@ -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):

View File

@ -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"/>

View File

@ -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">

View File

@ -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
,,,,,,,
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
10 access_recruitment_attachments_user access_candidate_experience_user access.recruitment.attachments.user access.candidate.experience.user model_recruitment_attachments model_candidate_experience base.group_user 1 1 0 1 0 1 0
11 access_post_onboarding_attachment_wizard access_recruitment_attachments_user access.post.onboarding.attachment.wizard access.recruitment.attachments.user model_post_onboarding_attachment_wizard model_recruitment_attachments base.group_user 1 1 1 1
12 access_employee_recruitment_attachments access_post_onboarding_attachment_wizard employee.recruitment.attachments access.post.onboarding.attachment.wizard model_employee_recruitment_attachments model_post_onboarding_attachment_wizard base.group_user 1 1 1 1
13 hr_recruitment.access_hr_applicant_interviewer access_employee_recruitment_attachments hr.applicant.interviewer employee.recruitment.attachments hr_recruitment.model_hr_applicant model_employee_recruitment_attachments hr_recruitment.group_hr_recruitment_interviewer base.group_user 1 1 1 0 1
14 hr_recruitment.access_hr_recruitment_stage_user hr_recruitment.access_hr_applicant_interviewer hr.recruitment.stage.user hr.applicant.interviewer hr_recruitment.model_hr_recruitment_stage hr_recruitment.model_hr_applicant hr_recruitment.group_hr_recruitment_user hr_recruitment.group_hr_recruitment_interviewer 1 1 1 0
15 access_application_stage_status hr_recruitment.access_hr_recruitment_stage_user application.stage.status hr.recruitment.stage.user model_application_stage_status hr_recruitment.model_hr_recruitment_stage base.group_user hr_recruitment.group_hr_recruitment_user 1 1 1 1 0
16 access_ats_invite_mail_template_wizard access_hr_recruitment_stage_hr ats.invite.mail.template.wizard.user hr.recruitment.stage.hr hr_recruitment_extended.model_ats_invite_mail_template_wizard hr_recruitment.model_hr_recruitment_stage hr.group_hr_manager 1 1 0 1 0 1 0
23
24
25
26
27
28
29
30
31
32
33
34
35

View File

@ -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 &amp;&amp; presentStreet) permanentStreet.value = presentStreet.value;
if (permanentStreet2 &amp;&amp; presentStreet2) permanentStreet2.value = presentStreet2.value;
if (permanentCity &amp;&amp; presentCity) permanentCity.value = presentCity.value;
if (permanentZip &amp;&amp; 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 &amp;&amp; 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>

View File

@ -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))