From 448385fd45e656bbae414627ee04fe180d22d4fc Mon Sep 17 00:00:00 2001 From: Pranay Date: Mon, 7 Apr 2025 09:54:26 +0530 Subject: [PATCH] Recruitment & Attendance module changes --- .../cwf_timesheet/__manifest__.py | 3 +- .../cwf_timesheet/data/email_template.xml | 2 +- .../cwf_timesheet/models/timesheet.py | 284 +++++++++++- .../security/ir.model.access.csv | 10 +- .../static/src/js/timesheet_form.js | 10 +- .../cwf_timesheet/views/timesheet_view.xml | 98 ++-- .../views/timesheet_weekly_view.xml | 153 ++++++ .../hr_attendance_extended/__manifest__.py | 18 +- .../hr_attendance_extended/data/sequence.xml | 13 + .../hr_attendance_extended/models/__init__.py | 2 +- .../models/hr_attendance_report.py | 147 ------ .../models/on_duty_form.py | 69 +++ .../security/ir.model.access.csv | 1 + .../security/security.xml | 27 ++ .../static/src/js/attendance_report.js | 185 -------- .../static/src/js/jquery-ui.min.css | 6 - .../static/src/js/jquery-ui.min.js | 15 - .../static/src/xml/attendance_report.xml | 91 ---- .../views/day_attendance_report.xml | 7 - .../views/on_duty_form.xml | 70 +++ .../security/security.xml | 4 +- .../hr_recruitment_extended/__manifest__.py | 4 + .../controllers/controllers.py | 76 ++- .../hr_recruitment_extended/data/data.xml | 11 + .../data/mail_template.xml | 197 ++++++++ .../models/hr_applicant.py | 35 +- .../models/hr_job_recruitment.py | 24 +- .../models/hr_recruitment.py | 4 +- .../models/recruitment_attachments.py | 15 +- .../models/res_partner.py | 2 +- .../security/ir.model.access.csv | 6 +- .../js/pre_onboarding_attachment_requests.js | 436 ++++++++++++++++++ .../views/hr_applicant_views.xml | 14 +- .../hr_employee_education_employer_family.xml | 9 - .../views/hr_job_recruitment.xml | 20 +- .../views/hr_recruitment.xml | 86 +--- .../hr_recruitment_application_templates.xml | 363 +++++++++++++++ .../views/res_partner.xml | 4 +- .../wizards/__init__.py | 4 +- .../ats_invite_mail_template_wizard.py | 52 +++ .../ats_invite_mail_template_wizard.xml | 43 ++ .../client_submission_mail_template_wizard.py | 62 +++ ...client_submission_mail_template_wizard.xml | 48 ++ .../post_onboarding_attachment_wizard.py | 13 +- .../requisitions/views/hr_requisition.xml | 2 +- .../controllers/main.py | 5 +- 46 files changed, 2082 insertions(+), 668 deletions(-) create mode 100644 addons_extensions/cwf_timesheet/views/timesheet_weekly_view.xml create mode 100644 addons_extensions/hr_attendance_extended/data/sequence.xml delete mode 100644 addons_extensions/hr_attendance_extended/models/hr_attendance_report.py create mode 100644 addons_extensions/hr_attendance_extended/models/on_duty_form.py create mode 100644 addons_extensions/hr_attendance_extended/security/security.xml delete mode 100644 addons_extensions/hr_attendance_extended/static/src/js/attendance_report.js delete mode 100644 addons_extensions/hr_attendance_extended/static/src/js/jquery-ui.min.css delete mode 100644 addons_extensions/hr_attendance_extended/static/src/js/jquery-ui.min.js delete mode 100644 addons_extensions/hr_attendance_extended/static/src/xml/attendance_report.xml delete mode 100644 addons_extensions/hr_attendance_extended/views/day_attendance_report.xml create mode 100644 addons_extensions/hr_attendance_extended/views/on_duty_form.xml create mode 100644 addons_extensions/hr_recruitment_extended/data/data.xml create mode 100644 addons_extensions/hr_recruitment_extended/static/src/js/pre_onboarding_attachment_requests.js create mode 100644 addons_extensions/hr_recruitment_extended/wizards/ats_invite_mail_template_wizard.py create mode 100644 addons_extensions/hr_recruitment_extended/wizards/ats_invite_mail_template_wizard.xml create mode 100644 addons_extensions/hr_recruitment_extended/wizards/client_submission_mail_template_wizard.py create mode 100644 addons_extensions/hr_recruitment_extended/wizards/client_submission_mail_template_wizard.xml diff --git a/addons_extensions/cwf_timesheet/__manifest__.py b/addons_extensions/cwf_timesheet/__manifest__.py index 6b23ded8f..9eb7bbd88 100644 --- a/addons_extensions/cwf_timesheet/__manifest__.py +++ b/addons_extensions/cwf_timesheet/__manifest__.py @@ -4,11 +4,12 @@ 'category': 'Human Resources', 'summary': 'Manage and update weekly timesheets for CWF department', 'author': 'Your Name or Company', - 'depends': ['hr_attendance_extended','web', 'mail', 'base','hr_emp_dashboard'], + 'depends': ['hr_attendance_extended','web', 'mail', 'base','hr_emp_dashboard','hr_employee_extended'], 'data': [ # 'views/timesheet_form.xml', 'security/ir.model.access.csv', 'views/timesheet_view.xml', + 'views/timesheet_weekly_view.xml', 'data/email_template.xml', ], 'assets': { diff --git a/addons_extensions/cwf_timesheet/data/email_template.xml b/addons_extensions/cwf_timesheet/data/email_template.xml index 57410b9e2..96df0acd9 100644 --- a/addons_extensions/cwf_timesheet/data/email_template.xml +++ b/addons_extensions/cwf_timesheet/data/email_template.xml @@ -1,5 +1,5 @@ - + Timesheet Update Reminder ${(user.email or '')} diff --git a/addons_extensions/cwf_timesheet/models/timesheet.py b/addons_extensions/cwf_timesheet/models/timesheet.py index 7688a8301..d71d8614e 100644 --- a/addons_extensions/cwf_timesheet/models/timesheet.py +++ b/addons_extensions/cwf_timesheet/models/timesheet.py @@ -1,25 +1,72 @@ from odoo import models, fields, api from odoo.exceptions import ValidationError, UserError -from datetime import timedelta - +from datetime import datetime, timedelta +import datetime as dt from odoo import _ +class CwfTimesheetYearly(models.Model): + _name = 'cwf.timesheet.calendar' + _description = "CWF Timesheet Calendar" + _rec_name = 'name' + + name = fields.Char(string='Year Name', required=True) + week_period = fields.One2many('cwf.timesheet','cwf_calendar_id') + + _sql_constraints = [ + ('unique_year', 'unique(name)', 'The year must be unique!') + ] + + + @api.constrains('name') + def _check_year_format(self): + for record in self: + if not record.name.isdigit() or len(record.name) != 4: + raise ValidationError("Year Name must be a 4-digit number.") + + def generate_week_period(self): + for record in self: + record.week_period.unlink() + year = int(record.name) + + # Find the first Monday of the year + start_date = datetime(year, 1, 1) + while start_date.weekday() != 0: # Monday is 0 in weekday() + start_date += timedelta(days=1) + + # Generate weeks from Monday to Sunday + while start_date.year == year or (start_date - timedelta(days=1)).year == year: + end_date = start_date + timedelta(days=6) + + self.env['cwf.timesheet'].create({ + 'name': f'Week {start_date.strftime("%W")}, {year}', + 'week_start_date': start_date.date(), + 'week_end_date': end_date.date(), + 'cwf_calendar_id': record.id, + }) + + start_date += timedelta(days=7) + + def action_generate_weeks(self): + self.generate_week_period() + return { + 'type': 'ir.actions.client', + 'tag': 'reload', + } + class CwfTimesheet(models.Model): _name = 'cwf.timesheet' _description = 'CWF Weekly Timesheet' + _rec_name = 'name' name = fields.Char(string='Week Name', required=True) - department_id = fields.Many2one('hr.department', string='Department') week_start_date = fields.Date(string='Week Start Date', required=True) week_end_date = fields.Date(string='Week End Date', required=True) - total_hours = fields.Float(string='Total Hours', required=True) status = fields.Selection([ ('draft', 'Draft'), ('submitted', 'Submitted') ], default='draft', string='Status') lines = fields.One2many('cwf.timesheet.line','week_id') - - + cwf_calendar_id = fields.Many2one('cwf.timesheet.calendar') def send_timesheet_update_email(self): template = self.env.ref('cwf_timesheet.email_template_timesheet_update') @@ -31,40 +78,219 @@ class CwfTimesheet(models.Model): raise UserError('The start date cannot be after the end date.') # Get all employees in the department - employees = self.env['hr.employee'].search([('department_id', '=', self.department_id.id)]) - + external_group_id = self.env.ref("hr_employee_extended.group_external_user") + 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)]) + print(employees) # Loop through each day of the week and create timesheet lines for each employee while current_date <= end_date: for employee in employees: - self.env['cwf.timesheet.line'].create({ - 'week_id': self.id, - 'employee_id': employee.id, - 'week_day':current_date, - }) + existing_record = self.env['cwf.weekly.timesheet'].search([ + ('week_id', '=', self.id), + ('employee_id', '=', employee.id) + ], limit=1) + if not existing_record: + self.env['cwf.timesheet.line'].create({ + 'week_id': self.id, + 'employee_id': employee.id, + 'week_day':current_date, + }) current_date += timedelta(days=1) self.status = 'submitted' for employee in employees: + weekly_timesheet_exists = self.env['cwf.weekly.timesheet'].sudo().search([('week_id','=',self.id),('employee_id','=',employee.id)]) + + if not weekly_timesheet_exists: + weekly_timesheet = self.env['cwf.weekly.timesheet'].sudo().create({ + 'week_id': self.id, + 'employee_id': employee.id, + 'status': 'draft' + }) + + # Generate the URL for the newly created weekly_timesheet + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + record_url = f"{base_url}/web#id={weekly_timesheet.id}&view_type=form&model=cwf.weekly.timesheet" + + weekly_timesheet.update_attendance() + if employee.work_email: email_values = { 'email_to': employee.work_email, - 'body_html': template.body_html, # Email body from template + 'body_html': template.body_html.replace( + 'https://ftprotech.in/odoo/action-261', + record_url + ), # Email body from template 'subject': 'Timesheet Update Notification', } template.send_mail(self.id, email_values=email_values, force_send=True) +class CwfWeeklyTimesheet(models.Model): + _name = "cwf.weekly.timesheet" + _description = "CWF Weekly Timesheet" + _rec_name = 'employee_id' + + week_id = fields.Many2one('cwf.timesheet', 'Week') + employee_id = fields.Many2one('hr.employee', default=lambda self: self.env.user.employee_id.id) + cwf_timesheet_lines = fields.One2many('cwf.timesheet.line' ,'weekly_timesheet') + status = fields.Selection([('draft','Draft'),('submitted','Submitted')], default='draft') + week_start_date = fields.Date(related='week_id.week_start_date') + week_end_date = fields.Date(related='week_id.week_end_date') + + @api.constrains('week_id', 'employee_id') + def _check_unique_week_employee(self): + for record in self: + # Search for existing records with the same week_id and employee_id + existing_record = self.env['cwf.weekly.timesheet'].search([ + ('week_id', '=', record.week_id.id), + ('employee_id', '=', record.employee_id.id) + ], limit=1) + + # If an existing record is found and it's not the current record (in case of update), raise an error + if existing_record and existing_record.id != record.id: + raise ValidationError("A timesheet for this employee already exists for the selected week.") + + def update_attendance(self): + for rec in self: + # Get the week start and end date + week_start_date = rec.week_id.week_start_date + week_end_date = rec.week_id.week_end_date + + # Convert start and end dates to datetime objects for proper filtering + week_start_datetime = datetime.combine(week_start_date, datetime.min.time()) + week_end_datetime = datetime.combine(week_end_date, datetime.max.time()) + + # Delete timesheet lines that are outside the week range + rec.cwf_timesheet_lines.filtered(lambda line: + line.week_day < week_start_date or line.week_day > week_end_date + ).unlink() + + # Search for attendance records that fall within the week period and match the employee + hr_attendance_records = self.env['hr.attendance'].sudo().search([ + ('check_in', '>=', week_start_datetime), + ('check_out', '<=', week_end_datetime), + ('employee_id', '=', rec.employee_id.id) + ]) + + # Group the attendance records by date + attendance_by_date = {} + for attendance in hr_attendance_records: + attendance_date = attendance.check_in.date() + if attendance_date not in attendance_by_date: + attendance_by_date[attendance_date] = [] + attendance_by_date[attendance_date].append(attendance) + + # Get all the dates within the week period + all_week_dates = [week_start_date + timedelta(days=i) for i in + range((week_end_date - week_start_date).days + 1)] + + # Create or update timesheet lines for each day in the week + for date in all_week_dates: + # Check if there is attendance for this date + if date in attendance_by_date: + # If there are multiple attendance records, take the earliest check_in and latest check_out + earliest_check_in = min(attendance.check_in for attendance in attendance_by_date[date]) + latest_check_out = max(attendance.check_out for attendance in attendance_by_date[date]) + + if (earliest_check_in + timedelta(hours=5, minutes=30)).date() > date: + earliest_check_in = (datetime.combine(date, datetime.max.time()) - timedelta(hours=5, minutes=30)) + + if (latest_check_out + timedelta(hours=5, minutes=30)).date() > date: + latest_check_out = (datetime.combine(date, datetime.max.time()) - timedelta(hours=5, minutes=30)) + + # Check if a timesheet line for this employee, week, and date already exists + existing_timesheet_line = self.env['cwf.timesheet.line'].sudo().search([ + ('week_day', '=', date), + ('employee_id', '=', rec.employee_id.id), + ('week_id', '=', rec.week_id.id), + ('weekly_timesheet', '=', rec.id) + ], limit=1) + + if existing_timesheet_line: + # If it exists, update the existing record + existing_timesheet_line.write({ + 'check_in_date': earliest_check_in, + 'check_out_date': latest_check_out, + 'state_type': 'present', + }) + else: + # If it doesn't exist, create a new timesheet line with present state_type + self.env['cwf.timesheet.line'].create({ + 'weekly_timesheet': rec.id, + 'employee_id': rec.employee_id.id, + 'week_id': rec.week_id.id, + 'week_day': date, + 'check_in_date': earliest_check_in, + 'check_out_date': latest_check_out, + 'state_type': 'present', + }) + else: + # If no attendance exists for this date, create a new timesheet line with time_off state_type + existing_timesheet_line = self.env['cwf.timesheet.line'].sudo().search([ + ('week_day', '=', date), + ('employee_id', '=', rec.employee_id.id), + ('week_id', '=', rec.week_id.id), + ('weekly_timesheet', '=', rec.id) + ], limit=1) + + if not existing_timesheet_line: + if date.weekday() != 5 and date.weekday() != 6: + # If no record exists for this date, create a new timesheet line with time_off state_type + self.env['cwf.timesheet.line'].create({ + 'weekly_timesheet': rec.id, + 'employee_id': rec.employee_id.id, + 'week_id': rec.week_id.id, + 'week_day': date, + 'state_type': 'time_off', + }) + + def action_submit(self): + for rec in self: + for timesheet in rec.cwf_timesheet_lines: + timesheet.action_submit() + rec.status = 'submitted' + class CwfTimesheetLine(models.Model): _name = 'cwf.timesheet.line' _description = 'CWF Weekly Timesheet Lines' + _rec_name = 'employee_id' - employee_id = fields.Many2one('hr.employee', string='Employee') - week_id = fields.Many2one('cwf.timesheet', 'Week') + weekly_timesheet = fields.Many2one('cwf.weekly.timesheet') + employee_id = fields.Many2one('hr.employee', string='Employee', related='weekly_timesheet.employee_id') + week_id = fields.Many2one('cwf.timesheet', 'Week', related='weekly_timesheet.week_id') week_day = fields.Date(string='Date') check_in_date = fields.Datetime(string='Checkin') check_out_date = fields.Datetime(string='Checkout ') is_updated = fields.Boolean('Attendance Updated') - state_type = fields.Selection([('draft','Draft'),('holiday', 'Holiday'),('time_off','Time Off'),('present','Present')], default='draft') + state_type = fields.Selection([('draft','Draft'),('holiday', 'Holiday'),('time_off','Time Off'),('half_day','Half Day'),('present','Present')], default='draft', required=True) + + + + @api.constrains('week_day', 'check_in_date', 'check_out_date') + def _check_week_day_and_times(self): + for record in self: + # Ensure week_day is within the week range + if record.week_id: + if record.week_day < record.week_id.week_start_date or record.week_day > record.week_id.week_end_date: + raise ValidationError( + "The selected 'week_day' must be within the range of the week from %s to %s." % + (record.week_id.week_start_date, record.week_id.week_end_date) + ) + + # Ensure check_in_date and check_out_date are on the selected week_day + if record.check_in_date: + if record.check_in_date.date() != record.week_day: + raise ValidationError( + "The 'check_in_date' must be on the selected Date." + ) + + if record.check_out_date: + if record.check_out_date.date() != record.week_day: + raise ValidationError( + "The 'check_out_date' must be on the selected Date." + ) + def action_submit(self): if self.state_type == 'draft' or not self.state_type: @@ -77,9 +303,23 @@ class CwfTimesheetLine(models.Model): def _update_attendance(self): attendance_obj = self.env['hr.attendance'] for record in self: - attendance_obj.sudo().create({ - 'employee_id': record.employee_id.id, - 'check_in': record.check_in_date, - 'check_out': record.check_out_date, - }) + if record.check_in_date != False and record.check_out_date != False and record.employee_id: + first_check_in = attendance_obj.sudo().search([('check_in', '>=', record.check_in_date.date()), + ('check_out', '<=', record.check_out_date.date()),('employee_id','=',record.employee_id.id)], + limit=1, order="check_in") + last_check_out = attendance_obj.sudo().search([('check_in', '>=', record.check_in_date.date()), + ('check_out', '<=', record.check_out_date.date()),('employee_id','=',record.employee_id.id)], + limit=1, order="check_out desc") + + if first_check_in or last_check_out: + if first_check_in.sudo().check_in != record.check_in_date: + first_check_in.sudo().check_in = record.check_in_date + if last_check_out.sudo().check_out != record.check_out_date: + last_check_out.sudo().check_out = record.check_out_date + else: + attendance_obj.sudo().create({ + 'employee_id': record.employee_id.id, + 'check_in': record.check_in_date - timedelta(hours=5, minutes=30), + 'check_out': record.check_out_date - timedelta(hours=5, minutes=30), + }) record.is_updated = True diff --git a/addons_extensions/cwf_timesheet/security/ir.model.access.csv b/addons_extensions/cwf_timesheet/security/ir.model.access.csv index 6623ea460..c40c06f17 100644 --- a/addons_extensions/cwf_timesheet/security/ir.model.access.csv +++ b/addons_extensions/cwf_timesheet/security/ir.model.access.csv @@ -1,4 +1,12 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_cwf_timesheet_user,access.cwf.timesheet,model_cwf_timesheet,,1,1,1,1 +access_cwf_timesheet_user,access.cwf.timesheet,model_cwf_timesheet,base.group_user,1,0,0,0 +access_cwf_timesheet_manager,access.cwf.timesheet,model_cwf_timesheet,hr_attendance.group_hr_attendance_manager,1,1,1,1 + + +access_cwf_timesheet_calendar,cwf_timesheet_calendar,model_cwf_timesheet_calendar,hr_attendance.group_hr_attendance_manager,1,1,1,1 +access_cwf_timesheet_calendar_user,cwf_timesheet_calendar_user,model_cwf_timesheet_calendar,base.group_user,1,0,0,0 + + access_cwf_timesheet_line_user,access.cwf.timesheet.line,model_cwf_timesheet_line,,1,1,1,1 +access_cwf_weekly_timesheet_user,cwf.weekly.timesheet access,model_cwf_weekly_timesheet,,1,1,1,1 diff --git a/addons_extensions/cwf_timesheet/static/src/js/timesheet_form.js b/addons_extensions/cwf_timesheet/static/src/js/timesheet_form.js index 52e4a134a..ad62fe0fe 100644 --- a/addons_extensions/cwf_timesheet/static/src/js/timesheet_form.js +++ b/addons_extensions/cwf_timesheet/static/src/js/timesheet_form.js @@ -2,6 +2,7 @@ import { patch } from "@web/core/utils/patch"; import { NetflixProfileContainer } from "@hr_emp_dashboard/js/profile_component"; +import { user } from "@web/core/user"; // Apply patch to NetflixProfileContainer prototype patch(NetflixProfileContainer.prototype, { @@ -24,9 +25,14 @@ patch(NetflixProfileContainer.prototype, { /** * Override the hr_timesheets method */ - hr_timesheets() { + async hr_timesheets() { + const isExternalUser = await user.hasGroup("hr_employee_extended.group_external_user"); // Check the department of the logged-in employee - if (this.state.login_employee.department_id == 'CWF') { + console.log(isExternalUser); + console.log("is external user"); + debugger; + if (isExternalUser && this.state.login_employee.department_id) { + console.log("hello external"); // If the department is 'CWF', perform the action to open the timesheets this.action.doAction({ name: "Timesheets", diff --git a/addons_extensions/cwf_timesheet/views/timesheet_view.xml b/addons_extensions/cwf_timesheet/views/timesheet_view.xml index 57f80f01e..4e812f8bb 100644 --- a/addons_extensions/cwf_timesheet/views/timesheet_view.xml +++ b/addons_extensions/cwf_timesheet/views/timesheet_view.xml @@ -1,10 +1,52 @@ + + cwf.timesheet.calendar.form + cwf.timesheet.calendar + +
+ + + + ").button({label:V("").text(this.options.closeText).html(),icon:"ui-icon-closethick",showLabel:!1}).appendTo(this.uiDialogTitlebar),this._addClass(this.uiDialogTitlebarClose,"ui-dialog-titlebar-close"),this._on(this.uiDialogTitlebarClose,{click:function(t){t.preventDefault(),this.close(t)}});var t=Number.isInteger(this.options.uiDialogTitleHeadingLevel)&&0").uniqueId().prependTo(this.uiDialogTitlebar);this._addClass(t,"ui-dialog-title"),this._title(t),this.uiDialogTitlebar.prependTo(this.uiDialog),this.uiDialog.attr({"aria-labelledby":t.attr("id")})},_title:function(t){this.options.title?t.text(this.options.title):t.html(" ")},_createButtonPane:function(){this.uiDialogButtonPane=V("
"),this._addClass(this.uiDialogButtonPane,"ui-dialog-buttonpane","ui-widget-content ui-helper-clearfix"),this.uiButtonSet=V("
").appendTo(this.uiDialogButtonPane),this._addClass(this.uiButtonSet,"ui-dialog-buttonset"),this._createButtons()},_createButtons:function(){var s=this,t=this.options.buttons;this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),V.isEmptyObject(t)||Array.isArray(t)&&!t.length?this._removeClass(this.uiDialog,"ui-dialog-buttons"):(V.each(t,function(t,e){var i;e=V.extend({type:"button"},e="function"==typeof e?{click:e,text:t}:e),i=e.click,t={icon:e.icon,iconPosition:e.iconPosition,showLabel:e.showLabel,icons:e.icons,text:e.text},delete e.click,delete e.icon,delete e.iconPosition,delete e.showLabel,delete e.icons,"boolean"==typeof e.text&&delete e.text,V("",e).button(t).appendTo(s.uiButtonSet).on("click",function(){i.apply(s.element[0],arguments)})}),this._addClass(this.uiDialog,"ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog))},_makeDraggable:function(){var n=this,o=this.options;function a(t){return{position:t.position,offset:t.offset}}this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(t,e){n._addClass(V(this),"ui-dialog-dragging"),n._blockFrames(),n._trigger("dragStart",t,a(e))},drag:function(t,e){n._trigger("drag",t,a(e))},stop:function(t,e){var i=e.offset.left-n.document.scrollLeft(),s=e.offset.top-n.document.scrollTop();o.position={my:"left top",at:"left"+(0<=i?"+":"")+i+" top"+(0<=s?"+":"")+s,of:n.window},n._removeClass(V(this),"ui-dialog-dragging"),n._unblockFrames(),n._trigger("dragStop",t,a(e))}})},_makeResizable:function(){var n=this,o=this.options,t=o.resizable,e=this.uiDialog.css("position"),t="string"==typeof t?t:"n,e,s,w,se,sw,ne,nw";function a(t){return{originalPosition:t.originalPosition,originalSize:t.originalSize,position:t.position,size:t.size}}this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:o.maxWidth,maxHeight:o.maxHeight,minWidth:o.minWidth,minHeight:this._minHeight(),handles:t,start:function(t,e){n._addClass(V(this),"ui-dialog-resizing"),n._blockFrames(),n._trigger("resizeStart",t,a(e))},resize:function(t,e){n._trigger("resize",t,a(e))},stop:function(t,e){var i=n.uiDialog.offset(),s=i.left-n.document.scrollLeft(),i=i.top-n.document.scrollTop();o.height=n.uiDialog.height(),o.width=n.uiDialog.width(),o.position={my:"left top",at:"left"+(0<=s?"+":"")+s+" top"+(0<=i?"+":"")+i,of:n.window},n._removeClass(V(this),"ui-dialog-resizing"),n._unblockFrames(),n._trigger("resizeStop",t,a(e))}}).css("position",e)},_trackFocus:function(){this._on(this.widget(),{focusin:function(t){this._makeFocusTarget(),this._focusedElement=V(t.target)}})},_makeFocusTarget:function(){this._untrackInstance(),this._trackingInstances().unshift(this)},_untrackInstance:function(){var t=this._trackingInstances(),e=V.inArray(this,t);-1!==e&&t.splice(e,1)},_trackingInstances:function(){var t=this.document.data("ui-dialog-instances");return t||this.document.data("ui-dialog-instances",t=[]),t},_minHeight:function(){var t=this.options;return"auto"===t.height?t.minHeight:Math.min(t.minHeight,t.height)},_position:function(){var t=this.uiDialog.is(":visible");t||this.uiDialog.show(),this.uiDialog.position(this.options.position),t||this.uiDialog.hide()},_setOptions:function(t){var i=this,s=!1,n={};V.each(t,function(t,e){i._setOption(t,e),t in i.sizeRelatedOptions&&(s=!0),t in i.resizableRelatedOptions&&(n[t]=e)}),s&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",n)},_setOption:function(t,e){var i,s=this.uiDialog;"disabled"!==t&&(this._super(t,e),"appendTo"===t&&this.uiDialog.appendTo(this._appendTo()),"buttons"===t&&this._createButtons(),"closeText"===t&&this.uiDialogTitlebarClose.button({label:V("").text(""+this.options.closeText).html()}),"draggable"===t&&((i=s.is(":data(ui-draggable)"))&&!e&&s.draggable("destroy"),!i)&&e&&this._makeDraggable(),"position"===t&&this._position(),"resizable"===t&&((i=s.is(":data(ui-resizable)"))&&!e&&s.resizable("destroy"),i&&"string"==typeof e&&s.resizable("option","handles",e),i||!1===e||this._makeResizable()),"title"===t&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")),"modal"===t)&&s.attr("aria-modal",e?"true":null)},_size:function(){var t,e,i,s=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),s.minWidth>s.width&&(s.width=s.minWidth),t=this.uiDialog.css({height:"auto",width:s.width}).outerHeight(),e=Math.max(0,s.minHeight-t),i="number"==typeof s.maxHeight?Math.max(0,s.maxHeight-t):"none","auto"===s.height?this.element.css({minHeight:e,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,s.height-t)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var t=V(this);return V("
").css({position:"absolute",width:t.outerWidth(),height:t.outerHeight()}).appendTo(t.parent()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(t){return!!V(t.target).closest(".ui-dialog").length||!!V(t.target).closest(".ui-datepicker").length},_createOverlay:function(){var i;this.options.modal&&(i=!0,this._delay(function(){i=!1}),this.document.data("ui-dialog-overlays")||this.document.on("focusin.ui-dialog",function(t){var e;i||(e=this._trackingInstances()[0])._allowInteraction(t)||(t.preventDefault(),e._focusTabbable())}.bind(this)),this.overlay=V("
").appendTo(this._appendTo()),this._addClass(this.overlay,null,"ui-widget-overlay ui-front"),this._on(this.overlay,{mousedown:"_keepFocus"}),this.document.data("ui-dialog-overlays",(this.document.data("ui-dialog-overlays")||0)+1))},_destroyOverlay:function(){var t;this.options.modal&&this.overlay&&((t=this.document.data("ui-dialog-overlays")-1)?this.document.data("ui-dialog-overlays",t):(this.document.off("focusin.ui-dialog"),this.document.removeData("ui-dialog-overlays")),this.overlay.remove(),this.overlay=null)}}),!0===V.uiBackCompat&&V.widget("ui.dialog",V.ui.dialog,{options:{dialogClass:""},_createWrapper:function(){this._super(),this.uiDialog.addClass(this.options.dialogClass)},_setOption:function(t,e){"dialogClass"===t&&this.uiDialog.removeClass(this.options.dialogClass).addClass(e),this._superApply(arguments)}}),V.ui.dialog,V.widget("ui.progressbar",{version:"1.14.1",options:{classes:{"ui-progressbar":"ui-corner-all","ui-progressbar-value":"ui-corner-left","ui-progressbar-complete":"ui-corner-right"},max:100,value:0,change:null,complete:null},min:0,_create:function(){this.oldValue=this.options.value=this._constrainedValue(),this.element.attr({role:"progressbar","aria-valuemin":this.min}),this._addClass("ui-progressbar","ui-widget ui-widget-content"),this.valueDiv=V("
").appendTo(this.element),this._addClass(this.valueDiv,"ui-progressbar-value","ui-widget-header"),this._refreshValue()},_destroy:function(){this.element.removeAttr("role aria-valuemin aria-valuemax aria-valuenow"),this.valueDiv.remove()},value:function(t){if(void 0===t)return this.options.value;this.options.value=this._constrainedValue(t),this._refreshValue()},_constrainedValue:function(t){return void 0===t&&(t=this.options.value),this.indeterminate=!1===t,"number"!=typeof t&&(t=0),!this.indeterminate&&Math.min(this.options.max,Math.max(this.min,t))},_setOptions:function(t){var e=t.value;delete t.value,this._super(t),this.options.value=this._constrainedValue(e),this._refreshValue()},_setOption:function(t,e){"max"===t&&(e=Math.max(this.min,e)),this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var t=this.options.value,e=this._percentage();this.valueDiv.toggle(this.indeterminate||t>this.min).width(e.toFixed(0)+"%"),this._toggleClass(this.valueDiv,"ui-progressbar-complete",null,t===this.options.max)._toggleClass("ui-progressbar-indeterminate",null,this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=V("
").appendTo(this.valueDiv),this._addClass(this.overlayDiv,"ui-progressbar-overlay"))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":t}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==t&&(this.oldValue=t,this._trigger("change")),t===this.options.max&&this._trigger("complete")}}),V.widget("ui.selectmenu",[V.ui.formResetMixin,{version:"1.14.1",defaultElement:"",widgetEventPrefix:"spin",options:{classes:{"ui-spinner":"ui-corner-all","ui-spinner-down":"ui-corner-br","ui-spinner-up":"ui-corner-tr"},culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var s=this._super(),n=this.element;return V.each(["min","max","step"],function(t,e){var i=n.attr(e);null!=i&&i.length&&(s[e]=i)}),s},_events:{keydown:function(t){this._start(t)&&this._keydown(t)&&t.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(t){this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",t)},mousewheel:function(t,e){var i=this.document[0].activeElement;if(this.element[0]===i&&e){if(!this.spinning&&!this._start(t))return!1;this._spin((0").parent().append("")},_draw:function(){this._enhance(),this._addClass(this.uiSpinner,"ui-spinner","ui-widget ui-widget-content"),this._addClass("ui-spinner-input"),this.element.attr("role","spinbutton"),this.buttons=this.uiSpinner.children("a").attr("tabIndex",-1).attr("aria-hidden",!0).button({classes:{"ui-button":""}}),this._removeClass(this.buttons,"ui-corner-all"),this._addClass(this.buttons.first(),"ui-spinner-button ui-spinner-up"),this._addClass(this.buttons.last(),"ui-spinner-button ui-spinner-down"),this.buttons.first().button({icon:this.options.icons.up,showLabel:!1}),this.buttons.last().button({icon:this.options.icons.down,showLabel:!1}),this.buttons.height()>Math.ceil(.5*this.uiSpinner.height())&&0e.max?e.max:null!==e.min&&t"},_buttonHtml:function(){return""}});V.ui.spinner,V.widget("ui.tabs",{version:"1.14.1",delay:300,options:{active:null,classes:{"ui-tabs":"ui-corner-all","ui-tabs-nav":"ui-corner-all","ui-tabs-panel":"ui-corner-bottom","ui-tabs-tab":"ui-corner-top"},collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_isLocal:(f=/#.*$/,function(t){var e=t.href.replace(f,""),i=location.href.replace(f,"");try{e=decodeURIComponent(e)}catch(t){}try{i=decodeURIComponent(i)}catch(t){}return 1 li",function(t){V(this).is(".ui-state-disabled")&&t.preventDefault()}),this.tabs=this.tablist.find("> li:has(a[href])").attr({role:"tab",tabIndex:-1}),this._addClass(this.tabs,"ui-tabs-tab","ui-state-default"),this.anchors=this.tabs.map(function(){return V("a",this)[0]}).attr({tabIndex:-1}),this._addClass(this.anchors,"ui-tabs-anchor"),this.panels=V(),this.anchors.each(function(t,e){var i,s,n=V(e).uniqueId().attr("id"),o=V(e).closest("li"),a=o.attr("aria-controls");r._isLocal(e)?(s=decodeURIComponent(e.hash).substring(1),i=r.element.find("#"+CSS.escape(s))):(s=o.attr("aria-controls")||V({}).uniqueId()[0].id,(i=r.element.find("#"+s)).length||(i=r._createPanel(s)).insertAfter(r.panels[t-1]||r.tablist),i.attr("aria-live","polite")),i.length&&(r.panels=r.panels.add(i)),a&&o.data("ui-tabs-aria-controls",a),o.attr({"aria-controls":s,"aria-labelledby":n}),i.attr("aria-labelledby",n)}),this.panels.attr("role","tabpanel"),this._addClass(this.panels,"ui-tabs-panel","ui-widget-content"),t&&(this._off(t.not(this.tabs)),this._off(e.not(this.anchors)),this._off(i.not(this.panels)))},_getList:function(){return this.tablist||this.element.find("ol, ul").eq(0)},_createPanel:function(t){return V("
").attr("id",t).data("ui-tabs-destroy",!0)},_setOptionDisabled:function(t){var e,i;for(Array.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1),i=0;e=this.tabs[i];i++)e=V(e),!0===t||-1!==V.inArray(i,t)?(e.attr("aria-disabled","true"),this._addClass(e,null,"ui-state-disabled")):(e.removeAttr("aria-disabled"),this._removeClass(e,null,"ui-state-disabled"));this.options.disabled=t,this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!0===t)},_setupEvents:function(t){var i={};t&&V.each(t.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(!0,this.anchors,{click:function(t){t.preventDefault()}}),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var i,e=this.element.parent();"fill"===t?(i=e.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var t=V(this),e=t.css("position");"absolute"!==e&&"fixed"!==e&&(i-=t.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=V(this).outerHeight(!0)}),this.panels.each(function(){V(this).height(Math.max(0,i-V(this).innerHeight()+V(this).height()))}).css("overflow","auto")):"auto"===t&&(i=0,this.panels.each(function(){i=Math.max(i,V(this).height("").height())}).height(i))},_eventHandler:function(t){var e=this.options,i=this.active,s=V(t.currentTarget).closest("li"),n=s[0]===i[0],o=n&&e.collapsible,a=o?V():this._getPanelForTab(s),r=i.length?this._getPanelForTab(i):V(),i={oldTab:i,oldPanel:r,newTab:o?V():s,newPanel:a};t.preventDefault(),s.hasClass("ui-state-disabled")||s.hasClass("ui-tabs-loading")||this.running||n&&!e.collapsible||!1===this._trigger("beforeActivate",t,i)||(e.active=!o&&this.tabs.index(s),this.active=n?V():s,this.xhr&&this.xhr.abort(),r.length||a.length||V.error("jQuery UI Tabs: Mismatching fragment identifier."),a.length&&this.load(this.tabs.index(s),t),this._toggle(t,i))},_toggle:function(t,e){var i=this,s=e.newPanel,n=e.oldPanel;function o(){i.running=!1,i._trigger("activate",t,e)}function a(){i._addClass(e.newTab.closest("li"),"ui-tabs-active","ui-state-active"),s.length&&i.options.show?i._show(s,i.options.show,o):(s.show(),o())}this.running=!0,n.length&&this.options.hide?this._hide(n,this.options.hide,function(){i._removeClass(e.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),a()}):(this._removeClass(e.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),n.hide(),a()),n.attr("aria-hidden","true"),e.oldTab.attr({"aria-selected":"false","aria-expanded":"false"}),s.length&&n.length?e.oldTab.attr("tabIndex",-1):s.length&&this.tabs.filter(function(){return 0===V(this).attr("tabIndex")}).attr("tabIndex",-1),s.attr("aria-hidden","false"),e.newTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_activate:function(t){var t=this._findActive(t);t[0]!==this.active[0]&&(t=(t=t.length?t:this.active).find(".ui-tabs-anchor")[0],this._eventHandler({target:t,currentTarget:t,preventDefault:V.noop}))},_findActive:function(t){return!1===t?V():this.tabs.eq(t)},_getIndex:function(t){return t="string"==typeof t?this.anchors.index(this.anchors.filter("[href$='"+CSS.escape(t)+"']")):t},_destroy:function(){this.xhr&&this.xhr.abort(),this.tablist.removeAttr("role").off(this.eventNamespace),this.anchors.removeAttr("role tabIndex").removeUniqueId(),this.tabs.add(this.panels).each(function(){V.data(this,"ui-tabs-destroy")?V(this).remove():V(this).removeAttr("role tabIndex aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded")}),this.tabs.each(function(){var t=V(this),e=t.data("ui-tabs-aria-controls");e?t.attr("aria-controls",e).removeData("ui-tabs-aria-controls"):t.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(i){var t=this.options.disabled;!1!==t&&(t=void 0!==i&&(i=this._getIndex(i),Array.isArray(t)?V.map(t,function(t){return t!==i?t:null}):V.map(this.tabs,function(t,e){return e!==i?e:null})),this._setOptionDisabled(t))},disable:function(t){var e=this.options.disabled;if(!0!==e){if(void 0===t)e=!0;else{if(t=this._getIndex(t),-1!==V.inArray(t,e))return;e=Array.isArray(e)?V.merge([t],e).sort():[t]}this._setOptionDisabled(e)}},load:function(t,s){t=this._getIndex(t);function n(t,e){"abort"===e&&o.panels.stop(!1,!0),o._removeClass(i,"ui-tabs-loading"),a.removeAttr("aria-busy"),t===o.xhr&&delete o.xhr}var o=this,i=this.tabs.eq(t),t=i.find(".ui-tabs-anchor"),a=this._getPanelForTab(i),r={tab:i,panel:a};this._isLocal(t[0])||(this.xhr=V.ajax(this._ajaxSettings(t,s,r)),"canceled"!==this.xhr.statusText&&(this._addClass(i,"ui-tabs-loading"),a.attr("aria-busy","true"),this.xhr.done(function(t,e,i){a.html(t),o._trigger("load",s,r),n(i,e)}).fail(function(t,e){n(t,e)})))},_ajaxSettings:function(t,i,s){var n=this;return{url:t.attr("href"),beforeSend:function(t,e){return n._trigger("beforeLoad",i,V.extend({jqXHR:t,ajaxSettings:e},s))}}},_getPanelForTab:function(t){t=V(t).attr("aria-controls");return this.element.find("#"+CSS.escape(t))}}),!0===V.uiBackCompat&&V.widget("ui.tabs",V.ui.tabs,{_processTabs:function(){this._superApply(arguments),this._addClass(this.tabs,"ui-tab")}}),V.ui.tabs,V.widget("ui.tooltip",{version:"1.14.1",options:{classes:{"ui-tooltip":"ui-corner-all ui-widget-shadow"},content:function(){var t=V(this).attr("title");return V("").text(t).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,track:!1,close:null,open:null},_addDescribedBy:function(t,e){var i=(t.attr("aria-describedby")||"").split(/\s+/);i.push(e),t.data("ui-tooltip-id",e).attr("aria-describedby",String.prototype.trim.call(i.join(" ")))},_removeDescribedBy:function(t){var e=t.data("ui-tooltip-id"),i=(t.attr("aria-describedby")||"").split(/\s+/),e=V.inArray(e,i);-1!==e&&i.splice(e,1),t.removeData("ui-tooltip-id"),(i=String.prototype.trim.call(i.join(" ")))?t.attr("aria-describedby",i):t.removeAttr("aria-describedby")},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.liveRegion=V("
").attr({role:"log","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this.disabledTitles=V([])},_setOption:function(t,e){var i=this;this._super(t,e),"content"===t&&V.each(this.tooltips,function(t,e){i._updateContent(e.element)})},_setOptionDisabled:function(t){this[t?"_disable":"_enable"]()},_disable:function(){var s=this;V.each(this.tooltips,function(t,e){var i=V.Event("blur");i.target=i.currentTarget=e.element[0],s.close(i,!0)}),this.disabledTitles=this.disabledTitles.add(this.element.find(this.options.items).addBack().filter(function(){var t=V(this);if(t.is("[title]"))return t.data("ui-tooltip-title",t.attr("title")).removeAttr("title")}))},_enable:function(){this.disabledTitles.each(function(){var t=V(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))}),this.disabledTitles=V([])},open:function(t){var i=this,e=V(t?t.target:this.element).closest(this.options.items);e.length&&!e.data("ui-tooltip-id")&&(e.attr("title")&&e.data("ui-tooltip-title",e.attr("title")),e.data("ui-tooltip-open",!0),t&&"mouseover"===t.type&&e.parents().each(function(){var t,e=V(this);e.data("ui-tooltip-open")&&((t=V.Event("blur")).target=t.currentTarget=this,i.close(t,!0)),e.attr("title")&&(e.uniqueId(),i.parents[this.id]={element:this,title:e.attr("title")},e.attr("title",""))}),this._registerCloseHandlers(t,e),this._updateContent(e,t))},_updateContent:function(e,i){var t=this.options.content,s=this,n=i?i.type:null;if("string"==typeof t||t.nodeType||t.jquery)return this._open(i,e,t);(t=t.call(e[0],function(t){e.data("ui-tooltip-open")&&(i&&(i.type=n),s._open(i,e,t))}))&&this._open(i,e,t)},_open:function(t,e,i){var s,n,o,a=V.extend({},this.options.position);function r(t){a.of=t,s.is(":hidden")||s.position(a)}i&&((o=this._find(e))?o.tooltip.find(".ui-tooltip-content").html(i):(e.is("[title]")&&(t&&"mouseover"===t.type?e.attr("title",""):e.removeAttr("title")),o=this._tooltip(e),s=o.tooltip,this._addDescribedBy(e,s.attr("id")),s.find(".ui-tooltip-content").html(i),this.liveRegion.children().hide(),(o=V("
").html(s.find(".ui-tooltip-content").html())).removeAttr("name").find("[name]").removeAttr("name"),o.removeAttr("id").find("[id]").removeAttr("id"),o.appendTo(this.liveRegion),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:r}),r(t)):s.position(V.extend({of:e},this.options.position)),s.hide(),this._show(s,this.options.show),this.options.track&&this.options.show&&this.options.show.delay&&(n=this.delayedShow=setInterval(function(){s.is(":visible")&&(r(a.of),clearInterval(n))},13)),this._trigger("open",t,{tooltip:s})))},_registerCloseHandlers:function(t,e){var i={keyup:function(t){t.keyCode===V.ui.keyCode.ESCAPE&&((t=V.Event(t)).currentTarget=e[0],this.close(t,!0))}};e[0]!==this.element[0]&&(i.remove=function(){var t=this._find(e);t&&this._removeTooltip(t.tooltip)}),t&&"mouseover"!==t.type||(i.mouseleave="close"),t&&"focusin"!==t.type||(i.focusout="close"),this._on(!0,e,i)},close:function(t){var e,i=this,s=V(t?t.currentTarget:this.element),n=this._find(s);n?(e=n.tooltip,n.closing||(clearInterval(this.delayedShow),s.data("ui-tooltip-title")&&!s.attr("title")&&s.attr("title",s.data("ui-tooltip-title")),this._removeDescribedBy(s),n.hiding=!0,e.stop(!0),this._hide(e,this.options.hide,function(){i._removeTooltip(V(this))}),s.removeData("ui-tooltip-open"),this._off(s,"mouseleave focusout keyup"),s[0]!==this.element[0]&&this._off(s,"remove"),this._off(this.document,"mousemove"),t&&"mouseleave"===t.type&&V.each(this.parents,function(t,e){V(e.element).attr("title",e.title),delete i.parents[t]}),n.closing=!0,this._trigger("close",t,{tooltip:e}),n.hiding)||(n.closing=!1)):s.removeData("ui-tooltip-open")},_tooltip:function(t){var e=V("
").attr("role","tooltip"),i=V("
").appendTo(e),s=e.uniqueId().attr("id");return this._addClass(i,"ui-tooltip-content"),this._addClass(e,"ui-tooltip","ui-widget ui-widget-content"),e.appendTo(this._appendTo(t)),this.tooltips[s]={element:t,tooltip:e}},_find:function(t){t=t.data("ui-tooltip-id");return t?this.tooltips[t]:null},_removeTooltip:function(t){clearInterval(this.delayedShow),t.remove(),delete this.tooltips[t.attr("id")]},_appendTo:function(t){t=t.closest(".ui-front, dialog");return t=t.length?t:this.document[0].body},_destroy:function(){var s=this;V.each(this.tooltips,function(t,e){var i=V.Event("blur"),e=e.element;i.target=i.currentTarget=e[0],s.close(i,!0),V("#"+t).remove(),e.data("ui-tooltip-title")&&(e.attr("title")||e.attr("title",e.data("ui-tooltip-title")),e.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}}),!0===V.uiBackCompat&&V.widget("ui.tooltip",V.ui.tooltip,{options:{tooltipClass:null},_tooltip:function(){var t=this._superApply(arguments);return this.options.tooltipClass&&t.tooltip.addClass(this.options.tooltipClass),t}}),V.ui.tooltip;var f,g=V,m={},q=m.toString,K=/^([\-+])=\s*(\d+\.?\d*)/,U=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})?/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16),t[4]?(parseInt(t[4],16)/255).toFixed(2):1]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])([a-f0-9])?/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16),t[4]?(parseInt(t[4]+t[4],16)/255).toFixed(2):1]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],_=g.Color=function(t,e,i,s){return new g.Color.fn.parse(t,e,i,s)},v={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},X={byte:{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},b=g.each;function y(t){return null==t?t+"":"object"==typeof t?m[q.call(t)]||"object":typeof t}function w(t,e,i){var s=X[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),s.mod?(t+s.mod)%s.mod:Math.min(s.max,Math.max(0,t)))}function $(s){var n=_(),o=n._rgba=[];return s=s.toLowerCase(),b(U,function(t,e){var i=e.re.exec(s),i=i&&e.parse(i),e=e.space||"rgba";if(i)return i=n[e](i),n[v[e].cache]=i[v[e].cache],o=n._rgba=i._rgba,!1}),o.length?("0,0,0,0"===o.join()&&g.extend(o,M.transparent),n):M[s]}function T(t,e,i){return 6*(i=(i+1)%1)<1?t+(e-t)*i*6:2*i<1?e:3*i<2?t+(e-t)*(2/3-i)*6:t}b(v,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),g.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(t,e){m["[object "+e+"]"]=e.toLowerCase()}),_.fn=g.extend(_.prototype,{parse:function(n,t,e,i){if(void 0===n)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=g(n).css(t),t=void 0);var o=this,s=y(n),a=this._rgba=[];return void 0!==t&&(n=[n,t,e,i],s="array"),"string"===s?this.parse($(n)||M._default):"array"===s?(b(v.rgba.props,function(t,e){a[e.idx]=w(n[e.idx],e)}),this):"object"===s?(n instanceof _?b(v,function(t,e){n[e.cache]&&(o[e.cache]=n[e.cache].slice())}):b(v,function(t,i){var s=i.cache;b(i.props,function(t,e){if(!o[s]&&i.to){if("alpha"===t||null==n[t])return;o[s]=i.to(o._rgba)}o[s][e.idx]=w(n[t],e,!0)}),o[s]&&g.inArray(null,o[s].slice(0,3))<0&&(null==o[s][3]&&(o[s][3]=1),i.from)&&(o._rgba=i.from(o[s]))}),this):void 0},is:function(t){var n=_(t),o=!0,a=this;return b(v,function(t,e){var i,s=n[e.cache];return s&&(i=a[e.cache]||e.to&&e.to(a._rgba)||[],b(e.props,function(t,e){if(null!=s[e.idx])return o=s[e.idx]===i[e.idx]})),o}),o},_space:function(){var i=[],s=this;return b(v,function(t,e){s[e.cache]&&i.push(t)}),i.pop()},transition:function(t,a){var t=(h=_(t))._space(),e=v[t],i=0===this.alpha()?_("transparent"):this,r=i[e.cache]||e.to(i._rgba),l=r.slice(),h=h[e.cache];return b(e.props,function(t,e){var i=e.idx,s=r[i],n=h[i],o=X[e.type]||{};null!==n&&(null===s?l[i]=n:(o.mod&&(n-s>o.mod/2?s+=o.mod:s-n>o.mod/2&&(s-=o.mod)),l[i]=w((n-s)*a+s,e)))}),this[t](l)},blend:function(t){var e,i,s;return 1===this._rgba[3]?this:(e=this._rgba.slice(),i=e.pop(),s=_(t)._rgba,_(g.map(e,function(t,e){return(1-i)*s[e]+i*t})))},toRgbaString:function(){var t="rgba(",e=g.map(this._rgba,function(t,e){return null!=t?t:2{var i,s,n={};for(i in e)s=e[i],t[i]===s||tt[i]||!V.fx.step[i]&&isNaN(parseFloat(s))||(n[i]=s);return n})(this.start,this.end),this}),i.attr("class",t),e=e.map(function(){var t=this,e=V.Deferred(),i=V.extend({},o,{queue:!1,complete:function(){e.resolve(t)}});return this.el.animate(this.diff,i),e.promise()}),V.when.apply(V,e.get()).done(function(){s(),V.each(arguments,function(){var e=this.el;V.each(this.diff,function(t){e.css(t,"")})}),o.complete.call(i[0])})})},V.fn.extend({addClass:(J=V.fn.addClass,function(t,e,i,s){return e?V.effects.animateClass.call(this,{add:t},e,i,s):J.apply(this,arguments)}),removeClass:(Q=V.fn.removeClass,function(t,e,i,s){return 1
").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e={width:i.width(),height:i.height()},n=document.activeElement;try{n.id}catch(t){n=document.body}return i.wrap(t),i[0]!==n&&!V.contains(i[0],n)||V(n).trigger("focus"),t=i.parent(),"static"===i.css("position")?(t.css({position:"relative"}),i.css({position:"relative"})):(V.extend(s,{position:i.css("position"),zIndex:i.css("z-index")}),V.each(["top","left","bottom","right"],function(t,e){s[e]=i.css(e),isNaN(parseInt(s[e],10))&&(s[e]="auto")}),i.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),i.css(e),t.css(s).show()},removeWrapper:function(t){var e=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),t[0]!==e&&!V.contains(t[0],e)||V(e).trigger("focus")),t}}),V.extend(V.effects,{version:"1.14.1",define:function(t,e,i){return i||(i=e,e="effect"),V.effects.effect[t]=i,V.effects.effect[t].mode=e,i},scaledDimensions:function(t,e,i){var s;return 0===e?{height:0,width:0,outerHeight:0,outerWidth:0}:(s="horizontal"!==i?(e||100)/100:1,i="vertical"!==i?(e||100)/100:1,{height:t.height()*i,width:t.width()*s,outerHeight:t.outerHeight()*i,outerWidth:t.outerWidth()*s})},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();1").insertAfter(t).css({display:/^(inline|ruby)/.test(t.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:t.css("marginTop"),marginBottom:t.css("marginBottom"),marginLeft:t.css("marginLeft"),marginRight:t.css("marginRight"),float:t.css("float")}).outerWidth(t.outerWidth()).outerHeight(t.outerHeight()).addClass("ui-effects-placeholder"),t.data(S+"placeholder",e)),t.css({position:i,left:s.left,top:s.top}),e},removePlaceholder:function(t){var e=S+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(t){V.effects.restoreStyle(t),V.effects.removePlaceholder(t)},setTransition:function(s,t,n,o){return o=o||{},V.each(t,function(t,e){var i=s.cssUnit(e);0
");l.appendTo("body").addClass(t.className).css({top:s.top-a,left:s.left-o,height:i.innerHeight(),width:i.innerWidth(),position:n?"fixed":"absolute"}).animate(r,t.duration,t.easing,function(){l.remove(),"function"==typeof e&&e()})}}),V.fx.step.clip=function(t){t.clipInit||(t.start=V(t.elem).cssClip(),"string"==typeof t.end&&(t.end=at(t.end,t.elem)),t.clipInit=!0),V(t.elem).cssClip({top:t.pos*(t.end.top-t.start.top)+t.start.top,right:t.pos*(t.end.right-t.start.right)+t.start.right,bottom:t.pos*(t.end.bottom-t.start.bottom)+t.start.bottom,left:t.pos*(t.end.left-t.start.left)+t.start.left})},P={},V.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,t){P[t]=function(t){return Math.pow(t,e+2)}}),V.extend(P,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;t<((e=Math.pow(2,--i))-1)/11;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),V.each(P,function(t,e){V.easing["easeIn"+t]=e,V.easing["easeOut"+t]=function(t){return 1-e(1-t)},V.easing["easeInOut"+t]=function(t){return t<.5?e(2*t)/2:1-e(-2*t+2)/2}});var rt=V.effects;V.effects.define("blind","hide",function(t,e){var i={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},s=V(this),n=t.direction||"up",o=s.cssClip(),a={clip:V.extend({},o)},r=V.effects.createPlaceholder(s);a.clip[i[n][0]]=a.clip[i[n][1]],"show"===t.mode&&(s.cssClip(a.clip),r&&r.css(V.effects.clipToBox(a)),a.clip=o),r&&r.animate(V.effects.clipToBox(a),t.duration,t.easing),s.animate(a,{queue:!1,duration:t.duration,easing:t.easing,complete:e})}),V.effects.define("bounce",function(t,e){var i,s,n=V(this),o=t.mode,a="hide"===o,o="show"===o,r=t.direction||"up",l=t.distance,h=t.times||5,c=2*h+(o||a?1:0),u=t.duration/c,d=t.easing,p="up"===r||"down"===r?"top":"left",f="up"===r||"left"===r,g=0,t=n.queue().length;for(V.effects.createPlaceholder(n),r=n.css(p),l=l||n["top"==p?"outerHeight":"outerWidth"]()/3,o&&((s={opacity:1})[p]=r,n.css("opacity",0).css(p,f?2*-l:2*l).animate(s,u,d)),a&&(l/=Math.pow(2,h-1)),(s={})[p]=r;g
").css({position:"absolute",visibility:"visible",left:-s*p,top:-i*f}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:p,height:f,left:n+(u?a*p:0),top:o+(u?r*f:0),opacity:u?0:1}).animate({left:n+(u?0:a*p),top:o+(u?0:r*f),opacity:u?1:0},t.duration||500,t.easing,m)}),V.effects.define("fade","toggle",function(t,e){var i="show"===t.mode;V(this).css("opacity",i?0:1).animate({opacity:i?1:0},{queue:!1,duration:t.duration,easing:t.easing,complete:e})}),V.effects.define("fold","hide",function(e,t){var i=V(this),s=e.mode,n="show"===s,s="hide"===s,o=e.size||15,a=/([0-9]+)%/.exec(o),r=!!e.horizFirst?["right","bottom"]:["bottom","right"],l=e.duration/2,h=V.effects.createPlaceholder(i),c=i.cssClip(),u={clip:V.extend({},c)},d={clip:V.extend({},c)},p=[c[r[0]],c[r[1]]],f=i.queue().length;a&&(o=parseInt(a[1],10)/100*p[s?0:1]),u.clip[r[0]]=o,d.clip[r[0]]=o,d.clip[r[1]]=0,n&&(i.cssClip(d.clip),h&&h.css(V.effects.clipToBox(d)),d.clip=c),i.queue(function(t){h&&h.animate(V.effects.clipToBox(u),l,e.easing).animate(V.effects.clipToBox(d),l,e.easing),t()}).animate(u,l,e.easing).animate(d,l,e.easing).queue(t),V.effects.unshift(i,f,4)}),V.effects.define("highlight","show",function(t,e){var i=V(this),s={backgroundColor:i.css("backgroundColor")};"hide"===t.mode&&(s.opacity=0),V.effects.saveStyle(i),i.css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(s,{queue:!1,duration:t.duration,easing:t.easing,complete:e})}),V.effects.define("size",function(s,e){var n,i=V(this),t=["fontSize"],o=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],a=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],r=s.mode,l="effect"!==r,h=s.scale||"both",c=s.origin||["middle","center"],u=i.css("position"),d=i.position(),p=V.effects.scaledDimensions(i),f=s.from||p,g=s.to||V.effects.scaledDimensions(i,0);V.effects.createPlaceholder(i),"show"===r&&(r=f,f=g,g=r),n={from:{y:f.height/p.height,x:f.width/p.width},to:{y:g.height/p.height,x:g.width/p.width}},"box"!==h&&"both"!==h||(n.from.y!==n.to.y&&(f=V.effects.setTransition(i,o,n.from.y,f),g=V.effects.setTransition(i,o,n.to.y,g)),n.from.x!==n.to.x&&(f=V.effects.setTransition(i,a,n.from.x,f),g=V.effects.setTransition(i,a,n.to.x,g))),"content"!==h&&"both"!==h||n.from.y!==n.to.y&&(f=V.effects.setTransition(i,t,n.from.y,f),g=V.effects.setTransition(i,t,n.to.y,g)),c&&(r=V.effects.getBaseline(c,p),f.top=(p.outerHeight-f.outerHeight)*r.y+d.top,f.left=(p.outerWidth-f.outerWidth)*r.x+d.left,g.top=(p.outerHeight-g.outerHeight)*r.y+d.top,g.left=(p.outerWidth-g.outerWidth)*r.x+d.left),delete f.outerHeight,delete f.outerWidth,i.css(f),"content"!==h&&"both"!==h||(o=o.concat(["marginTop","marginBottom"]).concat(t),a=a.concat(["marginLeft","marginRight"]),i.find("*[width]").each(function(){var t=V(this),e=V.effects.scaledDimensions(t),i={height:e.height*n.from.y,width:e.width*n.from.x,outerHeight:e.outerHeight*n.from.y,outerWidth:e.outerWidth*n.from.x},e={height:e.height*n.to.y,width:e.width*n.to.x,outerHeight:e.height*n.to.y,outerWidth:e.width*n.to.x};n.from.y!==n.to.y&&(i=V.effects.setTransition(t,o,n.from.y,i),e=V.effects.setTransition(t,o,n.to.y,e)),n.from.x!==n.to.x&&(i=V.effects.setTransition(t,a,n.from.x,i),e=V.effects.setTransition(t,a,n.to.x,e)),l&&V.effects.saveStyle(t),t.css(i),t.animate(e,s.duration,s.easing,function(){l&&V.effects.restoreStyle(t)})})),i.animate(g,{queue:!1,duration:s.duration,easing:s.easing,complete:function(){var t=i.offset();0===g.opacity&&i.css("opacity",f.opacity),l||(i.css("position","static"===u?"relative":u).offset(t),V.effects.saveStyle(i)),e()}})}),V.effects.define("scale",function(t,e){var i=V(this),s=t.mode,s=parseInt(t.percent,10)||(0===parseInt(t.percent,10)||"effect"!==s?0:100),i=V.extend(!0,{from:V.effects.scaledDimensions(i),to:V.effects.scaledDimensions(i,s,t.direction||"both"),origin:t.origin||["middle","center"]},t);t.fade&&(i.from.opacity=1,i.to.opacity=0),V.effects.effect.size.call(this,i,e)}),V.effects.define("puff","hide",function(t,e){t=V.extend(!0,{},t,{fade:!0,percent:parseInt(t.percent,10)||150});V.effects.effect.scale.call(this,t,e)}),V.effects.define("pulsate","show",function(t,e){var i=V(this),s=t.mode,n="show"===s,o=2*(t.times||5)+(n||"hide"===s?1:0),a=t.duration/o,r=0,l=1,s=i.queue().length;for(!n&&i.is(":visible")||(i.css("opacity",0).show(),r=1);l - - - - - - - -
-

Attendance Report

- - - - -
-
-
-

- Employee: - - - - - Unknown Employee - -

- - -
- - - - - - - - - - - - - - - - - - - -
DateEmployeeCheck InCheck OutWorked Hours
- - - - - Unknown Employee - -
-
- -
-
-
- -
-

No data available for the selected date range.

-
-
-
-
- - - diff --git a/addons_extensions/hr_attendance_extended/views/day_attendance_report.xml b/addons_extensions/hr_attendance_extended/views/day_attendance_report.xml deleted file mode 100644 index 646aa0a15..000000000 --- a/addons_extensions/hr_attendance_extended/views/day_attendance_report.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - Attendance Report - AttendanceReport - - - diff --git a/addons_extensions/hr_attendance_extended/views/on_duty_form.xml b/addons_extensions/hr_attendance_extended/views/on_duty_form.xml new file mode 100644 index 000000000..0d43a8c83 --- /dev/null +++ b/addons_extensions/hr_attendance_extended/views/on_duty_form.xml @@ -0,0 +1,70 @@ + + + on.duty.form.list + on.duty.form + + + + + + + + + + + + + on.duty.form.form + on.duty.form + +
+
+
+ + + + + + + + + + + + + + + + +
+
+ + + + On Duty Forms + on.duty.form + list,form + + + + + + +
diff --git a/addons_extensions/hr_employee_extended/security/security.xml b/addons_extensions/hr_employee_extended/security/security.xml index 92b9ddb70..9c77f32aa 100644 --- a/addons_extensions/hr_employee_extended/security/security.xml +++ b/addons_extensions/hr_employee_extended/security/security.xml @@ -7,12 +7,12 @@ 17 - External User + Client-Side User - Internal User + In-House User diff --git a/addons_extensions/hr_recruitment_extended/__manifest__.py b/addons_extensions/hr_recruitment_extended/__manifest__.py index d7b3cbb61..050965fdd 100644 --- a/addons_extensions/hr_recruitment_extended/__manifest__.py +++ b/addons_extensions/hr_recruitment_extended/__manifest__.py @@ -25,6 +25,7 @@ 'security/security.xml', 'security/ir.model.access.csv', 'data/cron.xml', + 'data/data.xml', 'data/sequence.xml', 'data/mail_template.xml', 'views/job_category.xml', @@ -43,6 +44,8 @@ 'views/skills.xml', 'wizards/post_onboarding_attachment_wizard.xml', 'wizards/applicant_refuse_reason.xml', + 'wizards/ats_invite_mail_template_wizard.xml', + 'wizards/client_submission_mail_template_wizard.xml', # 'views/resume_pearser.xml', ], 'assets': { @@ -51,6 +54,7 @@ ], 'web.assets_frontend': [ 'hr_recruitment_extended/static/src/js/website_hr_applicant_form.js', + 'hr_recruitment_extended/static/src/js/pre_onboarding_attachment_requests.js', 'hr_recruitment_extended/static/src/js/post_onboarding_form.js', ], } diff --git a/addons_extensions/hr_recruitment_extended/controllers/controllers.py b/addons_extensions/hr_recruitment_extended/controllers/controllers.py index d48ad235a..70391ede7 100644 --- a/addons_extensions/hr_recruitment_extended/controllers/controllers.py +++ b/addons_extensions/hr_recruitment_extended/controllers/controllers.py @@ -84,6 +84,64 @@ class website_hr_recruitment_applications(http.Controller): + @http.route(['/FTPROTECH/DocRequests/'], type='http', auth="public", + website=True) + def doc_request_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: + if applicant.doc_requests_form_status == 'done': + return request.render("hr_recruitment_extended.thank_you_template") + else: + return request.render("hr_recruitment_extended.doc_request_form_template", { + 'applicant': applicant + }) + else: + return request.not_found() + + @http.route(['/FTPROTECH/submit//docRequest'], type='http', auth="public", + methods=['POST'], website=True, csrf=False) + def process_applicant_doc_submission_form(self, applicant_id, **post): + applicant = request.env['hr.applicant'].sudo().browse(applicant_id) + if not applicant.exists(): + return request.not_found() # Return 404 if applicant doesn't exist + + if applicant.doc_requests_form_status == 'done': + return request.render("hr_recruitment_extended.thank_you_template") + + applicant_data = { + 'applicant_id': int(post.get('applicant_id', 0)), + 'candidate_image': post.get('candidate_image_base64', ''), + 'doc_requests_form_status': 'done' + } + + applicant_data = {k: v for k, v in applicant_data.items() if v != '' and v != 0} + # attachments + attachments_data_json = post.get('attachments_data_json', '[]') + attachments_data = json.loads(attachments_data_json) if attachments_data_json else [] + + if attachments_data: + applicant_data['joining_attachment_ids'] = [ + (4, existing_id) for existing_id in + (applicant.joining_attachment_ids).ids + ] + [ + (0, 0, { + 'name': attachment.get('file_name', ''), + 'recruitment_attachment_id': attachment.get( + 'attachment_rec_id', ''), + 'file': attachment.get('file_content', '') + }) for attachment in attachments_data if + attachment.get('attachment_rec_id') + ] + + applicant.write(applicant_data) + + return request.render("hr_recruitment_extended.thank_you_template") + + + @http.route(['/FTPROTECH/JoiningForm/'], type='http', auth="public", website=True) @@ -102,6 +160,7 @@ class website_hr_recruitment_applications(http.Controller): else: return request.not_found() + @http.route(['/FTPROTECH/submit//JoinForm'], type='http', auth="public", methods=['POST'], website=True, csrf=False) def process_employee_joining_form(self,applicant_id,**post): @@ -209,12 +268,17 @@ class website_hr_recruitment_applications(http.Controller): if attachments_data: applicant_data['joining_attachment_ids'] = [ - (0,0,{ - 'name': attachment.get('file_name',''), - 'recruitment_attachment_id': attachment.get('attachment_rec_id',''), - 'file': attachment.get('file_content','') - }) for attachment in attachments_data if attachment.get('attachment_rec_id') - ] + (4, existing_id) for existing_id in + (applicant.joining_attachment_ids).ids + ] + [ + (0, 0, { + 'name': attachment.get('file_name', ''), + 'recruitment_attachment_id': attachment.get( + 'attachment_rec_id', ''), + 'file': attachment.get('file_content', '') + }) for attachment in attachments_data if + attachment.get('attachment_rec_id') + ] applicant.write(applicant_data) diff --git a/addons_extensions/hr_recruitment_extended/data/data.xml b/addons_extensions/hr_recruitment_extended/data/data.xml new file mode 100644 index 000000000..5cd46631b --- /dev/null +++ b/addons_extensions/hr_recruitment_extended/data/data.xml @@ -0,0 +1,11 @@ + + + + + + Attachment Preview + binary + + + + \ No newline at end of file diff --git a/addons_extensions/hr_recruitment_extended/data/mail_template.xml b/addons_extensions/hr_recruitment_extended/data/mail_template.xml index 3597e9271..cbf2a0b60 100644 --- a/addons_extensions/hr_recruitment_extended/data/mail_template.xml +++ b/addons_extensions/hr_recruitment_extended/data/mail_template.xml @@ -191,6 +191,104 @@ + + Document Submission Request + + {{ user.company_id.email or user.email_formatted }} + {{ object.email_from }} + Important: Document Submission Request | FTPROTECH + + Email requesting necessary documents from applicants. + + + + +
+ + + Joining Formalities Notification @@ -421,5 +519,104 @@ + + + Recruitment Assignment Notification + + {{ user.email_formatted }} + {{ object.user_id.email }} + Job Assignment - {{ object.job_id.name }} + + Notification to recruiter regarding new job requisition assignment. + + +

+ Dear Recruiter, +
+
+ A new job requisition has been assigned to you. +
+
+

    + +
  • + Job ID: + +
  • +
    + +
  • + Job Title: + +
  • +
    + +
  • + Priority Level: + +
  • +
    + +
  • + Location: + +
  • +
    + +
  • + Budget: + +
  • +
    + +
  • + Job Description: + + View Job Details + +
  • +
    +
+
+ Kindly review the requisition and start sourcing suitable candidates in the ATS. +
+
+ Regards, +
+ Hiring Manager +

+ +
+
+ + + Applicant Client Submissions + + {{ user.email_formatted }} + {{ object.hr_job_recruitment.requested_by.email }} + Applicant Submission + + Submitting the Applicant Details to Client. + + +

+ Dear Sir/Madam, +
+
+ Submitting new applicant. +
+ Kindly review the Applicant. +
+
+ Regards, +
+ Hiring Manager +

+ +
+
+ \ No newline at end of file diff --git a/addons_extensions/hr_recruitment_extended/models/hr_applicant.py b/addons_extensions/hr_recruitment_extended/models/hr_applicant.py index 3331209ff..20c720d6f 100644 --- a/addons_extensions/hr_recruitment_extended/models/hr_applicant.py +++ b/addons_extensions/hr_recruitment_extended/models/hr_applicant.py @@ -20,6 +20,12 @@ class HRApplicant(models.Model): refused_stage = fields.Many2one('hr.recruitment.stage') refused_comments = fields.Text() + @api.constrains('candidate_id','hr_job_recruitment') + def hr_applicant_constrains(self): + for rec in self: + if rec.candidate_id and rec.hr_job_recruitment: + self.sudo().search([('candidate_id','=',rec.candidate_id.id),('hr_job_recruitment','=',rec.hr_job_recruitment.id),('id','!=',rec.id)]) + @api.model def _read_group_recruitment_stage_ids(self, stages, domain): # retrieve job_id from the context and write the domain: ids + contextual columns (job or default) @@ -101,7 +107,7 @@ class HRApplicant(models.Model): second_application_form_status = fields.Selection([('draft','Draft'),('email_sent_to_candidate','Email Sent to Candidate'),('done','Done')], default='draft') send_post_onboarding_form = fields.Boolean(related='recruitment_stage_id.post_onboarding_form') post_onboarding_form_status = fields.Selection([('draft','Draft'),('email_sent_to_candidate','Email Sent to Candidate'),('done','Done')], default='draft') - + doc_requests_form_status = fields.Selection([('draft','Draft'),('email_sent_to_candidate','Email Sent to Candidate'),('done','Done')], default='draft') legend_blocked = fields.Char(related='recruitment_stage_id.legend_blocked', string='Kanban Blocked') legend_done = fields.Char(related='recruitment_stage_id.legend_done', string='Kanban Valid') legend_normal = fields.Char(related='recruitment_stage_id.legend_normal', string='Kanban Ongoing') @@ -129,11 +135,19 @@ class HRApplicant(models.Model): warnings.warn( "Max no of submissions for this JD has been reached", DeprecationWarning, - stacklevel=2, ) - rec.submitted_to_client = True - rec.client_submission_date = fields.Datetime.now() - rec.submitted_stage = rec.recruitment_stage_id.id + return { + 'type': 'ir.actions.act_window', + 'name': 'Submission', + 'res_model': 'client.submission.mails.template.wizard', + 'view_mode': 'form', + 'view_id': self.env.ref('hr_recruitment_extended.view_client_submission_mails_template_wizard_form').id, + 'target': 'new', + 'context': {'default_template_id': self.env.ref( + "hr_recruitment_extended.application_client_submission_email_template").id, + }, + } + def submit_for_approval(self): for rec in self: @@ -208,7 +222,16 @@ class HRApplicant(models.Model): } - + def send_pre_onboarding_doc_request_form_to_candidate(self): + return { + 'type': 'ir.actions.act_window', + 'name': 'Select Attachments', + 'res_model': 'post.onboarding.attachment.wizard', + 'view_mode': 'form', + 'view_type': 'form', + 'target': 'new', + 'context': {'default_attachment_ids': [],'default_is_pre_onboarding_attachment_request': True} + } def _track_template(self, changes): res = super(HRApplicant, self)._track_template(changes) applicant = self[0] diff --git a/addons_extensions/hr_recruitment_extended/models/hr_job_recruitment.py b/addons_extensions/hr_recruitment_extended/models/hr_job_recruitment.py index 232199e08..5a51c91af 100644 --- a/addons_extensions/hr_recruitment_extended/models/hr_job_recruitment.py +++ b/addons_extensions/hr_recruitment_extended/models/hr_job_recruitment.py @@ -158,7 +158,7 @@ class HRJobRecruitment(models.Model): 'res.partner', "Job Location", default=_default_address_id, domain="[('is_company','=',True),('contact_type','=',recruitment_type)]", help="Select the location where the applicant will work. Addresses listed here are defined on the company's contact information.", exportable=False) - recruitment_type = fields.Selection([('internal','Internal'),('external','External')], required=True, default='internal') + recruitment_type = fields.Selection([('internal','In-House'),('external','Client-Side')], required=True, default='internal') requested_by = fields.Many2one('res.partner', string="Requested By", default=lambda self: self.env.user.partner_id, domain="[('contact_type','=',recruitment_type)]") @@ -167,6 +167,26 @@ class HRJobRecruitment(models.Model): self.requested_by = False self.address_id = False + def send_mail_to_recruiters(self): + for rec in self: + """ Open the email wizard """ + users = rec.interviewer_ids.ids + primary_user = rec.user_id.id + # template = self.env.ref('hr_recruitment_extended.email_template_recruiter_assignment_template') + # template.sudo().send_mail(rec.id, force_send=True) + + users.append(primary_user) + return { + 'type': 'ir.actions.act_window', + 'name': 'Send Email', + 'res_model': 'ats.invite.mail.template.wizard', + 'view_mode': 'form', + 'view_id': self.env.ref('hr_recruitment_extended.view_ats_invite_mail_template_wizard_form').id, + 'target': 'new', + 'context': {'default_partner_ids': [(6, 0, users)],'default_template_id': self.env.ref("hr_recruitment_extended.email_template_recruiter_assignment_template").id, + }, + } + @api.onchange('requested_by') def _onchange_requested_by(self): for rec in self: @@ -209,7 +229,7 @@ class HRJobRecruitment(models.Model): job_category = fields.Many2one("job.category", string="Category") - + job_priority = fields.Selection([('low','Low'),('medium','Medium'),('high','High')], string="Pirority") @api.onchange('job_id','job_category') def onchange_job_id(self): diff --git a/addons_extensions/hr_recruitment_extended/models/hr_recruitment.py b/addons_extensions/hr_recruitment_extended/models/hr_recruitment.py index 88cc1f166..7a2c2267d 100644 --- a/addons_extensions/hr_recruitment_extended/models/hr_recruitment.py +++ b/addons_extensions/hr_recruitment_extended/models/hr_recruitment.py @@ -199,8 +199,8 @@ class HRApplicant(models.Model): relevant_exp = fields.Float(string="Relevant Experience") total_exp_type = fields.Selection([('month',"Month's"),('year',"Year's")], default='year') relevant_exp_type = fields.Selection([('month',"Month's"),('year',"Year's")], default='year') - notice_period = fields.Integer(string="Notice Period") - notice_period_type = fields.Selection([('day',"Day's"),('month',"Month's"),('year',"Year's")], string='Type', default='day') + notice_period = fields.Char(string="Notice Period") + notice_period_type = fields.Selection([('day',"Day's"),('month',"Month's"),('year',"Year's")], string='Type', default='day', invisible=True) current_ctc = fields.Float(string="Current CTC", aggregator="avg", help="Applicant Current Salary", tracking=True, groups="hr_recruitment.group_hr_recruitment_user") salary_expected = fields.Float("Expected CTC", aggregator="avg", help="Salary Expected by Applicant", tracking=True, groups="hr_recruitment.group_hr_recruitment_user") diff --git a/addons_extensions/hr_recruitment_extended/models/recruitment_attachments.py b/addons_extensions/hr_recruitment_extended/models/recruitment_attachments.py index f3268f2fb..11ca4a3ea 100644 --- a/addons_extensions/hr_recruitment_extended/models/recruitment_attachments.py +++ b/addons_extensions/hr_recruitment_extended/models/recruitment_attachments.py @@ -22,8 +22,21 @@ class EmployeeRecruitmentAttachments(models.Model): recruitment_attachment_id = fields.Many2one('recruitment.attachments') recruitment_attachment_type = fields.Selection([('personal','Personal Documents'),('education','Education Documents'),('previous_employer','Previous Employer'),('others','Others')],related='recruitment_attachment_id.attachment_type') file = fields.Binary(string='File', required=True) + review_status = fields.Selection([('draft','Under Review'),('pass','PASS'),('fail','FAIL')], default='draft') + review_comments = fields.Char() - + def action_preview_file(self): + """ Returns a URL to preview the attachment in a popup """ + for record in self: + if record.file: + attachment = self.env.ref("hr_recruitment_extended.employee_recruitment_attachments_preview") + attachment.datas = record.file + return { + 'name': "File Preview", + 'type': 'ir.actions.act_url', + 'url': f'/web/content/{attachment.id}?download=false', + 'target': 'current', # Opens in a new tab + } @api.model def create(self, vals): diff --git a/addons_extensions/hr_recruitment_extended/models/res_partner.py b/addons_extensions/hr_recruitment_extended/models/res_partner.py index c3e24c064..de1618985 100644 --- a/addons_extensions/hr_recruitment_extended/models/res_partner.py +++ b/addons_extensions/hr_recruitment_extended/models/res_partner.py @@ -4,4 +4,4 @@ from odoo import models, fields, api, _ class ResPartner(models.Model): _inherit = 'res.partner' - contact_type = fields.Selection([('internal','Internal'),('external','External')], required=True, default='external') \ No newline at end of file + contact_type = fields.Selection([('internal','In-House'),('external','Client-Side')], required=True, default='external') \ No newline at end of file diff --git a/addons_extensions/hr_recruitment_extended/security/ir.model.access.csv b/addons_extensions/hr_recruitment_extended/security/ir.model.access.csv index 4905d5b5a..280ff16f1 100644 --- a/addons_extensions/hr_recruitment_extended/security/ir.model.access.csv +++ b/addons_extensions/hr_recruitment_extended/security/ir.model.access.csv @@ -25,4 +25,8 @@ hr_recruitment.access_hr_applicant_interviewer,hr.applicant.interviewer,hr_recru 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_application_stage_status,application.stage.status,model_application_stage_status,base.group_user,1,1,1,1 \ No newline at end of file +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 \ No newline at end of file diff --git a/addons_extensions/hr_recruitment_extended/static/src/js/pre_onboarding_attachment_requests.js b/addons_extensions/hr_recruitment_extended/static/src/js/pre_onboarding_attachment_requests.js new file mode 100644 index 000000000..3e14f1dd1 --- /dev/null +++ b/addons_extensions/hr_recruitment_extended/static/src/js/pre_onboarding_attachment_requests.js @@ -0,0 +1,436 @@ +import publicWidget from "@web/legacy/js/public/public_widget"; +import { _t } from "@web/core/l10n/translation"; +import { rpc } from "@web/core/network/rpc"; +import { assets, loadCSS, loadJS } from "@web/core/assets"; + +publicWidget.registry.hrRecruitmentDocs = publicWidget.Widget.extend({ + selector: "#doc_request_form", + + events: { + "change [name='candidate_image']": "previewApplicantPhoto", + "click #delete-photo-btn": "deleteCandidatePhoto", + "click #preview-photo-btn": "previewFullImage", + 'change .attachment-input': 'handleAttachmentUpload', + 'click .upload-new-btn': 'handleUploadNewFile', + 'click .delete-file-btn': 'handleDeleteUploadedFile', + 'input .file-name-input': 'handleFileNameChange', + "click .remove-file": "removeFile", + "click .preview-file": "previewFile", + "click .view-attachments-btn": "openAttachmentModal", // Opens modal with files + "click .close-modal-btn": "closeAttachmentModal", // Close modal + "submit": "handleFormSubmit", + }, + + + uploadedFiles: {}, // Store files per attachment ID + addUploadedFileRow(attachmentId, file, base64String) { + const tableBody = this.$(`#preview_body_${attachmentId}`); + + if (!this.uploadedFiles[attachmentId]) { + this.uploadedFiles[attachmentId] = []; + } + + // Generate a unique file ID using attachmentId and a timestamp + const fileId = `${attachmentId}-${Date.now()}`; + + const fileRecord = { + attachment_rec_id : attachmentId, + id: fileId, // Unique file ID + name: file.name, + base64: base64String, + type: file.type, + }; + + this.uploadedFiles[attachmentId].push(fileRecord); + + const fileIndex = this.uploadedFiles[attachmentId].length - 1; + const previewImageId = `preview_image_${fileId}`; + const fileNameInputId = `file_name_input_${fileId}`; + let previewContent = ''; + let previewClickHandler = ''; + + // Check if the file is an image or PDF and set preview content accordingly + if (file.type.startsWith('image/')) { + previewContent = `
+ +
`; + previewClickHandler = () => { + this.$('#modal_attachment_photo_preview').attr('src', `data:image/png;base64,${base64String}`); + this.$('#modal_attachment_photo_preview').show(); + this.$('#modal_attachment_pdf_preview').hide(); // Hide PDF preview + this.$('#attachmentPreviewModal').modal('show'); + }; + } else if (file.type === 'application/pdf') { + previewContent = `
+ +
`; + previewClickHandler = () => { + this.$('#modal_attachment_pdf_preview').attr('src', `data:application/pdf;base64,${base64String}`); + this.$('#modal_attachment_pdf_preview').show(); + this.$('#modal_attachment_photo_preview').hide(); // Hide image preview + this.$('#attachmentPreviewModal').modal('show'); + }; + } + + // Append new row to the table with a preview and buttons + tableBody.append(` + + + + + +
+ ${previewContent} +
+ + +
+
+ + + `); + + this.$(`#preview_table_container_${attachmentId}`).removeClass('d-none'); + + // Attach click handler for preview (image or PDF) + this.$(`#preview_wrapper_${fileId}`).on('click', previewClickHandler); + + // Attach click handler for the preview button (to trigger modal) + this.$(`.preview-btn[data-attachment-id="${attachmentId}"][data-file-id="${fileId}"]`).on('click', previewClickHandler); + }, + + handleDeleteUploadedFile(ev) { + const button = ev.currentTarget; + const attachmentId = $(button).data('attachment-id'); + const fileId = $(button).data('file-id'); + + // Find the index of the file to delete based on unique file ID + const fileIndex = this.uploadedFiles[attachmentId].findIndex(f => f.id === fileId); + + if (fileIndex !== -1) { + this.uploadedFiles[attachmentId].splice(fileIndex, 1); // Remove from array + } + + // Remove the row from DOM + this.$(`tr[data-file-id="${fileId}"]`).remove(); + + // Hide table if no files left + if (this.uploadedFiles[attachmentId].length === 0) { + this.$(`#preview_table_container_${attachmentId}`).addClass('d-none'); + } + }, + + + handleAttachmentUpload(ev) { + const input = ev.target; + const attachmentId = $(input).data('attachment-id'); + + if (input.files.length > 0) { + Array.from(input.files).forEach((file) => { + const reader = new FileReader(); + reader.onload = (e) => { + const base64String = e.target.result.split(',')[1]; + this.addUploadedFileRow(attachmentId, file, base64String); + }; + reader.readAsDataURL(file); + }); + } + }, + + handleUploadNewFile(ev) { + console.log("hello upload file"); + const button = $(ev.currentTarget); + const attachmentId = button.data('attachment-id'); + const index = button.data('index'); + + // Find the hidden file input specific to this attachmentId + const hiddenInput = this.$(`.upload-new-file-input[data-attachment-id='${attachmentId}']`); + + // When file is selected, update preview and replace old file + hiddenInput.off('change').on('change', (e) => { + const file = e.target.files[0]; + + if (!file) { + return; // Do nothing if no file is selected + } + + const reader = new FileReader(); + + reader.onload = (event) => { + const base64String = event.target.result.split(',')[1]; + + // Replace the existing file in uploadedFiles + this.uploadedFiles[attachmentId][index] = { + name: file.name, + base64: base64String, + }; + + // Update the preview image in the table + const imageId = `preview_image_${attachmentId}_${index}`; + const filePreviewWrapperId = `preview_wrapper_${attachmentId}_${index}`; + + // Check if the file is an image or PDF and update accordingly + const fileType = file.type; + + if (fileType.startsWith('image/')) { + this.$(`#${filePreviewWrapperId}`).html(` + + `); + } else if (fileType === 'application/pdf') { + this.$(`#${filePreviewWrapperId}`).html(` + + `); + } + + // Optional: Update the file name in the input field + const fileNameInputId = `file_name_input_${attachmentId}_${index}`; + this.$(`#${fileNameInputId}`).val(file.name); + }; + + reader.readAsDataURL(file); + }); + + // Trigger the hidden file input to open the file selection dialog + console.log("Triggering file input..."); + hiddenInput.trigger('click'); // Ensure this is working properly + }, + + + + handleFileNameChange(event) { + const attachmentId = $(event.target).closest('tr').data('attachment-id'); + const fileId = $(event.target).closest('tr').data('file-id'); + const newFileName = event.target.value; + + if (!attachmentId || !fileId) { + console.error('Missing attachmentId or fileId'); + return; + } + + const fileList = this.uploadedFiles[attachmentId]; + + if (!fileList) { + console.error(`No files found for attachmentId: ${attachmentId}`); + return; + } + + const fileRecord = fileList.find(file => file.id === fileId); + + if (!fileRecord) { + console.error(`File with ID ${fileId} not found under attachment ${attachmentId}`); + return; + } + + fileRecord.name = newFileName; + }, + + + +renderFilePreview(attachmentId) { + const container = this.$(`#preview_container_${attachmentId}`); + container.empty(); + + if (this.uploadedFiles[attachmentId].length === 0) { + container.addClass("d-none"); + return; + } + + container.removeClass("d-none"); + + this.uploadedFiles[attachmentId].forEach((file, index) => { + const fileHtml = $(` +
+
+ + +
+ ${file.name} +
+ `); + + fileHtml.find("img").on("click", this.previewAttachmentImage.bind(this)); + fileHtml.find(".remove-file").on("click", this.removeFile.bind(this)); + + container.append(fileHtml); + }); +}, + +previewAttachmentImage(ev) { + const attachmentId = $(ev.currentTarget).data("attachment-id"); + const index = $(ev.currentTarget).data("index"); + const fileData = this.uploadedFiles[attachmentId][index]; + + this.$("#attachment_modal_preview").attr("src", fileData.base64); + this.$("#attachmentPreviewModal").modal("show"); +}, + +removeFile(ev) { + const attachmentId = $(ev.currentTarget).data("attachment-id"); + const index = $(ev.currentTarget).data("index"); + + this.uploadedFiles[attachmentId].splice(index, 1); + this.renderFilePreview(attachmentId); +}, + + + previewFile(ev) { + const fileUrl = ev.currentTarget.dataset.fileUrl; + const modal = this.$("#photoPreviewModal"); + + this.$("#modal_photo_preview").attr("src", fileUrl); + modal.modal("show"); + }, + + + + /** + * Open modal and display uploaded files + */ + openAttachmentModal(event) { + console.log("openAttachmentModal"); + const rowId = $(event.currentTarget).closest("tr").index(); // Get the row index + this.currentRowId = rowId; // Store rowId for reference + this.renderAttachmentModal(rowId); // Render the modal for the row + }, + + renderAttachmentModal(rowId) { + const fileList = this.uploadedFiles && this.uploadedFiles[rowId] ? this.uploadedFiles[rowId] : []; + + let modalHtml = ` + `; + + // Remove old modal and append new one + $("#attachmentModal").remove(); + $("body").append(modalHtml); + + // Attach remove event to new modal content + $("#attachmentModal").on("click", ".remove-file", this.removeFile.bind(this)); + $("#attachmentModal").on("click", ".close-modal-btn", this.closeAttachmentModal.bind(this)); + }, + + /** + * Close attachment modal + */ + closeAttachmentModal() { + console.log("Closing modal"); + $("#attachmentModal").remove(); // Remove the modal from the DOM + }, + + + // Function to preview the uploaded image + previewApplicantPhoto(ev) { + const input = ev.currentTarget; + const preview = this.$("#photo_preview"); + + if (input.files && input.files[0]) { + const reader = new FileReader(); + reader.onload = (e) => { + const base64String = e.target.result.split(",")[1]; // Get only Base64 part + preview.attr("src", e.target.result); + + // Store the base64 in a hidden input field + this.$("input[name='candidate_image_base64']").val(base64String); + }; + reader.readAsDataURL(input.files[0]); + } + }, + + // Function to delete the uploaded image + deleteCandidatePhoto() { + const preview = this.$("#photo_preview"); + const inputFile = this.$("input[name='candidate_image']"); + + preview.attr("src", "data:image/png;base64,"); // Reset preview + inputFile.val(""); // Reset file input + }, + + // Function to preview full image inside a modal + previewFullImage() { + const previewSrc = this.$("#photo_preview").attr("src"); + if (previewSrc) { + this.$("#modal_photo_preview").attr("src", previewSrc); + this.$("#photoPreviewModal").modal("show"); // Use jQuery to show the modal + } + }, + + + handleFormSubmit(ev) { + ev.preventDefault(); + + let attachments = []; + let fileReadPromises = []; + let attachmentInputs = this.uploadedFiles; // your object {1: Array(1), 2: Array(2)} + + Object.keys(attachmentInputs).forEach(key => { + let filesArray = attachmentInputs[key]; // This is an array + filesArray.forEach(file => { + attachments.push({ + attachment_rec_id: file.attachment_rec_id, + file_name: file.name, + file_content: file.base64, // Assuming base64 is already present + }); + }); + }); + + + Promise.all(fileReadPromises).then(() => { + this.$("#attachments_data_json").val(JSON.stringify(attachments)); + + let formElement = this.$el.is("form") ? this.$el[0] : this.$el.find("form")[0]; + if (formElement) { + formElement.submit(); + } else { + console.error("Form element not found."); + } + }).catch((error) => { + console.error("Error reading files:", error); + }); + }, + + + start() { + this._super(...arguments); + this.$el.on("submit", this.handleFormSubmit.bind(this)); + + }, +}); diff --git a/addons_extensions/hr_recruitment_extended/views/hr_applicant_views.xml b/addons_extensions/hr_recruitment_extended/views/hr_applicant_views.xml index 3b26f5187..cfb637431 100644 --- a/addons_extensions/hr_recruitment_extended/views/hr_applicant_views.xml +++ b/addons_extensions/hr_recruitment_extended/views/hr_applicant_views.xml @@ -50,6 +50,7 @@ + @@ -69,6 +70,10 @@
@@ -339,6 +349,10 @@


+
+ Priority : +
+
Budget :
diff --git a/addons_extensions/hr_recruitment_extended/views/hr_recruitment.xml b/addons_extensions/hr_recruitment_extended/views/hr_recruitment.xml index 24feae03e..19fc78729 100644 --- a/addons_extensions/hr_recruitment_extended/views/hr_recruitment.xml +++ b/addons_extensions/hr_recruitment_extended/views/hr_recruitment.xml @@ -310,96 +310,12 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {'search_default_my_candidates': 1} + {'search_default_my_candidates': 1,'active_test': False} + +