Commit e966ee3b by Augusto

conclusion and occurence

parent fd8fbe81
......@@ -50,6 +50,7 @@ model Occurrence {
// Relations
comments Comment[]
attachments Attachment[]
conclusion Conclusion?
@@map("occurrences")
}
......@@ -89,6 +90,23 @@ model Attachment {
@@map("attachments")
}
model Conclusion {
id String @id @default(cuid())
description String @db.Text
fieldMaterial Boolean @default(false)
materialUsed String? @db.Text
// Relations
occurrence Occurrence @relation(fields: [occurrenceId], references: [id], onDelete: Cascade)
occurrenceId String @unique
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("conclusions")
}
enum UserRole {
USER
MODERATOR
......
......@@ -8,6 +8,7 @@ import { AuthModule } from './modules/auth/auth.module';
import { OccurrenceModule } from './modules/occurrence/occurrence.module';
import { CommentModule } from './modules/comment/comment.module';
import { AttachmentModule } from './modules/attachment/attachment.module';
import { ConclusionModule } from './modules/conclusion/conclusion.module';
import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard';
@Module({
......@@ -17,6 +18,7 @@ import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard';
OccurrenceModule,
CommentModule,
AttachmentModule,
ConclusionModule,
],
controllers: [AppController],
providers: [
......
......@@ -30,7 +30,6 @@ async function bootstrap() {
.setVersion('1.0')
.addTag('auth', 'Authentication endpoints')
.addTag('users', 'User management endpoints')
.addTag('forms', 'Form management endpoints')
.addBearerAuth(
{
type: 'http',
......
# Conclusion Module
This module handles conclusions for occurrences in the system. Every occurrence can have one conclusion that provides a final assessment and details about the resolution.
## Features
- **One-to-One Relationship**: Each occurrence can have exactly one conclusion
- **Field Material Tracking**: Tracks whether field material was used in the resolution
- **Material Description**: When field material is used, provides a detailed description
- **CRUD Operations**: Full create, read, update, and delete operations
## Model Structure
The `Conclusion` model includes:
- `id`: Unique identifier
- `description`: Conclusion description about the occurrence
- `fieldMaterial`: Boolean flag indicating if field material was used
- `materialUsed`: Optional text description of materials used (required when `fieldMaterial` is true)
- `occurrenceId`: Reference to the associated occurrence
- `createdAt`: Creation timestamp
- `updatedAt`: Last update timestamp
## API Endpoints
### Create Conclusion
`POST /conclusions/occurrence/:occurrenceId`
- Creates a new conclusion for the specified occurrence
- Validates that the occurrence exists
- Ensures only one conclusion per occurrence
- Validates material fields consistency
### Get Conclusion by Occurrence
`GET /conclusions/occurrence/:occurrenceId`
- Retrieves the conclusion for a specific occurrence
- Returns null if no conclusion exists
### Get Conclusion by ID
`GET /conclusions/:id`
- Retrieves a specific conclusion by its ID
### Update Conclusion
`PATCH /conclusions/:id`
- Updates an existing conclusion
- Validates material fields consistency
### Delete Conclusion
`DELETE /conclusions/:id`
- Deletes a specific conclusion
### Delete Conclusion by Occurrence
`DELETE /conclusions/occurrence/:occurrenceId`
- Deletes the conclusion associated with a specific occurrence
## Business Rules
1. **One Conclusion per Occurrence**: Each occurrence can have at most one conclusion
2. **Material Validation**:
- If `fieldMaterial` is true, `materialUsed` must be provided
- If `fieldMaterial` is false, `materialUsed` should be null
3. **Occurrence Relationship**: Conclusions are automatically deleted when their associated occurrence is deleted (cascade delete)
## Integration
The Conclusion module is integrated with the Occurrence module:
- Occurrence responses now include conclusion data
- All occurrence queries automatically fetch related conclusion information
- The relationship is properly defined in the Prisma schema
## Usage Example
```typescript
// Create a conclusion
const conclusion = await conclusionService.create('occurrence-id', {
description: 'Equipment was replaced and system is now operational',
fieldMaterial: true,
materialUsed: 'Replacement cable (Cat6) and new connector from field kit',
});
// Get conclusion for an occurrence
const conclusion = await conclusionService.findByOccurrence('occurrence-id');
// Update conclusion
const updatedConclusion = await conclusionService.update('conclusion-id', {
fieldMaterial: false,
materialUsed: null, // Will be automatically cleared when fieldMaterial is false
});
```
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
HttpCode,
HttpStatus,
UseGuards,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
} from '@nestjs/swagger';
import { ConclusionService } from './conclusion.service';
import { CreateConclusionDto } from './dto/create-conclusion.dto';
import { UpdateConclusionDto } from './dto/update-conclusion.dto';
import { ConclusionResponseDto } from './dto/conclusion-response.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
@ApiTags('conclusions')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Controller('conclusions')
export class ConclusionController {
constructor(private readonly conclusionService: ConclusionService) {}
@Post('occurrence/:occurrenceId')
@ApiOperation({ summary: 'Create a conclusion for an occurrence' })
@ApiResponse({
status: 201,
description: 'The conclusion has been successfully created.',
type: ConclusionResponseDto,
})
@ApiResponse({ status: 400, description: 'Bad Request.' })
@ApiResponse({ status: 404, description: 'Occurrence not found.' })
@ApiResponse({
status: 409,
description: 'Conclusion already exists for this occurrence.',
})
async create(
@Param('occurrenceId') occurrenceId: string,
@Body() createConclusionDto: CreateConclusionDto,
): Promise<ConclusionResponseDto> {
return this.conclusionService.create(occurrenceId, createConclusionDto);
}
@Get('occurrence/:occurrenceId')
@ApiOperation({ summary: 'Get conclusion by occurrence ID' })
@ApiResponse({
status: 200,
description: 'The conclusion has been successfully retrieved.',
type: ConclusionResponseDto,
})
@ApiResponse({ status: 404, description: 'Conclusion not found.' })
async findByOccurrence(
@Param('occurrenceId') occurrenceId: string,
): Promise<ConclusionResponseDto | null> {
return this.conclusionService.findByOccurrence(occurrenceId);
}
@Get(':id')
@ApiOperation({ summary: 'Get conclusion by ID' })
@ApiResponse({
status: 200,
description: 'The conclusion has been successfully retrieved.',
type: ConclusionResponseDto,
})
@ApiResponse({ status: 404, description: 'Conclusion not found.' })
async findOne(@Param('id') id: string): Promise<ConclusionResponseDto> {
return this.conclusionService.findOne(id);
}
@Patch(':id')
@ApiOperation({ summary: 'Update a conclusion' })
@ApiResponse({
status: 200,
description: 'The conclusion has been successfully updated.',
type: ConclusionResponseDto,
})
@ApiResponse({ status: 400, description: 'Bad Request.' })
@ApiResponse({ status: 404, description: 'Conclusion not found.' })
async update(
@Param('id') id: string,
@Body() updateConclusionDto: UpdateConclusionDto,
): Promise<ConclusionResponseDto> {
return this.conclusionService.update(id, updateConclusionDto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete a conclusion' })
@ApiResponse({
status: 204,
description: 'The conclusion has been successfully deleted.',
})
@ApiResponse({ status: 404, description: 'Conclusion not found.' })
async remove(@Param('id') id: string): Promise<void> {
return this.conclusionService.remove(id);
}
@Delete('occurrence/:occurrenceId')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete conclusion by occurrence ID' })
@ApiResponse({
status: 204,
description: 'The conclusion has been successfully deleted.',
})
async removeByOccurrence(
@Param('occurrenceId') occurrenceId: string,
): Promise<void> {
return this.conclusionService.removeByOccurrence(occurrenceId);
}
}
import { Module } from '@nestjs/common';
import { ConclusionService } from './conclusion.service';
import { ConclusionController } from './conclusion.controller';
import { PrismaService } from '../../common/prisma.service';
@Module({
controllers: [ConclusionController],
providers: [ConclusionService, PrismaService],
exports: [ConclusionService],
})
export class ConclusionModule {}
import {
Injectable,
NotFoundException,
BadRequestException,
ConflictException,
} from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { CreateConclusionDto } from './dto/create-conclusion.dto';
import { UpdateConclusionDto } from './dto/update-conclusion.dto';
import { ConclusionResponseDto } from './dto/conclusion-response.dto';
@Injectable()
export class ConclusionService {
constructor(private readonly prisma: PrismaService) {}
async create(
occurrenceId: string,
createConclusionDto: CreateConclusionDto,
): Promise<ConclusionResponseDto> {
// Check if occurrence exists
const occurrence = await this.prisma.occurrence.findUnique({
where: { id: occurrenceId },
});
if (!occurrence) {
throw new NotFoundException(
`Occurrence with ID ${occurrenceId} not found`,
);
}
// Check if conclusion already exists for this occurrence
const existingConclusion = await this.prisma.conclusion.findUnique({
where: { occurrenceId },
});
if (existingConclusion) {
throw new ConflictException(
`Conclusion already exists for occurrence ${occurrenceId}`,
);
}
// Validate material fields
if (
createConclusionDto.fieldMaterial &&
!createConclusionDto.materialUsed
) {
throw new BadRequestException(
'Material description is required when field material is used',
);
}
if (
!createConclusionDto.fieldMaterial &&
createConclusionDto.materialUsed
) {
throw new BadRequestException(
'Material description should not be provided when field material is not used',
);
}
const conclusion = await this.prisma.conclusion.create({
data: {
...createConclusionDto,
occurrenceId,
},
});
return conclusion;
}
async findByOccurrence(
occurrenceId: string,
): Promise<ConclusionResponseDto | null> {
const conclusion = await this.prisma.conclusion.findUnique({
where: { occurrenceId },
});
return conclusion;
}
async findOne(id: string): Promise<ConclusionResponseDto> {
const conclusion = await this.prisma.conclusion.findUnique({
where: { id },
});
if (!conclusion) {
throw new NotFoundException(`Conclusion with ID ${id} not found`);
}
return conclusion;
}
async update(
id: string,
updateConclusionDto: UpdateConclusionDto,
): Promise<ConclusionResponseDto> {
// Check if conclusion exists
const existingConclusion = await this.findOne(id);
// Validate material fields
const fieldMaterial =
updateConclusionDto.fieldMaterial ?? existingConclusion.fieldMaterial;
const materialUsed =
updateConclusionDto.materialUsed ?? existingConclusion.materialUsed;
if (
fieldMaterial &&
!materialUsed &&
updateConclusionDto.materialUsed !== ''
) {
throw new BadRequestException(
'Material description is required when field material is used',
);
}
if (
!fieldMaterial &&
materialUsed &&
updateConclusionDto.fieldMaterial !== true
) {
// If fieldMaterial is being set to false, clear materialUsed
updateConclusionDto.materialUsed = null as any;
}
const conclusion = await this.prisma.conclusion.update({
where: { id },
data: updateConclusionDto,
});
return conclusion;
}
async remove(id: string): Promise<void> {
// Check if conclusion exists
await this.findOne(id);
await this.prisma.conclusion.delete({
where: { id },
});
}
async removeByOccurrence(occurrenceId: string): Promise<void> {
const conclusion = await this.prisma.conclusion.findUnique({
where: { occurrenceId },
});
if (conclusion) {
await this.prisma.conclusion.delete({
where: { id: conclusion.id },
});
}
}
}
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class ConclusionResponseDto {
@ApiProperty({
description: 'Unique identifier for the conclusion',
example: 'clxy123456789',
})
id: string;
@ApiProperty({
description: 'Conclusion description about the occurrence',
example: 'The occurrence was resolved by replacing the faulty equipment',
})
description: string;
@ApiProperty({
description: 'Whether field material was used',
example: true,
})
fieldMaterial: boolean;
@ApiPropertyOptional({
description: 'Description of the material used',
example: 'Used spare cable and replacement connector from field toolkit',
})
materialUsed?: string | null;
@ApiProperty({
description: 'ID of the associated occurrence',
example: 'clxy987654321',
})
occurrenceId: string;
@ApiProperty({
description: 'Date and time when the conclusion was created',
example: '2023-12-01T10:00:00Z',
})
createdAt: Date;
@ApiProperty({
description: 'Date and time when the conclusion was last updated',
example: '2023-12-01T10:30:00Z',
})
updatedAt: Date;
}
import { IsString, IsBoolean, IsOptional, IsNotEmpty } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateConclusionDto {
@ApiProperty({
description: 'Conclusion description about the occurrence',
example: 'The occurrence was resolved by replacing the faulty equipment',
})
@IsString()
@IsNotEmpty()
description: string;
@ApiPropertyOptional({
description: 'Whether field material was used',
example: true,
default: false,
})
@IsBoolean()
@IsOptional()
fieldMaterial?: boolean;
@ApiPropertyOptional({
description:
'Description of the material used (required if fieldMaterial is true)',
example: 'Used spare cable and replacement connector from field toolkit',
})
@IsString()
@IsOptional()
materialUsed?: string | null;
}
import { PartialType } from '@nestjs/swagger';
import { CreateConclusionDto } from './create-conclusion.dto';
export class UpdateConclusionDto extends PartialType(CreateConclusionDto) {}
export * from './conclusion.module';
export * from './conclusion.service';
export * from './conclusion.controller';
export * from './dto/create-conclusion.dto';
export * from './dto/update-conclusion.dto';
export * from './dto/conclusion-response.dto';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { OccurrenceStatus, Priority } from '../../../../generated/prisma';
import { ConclusionResponseDto } from '../../conclusion/dto/conclusion-response.dto';
export class OccurrenceResponseDto {
@ApiProperty({
......@@ -118,4 +119,10 @@ export class OccurrenceResponseDto {
comments: number;
attachments: number;
};
@ApiPropertyOptional({
description: 'Conclusion for this occurrence',
type: ConclusionResponseDto,
})
conclusion?: ConclusionResponseDto | null;
}
......@@ -56,6 +56,7 @@ export class OccurrenceService {
email: true,
},
},
conclusion: true,
_count: {
select: {
comments: true,
......@@ -116,6 +117,7 @@ export class OccurrenceService {
email: true,
},
},
conclusion: true,
_count: {
select: {
comments: true,
......@@ -153,6 +155,7 @@ export class OccurrenceService {
email: true,
},
},
conclusion: true,
_count: {
select: {
comments: true,
......@@ -226,6 +229,7 @@ export class OccurrenceService {
email: true,
},
},
conclusion: true,
_count: {
select: {
comments: true,
......@@ -342,6 +346,7 @@ export class OccurrenceService {
email: true,
},
},
conclusion: true,
_count: {
select: {
comments: true,
......@@ -398,6 +403,7 @@ export class OccurrenceService {
email: true,
},
},
conclusion: true,
_count: {
select: {
comments: true,
......@@ -453,6 +459,7 @@ export class OccurrenceService {
email: true,
},
},
conclusion: true,
_count: {
select: {
comments: true,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment