Files
Sheep-Service/api/services/stand.service.js
Rozenrod 04f39da611 Змінено директорії
Додано скрипти CRON
Поліпшено механізм запису стендів та їх редагування
2025-10-27 00:11:18 +02:00

341 lines
13 KiB
JavaScript

const db = require("../config/db");
const Notification = require("../utils/notification.js");
class StandService {
getStand(id) {
return new Promise((res, rej) => {
const sql = `SELECT * FROM stand_list WHERE id = ?`;
db.get(sql, [id], (err, row) => {
if (err) {
console.error(err.message);
return res(false);
}
if (!row) {
console.log({ error: "id not found" });
return res(false);
}
let data = {
"id": Number(row.id),
"title": row.title,
"geo": JSON.parse(row.geo),
"hour_start": Number(row.hour_start),
"hour_end": Number(row.hour_end),
"quantity_sheep": Number(row.quantity_sheep),
"week_days": JSON.parse(row.week_days),
"processing_time": Number(row.processing_time),
"status": row.status == 1 ? true : false,
"updated_at": Number(row.updated_at),
"created_at": Number(row.created_at)
}
return res(data);
});
});
}
getList() {
return new Promise((res, rej) => {
const sql = `
SELECT
*
FROM
stand_list
ORDER BY
id
`;
db.all(sql, (err, rows) => {
if (err) {
console.error(err.message);
return res(false);
} else {
let data = rows.map((row) => {
return {
"id": Number(row.id),
"title": row.title,
"geo": JSON.parse(row.geo),
"hour_start": Number(row.hour_start),
"hour_end": Number(row.hour_end),
"quantity_sheep": Number(row.quantity_sheep),
"week_days": JSON.parse(row.week_days),
"processing_time": Number(row.processing_time),
"status": row.status == 1 ? true : false,
"updated_at": Number(row.updated_at),
"created_at": Number(row.created_at)
}
})
return res(data);
}
});
});
}
createStand(data) {
return new Promise((res, rej) => {
let sql = 'INSERT INTO stand_list(title, geo, hour_start, hour_end, quantity_sheep, week_days, processing_time, status, updated_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
db.run(sql, [
data.title,
JSON.stringify(data.geo || []),
Number(data.hour_start) || 9,
Number(data.hour_end) || 18,
Number(data.quantity_sheep) || 2,
JSON.stringify(data.week_days || [1]),
Number(data.processing_time) || 1,
1,
Math.floor(Date.now()),
Math.floor(Date.now())
], function (err) {
if (err) {
console.error(err.message);
return res(false);
} else {
res({ "status": "ok", "id": this.lastID });
}
});
});
}
updateStand(stand_id, data) {
return new Promise((res, rej) => {
const sql = `
UPDATE stand_list SET
title = COALESCE(?, title),
geo = COALESCE(?, geo),
hour_start = COALESCE(?, hour_start),
hour_end = COALESCE(?, hour_end),
quantity_sheep = COALESCE(?, quantity_sheep),
processing_time = COALESCE(?, processing_time),
week_days = COALESCE(?, week_days),
status = COALESCE(?, status),
updated_at = ?
WHERE id = ?
`;
db.run(sql, [
data.title ?? null,
data.geo !== undefined ? JSON.stringify(data.geo) : null,
data.hour_start !== undefined ? Number(data.hour_start) : null,
data.hour_end !== undefined ? Number(data.hour_end) : null,
data.quantity_sheep !== undefined ? Number(data.quantity_sheep) : null,
data.processing_time !== undefined ? Number(data.processing_time) : null,
data.week_days !== undefined ? JSON.stringify(data.week_days) : null,
data.status !== undefined ? (data.status ? 1 : 0) : null,
Date.now(),
stand_id
], function (err) {
if (err) {
console.error(err.message);
return res(false);
} else if (this.changes === 0) {
return res(false);
} else {
res({ status: "ok", id: stand_id });
}
});
});
}
deleteStand(stand_id) {
return new Promise((res, rej) => {
db.run('DELETE FROM stand_list WHERE id = ?', [stand_id], function (err) {
if (err) {
console.error(err.message);
return res(false);
} else if (this.changes === 0) {
return res(false);
} else {
res({ "status": "ok", "id": stand_id });
}
});
});
}
createSchedule(stand_id) {
return new Promise((res, rej) => {
let date_start;
const MS_DAY = 24 * 60 * 60 * 1000;
// Нормализует timestamp из БД: если в секундах — умножает на 1000
const normalizeTs = (ts) => {
if (!ts) return null;
// если строка — привести к числу
const n = Number(ts);
if (Number.isNaN(n)) return null;
// если кажется секундами (меньше ~1e12) — умножаем
return n < 1e12 ? n * 1000 : n;
};
// Возвращает timestamp (ms) следующего понедельника после ts
const getNextMonday = (ts) => {
let date = new Date(ts);
// нормализуем на начало дня
date.setHours(0, 0, 0, 0);
// всегда переходить к следующему дню (чтобы гарантированно получить следующий понедельник,
// даже если ts уже в понедельник)
date.setDate(date.getDate() + 1);
// пока не понедельник (в JS: 1 — понедельник)
while (date.getDay() !== 1) {
date.setDate(date.getDate() + 1);
}
return date.getTime();
};
// 1. Получаем стенд
db.get(`SELECT * FROM stand_list WHERE id = ?`, [stand_id], (err, stand) => {
if (err) {
console.error(err.message);
return res(false);
}
if (!stand) {
console.log({ error: "id not found" });
return res(false);
}
try {
stand.geo = JSON.parse(stand.geo);
} catch (e) {
stand.geo = stand.geo;
}
try {
stand.week_days = JSON.parse(stand.week_days);
} catch (e) {
stand.week_days = Array.isArray(stand.week_days) ? stand.week_days : [];
}
// 2. Берём последний date из расписания
db.get(
`SELECT MAX(date) AS max_date FROM stand_schedule WHERE stand_id = ?`,
[stand.id],
(err, row) => {
if (err) {
console.error(err.message);
return res(false);
}
const normalized = normalizeTs(row && row.max_date ? row.max_date : null);
if (normalized) {
date_start = getNextMonday(normalized);
} else {
date_start = getNextMonday(Date.now());
}
// 3. Генерация новых записей
// вычисляем количество слотов (целое)
const stand_length = Math.max(0, Math.floor((stand.hour_end - stand.hour_start) / stand.processing_time));
const timestamp = Date.now();
const list = [];
// Ожидается, что stand.week_days — массив чисел 0..6, где 0 -> Понедельник, 6 -> Воскресенье
for (const dayOffsetRaw of stand.week_days) {
const dayOffset = Number(dayOffsetRaw);
if (!Number.isFinite(dayOffset) || dayOffset < 0 || dayOffset > 6) continue;
// dayOffset: 0 => Monday, 6 => Sunday
const stand_date = date_start + (dayOffset * MS_DAY);
for (let i = 0; i < stand_length; i++) {
for (let q = 0; q < (Number(stand.quantity_sheep) || 0); q++) {
list.push([
stand.hour_start + (stand.processing_time * i),
q,
stand_date,
stand.id,
timestamp,
timestamp
]);
}
}
}
if (list.length === 0) {
return res({ status: "empty" });
}
// 4. Массовая вставка
const placeholders = list.map(() => "(?, ?, ?, ?, ?, ?)").join(",");
const values = list.flat();
const insertSQL = `
INSERT INTO stand_schedule(hour, number_sheep, date, stand_id, updated_at, created_at)
VALUES ${placeholders}
`;
db.run(insertSQL, values, function (err) {
if (err) {
console.error(err.message);
return res(false);
}
// Notification.sendStand({
// title: "Додан новий день служіння",
// body: `Стенд «${stand.title}» поповнився, встигніть записатися.`,
// page: `/stand/card/${stand.id}`
// });
res({ status: "ok", inserted: list.length });
});
}
);
});
});
}
getScheduleList(stand_id) {
return new Promise((res, rej) => {
const sql = `
SELECT
ss.*,
s.name AS sheep_name
FROM
stand_schedule AS ss
LEFT JOIN
sheeps AS s
ON
s.id = ss.sheep_id
WHERE
ss.stand_id = ?
AND
date(ss.date / 1000, 'unixepoch') >= date('now')
ORDER BY
ss.id;
`;
db.all(sql, [stand_id], (err, rows) => {
if (err) {
console.error(err.message);
return res(false);
} else {
let data = rows.map((row) => {
return {
"id": Number(row.id),
"stand_id": Number(row.stand_id),
"date": Number(row.date),
"hour": Number(row.hour),
"sheep_id": Number(row.sheep_id),
"sheep_name": row.sheep_name,
"number_sheep": Number(row.number_sheep),
"updated_at": Number(row.updated_at),
"created_at": Number(row.created_at)
}
})
return res(data);
}
});
});
}
getScheduleHistory(id) {
return new Promise((res, rej) => {
return res({ id });
});
}
}
module.exports = new StandService();