From 456f9b071c8d99564c05283be96f3c5872afb07f Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Thu, 17 Jul 2025 14:11:06 +0300 Subject: [PATCH] feat: refactor order and slot lifecycles to use datetime fields for time validation --- .../order/content-types/order/lifecycles.ts | 32 +++++----- src/api/order/content-types/order/schema.json | 8 +++ src/api/slot/content-types/slot/lifecycles.ts | 60 +++++++------------ src/api/slot/content-types/slot/schema.json | 8 +++ types/generated/contentTypes.d.ts | 4 ++ 5 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/api/order/content-types/order/lifecycles.ts b/src/api/order/content-types/order/lifecycles.ts index 1687369..50307cb 100644 --- a/src/api/order/content-types/order/lifecycles.ts +++ b/src/api/order/content-types/order/lifecycles.ts @@ -12,14 +12,10 @@ const ERR_MISSING_CLIENT = 'Не указан клиент'; const ERR_MISSING_SLOT = 'Не указан слот'; const ERR_MISSING_SERVICE = 'Не указан сервис'; -function timeToDate(time: string) { - return new Date(`1970-01-01T${time}Z`); -} - export default { async beforeCreate(event) { const { data } = event.params; - const { time_start, time_end, client, services } = data; + const { datetime_start, datetime_end, client, services } = data; const clientId = extractId(client); const slotId = extractId(data.slot); @@ -29,10 +25,10 @@ export default { if (!extractId(services)) throw new Error(ERR_MISSING_SERVICE); // Проверка корректности времени заказа. - if (!time_start || !time_end) { + if (!datetime_start || !datetime_end) { throw new Error(ERR_MISSING_TIME); } - if (timeToDate(time_start) >= timeToDate(time_end)) { + if (new Date(datetime_start) >= new Date(datetime_end)) { throw new Error(ERR_INVALID_TIME); } @@ -96,8 +92,8 @@ export default { .findMany({ where: { documentId: { $ne: data.documentId }, - time_start: { $lt: time_end }, - time_end: { $gt: time_start }, + datetime_start: { $lt: datetime_end }, + datetime_end: { $gt: datetime_start }, slot: { id: { $eq: slotId }, }, @@ -116,15 +112,15 @@ export default { async beforeUpdate(event) { const { data, where } = event.params; const { id: entityId } = where; - const { time_start, time_end, state } = data; + const { datetime_start, datetime_end, state } = data; const existingOrder = await strapi.db.query('api::order.order').findOne({ where: { id: entityId }, - select: ['documentId', 'time_start', 'time_end'], + select: ['documentId', 'datetime_start', 'datetime_end'], populate: ['slot', 'client'], }); - if (state && !time_start && !time_end) { + if (state && !datetime_start && !datetime_end) { if (state === 'completed') { const clientId = extractId(existingOrder.client); @@ -148,17 +144,17 @@ export default { } if ( - existingOrder.time_start === time_start && - existingOrder.time_end === time_end + existingOrder.datetime_start === datetime_start && + existingOrder.datetime_end === datetime_end ) { return; } - if (!time_start || !time_end) { + if (!datetime_start || !datetime_end) { throw new Error(ERR_INVALID_TIME); } - if (timeToDate(time_start) >= timeToDate(time_end)) { + if (new Date(datetime_start) >= new Date(datetime_end)) { throw new Error(ERR_INVALID_TIME); } @@ -173,8 +169,8 @@ export default { .findMany({ where: { id: { $ne: entityId }, - time_start: { $lt: time_end }, - time_end: { $gt: time_start }, + datetime_start: { $lt: datetime_end }, + datetime_end: { $gt: datetime_start }, slot: { documentId: { $eq: slotId }, }, diff --git a/src/api/order/content-types/order/schema.json b/src/api/order/content-types/order/schema.json index 00bd675..b736647 100644 --- a/src/api/order/content-types/order/schema.json +++ b/src/api/order/content-types/order/schema.json @@ -62,6 +62,14 @@ "relation": "manyToMany", "target": "api::service.service", "inversedBy": "orders" + }, + "datetime_start": { + "type": "datetime", + "required": true + }, + "datetime_end": { + "type": "datetime", + "required": true } } } diff --git a/src/api/slot/content-types/slot/lifecycles.ts b/src/api/slot/content-types/slot/lifecycles.ts index 65d8286..f5624b1 100644 --- a/src/api/slot/content-types/slot/lifecycles.ts +++ b/src/api/slot/content-types/slot/lifecycles.ts @@ -17,14 +17,10 @@ const ERR_INVALID_MASTER = 'Некорректный мастер'; const ERR_PAST_SLOT = 'Нельзя создать слот в прошлом'; const ERR_SLOT_HAS_ORDERS = 'Нельзя удалить слот с активными заказами'; -function timeToDate(date, time) { - return new Date(`${date}T${time}:00Z`); -} - export default { async beforeCreate(event) { const { data } = event.params; - const { master, date, time_start, time_end } = data; + const { master, datetime_start, datetime_end } = data; // Проверка, что мастер существует и активен const masterId = extractId(master); @@ -39,70 +35,63 @@ export default { } // Проверка, что слот не создаётся в прошлом - if (date && time_start) { - const slotDate = timeToDate(date, time_start); - if (slotDate < new Date()) { - throw new Error(ERR_PAST_SLOT); - } + if (datetime_start && new Date(datetime_start) < new Date()) { + throw new Error(ERR_PAST_SLOT); } - // --- Существующие проверки времени и пересечений --- - if (!time_start || !time_end) { - throw new Error('Некорректное время'); + // Проверка валидности времени + if (!datetime_start || !datetime_end) { + throw new Error(ERR_INVALID_TIME); } - if (timeToDate(date, time_start) >= timeToDate(date, time_end)) { - throw new Error('Некорректное время'); + if (new Date(datetime_start) >= new Date(datetime_end)) { + throw new Error(ERR_INVALID_TIME); } const overlappingEntities = await strapi.db .query('api::slot.slot') .findMany({ where: { - date, documentId: { $ne: data.documentId }, - time_start: { $lt: time_end }, - time_end: { $gt: time_start }, + datetime_start: { $lt: datetime_end }, + datetime_end: { $gt: datetime_start }, master: masterId, }, }); if (overlappingEntities.length > 0) { - throw new Error('Время пересекается с другими слотами'); + throw new Error(ERR_OVERLAPPING_TIME); } }, async beforeUpdate(event) { const { data, where } = event.params; const { id: entityId } = where; - // Если меняется хотя бы одно из полей времени или дата - const isTimeChange = - 'time_start' in data || 'time_end' in data || 'date' in data; + // Если меняется хотя бы одно из полей времени + const isTimeChange = 'datetime_start' in data || 'datetime_end' in data; if (isTimeChange) { - let date = data.date; - let time_start = data.time_start; - let time_end = data.time_end; + let datetime_start = data.datetime_start; + let datetime_end = data.datetime_end; // Подтянуть недостающие значения из существующего слота const existingSlot = await strapi.db.query('api::slot.slot').findOne({ where: { id: entityId }, - select: ['date', 'time_start', 'time_end'], + select: ['datetime_start', 'datetime_end'], }); - if (!date) date = existingSlot?.date; - if (!time_start) time_start = existingSlot?.time_start; - if (!time_end) time_end = existingSlot?.time_end; + if (!datetime_start) datetime_start = existingSlot?.datetime_start; + if (!datetime_end) datetime_end = existingSlot?.datetime_end; // Проверка: оба времени должны быть определены - if (!time_start || !time_end) { + if (!datetime_start || !datetime_end) { throw new Error(ERR_INVALID_TIME); } // Проверка валидности времени - if (timeToDate(date, time_start) >= timeToDate(date, time_end)) { + if (new Date(datetime_start) >= new Date(datetime_end)) { throw new Error(ERR_INVALID_TIME); } const existingEntity = await strapi.db.query('api::slot.slot').findOne({ where: { id: entityId }, - select: ['date', 'documentId'], + select: ['documentId'], populate: ['orders'], }); @@ -120,19 +109,16 @@ export default { } const { documentId } = existingEntity; - const overlappingEntities = await strapi.db .query('api::slot.slot') .findMany({ where: { - date, id: { $ne: entityId }, documentId: { $ne: documentId }, - time_start: { $lt: time_end }, - time_end: { $gt: time_start }, + datetime_start: { $lt: datetime_end }, + datetime_end: { $gt: datetime_start }, }, }); - if (overlappingEntities.length > 0) { throw new Error(ERR_OVERLAPPING_TIME); } diff --git a/src/api/slot/content-types/slot/schema.json b/src/api/slot/content-types/slot/schema.json index fa6175b..b3284b8 100644 --- a/src/api/slot/content-types/slot/schema.json +++ b/src/api/slot/content-types/slot/schema.json @@ -42,6 +42,14 @@ }, "date": { "type": "date" + }, + "datetime_end": { + "type": "datetime", + "required": true + }, + "datetime_start": { + "type": "datetime", + "required": true } } } diff --git a/types/generated/contentTypes.d.ts b/types/generated/contentTypes.d.ts index cca6aa0..f06b865 100644 --- a/types/generated/contentTypes.d.ts +++ b/types/generated/contentTypes.d.ts @@ -470,6 +470,8 @@ export interface ApiOrderOrder extends Struct.CollectionTypeSchema { createdAt: Schema.Attribute.DateTime; createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; + datetime_end: Schema.Attribute.DateTime & Schema.Attribute.Required; + datetime_start: Schema.Attribute.DateTime & Schema.Attribute.Required; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation<'oneToMany', 'api::order.order'> & Schema.Attribute.Private; @@ -578,6 +580,8 @@ export interface ApiSlotSlot extends Struct.CollectionTypeSchema { createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; date: Schema.Attribute.Date; + datetime_end: Schema.Attribute.DateTime & Schema.Attribute.Required; + datetime_start: Schema.Attribute.DateTime & Schema.Attribute.Required; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation<'oneToMany', 'api::slot.slot'> & Schema.Attribute.Private;