fix(slots): update error messages and validation logic for slot creation and updates, including handling of datetime fields and master status
This commit is contained in:
parent
3941377e78
commit
3448bff1a2
@ -2,9 +2,9 @@
|
||||
import { getClientWithToken } from '../apollo/client';
|
||||
import * as GQL from '../types';
|
||||
|
||||
const ERRORS = {
|
||||
CUSTOMER_NOT_FOUND: 'Customer not found',
|
||||
MISSING_TELEGRAM_ID: 'Missing telegram id',
|
||||
export const ERRORS = {
|
||||
CUSTOMER_NOT_FOUND: 'Пользователь не найден',
|
||||
MISSING_TELEGRAM_ID: 'Не указан Telegram ID',
|
||||
};
|
||||
|
||||
type UserProfile = {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { getClientWithToken } from '../apollo/client';
|
||||
import * as GQL from '../types';
|
||||
import { ERRORS as BASE_ERRORS } from './base';
|
||||
import { ERRORS, SlotsService } from './slots';
|
||||
import dayjs from 'dayjs';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../apollo/client');
|
||||
@ -29,9 +31,11 @@ describe('SlotsService', () => {
|
||||
telegramId: 123_456_789,
|
||||
};
|
||||
|
||||
const now = dayjs();
|
||||
|
||||
const mockSlot = {
|
||||
datetime_end: '2024-01-01T11:00:00Z',
|
||||
datetime_start: '2024-01-01T10:00:00Z',
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
documentId: 'slot-123',
|
||||
master: mockCustomer,
|
||||
orders: [],
|
||||
@ -59,11 +63,371 @@ describe('SlotsService', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('createSlot', () => {
|
||||
const mockVariables = {
|
||||
input: {
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
state: GQL.Enum_Slot_State.Open,
|
||||
},
|
||||
};
|
||||
|
||||
const mockMutationResult = {
|
||||
data: {
|
||||
createSlot: mockSlot,
|
||||
},
|
||||
errors: undefined,
|
||||
};
|
||||
|
||||
const mockMasterCustomer = {
|
||||
...mockCustomer,
|
||||
active: true,
|
||||
role: 'master',
|
||||
};
|
||||
|
||||
const mockGetMasterCustomerResult = {
|
||||
data: {
|
||||
customers: [mockMasterCustomer],
|
||||
},
|
||||
};
|
||||
|
||||
it('should successfully create slot when master is active', async () => {
|
||||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve(mockGetMasterCustomerResult);
|
||||
}
|
||||
|
||||
if (query === GQL.GetSlotsDocument) {
|
||||
return Promise.resolve({ data: { slots: [] } }); // нет пересекающихся слотов
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: mockMutate,
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.createSlot({
|
||||
input: {
|
||||
...mockVariables.input,
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||||
});
|
||||
|
||||
it('should throw error when master is not found', async () => {
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve({
|
||||
data: { customers: [] }, // мастер не найден
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: vi.fn(),
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.createSlot(mockVariables);
|
||||
|
||||
await expect(result).rejects.toThrow(BASE_ERRORS.CUSTOMER_NOT_FOUND);
|
||||
});
|
||||
|
||||
it('should throw error when master is not active', async () => {
|
||||
const inactiveMaster = {
|
||||
...mockMasterCustomer,
|
||||
active: false,
|
||||
};
|
||||
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve({
|
||||
data: { customers: [inactiveMaster] },
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: vi.fn(),
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.createSlot(mockVariables);
|
||||
|
||||
await expect(result).rejects.toThrow(ERRORS.INACTIVE_MASTER);
|
||||
});
|
||||
|
||||
it('should throw error when master role is not master', async () => {
|
||||
const nonMasterCustomer = {
|
||||
...mockMasterCustomer,
|
||||
role: 'customer',
|
||||
};
|
||||
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve({
|
||||
data: { customers: [nonMasterCustomer] },
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: vi.fn(),
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.createSlot(mockVariables);
|
||||
|
||||
await expect(result).rejects.toThrow(ERRORS.INACTIVE_MASTER);
|
||||
});
|
||||
|
||||
it('should throw error when datetime_start is missing', async () => {
|
||||
const variablesWithoutStart = {
|
||||
input: {
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
state: GQL.Enum_Slot_State.Open,
|
||||
},
|
||||
};
|
||||
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve(mockGetMasterCustomerResult);
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: vi.fn(),
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.createSlot(variablesWithoutStart);
|
||||
|
||||
await expect(result).rejects.toThrow(ERRORS.MISSING_DATETIME_START);
|
||||
});
|
||||
|
||||
it('should throw error when datetime_end is missing', async () => {
|
||||
const variablesWithoutEnd = {
|
||||
input: {
|
||||
datetime_start: now.toISOString(),
|
||||
state: GQL.Enum_Slot_State.Open,
|
||||
},
|
||||
};
|
||||
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve(mockGetMasterCustomerResult);
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: vi.fn(),
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.createSlot(variablesWithoutEnd);
|
||||
|
||||
await expect(result).rejects.toThrow(ERRORS.MISSING_DATETIME_END);
|
||||
});
|
||||
|
||||
it('should throw error when datetime_end is before datetime_start', async () => {
|
||||
const variablesWithInvalidTime = {
|
||||
input: {
|
||||
datetime_end: now.toISOString(),
|
||||
datetime_start: now.add(6, 'hour').toISOString(),
|
||||
state: GQL.Enum_Slot_State.Open,
|
||||
},
|
||||
};
|
||||
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve(mockGetMasterCustomerResult);
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: vi.fn(),
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.createSlot(variablesWithInvalidTime);
|
||||
|
||||
await expect(result).rejects.toThrow(ERRORS.INVALID_TIME);
|
||||
});
|
||||
|
||||
it('should throw error when datetime_end equals datetime_start', async () => {
|
||||
const variablesWithEqualTime = {
|
||||
input: {
|
||||
datetime_end: now.toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
state: GQL.Enum_Slot_State.Open,
|
||||
},
|
||||
};
|
||||
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve(mockGetMasterCustomerResult);
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: vi.fn(),
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.createSlot(variablesWithEqualTime);
|
||||
|
||||
await expect(result).rejects.toThrow(ERRORS.INVALID_TIME);
|
||||
});
|
||||
|
||||
it('should throw error when slot is created in the past', async () => {
|
||||
const yesterday = dayjs().subtract(1, 'day');
|
||||
|
||||
const variablesWithPastTime = {
|
||||
input: {
|
||||
datetime_end: yesterday.add(1, 'hour').toISOString(),
|
||||
datetime_start: yesterday.toISOString(),
|
||||
state: GQL.Enum_Slot_State.Open,
|
||||
},
|
||||
};
|
||||
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve(mockGetMasterCustomerResult);
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: vi.fn(),
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.createSlot(variablesWithPastTime);
|
||||
|
||||
await expect(result).rejects.toThrow(ERRORS.PAST_SLOT);
|
||||
});
|
||||
|
||||
it('should throw error when time overlaps with other slots', async () => {
|
||||
const overlappingSlot = {
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
documentId: 'slot-456',
|
||||
master: mockMasterCustomer,
|
||||
orders: [],
|
||||
state: GQL.Enum_Slot_State.Open,
|
||||
};
|
||||
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve(mockGetMasterCustomerResult);
|
||||
}
|
||||
|
||||
if (query === GQL.GetSlotsDocument) {
|
||||
return Promise.resolve({
|
||||
data: { slots: [overlappingSlot] },
|
||||
}); // есть пересекающиеся слоты
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: vi.fn(),
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.createSlot(mockVariables);
|
||||
|
||||
await expect(result).rejects.toThrow(ERRORS.OVERLAPPING_TIME);
|
||||
});
|
||||
|
||||
it('should allow creation when time does not overlap with other slots', async () => {
|
||||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve(mockGetMasterCustomerResult);
|
||||
}
|
||||
|
||||
if (query === GQL.GetSlotsDocument) {
|
||||
return Promise.resolve({
|
||||
data: { slots: [] },
|
||||
}); // нет пересекающихся слотов
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: mockMutate,
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
const result = slotsService.createSlot(mockVariables);
|
||||
|
||||
await expect(result).resolves.toBe(mockMutationResult.data);
|
||||
});
|
||||
|
||||
it('should include master documentId in mutation variables', async () => {
|
||||
const mockMutate = vi.fn().mockResolvedValue(mockMutationResult);
|
||||
const mockQuery = vi.fn().mockImplementation(({ query }) => {
|
||||
if (query === GQL.GetCustomerDocument) {
|
||||
return Promise.resolve(mockGetMasterCustomerResult);
|
||||
}
|
||||
|
||||
if (query === GQL.GetSlotsDocument) {
|
||||
return Promise.resolve({ data: { slots: [] } });
|
||||
}
|
||||
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
|
||||
mockGetClientWithToken.mockResolvedValue({
|
||||
mutate: mockMutate,
|
||||
query: mockQuery,
|
||||
});
|
||||
|
||||
await slotsService.createSlot(mockVariables);
|
||||
|
||||
expect(mockMutate).toHaveBeenCalledWith({
|
||||
mutation: GQL.CreateSlotDocument,
|
||||
variables: {
|
||||
...mockVariables,
|
||||
input: {
|
||||
...mockVariables.input,
|
||||
master: mockMasterCustomer.documentId,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateSlot', () => {
|
||||
const mockVariables = {
|
||||
data: {
|
||||
datetime_end: '2024-01-01T11:00:00Z',
|
||||
datetime_start: '2024-01-01T10:00:00Z',
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
state: GQL.Enum_Slot_State.Open,
|
||||
},
|
||||
documentId: 'slot-123',
|
||||
@ -210,13 +574,13 @@ describe('SlotsService', () => {
|
||||
|
||||
const result = slotsService.updateSlot(mockVariables);
|
||||
|
||||
await expect(result).rejects.toThrow('Customer not found');
|
||||
await expect(result).rejects.toThrow(BASE_ERRORS.CUSTOMER_NOT_FOUND);
|
||||
});
|
||||
|
||||
it('should throw error when datetime_start is missing', async () => {
|
||||
const variablesWithMissingStart = {
|
||||
data: {
|
||||
datetime_end: '2024-01-01T11:00:00Z',
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
},
|
||||
documentId: 'slot-123',
|
||||
};
|
||||
@ -253,7 +617,7 @@ describe('SlotsService', () => {
|
||||
it('should throw error when datetime_end is missing', async () => {
|
||||
const variablesWithMissingEnd = {
|
||||
data: {
|
||||
datetime_start: '2024-01-01T10:00:00Z',
|
||||
datetime_start: now.toISOString(),
|
||||
},
|
||||
documentId: 'slot-123',
|
||||
};
|
||||
@ -290,8 +654,8 @@ describe('SlotsService', () => {
|
||||
it('should throw error when datetime_end is before datetime_start', async () => {
|
||||
const variablesWithInvalidTime = {
|
||||
data: {
|
||||
datetime_end: '2024-01-01T10:00:00Z',
|
||||
datetime_start: '2024-01-01T11:00:00Z',
|
||||
datetime_end: now.toISOString(),
|
||||
datetime_start: now.add(6, 'hour').toISOString(),
|
||||
},
|
||||
documentId: 'slot-123',
|
||||
};
|
||||
@ -321,8 +685,8 @@ describe('SlotsService', () => {
|
||||
it('should throw error when datetime_end equals datetime_start', async () => {
|
||||
const variablesWithEqualTime = {
|
||||
data: {
|
||||
datetime_end: '2024-01-01T10:00:00Z',
|
||||
datetime_start: '2024-01-01T10:00:00Z',
|
||||
datetime_end: now.toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
},
|
||||
documentId: 'slot-123',
|
||||
};
|
||||
@ -354,8 +718,8 @@ describe('SlotsService', () => {
|
||||
...mockSlot,
|
||||
orders: [
|
||||
{
|
||||
datetime_end: '2024-01-01T10:30:00Z',
|
||||
datetime_start: '2024-01-01T10:00:00Z',
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
documentId: 'order-123',
|
||||
state: GQL.Enum_Order_State.Scheduled,
|
||||
},
|
||||
@ -391,8 +755,8 @@ describe('SlotsService', () => {
|
||||
...mockSlot,
|
||||
orders: [
|
||||
{
|
||||
datetime_end: '2024-01-01T10:30:00Z',
|
||||
datetime_start: '2024-01-01T10:00:00Z',
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
documentId: 'order-123',
|
||||
state: GQL.Enum_Order_State.Approved,
|
||||
},
|
||||
@ -428,8 +792,8 @@ describe('SlotsService', () => {
|
||||
...mockSlot,
|
||||
orders: [
|
||||
{
|
||||
datetime_end: '2024-01-01T10:30:00Z',
|
||||
datetime_start: '2024-01-01T10:00:00Z',
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
documentId: 'order-123',
|
||||
state: GQL.Enum_Order_State.Completed,
|
||||
},
|
||||
@ -465,8 +829,8 @@ describe('SlotsService', () => {
|
||||
...mockSlot,
|
||||
orders: [
|
||||
{
|
||||
datetime_end: '2024-01-01T10:30:00Z',
|
||||
datetime_start: '2024-01-01T10:00:00Z',
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
documentId: 'order-123',
|
||||
state: GQL.Enum_Order_State.Cancelled,
|
||||
},
|
||||
@ -502,8 +866,8 @@ describe('SlotsService', () => {
|
||||
...mockSlot,
|
||||
orders: [
|
||||
{
|
||||
datetime_end: '2024-01-01T10:30:00Z',
|
||||
datetime_start: '2024-01-01T10:00:00Z',
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
documentId: 'order-123',
|
||||
state: GQL.Enum_Order_State.Draft, // не запрещенное состояние
|
||||
},
|
||||
@ -541,8 +905,8 @@ describe('SlotsService', () => {
|
||||
|
||||
it('should throw error when time overlaps with other slots', async () => {
|
||||
const overlappingSlot = {
|
||||
datetime_end: '2024-01-01T11:30:00Z',
|
||||
datetime_start: '2024-01-01T10:30:00Z',
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
documentId: 'slot-456',
|
||||
master: mockCustomer,
|
||||
orders: [],
|
||||
@ -651,8 +1015,8 @@ describe('SlotsService', () => {
|
||||
...mockSlot,
|
||||
orders: [
|
||||
{
|
||||
datetime_end: '2024-01-01T11:00:00Z',
|
||||
datetime_start: '2024-01-01T10:00:00Z',
|
||||
datetime_end: now.add(6, 'hour').toISOString(),
|
||||
datetime_start: now.toISOString(),
|
||||
documentId: 'order-123',
|
||||
state: GQL.Enum_Order_State.Scheduled,
|
||||
},
|
||||
|
||||
@ -1,21 +1,23 @@
|
||||
/* eslint-disable canonical/id-match */
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { getClientWithToken } from '../apollo/client';
|
||||
import * as GQL from '../types';
|
||||
import { BaseService } from './base';
|
||||
import { ServicesService } from './services';
|
||||
import { type VariablesOf } from '@graphql-typed-document-node/core';
|
||||
import { getMinutes } from '@repo/utils/datetime-format';
|
||||
import { getMinutes, isBeforeToday } from '@repo/utils/datetime-format';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const ERRORS = {
|
||||
HAS_ORDERS: 'Слот имеет активные заказы',
|
||||
INACTIVE_MASTER: 'Пользователь не является активным или мастером',
|
||||
INVALID_TIME: 'Некорректное время',
|
||||
MASTER_NOT_FOUND: 'Мастер не найден',
|
||||
MISSING_DATETIME_END: 'Не указана дата окончания',
|
||||
MISSING_DATETIME_START: 'Не указана дата начала',
|
||||
MISSING_SERVICE_ID: 'Не указана услуга',
|
||||
NO_PERMISSION: 'Нет доступа',
|
||||
OVERLAPPING_TIME: 'Время пересекается с другими слотами',
|
||||
PAST_SLOT: 'Нельзя создать слот в прошлом',
|
||||
SERVICE_NOT_FOUND: 'Слот не найден',
|
||||
SLOT_NOT_FOUND: 'Слот не найден',
|
||||
};
|
||||
@ -29,6 +31,8 @@ const FORBIDDEN_ORDER_STATES: GQL.Enum_Order_State[] = [
|
||||
|
||||
export class SlotsService extends BaseService {
|
||||
async createSlot(variables: VariablesOf<typeof GQL.CreateSlotDocument>) {
|
||||
await this.checkCreate(variables);
|
||||
|
||||
const { customer } = await this._getUser();
|
||||
|
||||
const { mutate } = await getClientWithToken();
|
||||
@ -51,7 +55,7 @@ export class SlotsService extends BaseService {
|
||||
}
|
||||
|
||||
async deleteSlot(variables: VariablesOf<typeof GQL.DeleteSlotDocument>) {
|
||||
await this.checkPermission(variables);
|
||||
await this.checkUpdatePermission(variables);
|
||||
|
||||
const { slot } = await this.getSlot({ documentId: variables.documentId });
|
||||
|
||||
@ -161,8 +165,8 @@ export class SlotsService extends BaseService {
|
||||
}
|
||||
|
||||
async updateSlot(variables: VariablesOf<typeof GQL.UpdateSlotDocument>) {
|
||||
await this.checkPermission(variables);
|
||||
await this.checkIsTimeChanging(variables);
|
||||
await this.checkUpdatePermission(variables);
|
||||
await this.checkUpdateIsTimeChanging(variables);
|
||||
|
||||
const { mutate } = await getClientWithToken();
|
||||
|
||||
@ -177,13 +181,54 @@ export class SlotsService extends BaseService {
|
||||
return mutationResult.data;
|
||||
}
|
||||
|
||||
private async checkIsTimeChanging(variables: VariablesOf<typeof GQL.UpdateSlotDocument>) {
|
||||
private async checkCreate(variables: VariablesOf<typeof GQL.CreateSlotDocument>) {
|
||||
const { datetime_end, datetime_start } = variables.input;
|
||||
|
||||
if (!datetime_start) throw new Error(ERRORS.MISSING_DATETIME_START);
|
||||
if (!datetime_end) throw new Error(ERRORS.MISSING_DATETIME_END);
|
||||
|
||||
// Проверка, что мастер существует и активен
|
||||
const { customer: masterEntity } = await this._getUser();
|
||||
|
||||
if (!masterEntity) throw new Error(ERRORS.MASTER_NOT_FOUND);
|
||||
|
||||
if (!masterEntity?.active || masterEntity.role !== 'master') {
|
||||
throw new Error(ERRORS.INACTIVE_MASTER);
|
||||
}
|
||||
|
||||
// Проверка, что слот не создаётся в прошлом
|
||||
if (datetime_start && isBeforeToday(datetime_start)) {
|
||||
throw new Error(ERRORS.PAST_SLOT);
|
||||
}
|
||||
|
||||
// Проверка валидности времени
|
||||
if (!datetime_start || !datetime_end) {
|
||||
throw new Error(ERRORS.INVALID_TIME);
|
||||
}
|
||||
|
||||
if (new Date(datetime_end) <= new Date(datetime_start)) {
|
||||
throw new Error(ERRORS.INVALID_TIME);
|
||||
}
|
||||
|
||||
const overlappingEntities = await this.getSlots({
|
||||
filters: {
|
||||
datetime_end: { gt: datetime_start },
|
||||
datetime_start: { lt: datetime_end },
|
||||
master: { telegramId: { eq: this._user.telegramId } },
|
||||
},
|
||||
});
|
||||
|
||||
if (overlappingEntities?.slots?.length) {
|
||||
throw new Error(ERRORS.OVERLAPPING_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
private async checkUpdateIsTimeChanging(variables: VariablesOf<typeof GQL.UpdateSlotDocument>) {
|
||||
const { slot } = await this.getSlot({ documentId: variables.documentId });
|
||||
|
||||
if (!slot) throw new Error(ERRORS.SLOT_NOT_FOUND);
|
||||
|
||||
const datetime_start = variables.data.datetime_start;
|
||||
const datetime_end = variables.data.datetime_end;
|
||||
const { datetime_end, datetime_start } = variables.data;
|
||||
|
||||
const isTimeChanging = datetime_start || datetime_end;
|
||||
|
||||
@ -213,6 +258,7 @@ export class SlotsService extends BaseService {
|
||||
datetime_end: { gt: datetime_start },
|
||||
datetime_start: { lt: datetime_end },
|
||||
documentId: { not: { eq: documentId } },
|
||||
master: { telegramId: { eq: this._user.telegramId } },
|
||||
},
|
||||
});
|
||||
|
||||
@ -221,7 +267,7 @@ export class SlotsService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
private async checkPermission(
|
||||
private async checkUpdatePermission(
|
||||
variables: Pick<VariablesOf<typeof GQL.GetSlotDocument>, 'documentId'>,
|
||||
) {
|
||||
const { customer } = await this._getUser();
|
||||
|
||||
@ -92,10 +92,10 @@ export function getTimeZoneLabel(tz: string = DEFAULT_TZ): string {
|
||||
}
|
||||
|
||||
export function isBeforeToday(date: Date | string) {
|
||||
const nowUtc = dayjs().tz(DEFAULT_TZ);
|
||||
const inputDateUtc = dayjs(date).tz(DEFAULT_TZ);
|
||||
const now = dayjs().tz(DEFAULT_TZ);
|
||||
const inputDate = dayjs(date).tz(DEFAULT_TZ);
|
||||
|
||||
return inputDateUtc.isBefore(nowUtc, 'day');
|
||||
return inputDate.isBefore(now, 'day');
|
||||
}
|
||||
|
||||
export function isTodayOrAfter(date: Date | string) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user