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 && normalized > Date.now()) { 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); } let text = [ `Стенд «${stand.title}» отримав новий графік. Можна сміливо записуватися 🙂`, `Для «${stand.title}» відкрито новий розклад. Хто планував — саме час.`, `Новий графік для «${stand.title}» вже доступний. Обирайте зручний час 👍`, `Стенд «${stand.title}» оновив розклад. Запис розпочато.`, `З’явилися нові дати у «${stand.title}». Встигніть обрати свою 😉`, `«${stand.title}» відкрив новий період запису. Плануємо заздалегідь 🙂`, `Оновлення для «${stand.title}». Додано новий графік.`, `Новий розклад для «${stand.title}» вже чекає на охочих 📋`, `Стенд «${stand.title}» додав нові години для запису ⏰`, `Графік «${stand.title}» поповнено. Можна бронювати час 😊`, `У «${stand.title}» з’явилися нові можливості для запису`, `Свіжий графік для «${stand.title}» уже доступний 🚀` ]; let randomMessage = text[Math.floor(Math.random() * text.length)]; Notification.sendStand({ title: "Додано новий день служіння", body: randomMessage, 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', 'localtime') >= date('now', 'localtime') 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();