From ce79d289f3b6b0895c7f76ca6e52320c03463e03 Mon Sep 17 00:00:00 2001 From: aid-orchestrator Date: Wed, 22 Apr 2026 05:46:54 +0000 Subject: [PATCH] feat: add generated code --- backend/prisma/schema.prisma | 48 +++++++ backend/src/app.module.ts | 15 ++ .../change-equipment-status.controller.ts | 130 ++++++++++++++++++ .../change-equipment-status.module.ts | 10 ++ .../change-equipment-status.service.ts | 69 ++++++++++ ...hange-equipment-status-list-request.dto.ts | 15 ++ ...ange-equipment-status-list-response.dto.ts | 10 ++ .../dto/create-change-equipment-status.dto.ts | 30 ++++ .../dto/update-change-equipment-status.dto.ts | 36 +++++ backend/src/common/dto/page-info.dto.ts | 15 ++ backend/src/common/dto/page-request.dto.ts | 16 +++ .../src/equipment/dto/create-equipment.dto.ts | 32 +++++ .../dto/equipment-list-request.dto.ts | 15 ++ .../dto/equipment-list-response.dto.ts | 10 ++ .../src/equipment/dto/update-equipment.dto.ts | 36 +++++ backend/src/equipment/equipment.controller.ts | 98 +++++++++++++ backend/src/equipment/equipment.module.ts | 10 ++ backend/src/equipment/equipment.service.ts | 41 ++++++ frontend/src/App.tsx | 36 +++++ frontend/src/main.tsx | 19 +++ .../ChangeEquipmentStatusCreate.tsx | 25 ++++ .../ChangeEquipmentStatusEdit.tsx | 25 ++++ .../ChangeEquipmentStatusList.tsx | 23 ++++ .../ChangeEquipmentStatusShow.tsx | 17 +++ .../change-equipment-status/index.ts | 4 + .../resources/equipment/EquipmentCreate.tsx | 25 ++++ .../src/resources/equipment/EquipmentEdit.tsx | 25 ++++ .../src/resources/equipment/EquipmentList.tsx | 21 +++ .../src/resources/equipment/EquipmentShow.tsx | 17 +++ .../equipment/EquipmentStatusField.tsx | 15 ++ frontend/src/resources/equipment/index.ts | 4 + 31 files changed, 892 insertions(+) create mode 100644 backend/prisma/schema.prisma create mode 100644 backend/src/app.module.ts create mode 100644 backend/src/change-equipment-status/change-equipment-status.controller.ts create mode 100644 backend/src/change-equipment-status/change-equipment-status.module.ts create mode 100644 backend/src/change-equipment-status/change-equipment-status.service.ts create mode 100644 backend/src/change-equipment-status/dto/change-equipment-status-list-request.dto.ts create mode 100644 backend/src/change-equipment-status/dto/change-equipment-status-list-response.dto.ts create mode 100644 backend/src/change-equipment-status/dto/create-change-equipment-status.dto.ts create mode 100644 backend/src/change-equipment-status/dto/update-change-equipment-status.dto.ts create mode 100644 backend/src/common/dto/page-info.dto.ts create mode 100644 backend/src/common/dto/page-request.dto.ts create mode 100644 backend/src/equipment/dto/create-equipment.dto.ts create mode 100644 backend/src/equipment/dto/equipment-list-request.dto.ts create mode 100644 backend/src/equipment/dto/equipment-list-response.dto.ts create mode 100644 backend/src/equipment/dto/update-equipment.dto.ts create mode 100644 backend/src/equipment/equipment.controller.ts create mode 100644 backend/src/equipment/equipment.module.ts create mode 100644 backend/src/equipment/equipment.service.ts create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/resources/change-equipment-status/ChangeEquipmentStatusCreate.tsx create mode 100644 frontend/src/resources/change-equipment-status/ChangeEquipmentStatusEdit.tsx create mode 100644 frontend/src/resources/change-equipment-status/ChangeEquipmentStatusList.tsx create mode 100644 frontend/src/resources/change-equipment-status/ChangeEquipmentStatusShow.tsx create mode 100644 frontend/src/resources/change-equipment-status/index.ts create mode 100644 frontend/src/resources/equipment/EquipmentCreate.tsx create mode 100644 frontend/src/resources/equipment/EquipmentEdit.tsx create mode 100644 frontend/src/resources/equipment/EquipmentList.tsx create mode 100644 frontend/src/resources/equipment/EquipmentShow.tsx create mode 100644 frontend/src/resources/equipment/EquipmentStatusField.tsx create mode 100644 frontend/src/resources/equipment/index.ts diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 0000000..8ac5a77 --- /dev/null +++ b/backend/prisma/schema.prisma @@ -0,0 +1,48 @@ +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} + +enum EquipmentStatus { + Active + Repair + Reserve + WriteOff +} + +model Equipment { + id String @id @default(uuid()) + name String + serialNumber String + dateOfInspection DateTime? + commissionedAt DateTime? + status EquipmentStatus + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + changeEquipmentStatuses ChangeEquipmentStatus[] + + @@map("equipment") +} + +model ChangeEquipmentStatus { + id String @id @default(uuid()) + equipmentId String + newStatus EquipmentStatus + number String? + date DateTime + responsible String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + equipment Equipment @relation(fields: [equipmentId], references: [id]) + + @@unique([equipmentId, newStatus]) + @@map("change_equipment_status") +} diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts new file mode 100644 index 0000000..c3b97ab --- /dev/null +++ b/backend/src/app.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { EquipmentModule } from './equipment/equipment.module'; +import { ChangeEquipmentStatusModule } from './change-equipment-status/change-equipment-status.module'; +import { AuthModule } from './auth/auth.module'; +import { PrismaService } from './prisma/prisma.service'; + +@Module({ + imports: [ + EquipmentModule, + ChangeEquipmentStatusModule, + AuthModule, + ], + providers: [PrismaService], +}) +export class AppModule {} \ No newline at end of file diff --git a/backend/src/change-equipment-status/change-equipment-status.controller.ts b/backend/src/change-equipment-status/change-equipment-status.controller.ts new file mode 100644 index 0000000..1ca7261 --- /dev/null +++ b/backend/src/change-equipment-status/change-equipment-status.controller.ts @@ -0,0 +1,130 @@ +import { Controller, Get, Post, Body, Put, Param, Delete, Query, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ChangeEquipmentStatusService } from './change-equipment-status.service'; +import { CreateChangeEquipmentStatusDto } from './dto/create-change-equipment-status.dto'; +import { UpdateChangeEquipmentStatusDto } from './dto/update-change-equipment-status.dto'; +import { ChangeEquipmentStatusListRequestDto } from './dto/change-equipment-status-list-request.dto'; +import { ChangeEquipmentStatusListResponseDto } from './dto/change-equipment-status-list-response.dto'; +import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { EquipmentStatus } from '@prisma/client'; + +@ApiTags('change-equipment-status') +@ApiBearerAuth() +@UseGuards(JwtAuthGuard) +@Controller('change-equipment-status') +export class ChangeEquipmentStatusController { + constructor(private readonly changeEquipmentStatusService: ChangeEquipmentStatusService) {} + + @Post('page') + @ApiOperation({ summary: 'Постраничный список документов изменения статуса с фильтрацией' }) + @ApiResponse({ + status: 200, + description: 'Список документов изменения статуса', + type: ChangeEquipmentStatusListResponseDto + }) + async findAll( + @Body() dto: ChangeEquipmentStatusListRequestDto, + ) { + const { page, filter } = dto; + const skip = page?.page ? page.page * page.size : 0; + const take = page?.size || 25; + + const where = filter ? this.buildWhereClause(filter) : {}; + + const [content, total] = await Promise.all([ + this.changeEquipmentStatusService.findAll({ skip, take, where }), + this.changeEquipmentStatusService.count(where) + ]); + + return { + content, + pageInfo: { + page: page?.page || 0, + size: take, + total, + totalPages: Math.ceil(total / take), + }, + }; + } + + @Get(':equipmentId/:newStatus') + @ApiOperation({ summary: 'Получить документ изменения статуса по ключу' }) + @ApiResponse({ status: 200, description: 'Документ изменения статуса найден' }) + @ApiResponse({ status: 404, description: 'Документ изменения статуса не найден' }) + findOne( + @Param('equipmentId') equipmentId: string, + @Param('newStatus') newStatus: EquipmentStatus, + ) { + return this.changeEquipmentStatusService.findOne({ + equipmentId_newStatus: { equipmentId, newStatus } + }); + } + + @Post() + @ApiOperation({ summary: 'Создать документ изменения статуса' }) + @ApiResponse({ status: 201, description: 'Документ изменения статуса создан' }) + create(@Body() dto: CreateChangeEquipmentStatusDto) { + return this.changeEquipmentStatusService.create(dto); + } + + @Put(':equipmentId/:newStatus') + @ApiOperation({ summary: 'Обновить документ изменения статуса' }) + @ApiResponse({ status: 200, description: 'Документ изменения статуса обновлен' }) + @ApiResponse({ status: 404, description: 'Документ изменения статуса не найден' }) + update( + @Param('equipmentId') equipmentId: string, + @Param('newStatus') newStatus: EquipmentStatus, + @Body() dto: UpdateChangeEquipmentStatusDto, + ) { + return this.changeEquipmentStatusService.update({ + where: { equipmentId_newStatus: { equipmentId, newStatus } }, + data: dto + }); + } + + @Delete(':equipmentId/:newStatus') + @ApiOperation({ summary: 'Удалить документ изменения статуса' }) + @ApiResponse({ status: 200, description: 'Документ изменения статуса удален' }) + @ApiResponse({ status: 404, description: 'Документ изменения статуса не найден' }) + remove( + @Param('equipmentId') equipmentId: string, + @Param('newStatus') newStatus: EquipmentStatus, + ) { + return this.changeEquipmentStatusService.remove({ + equipmentId_newStatus: { equipmentId, newStatus } + }); + } + + private buildWhereClause(filter: any): any { + // Basic implementation - can be extended based on filter structure + const where: any = {}; + + if (filter.equipmentId) { + where.equipmentId = filter.equipmentId; + } + + if (filter.newStatus) { + where.newStatus = filter.newStatus; + } + + if (filter.number) { + where.number = { contains: filter.number, mode: 'insensitive' }; + } + + if (filter.responsible) { + where.responsible = { contains: filter.responsible, mode: 'insensitive' }; + } + + if (filter.dateFrom || filter.dateTo) { + where.date = {}; + if (filter.dateFrom) { + where.date.gte = new Date(filter.dateFrom); + } + if (filter.dateTo) { + where.date.lte = new Date(filter.dateTo); + } + } + + return where; + } +} \ No newline at end of file diff --git a/backend/src/change-equipment-status/change-equipment-status.module.ts b/backend/src/change-equipment-status/change-equipment-status.module.ts new file mode 100644 index 0000000..27bf136 --- /dev/null +++ b/backend/src/change-equipment-status/change-equipment-status.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ChangeEquipmentStatusService } from './change-equipment-status.service'; +import { ChangeEquipmentStatusController } from './change-equipment-status.controller'; +import { PrismaService } from '../prisma/prisma.service'; + +@Module({ + controllers: [ChangeEquipmentStatusController], + providers: [ChangeEquipmentStatusService, PrismaService], +}) +export class ChangeEquipmentStatusModule {} \ No newline at end of file diff --git a/backend/src/change-equipment-status/change-equipment-status.service.ts b/backend/src/change-equipment-status/change-equipment-status.service.ts new file mode 100644 index 0000000..9387fdc --- /dev/null +++ b/backend/src/change-equipment-status/change-equipment-status.service.ts @@ -0,0 +1,69 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { ChangeEquipmentStatus, Prisma } from '@prisma/client'; + +@Injectable() +export class ChangeEquipmentStatusService { + constructor(private prisma: PrismaService) {} + + async findAll(params: { + skip?: number; + take?: number; + where?: Prisma.ChangeEquipmentStatusWhereInput; + orderBy?: Prisma.ChangeEquipmentStatusOrderByWithRelationInput; + }): Promise { + const { skip, take, where, orderBy } = params; + return this.prisma.changeEquipmentStatus.findMany({ + skip, + take, + where, + orderBy, + include: { + equipment: true, + }, + }); + } + + async count(where?: Prisma.ChangeEquipmentStatusWhereInput): Promise { + return this.prisma.changeEquipmentStatus.count({ where }); + } + + async findOne(where: Prisma.ChangeEquipmentStatusWhereUniqueInput): Promise { + return this.prisma.changeEquipmentStatus.findUnique({ + where, + include: { + equipment: true, + }, + }); + } + + async create(data: Prisma.ChangeEquipmentStatusCreateInput): Promise { + return this.prisma.changeEquipmentStatus.create({ + data, + include: { + equipment: true, + }, + }); + } + + async update(params: { + where: Prisma.ChangeEquipmentStatusWhereUniqueInput; + data: Prisma.ChangeEquipmentStatusUpdateInput; + }): Promise { + return this.prisma.changeEquipmentStatus.update({ + ...params, + include: { + equipment: true, + }, + }); + } + + async remove(where: Prisma.ChangeEquipmentStatusWhereUniqueInput): Promise { + return this.prisma.changeEquipmentStatus.delete({ + where, + include: { + equipment: true, + }, + }); + } +} \ No newline at end of file diff --git a/backend/src/change-equipment-status/dto/change-equipment-status-list-request.dto.ts b/backend/src/change-equipment-status/dto/change-equipment-status-list-request.dto.ts new file mode 100644 index 0000000..6295ecb --- /dev/null +++ b/backend/src/change-equipment-status/dto/change-equipment-status-list-request.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; +import { PageRequestDto } from '../../common/dto/page-request.dto'; + +export class ChangeEquipmentStatusListRequestDto { + @ApiProperty({ description: 'Фильтр', required: false }) + @IsOptional() + filter?: any; // Using any since DTO.Filter is not defined in the schema + + @ApiProperty({ description: 'Параметры пагинации' }) + @ValidateNested() + @Type(() => PageRequestDto) + page: PageRequestDto; +} \ No newline at end of file diff --git a/backend/src/change-equipment-status/dto/change-equipment-status-list-response.dto.ts b/backend/src/change-equipment-status/dto/change-equipment-status-list-response.dto.ts new file mode 100644 index 0000000..164987f --- /dev/null +++ b/backend/src/change-equipment-status/dto/change-equipment-status-list-response.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PageInfoDto } from '../../common/dto/page-info.dto'; + +export class ChangeEquipmentStatusListResponseDto { + @ApiProperty({ description: 'Список документов изменения статуса', type: () => Object, isArray: true }) + content: any[]; + + @ApiProperty({ description: 'Метаданные пагинации', type: () => PageInfoDto }) + pageInfo: PageInfoDto; +} \ No newline at end of file diff --git a/backend/src/change-equipment-status/dto/create-change-equipment-status.dto.ts b/backend/src/change-equipment-status/dto/create-change-equipment-status.dto.ts new file mode 100644 index 0000000..a165f8f --- /dev/null +++ b/backend/src/change-equipment-status/dto/create-change-equipment-status.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional, IsDateString, IsUUID } from 'class-validator'; +import { EquipmentStatus } from '@prisma/client'; + +export class CreateChangeEquipmentStatusDto { + @ApiProperty({ description: 'Оборудование' }) + @IsUUID() + equipmentId: string; + + @ApiProperty({ + description: 'Новый статус', + enum: EquipmentStatus, + enumName: 'EquipmentStatus' + }) + newStatus: EquipmentStatus; + + @ApiProperty({ description: 'Номер', required: false }) + @IsOptional() + @IsString() + number?: string; + + @ApiProperty({ description: 'Дата изменения статуса' }) + @IsDateString() + date: string; + + @ApiProperty({ description: 'Ответственный', required: false }) + @IsOptional() + @IsString() + responsible?: string; +} \ No newline at end of file diff --git a/backend/src/change-equipment-status/dto/update-change-equipment-status.dto.ts b/backend/src/change-equipment-status/dto/update-change-equipment-status.dto.ts new file mode 100644 index 0000000..dc5de05 --- /dev/null +++ b/backend/src/change-equipment-status/dto/update-change-equipment-status.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional, IsDateString, IsUUID } from 'class-validator'; +import { EquipmentStatus } from '@prisma/client'; +import { PartialType } from '@nestjs/swagger'; +import { CreateChangeEquipmentStatusDto } from './create-change-equipment-status.dto'; + +export class UpdateChangeEquipmentStatusDto extends PartialType(CreateChangeEquipmentStatusDto) { + @ApiProperty({ description: 'Оборудование', required: false }) + @IsOptional() + @IsUUID() + equipmentId?: string; + + @ApiProperty({ + description: 'Новый статус', + enum: EquipmentStatus, + enumName: 'EquipmentStatus', + required: false + }) + @IsOptional() + newStatus?: EquipmentStatus; + + @ApiProperty({ description: 'Номер', required: false }) + @IsOptional() + @IsString() + number?: string; + + @ApiProperty({ description: 'Дата изменения статуса', required: false }) + @IsOptional() + @IsDateString() + date?: string; + + @ApiProperty({ description: 'Ответственный', required: false }) + @IsOptional() + @IsString() + responsible?: string; +} \ No newline at end of file diff --git a/backend/src/common/dto/page-info.dto.ts b/backend/src/common/dto/page-info.dto.ts new file mode 100644 index 0000000..3f35856 --- /dev/null +++ b/backend/src/common/dto/page-info.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class PageInfoDto { + @ApiProperty({ description: 'Номер страницы' }) + page: number; + + @ApiProperty({ description: 'Размер страницы' }) + size: number; + + @ApiProperty({ description: 'Общее количество элементов' }) + total: number; + + @ApiProperty({ description: 'Общее количество страниц' }) + totalPages: number; +} \ No newline at end of file diff --git a/backend/src/common/dto/page-request.dto.ts b/backend/src/common/dto/page-request.dto.ts new file mode 100644 index 0000000..0505898 --- /dev/null +++ b/backend/src/common/dto/page-request.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, Min } from 'class-validator'; + +export class PageRequestDto { + @ApiProperty({ description: 'Номер страницы', required: false, default: 0 }) + @IsOptional() + @IsNumber() + @Min(0) + page?: number = 0; + + @ApiProperty({ description: 'Размер страницы', required: false, default: 25 }) + @IsOptional() + @IsNumber() + @Min(1) + size?: number = 25; +} \ No newline at end of file diff --git a/backend/src/equipment/dto/create-equipment.dto.ts b/backend/src/equipment/dto/create-equipment.dto.ts new file mode 100644 index 0000000..83c0f05 --- /dev/null +++ b/backend/src/equipment/dto/create-equipment.dto.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional, IsDateString } from 'class-validator'; +import { EquipmentStatus } from '@prisma/client'; + +export class CreateEquipmentDto { + @ApiProperty({ description: 'Название оборудования' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Заводской (серийный) номер' }) + @IsString() + serialNumber: string; + + @ApiProperty({ description: 'Дата поверки', required: false }) + @IsOptional() + @IsDateString() + dateOfInspection?: string; + + @ApiProperty({ description: 'Дата изготовления', required: false }) + @IsOptional() + @IsDateString() + commissionedAt?: string; + + @ApiProperty({ + description: 'Текущий статус', + enum: EquipmentStatus, + enumName: 'EquipmentStatus', + required: false + }) + @IsOptional() + status?: EquipmentStatus; +} \ No newline at end of file diff --git a/backend/src/equipment/dto/equipment-list-request.dto.ts b/backend/src/equipment/dto/equipment-list-request.dto.ts new file mode 100644 index 0000000..dc64f90 --- /dev/null +++ b/backend/src/equipment/dto/equipment-list-request.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; +import { PageRequestDto } from '../../common/dto/page-request.dto'; + +export class EquipmentListRequestDto { + @ApiProperty({ description: 'Фильтр', required: false }) + @IsOptional() + filter?: any; // Using any since DTO.Filter is not defined in the schema + + @ApiProperty({ description: 'Параметры пагинации' }) + @ValidateNested() + @Type(() => PageRequestDto) + page: PageRequestDto; +} \ No newline at end of file diff --git a/backend/src/equipment/dto/equipment-list-response.dto.ts b/backend/src/equipment/dto/equipment-list-response.dto.ts new file mode 100644 index 0000000..c8cfae7 --- /dev/null +++ b/backend/src/equipment/dto/equipment-list-response.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PageInfoDto } from '../../common/dto/page-info.dto'; + +export class EquipmentListResponseDto { + @ApiProperty({ description: 'Список оборудования', type: () => Object, isArray: true }) + content: any[]; + + @ApiProperty({ description: 'Метаданные пагинации', type: () => PageInfoDto }) + pageInfo: PageInfoDto; +} \ No newline at end of file diff --git a/backend/src/equipment/dto/update-equipment.dto.ts b/backend/src/equipment/dto/update-equipment.dto.ts new file mode 100644 index 0000000..151adc9 --- /dev/null +++ b/backend/src/equipment/dto/update-equipment.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional, IsDateString } from 'class-validator'; +import { EquipmentStatus } from '@prisma/client'; +import { PartialType } from '@nestjs/swagger'; +import { CreateEquipmentDto } from './create-equipment.dto'; + +export class UpdateEquipmentDto extends PartialType(CreateEquipmentDto) { + @ApiProperty({ description: 'Название оборудования', required: false }) + @IsOptional() + @IsString() + name?: string; + + @ApiProperty({ description: 'Заводской (серийный) номер', required: false }) + @IsOptional() + @IsString() + serialNumber?: string; + + @ApiProperty({ description: 'Дата поверки', required: false }) + @IsOptional() + @IsDateString() + dateOfInspection?: string; + + @ApiProperty({ description: 'Дата изготовления', required: false }) + @IsOptional() + @IsDateString() + commissionedAt?: string; + + @ApiProperty({ + description: 'Текущий статус', + enum: EquipmentStatus, + enumName: 'EquipmentStatus', + required: false + }) + @IsOptional() + status?: EquipmentStatus; +} \ No newline at end of file diff --git a/backend/src/equipment/equipment.controller.ts b/backend/src/equipment/equipment.controller.ts new file mode 100644 index 0000000..c870232 --- /dev/null +++ b/backend/src/equipment/equipment.controller.ts @@ -0,0 +1,98 @@ +import { Controller, Get, Post, Body, Put, Param, Delete, Query, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { EquipmentService } from './equipment.service'; +import { CreateEquipmentDto } from './dto/create-equipment.dto'; +import { UpdateEquipmentDto } from './dto/update-equipment.dto'; +import { EquipmentListRequestDto } from './dto/equipment-list-request.dto'; +import { EquipmentListResponseDto } from './dto/equipment-list-response.dto'; +import { JwtAuthGuard } from '../auth/jwt-auth.guard'; + +@ApiTags('equipment') +@ApiBearerAuth() +@UseGuards(JwtAuthGuard) +@Controller('equipment') +export class EquipmentController { + constructor(private readonly equipmentService: EquipmentService) {} + + @Post('page') + @ApiOperation({ summary: 'Постраничный список оборудования с фильтрацией' }) + @ApiResponse({ + status: 200, + description: 'Список оборудования', + type: EquipmentListResponseDto + }) + async findAll( + @Body() dto: EquipmentListRequestDto, + ) { + const { page, filter } = dto; + const skip = page?.page ? page.page * page.size : 0; + const take = page?.size || 25; + + const where = filter ? this.buildWhereClause(filter) : {}; + + const [content, total] = await Promise.all([ + this.equipmentService.findAll({ skip, take, where }), + this.equipmentService.count(where) + ]); + + return { + content, + pageInfo: { + page: page?.page || 0, + size: take, + total, + totalPages: Math.ceil(total / take), + }, + }; + } + + @Get(':id') + @ApiOperation({ summary: 'Получить оборудование по идентификатору' }) + @ApiResponse({ status: 200, description: 'Оборудование найдено' }) + @ApiResponse({ status: 404, description: 'Оборудование не найдено' }) + findOne(@Param('id') id: string) { + return this.equipmentService.findOne({ id }); + } + + @Post() + @ApiOperation({ summary: 'Создать оборудование' }) + @ApiResponse({ status: 201, description: 'Оборудование создано' }) + create(@Body() dto: CreateEquipmentDto) { + return this.equipmentService.create(dto); + } + + @Put(':id') + @ApiOperation({ summary: 'Обновить оборудование' }) + @ApiResponse({ status: 200, description: 'Оборудование обновлено' }) + @ApiResponse({ status: 404, description: 'Оборудование не найдено' }) + update(@Param('id') id: string, @Body() dto: UpdateEquipmentDto) { + return this.equipmentService.update({ where: { id }, data: dto }); + } + + @Delete(':id') + @ApiOperation({ summary: 'Удалить оборудование' }) + @ApiResponse({ status: 200, description: 'Оборудование удалено' }) + @ApiResponse({ status: 404, description: 'Оборудование не найдено' }) + remove(@Param('id') id: string) { + return this.equipmentService.remove({ id }); + } + + private buildWhereClause(filter: any): any { + // Basic implementation - can be extended based on filter structure + const where: any = {}; + + if (filter.name) { + where.name = { contains: filter.name, mode: 'insensitive' }; + } + + if (filter.serialNumber) { + where.serialNumber = { contains: filter.serialNumber, mode: 'insensitive' }; + } + + if (filter.status) { + where.status = filter.status; + } + + return where; + } +} \ No newline at end of file diff --git a/backend/src/equipment/equipment.module.ts b/backend/src/equipment/equipment.module.ts new file mode 100644 index 0000000..4b324f7 --- /dev/null +++ b/backend/src/equipment/equipment.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { EquipmentService } from './equipment.service'; +import { EquipmentController } from './equipment.controller'; +import { PrismaService } from '../prisma/prisma.service'; + +@Module({ + controllers: [EquipmentController], + providers: [EquipmentService, PrismaService], +}) +export class EquipmentModule {} \ No newline at end of file diff --git a/backend/src/equipment/equipment.service.ts b/backend/src/equipment/equipment.service.ts new file mode 100644 index 0000000..a631a71 --- /dev/null +++ b/backend/src/equipment/equipment.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { Equipment, Prisma } from '@prisma/client'; + +@Injectable() +export class EquipmentService { + constructor(private prisma: PrismaService) {} + + async findAll(params: { + skip?: number; + take?: number; + where?: Prisma.EquipmentWhereInput; + orderBy?: Prisma.EquipmentOrderByWithRelationInput; + }): Promise { + const { skip, take, where, orderBy } = params; + return this.prisma.equipment.findMany({ skip, take, where, orderBy }); + } + + async count(where?: Prisma.EquipmentWhereInput): Promise { + return this.prisma.equipment.count({ where }); + } + + async findOne(where: Prisma.EquipmentWhereUniqueInput): Promise { + return this.prisma.equipment.findUnique({ where }); + } + + async create(data: Prisma.EquipmentCreateInput): Promise { + return this.prisma.equipment.create({ data }); + } + + async update(params: { + where: Prisma.EquipmentWhereUniqueInput; + data: Prisma.EquipmentUpdateInput; + }): Promise { + return this.prisma.equipment.update(params); + } + + async remove(where: Prisma.EquipmentWhereUniqueInput): Promise { + return this.prisma.equipment.delete({ where }); + } +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..3dfd829 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,36 @@ +import { Admin, Resource } from 'react-admin'; +import { dataProvider } from './dataProvider'; +import { authProvider } from './authProvider'; + +// Equipment resources +import { EquipmentList } from './resources/equipment/EquipmentList'; +import { EquipmentEdit } from './resources/equipment/EquipmentEdit'; +import { EquipmentCreate } from './resources/equipment/EquipmentCreate'; +import { EquipmentShow } from './resources/equipment/EquipmentShow'; + +// ChangeEquipmentStatus resources +import { ChangeEquipmentStatusList } from './resources/change-equipment-status/ChangeEquipmentStatusList'; +import { ChangeEquipmentStatusEdit } from './resources/change-equipment-status/ChangeEquipmentStatusEdit'; +import { ChangeEquipmentStatusCreate } from './resources/change-equipment-status/ChangeEquipmentStatusCreate'; +import { ChangeEquipmentStatusShow } from './resources/change-equipment-status/ChangeEquipmentStatusShow'; + +const App = () => ( + + + + +); + +export default App; \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..622b83b --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { initKeycloak } from './authProvider'; +import App from './App'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); + +// Initialize Keycloak before rendering the app +initKeycloak().then(() => { + root.render( + + + + ); +}).catch((error) => { + console.error('Failed to initialize Keycloak:', error); +}); \ No newline at end of file diff --git a/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusCreate.tsx b/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusCreate.tsx new file mode 100644 index 0000000..bee9386 --- /dev/null +++ b/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusCreate.tsx @@ -0,0 +1,25 @@ +import { Create, SimpleForm, TextInput, DateInput, SelectInput, ReferenceInput } from 'react-admin'; + +const equipmentStatusChoices = [ + { id: 'Active', name: 'В эксплуатации' }, + { id: 'Repair', name: 'В ремонте' }, + { id: 'Reserve', name: 'В резерве' }, + { id: 'WriteOff', name: 'Списано' }, +]; + +export const ChangeEquipmentStatusCreate = () => ( + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusEdit.tsx b/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusEdit.tsx new file mode 100644 index 0000000..13f1cb0 --- /dev/null +++ b/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusEdit.tsx @@ -0,0 +1,25 @@ +import { Edit, SimpleForm, TextInput, DateInput, SelectInput, ReferenceInput } from 'react-admin'; + +const equipmentStatusChoices = [ + { id: 'Active', name: 'В эксплуатации' }, + { id: 'Repair', name: 'В ремонте' }, + { id: 'Reserve', name: 'В резерве' }, + { id: 'WriteOff', name: 'Списано' }, +]; + +export const ChangeEquipmentStatusEdit = () => ( + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusList.tsx b/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusList.tsx new file mode 100644 index 0000000..bacd63b --- /dev/null +++ b/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusList.tsx @@ -0,0 +1,23 @@ +import { List, DataTable, TextField, DateField, EditButton, ShowButton, ReferenceField } from 'react-admin'; +import { EquipmentStatusField } from '../equipment/EquipmentStatusField'; + +export const ChangeEquipmentStatusList = () => ( + + + + + + + + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusShow.tsx b/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusShow.tsx new file mode 100644 index 0000000..b07b05e --- /dev/null +++ b/frontend/src/resources/change-equipment-status/ChangeEquipmentStatusShow.tsx @@ -0,0 +1,17 @@ +import { Show, SimpleShowLayout, TextField, DateField, ReferenceField } from 'react-admin'; +import { EquipmentStatusField } from '../equipment/EquipmentStatusField'; + +export const ChangeEquipmentStatusShow = () => ( + + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/change-equipment-status/index.ts b/frontend/src/resources/change-equipment-status/index.ts new file mode 100644 index 0000000..06e12bd --- /dev/null +++ b/frontend/src/resources/change-equipment-status/index.ts @@ -0,0 +1,4 @@ +export { ChangeEquipmentStatusList } from './ChangeEquipmentStatusList'; +export { ChangeEquipmentStatusEdit } from './ChangeEquipmentStatusEdit'; +export { ChangeEquipmentStatusCreate } from './ChangeEquipmentStatusCreate'; +export { ChangeEquipmentStatusShow } from './ChangeEquipmentStatusShow'; \ No newline at end of file diff --git a/frontend/src/resources/equipment/EquipmentCreate.tsx b/frontend/src/resources/equipment/EquipmentCreate.tsx new file mode 100644 index 0000000..b0a48f3 --- /dev/null +++ b/frontend/src/resources/equipment/EquipmentCreate.tsx @@ -0,0 +1,25 @@ +import { Create, SimpleForm, TextInput, DateInput, SelectInput } from 'react-admin'; + +const equipmentStatusChoices = [ + { id: 'Active', name: 'В эксплуатации' }, + { id: 'Repair', name: 'В ремонте' }, + { id: 'Reserve', name: 'В резерве' }, + { id: 'WriteOff', name: 'Списано' }, +]; + +export const EquipmentCreate = () => ( + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/equipment/EquipmentEdit.tsx b/frontend/src/resources/equipment/EquipmentEdit.tsx new file mode 100644 index 0000000..e471958 --- /dev/null +++ b/frontend/src/resources/equipment/EquipmentEdit.tsx @@ -0,0 +1,25 @@ +import { Edit, SimpleForm, TextInput, DateInput, SelectInput } from 'react-admin'; + +const equipmentStatusChoices = [ + { id: 'Active', name: 'В эксплуатации' }, + { id: 'Repair', name: 'В ремонте' }, + { id: 'Reserve', name: 'В резерве' }, + { id: 'WriteOff', name: 'Списано' }, +]; + +export const EquipmentEdit = () => ( + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/equipment/EquipmentList.tsx b/frontend/src/resources/equipment/EquipmentList.tsx new file mode 100644 index 0000000..321892b --- /dev/null +++ b/frontend/src/resources/equipment/EquipmentList.tsx @@ -0,0 +1,21 @@ +import { List, DataTable, TextField, DateField, EditButton, ShowButton } from 'react-admin'; +import { EquipmentStatusField } from './EquipmentStatusField'; + +export const EquipmentList = () => ( + + + + + + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/equipment/EquipmentShow.tsx b/frontend/src/resources/equipment/EquipmentShow.tsx new file mode 100644 index 0000000..50c9e9f --- /dev/null +++ b/frontend/src/resources/equipment/EquipmentShow.tsx @@ -0,0 +1,17 @@ +import { Show, SimpleShowLayout, TextField, DateField } from 'react-admin'; +import { EquipmentStatusField } from './EquipmentStatusField'; + +export const EquipmentShow = () => ( + + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/equipment/EquipmentStatusField.tsx b/frontend/src/resources/equipment/EquipmentStatusField.tsx new file mode 100644 index 0000000..5d18e63 --- /dev/null +++ b/frontend/src/resources/equipment/EquipmentStatusField.tsx @@ -0,0 +1,15 @@ +import { useRecordContext } from 'react-admin'; + +const statusLabels: Record = { + 'Active': 'В эксплуатации', + 'Repair': 'В ремонте', + 'Reserve': 'В резерве', + 'WriteOff': 'Списано', +}; + +export const EquipmentStatusField = ({ source }: { source: string }) => { + const record = useRecordContext(); + const status = record ? record[source] : null; + + return {status ? statusLabels[status] || status : ''}; +}; \ No newline at end of file diff --git a/frontend/src/resources/equipment/index.ts b/frontend/src/resources/equipment/index.ts new file mode 100644 index 0000000..8b4fd08 --- /dev/null +++ b/frontend/src/resources/equipment/index.ts @@ -0,0 +1,4 @@ +export { EquipmentList } from './EquipmentList'; +export { EquipmentEdit } from './EquipmentEdit'; +export { EquipmentCreate } from './EquipmentCreate'; +export { EquipmentShow } from './EquipmentShow'; \ No newline at end of file