Merge pull request 'wisdom' (#3) from wisdom into main

Reviewed-on: #3
This commit is contained in:
2025-10-22 05:27:56 +00:00
21 changed files with 469 additions and 43 deletions

View File

@@ -31,11 +31,11 @@ const poolPromise = new sql.ConnectionPool(config)
async function checkConnection() { async function checkConnection() {
try { try {
const pool = await poolPromise; const pool = await poolPromise;
await pool.request().query('SELECT 1 AS isConnected'); await pool.request().query("SELECT 1 AS isConnected");
console.log('🔍 SQL Server terkoneksi dengan baik'); console.log("🔍 SQL Server terkoneksi dengan baik");
return true; return true;
} catch (error) { } catch (error) {
console.error('⚠️ Gagal cek koneksi SQL Server:', error); console.error("⚠️ Gagal cek koneksi SQL Server:", error);
return false; return false;
} }
} }
@@ -58,13 +58,16 @@ async function query(text, params = []) {
return request.query(sqlText); return request.query(sqlText);
} }
/**
* Validasi tanggal
*/
function isValidDate(dateStr) { function isValidDate(dateStr) {
const d = new Date(dateStr); const d = new Date(dateStr);
return !isNaN(d.getTime()); // true kalau valid return !isNaN(d.getTime());
} }
/** /**
* Build filter query * Build filter query (AND)
*/ */
function buildFilterQuery(filterQuery = [], fixedParams = []) { function buildFilterQuery(filterQuery = [], fixedParams = []) {
let whereConditions = []; let whereConditions = [];
@@ -76,7 +79,9 @@ function buildFilterQuery(filterQuery = [], fixedParams = []) {
switch (f.type) { switch (f.type) {
case "string": case "string":
queryParams.push(`%${f.param}%`); queryParams.push(`%${f.param}%`);
whereConditions.push(`${f.column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`); whereConditions.push(
`${f.column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`
);
break; break;
case "number": case "number":
@@ -89,10 +94,9 @@ function buildFilterQuery(filterQuery = [], fixedParams = []) {
whereConditions.push(`${f.column} = $${queryParams.length}`); whereConditions.push(`${f.column} = $${queryParams.length}`);
break; break;
case 'between': case "between":
if (Array.isArray(f.param) && f.param.length === 2) { if (Array.isArray(f.param) && f.param.length === 2) {
const from = f.param[0]; const [from, to] = f.param;
const to = f.param[1];
if (isValidDate(from) && isValidDate(to)) { if (isValidDate(from) && isValidDate(to)) {
queryParams.push(from); queryParams.push(from);
queryParams.push(to); queryParams.push(to);
@@ -112,7 +116,7 @@ function buildFilterQuery(filterQuery = [], fixedParams = []) {
* Build OR ILIKE (SQL Server pakai LIKE + COLLATE) * Build OR ILIKE (SQL Server pakai LIKE + COLLATE)
*/ */
function buildStringOrIlike(columnParam, criteria, fixedParams = []) { function buildStringOrIlike(columnParam, criteria, fixedParams = []) {
if (!criteria) return { whereClause: "", whereParam: fixedParams }; if (!criteria) return { whereOrConditions: "", whereParamOr: fixedParams };
let orStringConditions = []; let orStringConditions = [];
let queryParams = [...fixedParams]; let queryParams = [...fixedParams];
@@ -120,7 +124,9 @@ function buildStringOrIlike(columnParam, criteria, fixedParams = []) {
columnParam.forEach((column) => { columnParam.forEach((column) => {
if (!column) return; if (!column) return;
queryParams.push(`%${criteria}%`); queryParams.push(`%${criteria}%`);
orStringConditions.push(`${column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`); orStringConditions.push(
`${column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`
);
}); });
const whereClause = orStringConditions.length const whereClause = orStringConditions.length
@@ -130,12 +136,60 @@ function buildStringOrIlike(columnParam, criteria, fixedParams = []) {
return { whereOrConditions: whereClause, whereParamOr: queryParams }; return { whereOrConditions: whereClause, whereParamOr: queryParams };
} }
/**
* Build Date Filter (harian / mingguan / bulanan)
*/
function buildDateFilter(column, type, dateValue, fixedParams = []) {
let whereCondition = "";
let queryParams = [...fixedParams];
if (!dateValue && type !== "monthly") {
return { whereDateCondition: "", whereDateParams: queryParams };
}
switch (type) {
case "daily": {
queryParams.push(dateValue);
whereCondition = `CAST(${column} AS DATE) = $${queryParams.length}`;
break;
}
case "weekly": {
const startDate = new Date(dateValue);
if (!isNaN(startDate.getTime())) {
const endDate = new Date(startDate);
endDate.setDate(startDate.getDate() + 6);
queryParams.push(startDate.toISOString().split("T")[0]);
queryParams.push(endDate.toISOString().split("T")[0]);
whereCondition = `CAST(${column} AS DATE) BETWEEN $${queryParams.length - 1} AND $${queryParams.length}`;
}
break;
}
case "monthly": {
const [year, month] = dateValue.split("-");
if (year && month) {
queryParams.push(parseInt(year), parseInt(month));
whereCondition = `YEAR(${column}) = $${queryParams.length - 1} AND MONTH(${column}) = $${queryParams.length}`;
}
break;
}
default:
whereCondition = "";
}
return { whereDateCondition: whereCondition, whereDateParams: queryParams };
}
/** /**
* Build dynamic UPDATE * Build dynamic UPDATE
*/ */
function buildDynamicUpdate(table, data, where) { function buildDynamicUpdate(table, data, where) {
data.updated_by = data.userId;
data.updated_by = data.userId
delete data.userId; delete data.userId;
const setParts = []; const setParts = [];
@@ -153,7 +207,6 @@ function buildDynamicUpdate(table, data, where) {
throw new Error("Tidak ada kolom untuk diupdate"); throw new Error("Tidak ada kolom untuk diupdate");
} }
// updated_at otomatis pakai CURRENT_TIMESTAMP
setParts.push(`updated_at = CURRENT_TIMESTAMP`); setParts.push(`updated_at = CURRENT_TIMESTAMP`);
const whereParts = []; const whereParts = [];
@@ -175,9 +228,8 @@ function buildDynamicUpdate(table, data, where) {
* Build dynamic INSERT * Build dynamic INSERT
*/ */
function buildDynamicInsert(table, data) { function buildDynamicInsert(table, data) {
data.created_by = data.userId;
data.created_by = data.userId data.updated_by = data.userId;
data.updated_by = data.userId
delete data.userId; delete data.userId;
const columns = []; const columns = [];
@@ -197,7 +249,6 @@ function buildDynamicInsert(table, data) {
throw new Error("Tidak ada kolom untuk diinsert"); throw new Error("Tidak ada kolom untuk diinsert");
} }
// created_at & updated_at otomatis
columns.push("created_at", "updated_at"); columns.push("created_at", "updated_at");
placeholders.push("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP"); placeholders.push("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP");
@@ -238,6 +289,7 @@ module.exports = {
checkConnection, checkConnection,
query, query,
buildFilterQuery, buildFilterQuery,
buildDateFilter,
buildStringOrIlike, buildStringOrIlike,
buildDynamicInsert, buildDynamicInsert,
buildDynamicUpdate, buildDynamicUpdate,

View File

@@ -0,0 +1,71 @@
const UserScheduleService = require('../services/user_schedule.service');
const { setResponse, setResponsePaging, checkValidate } = require('../helpers/utils');
const { insertUserScheduleSchema, updateUserScheduleSchema } = require('../validate/user_schedule.schema');
class UserScheduleController {
// Get all User Schedule
static async getAll(req, res) {
const queryParams = req.query;
const results = await UserScheduleService.getAllUserScheduleDb(queryParams);
const response = await setResponsePaging(queryParams, results, 'User Schedule found')
res.status(response.statusCode).json(response);
}
// Get User Schedule by ID
static async getById(req, res) {
const { id } = req.params;
const results = await UserScheduleService.getUserScheduleByID(id);
const response = await setResponse(results, 'User Schedule found')
res.status(response.statusCode).json(response);
}
// Create User Schedule
static async create(req, res) {
const { error, value } = await checkValidate(insertUserScheduleSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await UserScheduleService.createUserSchedules(value);
const response = await setResponse(results, 'User Schedule created successfully')
return res.status(response.statusCode).json(response);
}
// Update User Schedule
static async update(req, res) {
const { id } = req.params;
const { error, value } = checkValidate(updateUserScheduleSchema, req)
if (error) {
return res.status(400).json(setResponse(error, 'Validation failed', 400));
}
value.userId = req.user.user_id
const results = await UserScheduleService.updateUserSchedules(id, value);
const response = await setResponse(results, 'User Schedule updated successfully')
res.status(response.statusCode).json(response);
}
// Soft delete User Schedule
static async delete(req, res) {
const { id } = req.params;
const results = await UserScheduleService.deleteUserSchedules(id, req.user.user_id);
const response = await setResponse(results, 'User Schedule deleted successfully')
res.status(response.statusCode).json(response);
}
}
module.exports = UserScheduleController;

View File

@@ -34,7 +34,7 @@ const getAllBrandsDb = async (searchParams = {}) => {
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? whereOrConditions : ""} ${whereOrConditions ? whereOrConditions : ""}
ORDER BY b.brand_id ASC ORDER BY b.brand_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""}; ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`; `;
const result = await pool.query(queryText, queryParams); const result = await pool.query(queryText, queryParams);

View File

@@ -50,7 +50,7 @@ const getAllDevicesDb = async (searchParams = {}) => {
${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''} ${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''} ${whereOrConditions ? whereOrConditions : ''}
ORDER BY a.device_id ASC ORDER BY a.device_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ''}; ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`; `;
const result = await pool.query(queryText, queryParams); const result = await pool.query(queryText, queryParams);

View File

@@ -35,7 +35,7 @@ const getAllRolesDb = async (searchParams = {}) => {
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""} ${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.role_id ASC ORDER BY a.role_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""} ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`; `;
const result = await pool.query(queryText, queryParams); const result = await pool.query(queryText, queryParams);

View File

@@ -1,7 +1,11 @@
const pool = require("../config"); const pool = require("../config");
const { formattedDate } = require("../utils/date"); const { formattedDate } = require("../utils/date");
// Get all schedules const normalizeClause = (clause) => {
if (!clause) return "";
return clause.replace(/^\s*(?:AND|WHERE)\s*/i, "").trim();
};
const getAllScheduleDb = async (searchParams = {}) => { const getAllScheduleDb = async (searchParams = {}) => {
let queryParams = []; let queryParams = [];
@@ -18,11 +22,51 @@ const getAllScheduleDb = async (searchParams = {}) => {
if (whereParamOr) queryParams = whereParamOr; if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery( const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[{ column: "a.schedule_date", param: searchParams.name, type: "date" }], [
{
column: "a.schedule_date",
param: searchParams.name,
type: "date",
},
],
queryParams queryParams
); );
if (whereParamAnd) queryParams = whereParamAnd; if (whereParamAnd) queryParams = whereParamAnd;
const { whereDateCondition, whereDateParams } = pool.buildDateFilter(
"a.schedule_date",
searchParams.dateType,
searchParams.dateValue,
queryParams
);
if (whereDateParams) queryParams = whereDateParams;
const whereParts = [];
whereParts.push("a.deleted_at IS NULL");
if (Array.isArray(whereConditions) && whereConditions.length > 0) {
const joined = whereConditions.join(" AND ");
const norm = normalizeClause(joined);
if (norm) whereParts.push(norm);
} else if (typeof whereConditions === "string" && whereConditions.trim()) {
const norm = normalizeClause(whereConditions);
if (norm) whereParts.push(norm);
}
if (whereOrConditions && String(whereOrConditions).trim()) {
const norm = normalizeClause(whereOrConditions);
if (norm) whereParts.push(norm);
}
if (whereDateCondition && String(whereDateCondition).trim()) {
const norm = normalizeClause(whereDateCondition);
if (norm) whereParts.push(norm);
}
const whereClause =
whereParts.length > 0 ? `WHERE ${whereParts.join(" AND ")}` : "";
const queryText = ` const queryText = `
SELECT SELECT
COUNT(*) OVER() AS total_data, COUNT(*) OVER() AS total_data,
@@ -32,14 +76,13 @@ const getAllScheduleDb = async (searchParams = {}) => {
b.end_time b.end_time
FROM schedule a FROM schedule a
LEFT JOIN m_shift b ON a.shift_id = b.shift_id LEFT JOIN m_shift b ON a.shift_id = b.shift_id
WHERE a.deleted_at IS NULL ${whereClause}
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.schedule_id ASC ORDER BY a.schedule_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""} ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ""}
`; `;
const result = await pool.query(queryText, queryParams); const result = await pool.query(queryText, queryParams);
const total = const total =
result?.recordset?.length > 0 result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10) ? parseInt(result.recordset[0].total_data, 10)
@@ -48,6 +91,7 @@ const getAllScheduleDb = async (searchParams = {}) => {
return { data: result.recordset, total }; return { data: result.recordset, total };
}; };
// Get by ID
const getScheduleByIdDb = async (id) => { const getScheduleByIdDb = async (id) => {
const queryText = ` const queryText = `
SELECT SELECT
@@ -63,8 +107,9 @@ const getScheduleByIdDb = async (id) => {
return result.recordset?.[0] || null; return result.recordset?.[0] || null;
}; };
// Insert (bisa multi hari)
const insertScheduleDb = async (store) => { const insertScheduleDb = async (store) => {
const nextDays = Number(store.next_day ?? 0); // default 0 kalau tidak diisi const nextDays = Number(store.next_day ?? 0);
const insertedRecords = []; const insertedRecords = [];
for (let i = 0; i <= nextDays; i++) { for (let i = 0; i <= nextDays; i++) {
@@ -72,11 +117,7 @@ const insertScheduleDb = async (store) => {
nextDate.setDate(nextDate.getDate() + i); nextDate.setDate(nextDate.getDate() + i);
const formatted = formattedDate(nextDate); const formatted = formattedDate(nextDate);
const newStore = { ...store, schedule_date: formatted };
const newStore = {
...store,
schedule_date: formatted,
};
delete newStore.next_day; delete newStore.next_day;
const { query: queryText, values } = pool.buildDynamicInsert("schedule", newStore); const { query: queryText, values } = pool.buildDynamicInsert("schedule", newStore);
@@ -92,6 +133,7 @@ const insertScheduleDb = async (store) => {
return insertedRecords; return insertedRecords;
}; };
// Update
const updateScheduleDb = async (id, data) => { const updateScheduleDb = async (id, data) => {
const store = { ...data }; const store = { ...data };
const whereData = { schedule_id: id }; const whereData = { schedule_id: id };
@@ -106,7 +148,7 @@ const updateScheduleDb = async (id, data) => {
return getScheduleByIdDb(id); return getScheduleByIdDb(id);
}; };
// Soft delete schedule // Soft delete
const deleteScheduleDb = async (id, deletedBy) => { const deleteScheduleDb = async (id, deletedBy) => {
const queryText = ` const queryText = `
UPDATE schedule UPDATE schedule

View File

@@ -36,7 +36,7 @@ const getAllShiftDb = async (searchParams = {}) => {
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""} ${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.shift_id ASC ORDER BY a.shift_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""} ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`; `;
const result = await pool.query(queryText, queryParams); const result = await pool.query(queryText, queryParams);

View File

@@ -39,7 +39,7 @@ const getAllStatusDb = async (searchParams = {}) => {
${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''} ${whereConditions.length > 0 ? `AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''} ${whereOrConditions ? whereOrConditions : ''}
ORDER BY a.status_id ASC ORDER BY a.status_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ''}; ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`; `;
const result = await pool.query(queryText, queryParams); const result = await pool.query(queryText, queryParams);

View File

@@ -37,7 +37,7 @@ const getAllSubSectionsDb = async (searchParams = {}) => {
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? whereOrConditions : ""} ${whereOrConditions ? whereOrConditions : ""}
ORDER BY a.sub_section_id ASC ORDER BY a.sub_section_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""}; ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`; `;
const result = await pool.query(queryText, queryParams); const result = await pool.query(queryText, queryParams);

View File

@@ -62,7 +62,7 @@ const getAllTagsDb = async (searchParams = {}) => {
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""} ${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""} ${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.tag_id ASC ORDER BY a.tag_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""} ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`; `;
const result = await pool.query(queryText, queryParams); const result = await pool.query(queryText, queryParams);

View File

@@ -42,7 +42,7 @@ const getAllUnitsDb = async (searchParams = {}) => {
${whereConditions.length > 0 ? `AND ${whereConditions.join(" AND ")}` : ""} ${whereConditions.length > 0 ? `AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? whereOrConditions : ""} ${whereOrConditions ? whereOrConditions : ""}
ORDER BY a.unit_id ASC ORDER BY a.unit_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ""}; ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`; `;
const result = await pool.query(queryText, queryParams); const result = await pool.query(queryText, queryParams);

View File

@@ -53,7 +53,7 @@ const getAllUsersDb = async (searchParams = {}) => {
${whereConditions.length > 0 ? ` AND ${whereConditions.join(' AND ')}` : ''} ${whereConditions.length > 0 ? ` AND ${whereConditions.join(' AND ')}` : ''}
${whereOrConditions ? whereOrConditions : ''} ${whereOrConditions ? whereOrConditions : ''}
ORDER BY u.user_id ASC ORDER BY u.user_id ASC
${searchParams.limit ? `OFFSET $2 ROWS FETCH NEXT $1 ROWS ONLY` : ''}; ${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`; `;
const result = await pool.query(queryText, queryParams); const result = await pool.query(queryText, queryParams);

116
db/user_schedule.db.js Normal file
View File

@@ -0,0 +1,116 @@
const pool = require("../config");
const getAllUserScheduleDb = async (searchParams = {}) => {
let queryParams = [];
// Handle pagination
if (searchParams.limit) {
const page = Number(searchParams.page ?? 1) - 1;
queryParams = [Number(searchParams.limit ?? 10), page];
}
const { whereOrConditions, whereParamOr } = pool.buildStringOrIlike(
["a.user_id", "a.user_schedule_id", "a.schedule_id", "a.user_schedule_id"],
searchParams.criteria,
queryParams
);
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[
{ column: "a.user_id", param: searchParams.user_id, type: "int" },
{ column: "a.user_schedule_id", param: searchParams.user_schedule_id, type: "int" },
{ column: "a.schedule_id", param: searchParams.schedule_id, type: "int" },
{ column: "a.user_schedule_id", param: searchParams.user_schedule_id, type: "int" },
],
queryParams
);
if (whereParamAnd) queryParams = whereParamAnd;
const queryText = `
SELECT
COUNT(*) OVER() AS total_data,
a.*,
b.schedule_date,
c.shift_name,
c.start_time,
c.end_time,
d.user_fullname
FROM user_schedule a
LEFT JOIN schedule b ON a.user_schedule_id = b.schedule_id
LEFT JOIN m_shift c ON a.user_schedule_id = c.shift_id
LEFT JOIN m_users d ON a.user_schedule_id = d.user_id
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
ORDER BY a.user_schedule_id ASC
${searchParams.limit ? `OFFSET $2 * $1 ROWS FETCH NEXT $1 ROWS ONLY` : ''}
`;
const result = await pool.query(queryText, queryParams);
const total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
: 0;
return { data: result.recordset, total };
};
const getUserScheduleByIdDb = async (id) => {
const queryText = `
SELECT
a.*,
b.schedule_date,
c.shift_name,
c.start_time,
c.end_time,
d.user_fullname
FROM user_schedule a
LEFT JOIN schedule b ON a.user_schedule_id = b.schedule_id
LEFT JOIN m_shift c ON a.user_schedule_id = c.shift_id
LEFT JOIN m_users d ON a.user_schedule_id = d.user_id
WHERE a.user_schedule_id = $1 AND a.deleted_at IS NULL
`;
const result = await pool.query(queryText, [id]);
return result.recordset?.[0] || null;
};
const insertUserScheduleDb = async (store) => {
const { query: queryText, values } = pool.buildDynamicInsert("user_schedule", store);
const result = await pool.query(queryText, values);
const insertedId = result.recordset?.[0]?.inserted_id;
return insertedId ? await getUserScheduleByIdDb(insertedId) : null;
};
const updateUserScheduleDb = async (id, data) => {
const store = { ...data };
const whereData = { user_schedule_id: id };
const { query: queryText, values } = pool.buildDynamicUpdate(
"user_schedule",
store,
whereData
);
await pool.query(`${queryText} AND deleted_at IS NULL`, values);
return getUserScheduleByIdDb(id);
};
const deleteUserScheduleDb = async (id, deletedBy) => {
const queryText = `
UPDATE user_schedule
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1
WHERE user_schedule_id = $2 AND deleted_at IS NULL
`;
await pool.query(queryText, [deletedBy, id]);
return true;
};
module.exports = {
getAllUserScheduleDb,
getUserScheduleByIdDb,
insertUserScheduleDb,
updateUserScheduleDb,
deleteUserScheduleDb,
};

View File

@@ -9,6 +9,7 @@ const shift = require("./shift.route");
const schedule = require("./schedule.route"); const schedule = require("./schedule.route");
const status = require("./status.route"); const status = require("./status.route");
const unit = require("./unit.route") const unit = require("./unit.route")
const UserSchedule = require("./user_schedule.route")
router.use("/auth", auth); router.use("/auth", auth);
router.use("/user", users); router.use("/user", users);
@@ -20,6 +21,7 @@ router.use("/shift", shift);
router.use("/schedule", schedule); router.use("/schedule", schedule);
router.use("/status", status); router.use("/status", status);
router.use("/unit", unit); router.use("/unit", unit);
router.use("/user-schedule", UserSchedule)
module.exports = router; module.exports = router;

View File

@@ -0,0 +1,17 @@
const express = require('express');
const UserSchedulesController = require('../controllers/user_schedule.controller');
const verifyToken = require("../middleware/verifyToken")
const verifyAccess = require("../middleware/verifyAccess")
const router = express.Router();
router.route("/")
.get(verifyToken.verifyAccessToken, UserSchedulesController.getAll)
.post(verifyToken.verifyAccessToken, verifyAccess(), UserSchedulesController.create);
router.route("/:id")
.get(verifyToken.verifyAccessToken, UserSchedulesController.getById)
.put(verifyToken.verifyAccessToken, verifyAccess(), UserSchedulesController.update)
.delete(verifyToken.verifyAccessToken, verifyAccess(), UserSchedulesController.delete);
module.exports = router;

View File

@@ -0,0 +1,89 @@
const {
getAllUserScheduleDb,
getUserScheduleByIdDb,
insertUserScheduleDb,
updateUserScheduleDb,
deleteUserScheduleDb
} = require('../db/user_schedule.db');
const { ErrorHandler } = require('../helpers/error');
class UserScheduleService {
// Get all devices
static async getAllUserScheduleDb(param) {
try {
const results = await getAllUserScheduleDb(param);
results.data.map(element => {
});
return results
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Get device by ID
static async getUserScheduleByID(id) {
try {
const result = await getUserScheduleByIdDb(id);
if (result.length < 1) throw new ErrorHandler(404, 'User Schedule not found');
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Create device
static async createUserSchedules(data) {
try {
if (!data || typeof data !== 'object') data = {};
const result = await insertUserScheduleDb(data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Update device
static async updateUserSchedules(id, data) {
try {
if (!data || typeof data !== 'object') data = {};
const dataExist = await getUserScheduleByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'UserSchedules not found');
}
const result = await updateUserScheduleDb(id, data);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
// Soft delete device
static async deleteUserSchedules(id, userId) {
try {
const dataExist = await getUserScheduleByIdDb(id);
if (dataExist.length < 1) {
throw new ErrorHandler(404, 'UserSchedules not found');
}
const result = await deleteUserScheduleDb(id, userId);
return result;
} catch (error) {
throw new ErrorHandler(error.statusCode, error.message);
}
}
}
module.exports = UserScheduleService;

View File

@@ -3,7 +3,7 @@ const crypto = require('crypto');
const tokenSettings = { const tokenSettings = {
access: { access: {
expiresIn: '15m', expiresIn: '12h',
type: 'access', type: 'access',
secret: process.env.SECRET secret: process.env.SECRET
}, },

View File

@@ -13,6 +13,7 @@ const insertSubSectionSchema = Joi.object({
"any.required": "Sub section name is required" "any.required": "Sub section name is required"
}).required(), }).required(),
is_active: Joi.boolean().required(), is_active: Joi.boolean().required(),
value: Joi.string().max(100).optional()
}); });
const updateSubSectionSchema = Joi.object({ const updateSubSectionSchema = Joi.object({
@@ -23,6 +24,7 @@ const updateSubSectionSchema = Joi.object({
"string.max": "Sub section name cannot exceed 200 characters", "string.max": "Sub section name cannot exceed 200 characters",
}).optional(), }).optional(),
is_active: Joi.boolean().optional(), is_active: Joi.boolean().optional(),
value: Joi.string().max(100).optional()
}).min(1).messages({ }).min(1).messages({
"object.min": "At least one field must be provided to update", "object.min": "At least one field must be provided to update",
}); });

View File

@@ -12,7 +12,13 @@ const insertTagsSchema = Joi.object({
data_type: Joi.string().max(50).required(), data_type: Joi.string().max(50).required(),
unit: Joi.string().max(50).required(), unit: Joi.string().max(50).required(),
sub_section_id: Joi.number().optional(), sub_section_id: Joi.number().optional(),
is_alarm: Joi.boolean().required() is_alarm: Joi.boolean().required(),
is_report: Joi.boolean().required(),
is_history: Joi.boolean().required(),
lim_low_crash: Joi.number().optional(),
lim_low: Joi.number().optional(),
lim_high: Joi.number().optional(),
lim_high_crash: Joi.number().optional(),
}); });
const updateTagsSchema = Joi.object({ const updateTagsSchema = Joi.object({
@@ -24,6 +30,12 @@ const updateTagsSchema = Joi.object({
unit: Joi.string().max(50), unit: Joi.string().max(50),
is_alarm: Joi.boolean().optional(), is_alarm: Joi.boolean().optional(),
sub_section_id: Joi.number().optional(), sub_section_id: Joi.number().optional(),
is_report: Joi.boolean().optional(),
is_history: Joi.boolean().optional(),
lim_low_crash: Joi.number().optional(),
lim_low: Joi.number().optional(),
lim_high: Joi.number().optional(),
lim_high_crash: Joi.number().optional(),
}).min(1); }).min(1);
// ✅ Export dengan CommonJS // ✅ Export dengan CommonJS

View File

@@ -7,12 +7,14 @@ const insertUnitSchema = Joi.object({
unit_name: Joi.string().max(100).required(), unit_name: Joi.string().max(100).required(),
tag_id: Joi.number().integer().optional(), tag_id: Joi.number().integer().optional(),
is_active: Joi.boolean().required(), is_active: Joi.boolean().required(),
description: Joi. string().max(100).optional()
}); });
const updateUnitSchema = Joi.object({ const updateUnitSchema = Joi.object({
unit_name: Joi.string().max(100).optional(), unit_name: Joi.string().max(100).optional(),
tag_id: Joi.number().integer().optional(), tag_id: Joi.number().integer().optional(),
is_active: Joi.boolean().optional() is_active: Joi.boolean().optional(),
description: Joi. string().max(100).optional()
}).min(1); }).min(1);
module.exports = { module.exports = {

View File

@@ -0,0 +1,21 @@
const Joi = require("joi");
// ========================
// User Schedule Validation
// ========================
const insertUserScheduleSchema = Joi.object({
user_id: Joi.number().integer().required(),
schedule_id: Joi.number().integer().required(),
shift_id: Joi.number().required(),
});
const updateUserScheduleSchema = Joi.object({
user_id: Joi.number().integer().optional(),
schedule_id: Joi.number().integer().optional(),
shift_id: Joi.number().optional()
}).min(1);
module.exports = {
insertUserScheduleSchema,
updateUserScheduleSchema
};