add: filter schedule data harian/mingguan/bulanan

This commit is contained in:
Muhammad Afif
2025-10-22 10:21:27 +07:00
parent 9c08e51d31
commit 06582f51be
2 changed files with 125 additions and 31 deletions

View File

@@ -31,11 +31,11 @@ const poolPromise = new sql.ConnectionPool(config)
async function checkConnection() {
try {
const pool = await poolPromise;
await pool.request().query('SELECT 1 AS isConnected');
console.log('🔍 SQL Server terkoneksi dengan baik');
await pool.request().query("SELECT 1 AS isConnected");
console.log("🔍 SQL Server terkoneksi dengan baik");
return true;
} catch (error) {
console.error('⚠️ Gagal cek koneksi SQL Server:', error);
console.error("⚠️ Gagal cek koneksi SQL Server:", error);
return false;
}
}
@@ -58,13 +58,16 @@ async function query(text, params = []) {
return request.query(sqlText);
}
/**
* Validasi tanggal
*/
function isValidDate(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 = []) {
let whereConditions = [];
@@ -76,7 +79,9 @@ function buildFilterQuery(filterQuery = [], fixedParams = []) {
switch (f.type) {
case "string":
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;
case "number":
@@ -89,10 +94,9 @@ function buildFilterQuery(filterQuery = [], fixedParams = []) {
whereConditions.push(`${f.column} = $${queryParams.length}`);
break;
case 'between':
case "between":
if (Array.isArray(f.param) && f.param.length === 2) {
const from = f.param[0];
const to = f.param[1];
const [from, to] = f.param;
if (isValidDate(from) && isValidDate(to)) {
queryParams.push(from);
queryParams.push(to);
@@ -112,7 +116,7 @@ function buildFilterQuery(filterQuery = [], fixedParams = []) {
* Build OR ILIKE (SQL Server pakai LIKE + COLLATE)
*/
function buildStringOrIlike(columnParam, criteria, fixedParams = []) {
if (!criteria) return { whereClause: "", whereParam: fixedParams };
if (!criteria) return { whereOrConditions: "", whereParamOr: fixedParams };
let orStringConditions = [];
let queryParams = [...fixedParams];
@@ -120,7 +124,9 @@ function buildStringOrIlike(columnParam, criteria, fixedParams = []) {
columnParam.forEach((column) => {
if (!column) return;
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
@@ -130,12 +136,60 @@ function buildStringOrIlike(columnParam, criteria, fixedParams = []) {
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
*/
function buildDynamicUpdate(table, data, where) {
data.updated_by = data.userId
data.updated_by = data.userId;
delete data.userId;
const setParts = [];
@@ -153,7 +207,6 @@ function buildDynamicUpdate(table, data, where) {
throw new Error("Tidak ada kolom untuk diupdate");
}
// updated_at otomatis pakai CURRENT_TIMESTAMP
setParts.push(`updated_at = CURRENT_TIMESTAMP`);
const whereParts = [];
@@ -175,9 +228,8 @@ function buildDynamicUpdate(table, data, where) {
* Build dynamic INSERT
*/
function buildDynamicInsert(table, data) {
data.created_by = data.userId
data.updated_by = data.userId
data.created_by = data.userId;
data.updated_by = data.userId;
delete data.userId;
const columns = [];
@@ -197,7 +249,6 @@ function buildDynamicInsert(table, data) {
throw new Error("Tidak ada kolom untuk diinsert");
}
// created_at & updated_at otomatis
columns.push("created_at", "updated_at");
placeholders.push("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP");
@@ -238,6 +289,7 @@ module.exports = {
checkConnection,
query,
buildFilterQuery,
buildDateFilter,
buildStringOrIlike,
buildDynamicInsert,
buildDynamicUpdate,

View File

@@ -1,7 +1,11 @@
const pool = require("../config");
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 = {}) => {
let queryParams = [];
@@ -18,11 +22,51 @@ const getAllScheduleDb = async (searchParams = {}) => {
if (whereParamOr) queryParams = whereParamOr;
const { whereConditions, whereParamAnd } = pool.buildFilterQuery(
[{ column: "a.schedule_date", param: searchParams.name, type: "date" }],
[
{
column: "a.schedule_date",
param: searchParams.name,
type: "date",
},
],
queryParams
);
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 = `
SELECT
COUNT(*) OVER() AS total_data,
@@ -32,14 +76,13 @@ const getAllScheduleDb = async (searchParams = {}) => {
b.end_time
FROM schedule a
LEFT JOIN m_shift b ON a.shift_id = b.shift_id
WHERE a.deleted_at IS NULL
${whereConditions.length > 0 ? ` AND ${whereConditions.join(" AND ")}` : ""}
${whereOrConditions ? ` ${whereOrConditions}` : ""}
${whereClause}
ORDER BY a.schedule_id ASC
${searchParams.limit ? `OFFSET $2 * $1 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 total =
result?.recordset?.length > 0
? parseInt(result.recordset[0].total_data, 10)
@@ -48,6 +91,7 @@ const getAllScheduleDb = async (searchParams = {}) => {
return { data: result.recordset, total };
};
// Get by ID
const getScheduleByIdDb = async (id) => {
const queryText = `
SELECT
@@ -63,8 +107,9 @@ const getScheduleByIdDb = async (id) => {
return result.recordset?.[0] || null;
};
// Insert (bisa multi hari)
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 = [];
for (let i = 0; i <= nextDays; i++) {
@@ -72,11 +117,7 @@ const insertScheduleDb = async (store) => {
nextDate.setDate(nextDate.getDate() + i);
const formatted = formattedDate(nextDate);
const newStore = {
...store,
schedule_date: formatted,
};
const newStore = { ...store, schedule_date: formatted };
delete newStore.next_day;
const { query: queryText, values } = pool.buildDynamicInsert("schedule", newStore);
@@ -92,6 +133,7 @@ const insertScheduleDb = async (store) => {
return insertedRecords;
};
// Update
const updateScheduleDb = async (id, data) => {
const store = { ...data };
const whereData = { schedule_id: id };
@@ -106,7 +148,7 @@ const updateScheduleDb = async (id, data) => {
return getScheduleByIdDb(id);
};
// Soft delete schedule
// Soft delete
const deleteScheduleDb = async (id, deletedBy) => {
const queryText = `
UPDATE schedule