FIX BUG
This commit is contained in:
parent
af98a28428
commit
b34dcf6d5b
|
|
@ -1,50 +1,64 @@
|
||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
|
|
||||||
import { useEffect, useState, Component ,onMounted, useRef} from "@odoo/owl";
|
import { Component, useEffect, useState, onMounted, useRef, onWillStart } from "@odoo/owl";
|
||||||
import { registry } from "@web/core/registry";
|
import { registry } from "@web/core/registry";
|
||||||
const actionRegistry = registry.category("actions");
|
const actionRegistry = registry.category("actions");
|
||||||
import { useService } from "@web/core/utils/hooks";
|
import { useService } from "@web/core/utils/hooks";
|
||||||
import { ActivityMenu } from "@hr_attendance/components/attendance_menu/attendance_menu";
|
import { ActivityMenu } from "@hr_attendance/components/attendance_menu/attendance_menu";
|
||||||
import { patch } from "@web/core/utils/patch";
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
import { loadJS } from "@web/core/assets";
|
||||||
|
|
||||||
export class NetflixProfileContainer extends Component {
|
export class NetflixProfileContainer extends Component {
|
||||||
static template = 'employee_profile_template'; // The template for the profile
|
static template = 'employee_profile_template';
|
||||||
static props = ["*"];
|
static props = ["*"];
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
super.setup(...arguments);
|
|
||||||
debugger;
|
|
||||||
this.orm = useService("orm");
|
this.orm = useService("orm");
|
||||||
this.effect = useService("effect");
|
this.effect = useService("effect");
|
||||||
this.log_in_out = useRef("log_in_out")
|
|
||||||
this.action = useService("action");
|
|
||||||
this.action = useService("action");
|
this.action = useService("action");
|
||||||
|
this.chart = null;
|
||||||
|
|
||||||
|
|
||||||
// Initialize state for storing employee data
|
|
||||||
this.state = useState({
|
this.state = useState({
|
||||||
login_employee: {
|
login_employee: {
|
||||||
name: '',
|
name: '',
|
||||||
image_1920: '',
|
image_1920: '',
|
||||||
job_id: null,
|
job_id: null,
|
||||||
current_company_exp: null,
|
current_company_exp: null,
|
||||||
doj:'',
|
doj: '',
|
||||||
employee_id:null,
|
employee_id: null,
|
||||||
birthday: '',
|
birthday: '',
|
||||||
attendance_state:null,
|
attendance_state: null,
|
||||||
mobile_phone: '',
|
mobile_phone: '',
|
||||||
work_email: '',
|
work_email: '',
|
||||||
private_street: '',
|
private_street: '',
|
||||||
department_id:''
|
department_id: ''
|
||||||
},
|
},
|
||||||
attendance_lines:[],
|
attendance_lines: [],
|
||||||
leaves:[]
|
leaves: []
|
||||||
|
});
|
||||||
|
|
||||||
});
|
onWillStart(() => loadJS(["/web/static/lib/Chart/Chart.js"]));
|
||||||
onMounted(() => {
|
|
||||||
this.fetchEmployeeData();
|
onMounted(async () => {
|
||||||
});
|
await this.fetchEmployeeData();
|
||||||
|
|
||||||
|
// Make sure Chart is loaded and canvas is available
|
||||||
|
await loadJS(["/web/static/lib/Chart/Chart.js"]);
|
||||||
|
if (typeof Chart === 'undefined') {
|
||||||
|
console.error("Chart.js failed to load");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canvas = document.querySelector("#attendanceChart");
|
||||||
|
if (!canvas) {
|
||||||
|
console.warn("Canvas element not found. Skipping chart render.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
hr_timesheets() {
|
hr_timesheets() {
|
||||||
this.action.doAction({
|
this.action.doAction({
|
||||||
name: "Timesheets",
|
name: "Timesheets",
|
||||||
|
|
@ -52,52 +66,131 @@ export class NetflixProfileContainer extends Component {
|
||||||
res_model: 'account.analytic.line',
|
res_model: 'account.analytic.line',
|
||||||
view_mode: 'list,form',
|
view_mode: 'list,form',
|
||||||
views: [[false, 'list'], [false, 'form']],
|
views: [[false, 'list'], [false, 'form']],
|
||||||
context: {
|
context: { 'search_default_month': true },
|
||||||
'search_default_month': true,
|
domain: [['employee_id.user_id', '=', this.props.action.context.user_id]],
|
||||||
},
|
|
||||||
domain: [['employee_id.user_id','=', this.props.action.context.user_id]],
|
|
||||||
target: 'current'
|
target: 'current'
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
attendance_sign_in_out() {
|
|
||||||
if (this.state.login_employee.attendance_state == 'checked_out') {
|
hr_payslip() {
|
||||||
this.state.login_employee.attendance_state = 'checked_in'
|
this.action.doAction({
|
||||||
}
|
name: "Employee Payslips",
|
||||||
else{
|
type: 'ir.actions.act_window',
|
||||||
if (this.state.login_employee.attendance_state == 'checked_in') {
|
res_model: 'hr.payslip',
|
||||||
this.state.login_employee.attendance_state = 'checked_out'
|
view_mode: 'list,form,calendar',
|
||||||
}
|
views: [[false, 'list'],[false, 'form']],
|
||||||
}
|
domain: [['employee_id','=', this.props.action.context.user_id]],
|
||||||
this.update_attendance()
|
target: 'current'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
async update_attendance() {
|
async hr_contract() {
|
||||||
var self = this;
|
if(this.isHrManager){
|
||||||
var result = await this.orm.call('hr.employee', 'attendance_manual',[[this.props.action.context.user_id]])
|
this.action.doAction({
|
||||||
if (result) {
|
name: _t("Contracts"),
|
||||||
var attendance_state = this.state.login_employee.attendance_state;
|
type: 'ir.actions.act_window',
|
||||||
var message = ''
|
res_model: 'hr.contract',
|
||||||
if (attendance_state == 'checked_in'){
|
view_mode: 'tree,form,calendar',
|
||||||
message = 'Checked In'
|
views: [[false, 'list'],[false, 'form']],
|
||||||
this.env.bus.trigger('signin_signout', {
|
context: {
|
||||||
mode: "checked_in",
|
'search_default_employee_id': this.props.action.context.user_id,
|
||||||
});
|
},
|
||||||
}
|
target: 'current'
|
||||||
else if (attendance_state == 'checked_out'){
|
|
||||||
message = 'Checked Out'
|
|
||||||
this.env.bus.trigger('signin_signout', {
|
|
||||||
mode: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.effect.add({
|
|
||||||
message: ("Successfully " + message),
|
|
||||||
type: 'rainbow_man',
|
|
||||||
fadeout: "fast",
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
add_attendance() {
|
|
||||||
|
renderChart() {
|
||||||
|
const labels = this.state.attendance_lines.map(r => r.create_date);
|
||||||
|
const data = this.state.attendance_lines.map(r => parseFloat(r.worked_hours));
|
||||||
|
|
||||||
|
const ctx = document.querySelector("#attendanceChart");
|
||||||
|
if (!ctx) {
|
||||||
|
console.warn("Canvas context not found for chart.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.chart) {
|
||||||
|
this.chart.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chart = new Chart(ctx, {
|
||||||
|
type: "bar", // Base type
|
||||||
|
data: {
|
||||||
|
labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
label: "Hours Worked (Bar)",
|
||||||
|
data,
|
||||||
|
backgroundColor: "rgba(54, 162, 235, 0.6)",
|
||||||
|
borderColor: "rgba(54, 162, 235, 1)",
|
||||||
|
borderWidth: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
label: "Hours Worked (Line)",
|
||||||
|
data,
|
||||||
|
backgroundColor: "rgba(255, 99, 132, 0.2)",
|
||||||
|
borderColor: "rgba(255, 99, 132, 1)",
|
||||||
|
borderWidth: 2,
|
||||||
|
tension: 0.3,
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 4,
|
||||||
|
pointHoverRadius: 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: true },
|
||||||
|
tooltip: { mode: 'index', intersect: false }
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: "Hours"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: "Date"
|
||||||
|
},
|
||||||
|
reverse: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async attendance_sign_in_out() {
|
||||||
|
const result = await this.orm.call('hr.employee', 'attendance_manual', [[this.props.action.context.user_id]]);
|
||||||
|
if (result) {
|
||||||
|
let current = this.state.login_employee.attendance_state;
|
||||||
|
this.state.login_employee.attendance_state = (current === 'checked_in') ? 'checked_out' : 'checked_in';
|
||||||
|
|
||||||
|
let mode = this.state.login_employee.attendance_state;
|
||||||
|
let message = mode === 'checked_in' ? 'Checked In' : 'Checked Out';
|
||||||
|
|
||||||
|
this.env.bus.trigger('signin_signout', {
|
||||||
|
mode: mode === 'checked_in' ? "checked_in" : false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.effect.add({
|
||||||
|
message: "Successfully " + message,
|
||||||
|
type: 'rainbow_man',
|
||||||
|
fadeout: "fast"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_attendance() {
|
||||||
this.action.doAction({
|
this.action.doAction({
|
||||||
name: ("Attendances"),
|
name: "Attendances",
|
||||||
type: 'ir.actions.act_window',
|
type: 'ir.actions.act_window',
|
||||||
res_model: 'hr.attendance',
|
res_model: 'hr.attendance',
|
||||||
view_mode: 'form',
|
view_mode: 'form',
|
||||||
|
|
@ -105,9 +198,10 @@ export class NetflixProfileContainer extends Component {
|
||||||
target: 'new'
|
target: 'new'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
add_leave() {
|
add_leave() {
|
||||||
this.action.doAction({
|
this.action.doAction({
|
||||||
name: ("Leave Request"),
|
name: "Leave Request",
|
||||||
type: 'ir.actions.act_window',
|
type: 'ir.actions.act_window',
|
||||||
res_model: 'hr.leave',
|
res_model: 'hr.leave',
|
||||||
view_mode: 'form',
|
view_mode: 'form',
|
||||||
|
|
@ -116,95 +210,147 @@ export class NetflixProfileContainer extends Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Method to fetch employee data from hr.employee model
|
|
||||||
async fetchEmployeeData() {
|
async fetchEmployeeData() {
|
||||||
console.log(this.props.action.context.user_id)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const userId = this.props.action.context.user_id;
|
||||||
|
const employeeData = await this.orm.call("hr.employee", 'get_user_employee_details');
|
||||||
|
if (!employeeData.length) return;
|
||||||
|
|
||||||
const employeeData = await this.orm.call(
|
const employee = employeeData[0];
|
||||||
"hr.employee",'get_user_employee_details');
|
|
||||||
const attendanceLines = await this.orm.searchRead(
|
|
||||||
'hr.attendance',
|
|
||||||
[['employee_id', '=', employeeData[0].id]],
|
|
||||||
['create_date', 'check_in', 'check_out', 'worked_hours']);
|
|
||||||
const Leaves = await this.orm.searchRead('hr.leave',[['employee_id', '=', employeeData[0].id]],
|
|
||||||
['request_date_from', 'request_date_to', 'state','holiday_status_id']);
|
|
||||||
|
|
||||||
Leaves.forEach(line => {
|
const now = new Date();
|
||||||
// Extract the 'type' from 'holiday_status_id' and assign it
|
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
line['type'] = line['holiday_status_id'][1];
|
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||||
|
|
||||||
// Change state and set color based on the state
|
// Format as Odoo-compatible strings: 'YYYY-MM-DD'
|
||||||
if (line['state'] === 'confirm') {
|
const startDateStr = startOfMonth.toISOString().split('T')[0];
|
||||||
line['state'] = 'To Approve';
|
const endDateStr = endOfMonth.toISOString().split('T')[0];
|
||||||
line['color'] = 'orange';
|
|
||||||
} else if (line['state'] === 'validate1') {
|
|
||||||
line['state'] = 'Second Approval';
|
|
||||||
line['color'] = '#7CFC00';
|
|
||||||
} else if (line['state'] === 'validate') {
|
|
||||||
line['state'] = 'Approved';
|
|
||||||
line['color'] = 'green';
|
|
||||||
} else if (line['state'] === 'cancel') {
|
|
||||||
line['state'] = 'Cancelled';
|
|
||||||
line['color'] = 'red';
|
|
||||||
} else {
|
|
||||||
line['state'] = 'Refused';
|
|
||||||
line['color'] = 'red';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const attendanceLines = await this.orm.searchRead(
|
||||||
|
'hr.attendance',
|
||||||
|
[
|
||||||
|
['employee_id', '=', employee.id],
|
||||||
|
['create_date', '>=', startDateStr],
|
||||||
|
['create_date', '<=', endDateStr]
|
||||||
|
],
|
||||||
|
['create_date', 'check_in', 'check_out', 'worked_hours']
|
||||||
|
);
|
||||||
|
|
||||||
if (employeeData.length > 0) {
|
const leaves = await this.orm.searchRead(
|
||||||
const employee = employeeData[0];
|
'hr.leave',
|
||||||
attendanceLines.forEach(line => {
|
[['employee_id', '=', employee.id]],
|
||||||
let createDate = new Date(line.create_date);
|
['request_date_from', 'request_date_to', 'state', 'holiday_status_id']
|
||||||
line.create_date = createDate.toLocaleDateString('en-IN', { timeZone: 'Asia/Kolkata' }); // Format as 'YYYY-MM-DD'
|
);
|
||||||
let checkIn = new Date(line.check_in + 'Z');
|
|
||||||
line.check_in = checkIn.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit', timeZone:'Asia/Kolkata'}); // Format as 'HH:MM'
|
|
||||||
let checkOut = new Date(line.check_out + 'Z');
|
|
||||||
line.check_out = checkOut.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit', timeZone:'Asia/Kolkata'}); // Format as 'HH:MM'
|
|
||||||
line.worked_hours = line.worked_hours.toFixed(2);
|
|
||||||
});
|
|
||||||
this.state.attendance_lines = attendanceLines,
|
|
||||||
this.state.leaves = Leaves
|
|
||||||
|
|
||||||
this.state.login_employee = {
|
const groupedLines = {};
|
||||||
name: employee.name,
|
|
||||||
image_1920: employee.image_1920,
|
attendanceLines.forEach(line => {
|
||||||
doj:employee.doj,
|
const createDate = new Date(line.create_date);
|
||||||
job_id: employee.job_id,
|
const dateStr = createDate.toLocaleDateString('en-IN', { timeZone: 'Asia/Kolkata' });
|
||||||
employee_id:employee.employee_id,
|
|
||||||
current_company_exp: employee.current_company_exp,
|
const workedHours = parseFloat(line.worked_hours);
|
||||||
attendance_state:employee.attendance_state,
|
|
||||||
birthday: employee.birthday,
|
const checkIn = new Date(line.check_in + 'Z');
|
||||||
mobile_phone: employee.mobile_phone,
|
const checkOut = new Date(line.check_out + 'Z');
|
||||||
work_email: employee.work_email,
|
|
||||||
private_street: employee.private_street,
|
const checkInStr = checkIn.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', timeZone: 'Asia/Kolkata' });
|
||||||
department_id:employee.department_id[1]
|
const checkOutStr = checkOut.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', timeZone: 'Asia/Kolkata' });
|
||||||
};
|
|
||||||
}
|
if (!groupedLines[dateStr]) {
|
||||||
|
groupedLines[dateStr] = {
|
||||||
|
create_date: dateStr,
|
||||||
|
worked_hours: workedHours,
|
||||||
|
check_in: checkInStr,
|
||||||
|
check_out: checkOutStr,
|
||||||
|
earliestCheckIn: checkIn,
|
||||||
|
latestCheckOut: checkOut
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
groupedLines[dateStr].worked_hours += workedHours;
|
||||||
|
|
||||||
|
// Update earliest check-in
|
||||||
|
if (checkIn < groupedLines[dateStr].earliestCheckIn) {
|
||||||
|
groupedLines[dateStr].check_in = checkInStr;
|
||||||
|
groupedLines[dateStr].earliestCheckIn = checkIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update latest check-out
|
||||||
|
if (checkOut > groupedLines[dateStr].latestCheckOut) {
|
||||||
|
groupedLines[dateStr].check_out = checkOutStr;
|
||||||
|
groupedLines[dateStr].latestCheckOut = checkOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Format final result
|
||||||
|
const groupedAttendance = Object.values(groupedLines).map(line => ({
|
||||||
|
create_date: line.create_date,
|
||||||
|
worked_hours: line.worked_hours.toFixed(2),
|
||||||
|
check_in: line.check_in,
|
||||||
|
check_out: line.check_out
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Assign to state
|
||||||
|
this.state.attendance_lines = groupedAttendance;
|
||||||
|
this.renderChart();
|
||||||
|
|
||||||
|
leaves.forEach(line => {
|
||||||
|
line.type = line.holiday_status_id[1];
|
||||||
|
switch (line.state) {
|
||||||
|
case 'confirm':
|
||||||
|
line.state = 'To Approve';
|
||||||
|
line.color = 'orange';
|
||||||
|
break;
|
||||||
|
case 'validate1':
|
||||||
|
line.state = 'Second Approval';
|
||||||
|
line.color = '#7CFC00';
|
||||||
|
break;
|
||||||
|
case 'validate':
|
||||||
|
line.state = 'Approved';
|
||||||
|
line.color = 'green';
|
||||||
|
break;
|
||||||
|
case 'cancel':
|
||||||
|
line.state = 'Cancelled';
|
||||||
|
line.color = 'red';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
line.state = 'Refused';
|
||||||
|
line.color = 'red';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.state.login_employee = {
|
||||||
|
name: employee.name,
|
||||||
|
image_1920: employee.image_1920,
|
||||||
|
doj: employee.doj,
|
||||||
|
job_id: employee.job_id,
|
||||||
|
employee_id: employee.employee_id,
|
||||||
|
current_company_exp: employee.current_company_exp,
|
||||||
|
attendance_state: employee.attendance_state,
|
||||||
|
birthday: employee.birthday,
|
||||||
|
mobile_phone: employee.mobile_phone,
|
||||||
|
work_email: employee.work_email,
|
||||||
|
private_street: employee.private_street,
|
||||||
|
department_id: employee.department_id[1]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state.leaves = leaves;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching employee data:', error);
|
console.error("Error fetching employee data:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
registry.category("actions").add("NetflixProfileContainer", NetflixProfileContainer)
|
|
||||||
|
actionRegistry.add("NetflixProfileContainer", NetflixProfileContainer);
|
||||||
|
// Patch the attendance menu to update icon on signin/signout
|
||||||
patch(ActivityMenu.prototype, {
|
patch(ActivityMenu.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
super.setup();
|
super.setup();
|
||||||
var self = this
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
this.env.bus.addEventListener('signin_signout', ({
|
this.env.bus.addEventListener('signin_signout', ({ detail }) => {
|
||||||
detail
|
this.state.checkedIn = detail.mode === "checked_in" ? true : false;
|
||||||
}) => {
|
});
|
||||||
if (detail.mode == 'checked_in') {
|
});
|
||||||
self.state.checkedIn = detail.mode
|
}
|
||||||
} else {
|
});
|
||||||
self.state.checkedIn = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat-att" t-on-click="attendance_sign_in_out">
|
<div class="stat-att" t-on-click="attendance_sign_in_out">
|
||||||
<t t-if="this.state.login_employee.attendance_state == 'checked_out'">
|
<t t-if="this.state.login_employee.attendance_state == 'checked_out'">
|
||||||
<div class="stat-atticon">
|
<div class="stat-atticon">
|
||||||
|
|
@ -114,7 +115,26 @@
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stat-card" t-on-click="hr_payslip">
|
||||||
|
<div class="stat-icon">
|
||||||
|
<i class="fa fa-id-card">
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<p>
|
||||||
|
Payslips
|
||||||
|
</p>
|
||||||
|
<h2>
|
||||||
|
<t t-esc="this.state.login_employee['emp_timesheets']" />
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="container" >
|
||||||
|
<div style="width: 100%; max-width: 600px; display: block; margin-left: 0;">
|
||||||
|
<canvas id="attendanceChart" width="600" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="tables-section">
|
<div class="tables-section">
|
||||||
<!-- Attendance Table -->
|
<!-- Attendance Table -->
|
||||||
<div class="table-card">
|
<div class="table-card">
|
||||||
|
|
@ -147,7 +167,8 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr t-foreach="this.state.attendance_lines" t-as="line" t-key="line['id']">
|
<tr t-foreach="state.attendance_lines" t-as="line" t-key="line.create_date">
|
||||||
|
|
||||||
<td t-esc="line.create_date">
|
<td t-esc="line.create_date">
|
||||||
</td>
|
</td>
|
||||||
<td t-esc="line['check_in']">
|
<td t-esc="line['check_in']">
|
||||||
|
|
|
||||||
|
|
@ -172,39 +172,39 @@ class BiometricDeviceDetails(models.Model):
|
||||||
if conn:
|
if conn:
|
||||||
conn.disable_device()
|
conn.disable_device()
|
||||||
self.get_all_users()
|
self.get_all_users()
|
||||||
# self.action_set_timezone()
|
|
||||||
user = conn.get_users()
|
user = conn.get_users()
|
||||||
# get All Fingerprints
|
# fingers = conn.get_templates()
|
||||||
fingers = conn.get_templates()
|
# for use in user:
|
||||||
for use in user:
|
# for finger in fingers:
|
||||||
for finger in fingers:
|
# if finger.uid == use.uid:
|
||||||
if finger.uid == use.uid:
|
# templates = conn.get_user_template(uid=use.uid,
|
||||||
templates = conn.get_user_template(uid=use.uid,
|
# temp_id=finger.fid,
|
||||||
temp_id=finger.fid,
|
# user_id=use.user_id)
|
||||||
user_id=use.user_id)
|
# hex_data = templates.template.hex()
|
||||||
hex_data = templates.template.hex()
|
# # Convert hex data to binary
|
||||||
# Convert hex data to binary
|
# binary_data = binascii.unhexlify(hex_data)
|
||||||
binary_data = binascii.unhexlify(hex_data)
|
# base64_data = base64.b64encode(binary_data).decode(
|
||||||
base64_data = base64.b64encode(binary_data).decode(
|
# 'utf-8')
|
||||||
'utf-8')
|
# employee = self.env['hr.employee'].search(
|
||||||
employee = self.env['hr.employee'].search(
|
# [('device_id_num', '=', use.user_id),('company_id', '=', self.env.company.id)],limit=1)
|
||||||
[('device_id_num', '=', use.user_id),('company_id', '=', self.env.company.id)],limit=1)
|
# employee.device_ids |= self
|
||||||
employee.device_ids |= self
|
# if str(finger.fid) in employee.fingerprint_ids.mapped(
|
||||||
if str(finger.fid) in employee.fingerprint_ids.mapped(
|
# 'finger_id'):
|
||||||
'finger_id'):
|
# employee.fingerprint_ids.search(
|
||||||
employee.fingerprint_ids.search(
|
# [('finger_id', '=', finger.fid)]).update({
|
||||||
[('finger_id', '=', finger.fid)]).update({
|
# 'finger_template': base64_data,
|
||||||
'finger_template': base64_data,
|
# })
|
||||||
})
|
# else:
|
||||||
else:
|
# employee.fingerprint_ids.create({
|
||||||
employee.fingerprint_ids.create({
|
# 'finger_template': base64_data,
|
||||||
'finger_template': base64_data,
|
# 'finger_id': finger.fid,
|
||||||
'finger_id': finger.fid,
|
# 'employee_bio_id': employee.id,
|
||||||
'employee_bio_id': employee.id,
|
# 'filename': f'{employee.name}-finger-{finger.fid}'
|
||||||
'filename': f'{employee.name}-finger-{finger.fid}'
|
# })
|
||||||
})
|
|
||||||
# get all attendances
|
# get all attendances
|
||||||
|
print(help(zk.get_attendance))
|
||||||
attendance = conn.get_attendance()
|
attendance = conn.get_attendance()
|
||||||
|
print(attendance)
|
||||||
if attendance:
|
if attendance:
|
||||||
filtered_attendance = []
|
filtered_attendance = []
|
||||||
|
|
||||||
|
|
@ -240,53 +240,50 @@ class BiometricDeviceDetails(models.Model):
|
||||||
|
|
||||||
for uid in user:
|
for uid in user:
|
||||||
if uid.user_id == each.user_id:
|
if uid.user_id == each.user_id:
|
||||||
get_user_id = self.env['hr.employee'].search(
|
employee = self.env['hr.employee'].search([
|
||||||
[('device_id_num', '=', each.user_id), ('company_id', '=', self.env.company.id)],limit=1)
|
('device_id_num', '=', each.user_id),
|
||||||
check_in_today = hr_attendance.search([(
|
('company_id', '=', self.env.company.id)
|
||||||
'employee_id', '=', get_user_id.id),
|
], limit=1)
|
||||||
('check_in', '!=', False),('check_out', '=',False)])
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
# Define the tolerance (10 minutes)
|
if not employee:
|
||||||
tolerance = timedelta(minutes=10)
|
continue
|
||||||
|
|
||||||
# Convert the atten_time string to a datetime object
|
# Convert atten_time to datetime
|
||||||
|
|
||||||
# Calculate the lower and upper bounds with the tolerance
|
|
||||||
atten_time_obj = datetime.datetime.strptime(atten_time, "%Y-%m-%d %H:%M:%S")
|
atten_time_obj = datetime.datetime.strptime(atten_time, "%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
# Calculate the lower and upper bounds with the tolerance
|
# Find existing attendance for today
|
||||||
lower_bound = atten_time_obj - tolerance
|
start_of_day = atten_time_obj.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
upper_bound = atten_time_obj + tolerance
|
end_of_day = atten_time_obj.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||||||
|
|
||||||
# Ensure the 'check_in' and 'check_out' fields are datetime objects and compare them
|
attendance_today = self.env['hr.attendance'].search([
|
||||||
next_in = hr_attendance.search([
|
('employee_id', '=', employee.id),
|
||||||
('employee_id', '=', get_user_id.id),
|
('check_in', '>=', start_of_day),
|
||||||
('check_in', '>=', lower_bound),
|
('check_in', '<=', end_of_day)
|
||||||
('check_in', '<=', upper_bound)
|
], limit=1)
|
||||||
])
|
|
||||||
|
|
||||||
next_out = hr_attendance.search([
|
# IN logic
|
||||||
('employee_id', '=', get_user_id.id),
|
if self.display_name == 'IN':
|
||||||
('check_out', '>=', lower_bound),
|
if not attendance_today:
|
||||||
('check_out', '<=', upper_bound)
|
# No attendance yet, create new with check_in only
|
||||||
])
|
self.env['hr.attendance'].create({
|
||||||
if get_user_id:
|
'employee_id': employee.id,
|
||||||
if self.display_name == 'IN' and not check_in_today:
|
'check_in': atten_time_obj,
|
||||||
if next_in:
|
|
||||||
continue
|
|
||||||
hr_attendance.create({
|
|
||||||
'employee_id':get_user_id.id,
|
|
||||||
'check_in': atten_time,
|
|
||||||
})
|
})
|
||||||
get_user_id.attendance_state = 'checked_in'
|
employee.attendance_state = 'checked_in'
|
||||||
elif check_in_today and self.display_name != 'IN':
|
else:
|
||||||
if fields.Datetime.to_string(check_in_today.check_in) > atten_time or next_out:
|
attendance_today.check_out = False
|
||||||
continue
|
employee.attendance_state = 'checked_in'
|
||||||
check_in_today.write({
|
continue
|
||||||
'check_out': atten_time,
|
|
||||||
})
|
# OUT logic
|
||||||
get_user_id.attendance_state = 'checked_out'
|
elif self.display_name != 'IN':
|
||||||
|
if attendance_today:
|
||||||
|
# Only update checkout if it's not set or is earlier than current atten_time
|
||||||
|
if not attendance_today.check_out or attendance_today.check_out < atten_time_obj:
|
||||||
|
attendance_today.write({
|
||||||
|
'check_out': atten_time_obj,
|
||||||
|
})
|
||||||
|
employee.attendance_state = 'checked_out'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
@ -296,7 +293,7 @@ class BiometricDeviceDetails(models.Model):
|
||||||
('company_id', '=', self.env.company.id)])
|
('company_id', '=', self.env.company.id)])
|
||||||
if not duplicate_atten_ids:
|
if not duplicate_atten_ids:
|
||||||
zk_attendance.create({
|
zk_attendance.create({
|
||||||
'employee_id': get_user_id.id,
|
'employee_id': employee.id,
|
||||||
'device_id_num': each.user_id,
|
'device_id_num': each.user_id,
|
||||||
'attendance_type': str(1),
|
'attendance_type': str(1),
|
||||||
'punch_type': '0' if self.display_name == 'IN' else '1',
|
'punch_type': '0' if self.display_name == 'IN' else '1',
|
||||||
|
|
@ -566,7 +563,7 @@ class BiometricDeviceDetails(models.Model):
|
||||||
"group_id: %s\n"
|
"group_id: %s\n"
|
||||||
"user_id: %s\n"
|
"user_id: %s\n"
|
||||||
"Here is the debugging information:\n%s\n"
|
"Here is the debugging information:\n%s\n"
|
||||||
"Try Restarting the device")
|
"Try Reqing the device")
|
||||||
% (candidate_uid, employee.name, privilege, password,
|
% (candidate_uid, employee.name, privilege, password,
|
||||||
group_id, str(candidate_uid), e))
|
group_id, str(candidate_uid), e))
|
||||||
conn.enable_device()
|
conn.enable_device()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue