|
|
|
|
@@ -7,8 +7,6 @@ const moment = require('moment-timezone')
|
|
|
|
|
const path = require("path");
|
|
|
|
|
const fs = require("fs");
|
|
|
|
|
const axios = require("axios");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const User = db.User
|
|
|
|
|
const Attedances = db.Attedances
|
|
|
|
|
const Branch = db.Branch
|
|
|
|
|
@@ -90,6 +88,7 @@ const create = async (req, res) => {
|
|
|
|
|
return response.failed(res, 400, `Lokasi di luar area kantor (${distance.toFixed(2)} meter)`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let finalPhotoUrl = null;
|
|
|
|
|
|
|
|
|
|
if (req.file) {
|
|
|
|
|
@@ -112,8 +111,6 @@ const create = async (req, res) => {
|
|
|
|
|
finalPhotoUrl = filePath.replace("public", "").replace(/\\/g, "/");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// === Absen masuk ===
|
|
|
|
|
attendance = await Attedances.create({
|
|
|
|
|
user_id,
|
|
|
|
|
@@ -130,6 +127,7 @@ const create = async (req, res) => {
|
|
|
|
|
await t.commit();
|
|
|
|
|
return response.success(res, {
|
|
|
|
|
...attendance.toJSON(),
|
|
|
|
|
branch_name: branch.name,
|
|
|
|
|
clock_in: moment(attendance.clock_in).tz('Asia/Jakarta').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
|
|
clock_out: attendance.clock_out
|
|
|
|
|
? moment(attendance.clock_out).tz('Asia/Jakarta').format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
@@ -165,6 +163,7 @@ const clockOut = async (req, res) => {
|
|
|
|
|
await t.rollback();
|
|
|
|
|
return response.failed(res, 400, 'Sudah absen pulang hari ini');
|
|
|
|
|
}
|
|
|
|
|
const branch = await Branch.findOne({ where: { id: user.branch_id } });
|
|
|
|
|
|
|
|
|
|
// Set jam pulang (tanpa cek lokasi)
|
|
|
|
|
attendance.clock_out = now.toDate();
|
|
|
|
|
@@ -180,6 +179,7 @@ const clockOut = async (req, res) => {
|
|
|
|
|
|
|
|
|
|
return response.success(res, {
|
|
|
|
|
...attendance.toJSON(),
|
|
|
|
|
branch_name: branch.name,
|
|
|
|
|
clock_in: moment(attendance.clock_in).tz('Asia/Jakarta').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
|
|
clock_out: moment(attendance.clock_out).tz('Asia/Jakarta').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
|
|
work_duration: attendance.work_duration,
|
|
|
|
|
@@ -246,7 +246,31 @@ const history = async (req, res) => {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getAll = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
|
|
const today = moment().tz('Asia/Jakarta').format('YYYY-MM-DD');
|
|
|
|
|
|
|
|
|
|
const attendances = await Attedances.findAll({
|
|
|
|
|
where: {
|
|
|
|
|
date: today
|
|
|
|
|
},
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: User,
|
|
|
|
|
as: 'user',
|
|
|
|
|
attributes: ['id', 'name', 'email']
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
order: [['created_at', 'DESC']]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return response.success(res, attendances, 'List kehadiran hari ini');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
return response.error(res, 'Gagal mengambil data absensi hari ini');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const update = async (req, res) => {
|
|
|
|
|
const t = await sequelize.transaction();
|
|
|
|
|
@@ -255,18 +279,18 @@ const update = async (req, res) => {
|
|
|
|
|
const user_id = req.user.id;
|
|
|
|
|
const body = req.body;
|
|
|
|
|
|
|
|
|
|
const categories = await Category.findOne({
|
|
|
|
|
const attedances = await Attedances.findOne({
|
|
|
|
|
where: { id },
|
|
|
|
|
transaction: t
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const categoriesUpdate = await categories.update({
|
|
|
|
|
const attedancesUpdate = await attedances.update({
|
|
|
|
|
...body,
|
|
|
|
|
user_id,
|
|
|
|
|
}, { transaction: t });
|
|
|
|
|
|
|
|
|
|
await t.commit();
|
|
|
|
|
return response.success(res, categoriesUpdate, 'Category Berhasil Di update');
|
|
|
|
|
return response.success(res, attedancesUpdate, 'Category Berhasil Di update');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await t.rollback();
|
|
|
|
|
errorHandler(error, req, res);
|
|
|
|
|
@@ -278,20 +302,20 @@ const destroy = async (req, res) => {
|
|
|
|
|
const t = await sequelize.transaction();
|
|
|
|
|
try {
|
|
|
|
|
const id = req.params.id;
|
|
|
|
|
const category = await Category.findOne({
|
|
|
|
|
const attendance = await Attedances.findOne({
|
|
|
|
|
where: { id },
|
|
|
|
|
transaction: t,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!category) {
|
|
|
|
|
if (!attendance) {
|
|
|
|
|
await t.rollback();
|
|
|
|
|
return response.failed(res, 404, 'Category tidak ditemukan');
|
|
|
|
|
return response.failed(res, 404, 'Attendance tidak ditemukan');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await category.destroy({ transaction: t });
|
|
|
|
|
await attendance.destroy({ transaction: t });
|
|
|
|
|
await t.commit();
|
|
|
|
|
|
|
|
|
|
return response.success(res, null, 'Category berhasil dihapus');
|
|
|
|
|
return response.success(res, null, 'Attendance berhasil dihapus');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await t.rollback();
|
|
|
|
|
errorHandler(error, req, res);
|
|
|
|
|
@@ -299,11 +323,61 @@ const destroy = async (req, res) => {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const checkLocation = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const user_id = req.user.id;
|
|
|
|
|
const { lat, lng } = req.query;
|
|
|
|
|
|
|
|
|
|
if (!lat || !lng) {
|
|
|
|
|
return response.failed(res, 400, 'Latitude dan longitude wajib dikirim');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const user = await User.findOne({ where: { id: user_id } });
|
|
|
|
|
if (!user) return response.failed(res, 404, 'User tidak ditemukan');
|
|
|
|
|
|
|
|
|
|
const branch = await Branch.findOne({ where: { id: user.branch_id } });
|
|
|
|
|
if (!branch) return response.failed(res, 404, 'Data kantor tidak ditemukan');
|
|
|
|
|
|
|
|
|
|
// Hitung jarak user dengan kantor (dalam meter)
|
|
|
|
|
const distanceMeters = getDistance(branch.lat, branch.lng, lat, lng);
|
|
|
|
|
|
|
|
|
|
// Otomatis ubah ke km jika lebih dari 1000 meter
|
|
|
|
|
const isKm = distanceMeters >= 1000;
|
|
|
|
|
const distance = isKm ? distanceMeters / 1000 : distanceMeters;
|
|
|
|
|
const unit = isKm ? 'km' : 'm';
|
|
|
|
|
|
|
|
|
|
// Radius tetap pakai meter (biar konsisten)
|
|
|
|
|
const allowedRadius = parseFloat(process.env.ABSENCE_RADIUS) || 100;
|
|
|
|
|
|
|
|
|
|
if (distanceMeters <= allowedRadius) {
|
|
|
|
|
return response.success(res, {
|
|
|
|
|
inOffice: true,
|
|
|
|
|
branch_name: branch.name,
|
|
|
|
|
distance: distance.toFixed(2),
|
|
|
|
|
unit,
|
|
|
|
|
message: `Anda sedang berada di lokasi ${branch.name}, silakan absen.`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.success(res, {
|
|
|
|
|
inOffice: false,
|
|
|
|
|
branch_name: branch.name,
|
|
|
|
|
distance: distance.toFixed(2),
|
|
|
|
|
unit,
|
|
|
|
|
message: `Anda berada di luar area kantor ${distance.toFixed(2)} ${unit} dari ${branch.name}.`,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
errorHandler(error, req, res);
|
|
|
|
|
return response.failed(res, 500, error.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
create,
|
|
|
|
|
destroy,
|
|
|
|
|
history,
|
|
|
|
|
update,
|
|
|
|
|
clockOut
|
|
|
|
|
clockOut,
|
|
|
|
|
getAll,
|
|
|
|
|
checkLocation
|
|
|
|
|
}
|