Исправлено отображение дробных цифр: backend/utils/timeCalculator.js modified: frontend/README.md
134 lines
5.3 KiB
JavaScript
134 lines
5.3 KiB
JavaScript
// Утилиты для расчета рабочих часов
|
||
|
||
/**
|
||
* Проверяет, является ли день рабочим (понедельник-пятница)
|
||
* @param {Date} date - Дата для проверки
|
||
* @returns {boolean} true если рабочий день
|
||
*/
|
||
function isWorkingDay(date) {
|
||
const day = date.getDay(); // 0 = воскресенье, 1 = понедельник, ..., 6 = суббота
|
||
return day >= 1 && day <= 5; // понедельник-пятница
|
||
}
|
||
|
||
/**
|
||
* Преобразует время в формате HH:MM в минуты от начала дня
|
||
* @param {string} time - Время в формате HH:MM
|
||
* @returns {number} Минуты от начала дня
|
||
*/
|
||
function timeToMinutes(time) {
|
||
if (!time) return 0;
|
||
const [hours, minutes] = time.split(":").map(Number);
|
||
return hours * 60 + minutes;
|
||
}
|
||
|
||
/**
|
||
* Преобразует минуты от начала дня в формат HH:MM
|
||
* @param {number} minutes - Минуты от начала дня
|
||
* @returns {string} Время в формате HH:MM
|
||
*/
|
||
function minutesToTime(minutes) {
|
||
const hours = Math.floor(minutes / 60);
|
||
const mins = minutes % 60;
|
||
return `${hours.toString().padStart(2, "0")}:${mins
|
||
.toString()
|
||
.padStart(2, "0")}`;
|
||
}
|
||
|
||
/**
|
||
* Рассчитывает рабочие часы между двумя датами/временами
|
||
* Рабочий день: 09:00 - 18:00
|
||
* Обед: 12:00 - 13:00 (не учитывается в расчетах)
|
||
* Только рабочие дни (понедельник-пятница)
|
||
*
|
||
* @param {string} startDate - Дата начала (YYYY-MM-DD)
|
||
* @param {string} startTime - Время начала (HH:MM)
|
||
* @param {string} endDate - Дата окончания (YYYY-MM-DD) или null для открытой записи
|
||
* @param {string} endTime - Время окончания (HH:MM) или null для открытой записи
|
||
* @returns {number} Количество рабочих часов
|
||
*/
|
||
function calculateWorkHours(startDate, startTime, endDate, endTime) {
|
||
const startDateTime = new Date(`${startDate}T${startTime || "09:00"}`);
|
||
const endDateTime =
|
||
endDate && endTime ? new Date(`${endDate}T${endTime}`) : null;
|
||
|
||
// Если нет даты окончания - возвращаем 0 (открытая запись)
|
||
if (!endDateTime) {
|
||
return 0;
|
||
}
|
||
|
||
// Если дата окончания раньше даты начала - ошибка
|
||
if (endDateTime < startDateTime) {
|
||
throw new Error("Дата окончания не может быть раньше даты начала");
|
||
}
|
||
|
||
const WORK_START = 9 * 60; // 09:00 в минутах
|
||
const WORK_END = 18 * 60; // 18:00 в минутах
|
||
const LUNCH_START = 12 * 60; // 12:00 в минутах
|
||
const LUNCH_END = 13 * 60; // 13:00 в минутах
|
||
|
||
let totalMinutes = 0;
|
||
let currentDate = new Date(startDate);
|
||
|
||
// Проходим по каждому дню от начала до окончания
|
||
while (
|
||
currentDate <= (endDateTime ? new Date(endDate) : new Date(startDate))
|
||
) {
|
||
if (isWorkingDay(currentDate)) {
|
||
const dayStart = new Date(currentDate);
|
||
dayStart.setHours(0, 0, 0, 0);
|
||
|
||
const dayEnd = new Date(currentDate);
|
||
dayEnd.setHours(23, 59, 59, 999);
|
||
|
||
// Определяем время начала работы в этот день
|
||
let workStartMinutes = WORK_START;
|
||
if (currentDate.toDateString() === new Date(startDate).toDateString()) {
|
||
// Первый день - берем время начала работы
|
||
const startMinutes =
|
||
startDateTime.getHours() * 60 + startDateTime.getMinutes();
|
||
workStartMinutes = Math.max(startMinutes, WORK_START);
|
||
}
|
||
|
||
// Определяем время окончания работы в этот день
|
||
let workEndMinutes = WORK_END;
|
||
if (
|
||
endDateTime &&
|
||
currentDate.toDateString() === new Date(endDate).toDateString()
|
||
) {
|
||
// Последний день - берем время окончания работы
|
||
const endMinutes =
|
||
endDateTime.getHours() * 60 + endDateTime.getMinutes();
|
||
workEndMinutes = Math.min(endMinutes, WORK_END);
|
||
}
|
||
|
||
// Если время окончания раньше времени начала в этот день - пропускаем
|
||
if (workEndMinutes > workStartMinutes) {
|
||
let dayMinutes = workEndMinutes - workStartMinutes;
|
||
|
||
// Вычитаем обед, если он попадает в рабочий интервал
|
||
if (workStartMinutes < LUNCH_END && workEndMinutes > LUNCH_START) {
|
||
const lunchOverlapStart = Math.max(workStartMinutes, LUNCH_START);
|
||
const lunchOverlapEnd = Math.min(workEndMinutes, LUNCH_END);
|
||
if (lunchOverlapEnd > lunchOverlapStart) {
|
||
dayMinutes -= lunchOverlapEnd - lunchOverlapStart;
|
||
}
|
||
}
|
||
|
||
totalMinutes += Math.max(0, dayMinutes);
|
||
}
|
||
}
|
||
|
||
// Переходим к следующему дню
|
||
currentDate.setDate(currentDate.getDate() + 1);
|
||
}
|
||
|
||
return Math.round((totalMinutes / 60) * 10) / 10; // Возвращаем часы с округлением до 1 знака после запятой
|
||
}
|
||
|
||
module.exports = {
|
||
calculateWorkHours,
|
||
isWorkingDay,
|
||
timeToMinutes,
|
||
minutesToTime,
|
||
};
|