Commit 08db2e37 by Augusto Fonte

Inspection changes

parent c8c5d592
{ {
"name": "api-cellnex", "name": "api-cellnex",
"version": "0.0.1", "version": "0.0.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "api-cellnex", "name": "api-cellnex",
"version": "0.0.1", "version": "0.0.2",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@nestjs-modules/mailer": "^2.0.2", "@nestjs-modules/mailer": "^2.0.2",
...@@ -17,14 +17,16 @@ ...@@ -17,14 +17,16 @@
"@nestjs/passport": "^11.0.5", "@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.1.0", "@nestjs/platform-express": "^11.1.0",
"@nestjs/swagger": "^11.1.1", "@nestjs/swagger": "^11.1.1",
"@prisma/client": "^6.6.0", "@prisma/client": "^6.9.0",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"adm-zip": "^0.5.16",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"csv-parser": "^3.2.0", "csv-parser": "^3.2.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"fast-xml-parser": "^5.2.5",
"jimp": "^0.22.12", "jimp": "^0.22.12",
"multer": "^1.4.5-lts.2", "multer": "^1.4.5-lts.2",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
...@@ -4457,9 +4459,9 @@ ...@@ -4457,9 +4459,9 @@
} }
}, },
"node_modules/@prisma/client": { "node_modules/@prisma/client": {
"version": "6.6.0", "version": "6.9.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.6.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.9.0.tgz",
"integrity": "sha512-vfp73YT/BHsWWOAuthKQ/1lBgESSqYqAWZEYyTdGXyFAHpmewwWL2Iz6ErIzkj4aHbuc6/cGSsE6ZY+pBO04Cg==", "integrity": "sha512-Gg7j1hwy3SgF1KHrh0PZsYvAaykeR0PaxusnLXydehS96voYCGt1U5zVR31NIouYc63hWzidcrir1a7AIyCsNQ==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
...@@ -5983,6 +5985,15 @@ ...@@ -5983,6 +5985,15 @@
"node": ">=0.8" "node": ">=0.8"
} }
}, },
"node_modules/adm-zip": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
"integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
"license": "MIT",
"engines": {
"node": ">=12.0"
}
},
"node_modules/agent-base": { "node_modules/agent-base": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
...@@ -8753,6 +8764,24 @@ ...@@ -8753,6 +8764,24 @@
], ],
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/fast-xml-parser": {
"version": "5.2.5",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz",
"integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"strnum": "^2.1.0"
},
"bin": {
"fxparser": "src/cli/cli.js"
}
},
"node_modules/fastq": { "node_modules/fastq": {
"version": "1.19.1", "version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
...@@ -15245,6 +15274,18 @@ ...@@ -15245,6 +15274,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/strnum": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz",
"integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT"
},
"node_modules/strtok3": { "node_modules/strtok3": {
"version": "9.1.1", "version": "9.1.1",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz",
......
...@@ -29,14 +29,16 @@ ...@@ -29,14 +29,16 @@
"@nestjs/passport": "^11.0.5", "@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.1.0", "@nestjs/platform-express": "^11.1.0",
"@nestjs/swagger": "^11.1.1", "@nestjs/swagger": "^11.1.1",
"@prisma/client": "^6.6.0", "@prisma/client": "^6.9.0",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"adm-zip": "^0.5.16",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"csv-parser": "^3.2.0", "csv-parser": "^3.2.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"fast-xml-parser": "^5.2.5",
"jimp": "^0.22.12", "jimp": "^0.22.12",
"multer": "^1.4.5-lts.2", "multer": "^1.4.5-lts.2",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
......
import fs from 'fs';
import AdmZip from 'adm-zip';
import { XMLParser } from 'fast-xml-parser';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function importKmzToSites(kmzPath: string) {
// 1. Extract KML from KMZ
const zip = new AdmZip(kmzPath);
const kmlEntry = zip.getEntries().find((e) => e.entryName.endsWith('.kml'));
if (!kmlEntry) throw new Error('No KML file found in KMZ');
const kmlString = kmlEntry.getData().toString('utf8');
// 2. Parse KML
const parser = new XMLParser({ ignoreAttributes: false });
const kml = parser.parse(kmlString);
// 3. Extract Placemarks (handle both array and single object)
const placemarks = (() => {
const doc = kml.kml.Document;
if (Array.isArray(doc.Placemark)) return doc.Placemark;
if (doc.Placemark) return [doc.Placemark];
return [];
})();
for (const placemark of placemarks) {
const name = placemark.name || '';
const [longitude, latitude] = placemark.Point.coordinates
.split(',')
.map(Number);
// You can customize how you extract siteCode/siteName here
const siteCode = name;
const siteName = name;
// 4. Insert into Site model (skip if missing lat/lon)
if (latitude && longitude && siteCode) {
try {
await prisma.site.create({
data: {
siteCode,
siteName,
latitude,
longitude,
},
});
console.log(`Imported site: ${siteCode} (${latitude}, ${longitude})`);
} catch (err) {
console.error(`Failed to import site ${siteCode}:`, err.message);
}
}
}
console.log('Import complete!');
}
// Usage: npx ts-node prisma/import-sites-from-kmz.ts path/to/your/file.kmz
const kmzPath = process.argv[2];
if (!kmzPath) {
console.error(
'Usage: npx ts-node prisma/import-sites-from-kmz.ts <file.kmz>',
);
process.exit(1);
}
importKmzToSites(kmzPath)
.catch(console.error)
.finally(() => prisma.$disconnect());
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
console.log('Seeding maintenance questions...');
// Clear existing questions
await prisma.maintenanceQuestion.deleteMany({});
// Create questions
const questions = [
{
question: 'Site access condition',
orderIndex: 1,
},
{
question: 'Site infrastructure condition',
orderIndex: 2,
},
{
question: 'Equipment condition',
orderIndex: 3,
},
{
question: 'Power system condition',
orderIndex: 4,
},
{
question: 'Cooling system condition',
orderIndex: 5,
},
{
question: 'Security features condition',
orderIndex: 6,
},
{
question: 'Safety equipment presence and condition',
orderIndex: 7,
},
{
question: 'Site cleanliness',
orderIndex: 8,
},
{
question: 'Vegetation control',
orderIndex: 9,
},
{
question: 'Surrounding area condition',
orderIndex: 10,
},
];
for (const question of questions) {
await prisma.maintenanceQuestion.create({
data: question,
});
}
console.log('Maintenance questions have been seeded!');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
\ No newline at end of file
...@@ -4,32 +4,129 @@ import * as bcrypt from 'bcrypt'; ...@@ -4,32 +4,129 @@ import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient(); const prisma = new PrismaClient();
async function main() { async function main() {
const hashedPassword = await bcrypt.hash('brandit123465', 10); const hashedPassword = await bcrypt.hash('brandit123465', 10);
const superadmin = await prisma.user.upsert({ const superadmin = await prisma.user.upsert({
where: { email: 'augusto.fonte@brandit.pt' }, where: { email: 'augusto.fonte@brandit.pt' },
update: { update: {
isActive: true, isActive: true,
role: Role.SUPERADMIN, role: Role.SUPERADMIN,
password: hashedPassword, password: hashedPassword,
}, },
create: { create: {
email: 'augusto.fonte@brandit.pt', email: 'augusto.fonte@brandit.pt',
name: 'Augusto Fonte', name: 'Augusto Fonte',
password: hashedPassword, password: hashedPassword,
role: Role.SUPERADMIN, role: Role.SUPERADMIN,
isActive: true, isActive: true,
}, },
}); });
console.log('Created/Updated superadmin user:', superadmin);
console.log('Created/Updated superadmin user:', superadmin); // Inspection questions to seed
const inspectionQuestions = [
{
question:
'Cada componente de carril-guia foi ficado pelo meus uma vez, conforme dispositivo na secção B',
},
{
question:
'As distâncias entre fixações são no máx 1,68cm (2,24m escadas gémeas ZAL, Conforme dispositivo na secção B',
},
{
question:
'Nas uniões, as juntas são todas inferiores a 5mm, conforme dispositivo na secção B',
},
{
question:
'As uniões roscadas entre edificação e os elementos de fixação correspondem ai dispositivo da secção B',
},
{
question:
'Os elementos de fixação estão corretamente montados e todas as uniões roscadas (incluindo as de fábrica) estão firmemente apertadas. (binários de aperto conforme secção B)',
},
{
question:
'Todas as uniões roscadas estão protegidas contra o desaperto, em conformidade com o dispositivo da secção E',
},
{
question:
"No início do percurrso de subida encontra-se montado um 'batente de bloqueio inferior' em conformidade com o dispositivo em B",
},
{
question:
"No fim do percurso de subida encontra-se montado um 'batente de bloqueio superior' ou um 'batente de bloqueio terminal', em conforme indicado na secção B",
},
{
question:
"No início do percurso de subida, não montado diretamente ai nível do solo, estão montados dois (2) batentes de bloqueio 'inferior', conforme indicado na secção B",
},
{
question:
'o carril-guia passa pelo menos 1 metro acima da aresta superior do patamar, conforme o dispositivo na secção B',
},
{
question:
'Em conformidade com o dispositivo na secção B, não existem extensões acima da escada com mais de 38cm sem reforço de lonfarina (52,5xm em escadas YAL e ZAL)',
},
{
question:
'O reforço da longarina está corretamente montada conforme o dispositivo na secção B',
},
{
question:
'O ângulo de flexão máximo nas peças flexiveis foi observado (ver secção C)',
},
{
question:
'Todos os troços estão montados corretamente, em conformidade com o dispositivo da secção B',
},
{
question:
'As uniões dos trilhos-guia estão corretamente instaladas, conforme com o dispositivo da secção B',
},
{ question: 'A passagem entre trilhos-guia está alinhada' },
{ question: 'O carril-guia está livre de sujidades' },
{
question:
'Só foram utilizados elementos de fixação e uniões roscadas protegidos contra a corrosão (inspeções: os elementos de fixação e uniões não apresentam corrosão)',
},
{
question:
'O aparelho anti queda Soll só se deixa montar no sentido correto do seu duncionamento no percurso de subida e descida',
},
{ question: 'Existe a placa de identificação do fabricante' },
{
question:
'Foi realizado um percurso de ensaio e não forma detetadas quaisquer falhas',
},
{ question: 'Só foram instalados componentes do fabricante' },
{
question:
'A Escada e/ou elementos de suporte não apresentam danos visiveis, indício de deficiente fixação ou falhas de componentes que ponham em causa a sua utilização',
},
];
// Seed InspectionQuestions
await prisma.inspectionQuestion.deleteMany();
for (let i = 0; i < inspectionQuestions.length; i++) {
const q = inspectionQuestions[i];
await prisma.inspectionQuestion.create({
data: {
question: q.question,
orderIndex: i + 1,
},
});
}
console.log('Seeded InspectionQuestions:', inspectionQuestions.length);
} }
main() main()
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
process.exit(1); process.exit(1);
}) })
.finally(async () => { .finally(async () => {
await prisma.$disconnect(); await prisma.$disconnect();
}); });
\ No newline at end of file
...@@ -15,7 +15,7 @@ import { join } from 'path'; ...@@ -15,7 +15,7 @@ import { join } from 'path';
import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard'; import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard';
import { DashboardModule } from './modules/dashboard/dashboard.module'; import { DashboardModule } from './modules/dashboard/dashboard.module';
import { PartnersModule } from './modules/partners/partners.module'; import { PartnersModule } from './modules/partners/partners.module';
import { MaintenanceModule } from './modules/maintenance/maintenance.module'; import { InspectionModule } from './modules/inspection/inspection.module';
@Module({ @Module({
imports: [ imports: [
...@@ -55,7 +55,7 @@ import { MaintenanceModule } from './modules/maintenance/maintenance.module'; ...@@ -55,7 +55,7 @@ import { MaintenanceModule } from './modules/maintenance/maintenance.module';
CommentsModule, CommentsModule,
DashboardModule, DashboardModule,
PartnersModule, PartnersModule,
MaintenanceModule, InspectionModule,
], ],
controllers: [AppController], controllers: [AppController],
providers: [ providers: [
......
...@@ -39,7 +39,7 @@ async function bootstrap() { ...@@ -39,7 +39,7 @@ async function bootstrap() {
.addTag('users', 'User management endpoints') .addTag('users', 'User management endpoints')
.addTag('sites', 'Site management endpoints') .addTag('sites', 'Site management endpoints')
.addTag('candidates', 'Candidate management endpoints') .addTag('candidates', 'Candidate management endpoints')
.addTag('maintenance', 'Site maintenance management endpoints') .addTag('inspection', 'Site inspection management endpoints')
.addBearerAuth( .addBearerAuth(
{ {
type: 'http', type: 'http',
......
...@@ -6,12 +6,12 @@ import { ...@@ -6,12 +6,12 @@ import {
ValidateNested, ValidateNested,
} from 'class-validator'; } from 'class-validator';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { MaintenanceResponseOption } from './maintenance-response-option.enum'; import { InspectionResponseOption } from './inspection-response-option.enum';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateMaintenanceResponseDto { export class CreateInspectionResponseDto {
@ApiProperty({ @ApiProperty({
description: 'The ID of the maintenance question being answered', description: 'The ID of the inspection question being answered',
example: 1, example: 1,
type: Number, type: Number,
}) })
...@@ -19,13 +19,13 @@ export class CreateMaintenanceResponseDto { ...@@ -19,13 +19,13 @@ export class CreateMaintenanceResponseDto {
questionId: number; questionId: number;
@ApiProperty({ @ApiProperty({
description: 'The response to the maintenance question', description: 'The response to the inspection question',
enum: MaintenanceResponseOption, enum: InspectionResponseOption,
example: MaintenanceResponseOption.YES, example: InspectionResponseOption.YES,
enumName: 'MaintenanceResponseOption', enumName: 'InspectionResponseOption',
}) })
@IsString() @IsString()
response: MaintenanceResponseOption; response: InspectionResponseOption;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: description:
...@@ -39,9 +39,9 @@ export class CreateMaintenanceResponseDto { ...@@ -39,9 +39,9 @@ export class CreateMaintenanceResponseDto {
comment?: string; comment?: string;
} }
export class CreateMaintenanceDto { export class CreateInspectionDto {
@ApiProperty({ @ApiProperty({
description: 'Date when the maintenance was performed', description: 'Date when the inspection was performed',
example: '2025-05-21T13:00:00.000Z', example: '2025-05-21T13:00:00.000Z',
type: String, type: String,
}) })
...@@ -49,7 +49,7 @@ export class CreateMaintenanceDto { ...@@ -49,7 +49,7 @@ export class CreateMaintenanceDto {
date: string; date: string;
@ApiProperty({ @ApiProperty({
description: 'ID of the site where the maintenance was performed', description: 'ID of the site where the inspection was performed',
example: 1, example: 1,
type: Number, type: Number,
}) })
...@@ -57,8 +57,8 @@ export class CreateMaintenanceDto { ...@@ -57,8 +57,8 @@ export class CreateMaintenanceDto {
siteId: number; siteId: number;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Optional general comment about the maintenance', description: 'Optional general comment about the inspection',
example: 'Regular annual maintenance. Site is in good overall condition.', example: 'Regular annual inspection. Site is in good overall condition.',
type: String, type: String,
}) })
@IsString() @IsString()
...@@ -66,8 +66,8 @@ export class CreateMaintenanceDto { ...@@ -66,8 +66,8 @@ export class CreateMaintenanceDto {
comment?: string; comment?: string;
@ApiProperty({ @ApiProperty({
description: 'Responses to maintenance questions', description: 'Responses to inspection questions',
type: [CreateMaintenanceResponseDto], type: [CreateInspectionResponseDto],
example: [ example: [
{ {
questionId: 1, questionId: 1,
...@@ -87,6 +87,6 @@ export class CreateMaintenanceDto { ...@@ -87,6 +87,6 @@ export class CreateMaintenanceDto {
], ],
}) })
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => CreateMaintenanceResponseDto) @Type(() => CreateInspectionResponseDto)
responses: CreateMaintenanceResponseDto[]; responses: CreateInspectionResponseDto[];
} }
import { IsDateString, IsInt, IsOptional } from 'class-validator'; import { IsDateString, IsInt, IsOptional } from 'class-validator';
import { ApiPropertyOptional } from '@nestjs/swagger'; import { ApiPropertyOptional } from '@nestjs/swagger';
export class FindMaintenanceDto { export class FindInspectionDto {
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Filter maintenance records by site ID', description: 'Filter inspection records by site ID',
example: 1, example: 1,
type: Number, type: Number,
}) })
...@@ -13,7 +13,7 @@ export class FindMaintenanceDto { ...@@ -13,7 +13,7 @@ export class FindMaintenanceDto {
@ApiPropertyOptional({ @ApiPropertyOptional({
description: description:
'Filter maintenance records with date greater than or equal to this date', 'Filter inspection records with date greater than or equal to this date',
example: '2025-01-01T00:00:00.000Z', example: '2025-01-01T00:00:00.000Z',
type: String, type: String,
}) })
...@@ -23,7 +23,7 @@ export class FindMaintenanceDto { ...@@ -23,7 +23,7 @@ export class FindMaintenanceDto {
@ApiPropertyOptional({ @ApiPropertyOptional({
description: description:
'Filter maintenance records with date less than or equal to this date', 'Filter inspection records with date less than or equal to this date',
example: '2025-12-31T23:59:59.999Z', example: '2025-12-31T23:59:59.999Z',
type: String, type: String,
}) })
......
export * from './create-inspection.dto';
export * from './find-inspection.dto';
export * from './inspection-response.dto';
export * from './inspection-response-option.enum';
/** /**
* Response options for maintenance questions * Response options for inspection questions
* *
* YES - Item is in good condition/working properly * YES - Item is in good condition/working properly
* NO - Item needs attention/repair * NO - Item needs attention/repair
* NA - Not applicable for this site * NA - Not applicable for this site
*/ */
export enum MaintenanceResponseOption { export enum InspectionResponseOption {
YES = 'YES', YES = 'YES',
NO = 'NO', NO = 'NO',
NA = 'NA', NA = 'NA',
......
import { MaintenanceResponseOption } from './maintenance-response-option.enum'; import { InspectionResponseOption } from './inspection-response-option.enum';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class MaintenanceQuestionDto { export class InspectionQuestionDto {
@ApiProperty({ @ApiProperty({
description: 'Unique identifier of the maintenance question', description: 'Unique identifier of the inspection question',
example: 1, example: 1,
type: Number, type: Number,
}) })
id: number; id: number;
@ApiProperty({ @ApiProperty({
description: 'Text of the maintenance question', description: 'Text of the inspection question',
example: 'Site access condition', example: 'Site access condition',
type: String, type: String,
}) })
...@@ -24,9 +24,9 @@ export class MaintenanceQuestionDto { ...@@ -24,9 +24,9 @@ export class MaintenanceQuestionDto {
orderIndex: number; orderIndex: number;
} }
export class MaintenanceResponseDto { export class InspectionResponseDto {
@ApiProperty({ @ApiProperty({
description: 'Unique identifier of the maintenance response', description: 'Unique identifier of the inspection response',
example: 1, example: 1,
type: Number, type: Number,
}) })
...@@ -34,11 +34,11 @@ export class MaintenanceResponseDto { ...@@ -34,11 +34,11 @@ export class MaintenanceResponseDto {
@ApiProperty({ @ApiProperty({
description: 'Response option selected for the question', description: 'Response option selected for the question',
enum: MaintenanceResponseOption, enum: InspectionResponseOption,
example: MaintenanceResponseOption.YES, example: InspectionResponseOption.YES,
enumName: 'MaintenanceResponseOption', enumName: 'InspectionResponseOption',
}) })
response: MaintenanceResponseOption; response: InspectionResponseOption;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Optional comment providing additional details', description: 'Optional comment providing additional details',
...@@ -49,14 +49,14 @@ export class MaintenanceResponseDto { ...@@ -49,14 +49,14 @@ export class MaintenanceResponseDto {
@ApiProperty({ @ApiProperty({
description: 'The question this response answers', description: 'The question this response answers',
type: MaintenanceQuestionDto, type: InspectionQuestionDto,
}) })
question: MaintenanceQuestionDto; question: InspectionQuestionDto;
} }
export class MaintenancePhotoDto { export class InspectionPhotoDto {
@ApiProperty({ @ApiProperty({
description: 'Unique identifier of the maintenance photo', description: 'Unique identifier of the inspection photo',
example: 1, example: 1,
type: Number, type: Number,
}) })
...@@ -64,7 +64,7 @@ export class MaintenancePhotoDto { ...@@ -64,7 +64,7 @@ export class MaintenancePhotoDto {
@ApiProperty({ @ApiProperty({
description: 'URL to access the photo', description: 'URL to access the photo',
example: '/uploads/maintenance/1/photo1.jpg', example: '/uploads/inspection/1/photo1.jpg',
type: String, type: String,
}) })
url: string; url: string;
...@@ -77,30 +77,30 @@ export class MaintenancePhotoDto { ...@@ -77,30 +77,30 @@ export class MaintenancePhotoDto {
filename: string; filename: string;
} }
export class MaintenanceDto { export class InspectionDto {
@ApiProperty({ @ApiProperty({
description: 'Unique identifier of the maintenance record', description: 'Unique identifier of the inspection record',
example: 1, example: 1,
type: Number, type: Number,
}) })
id: number; id: number;
@ApiProperty({ @ApiProperty({
description: 'Date when the maintenance was performed', description: 'Date when the inspection was performed',
example: '2025-05-21T13:00:00.000Z', example: '2025-05-21T13:00:00.000Z',
type: Date, type: Date,
}) })
date: Date; date: Date;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Optional general comment about the maintenance', description: 'Optional general comment about the inspection',
example: 'Annual preventive maintenance completed with minor issues noted', example: 'Annual preventive inspection completed with minor issues noted',
type: String, type: String,
}) })
comment?: string; comment?: string;
@ApiProperty({ @ApiProperty({
description: 'ID of the site where maintenance was performed', description: 'ID of the site where inspection was performed',
example: 1, example: 1,
type: Number, type: Number,
}) })
...@@ -121,16 +121,16 @@ export class MaintenanceDto { ...@@ -121,16 +121,16 @@ export class MaintenanceDto {
updatedAt: Date; updatedAt: Date;
@ApiProperty({ @ApiProperty({
description: 'Responses to maintenance questions', description: 'Responses to inspection questions',
type: [MaintenanceResponseDto], type: [InspectionResponseDto],
isArray: true, isArray: true,
}) })
responses: MaintenanceResponseDto[]; responses: InspectionResponseDto[];
@ApiProperty({ @ApiProperty({
description: 'Photos attached to the maintenance record', description: 'Photos attached to the inspection record',
type: [MaintenancePhotoDto], type: [InspectionPhotoDto],
isArray: true, isArray: true,
}) })
photos: MaintenancePhotoDto[]; photos: InspectionPhotoDto[];
} }
/** /**
* Example requests and responses for the Maintenance API * Example requests and responses for the Inspection API
* This file is for documentation purposes only * This file is for documentation purposes only
*/ */
/** /**
* Example request for creating a maintenance record * Example request for creating a inspection record
*/ */
export const createMaintenanceExample = { export const createInspectionExample = {
date: '2025-05-21T13:00:00.000Z', date: '2025-05-21T13:00:00.000Z',
siteId: 1, siteId: 1,
comment: 'Regular annual maintenance. Site is in good overall condition.', comment: 'Regular annual inspection. Site is in good overall condition.',
responses: [ responses: [
{ {
questionId: 1, questionId: 1,
...@@ -41,12 +41,12 @@ export const createMaintenanceExample = { ...@@ -41,12 +41,12 @@ export const createMaintenanceExample = {
}; };
/** /**
* Example response for a maintenance record * Example response for a inspection record
*/ */
export const maintenanceResponseExample = { export const inspectionResponseExample = {
id: 1, id: 1,
date: '2025-05-21T13:00:00.000Z', date: '2025-05-21T13:00:00.000Z',
comment: 'Regular annual maintenance. Site is in good overall condition.', comment: 'Regular annual inspection. Site is in good overall condition.',
siteId: 1, siteId: 1,
createdAt: '2025-05-21T13:15:30.000Z', createdAt: '2025-05-21T13:15:30.000Z',
updatedAt: '2025-05-21T13:15:30.000Z', updatedAt: '2025-05-21T13:15:30.000Z',
...@@ -106,26 +106,26 @@ export const maintenanceResponseExample = { ...@@ -106,26 +106,26 @@ export const maintenanceResponseExample = {
photos: [ photos: [
{ {
id: 1, id: 1,
url: '/uploads/maintenance/1/entrance.jpg', url: '/uploads/inspection/1/entrance.jpg',
filename: 'entrance.jpg', filename: 'entrance.jpg',
}, },
{ {
id: 2, id: 2,
url: '/uploads/maintenance/1/damaged_fence.jpg', url: '/uploads/inspection/1/damaged_fence.jpg',
filename: 'damaged_fence.jpg', filename: 'damaged_fence.jpg',
}, },
{ {
id: 3, id: 3,
url: '/uploads/maintenance/1/equipment.jpg', url: '/uploads/inspection/1/equipment.jpg',
filename: 'equipment.jpg', filename: 'equipment.jpg',
}, },
], ],
}; };
/** /**
* Example response for maintenance questions * Example response for inspection questions
*/ */
export const maintenanceQuestionsExample = [ export const inspectionQuestionsExample = [
{ {
id: 1, id: 1,
question: 'Site access condition', question: 'Site access condition',
......
import { Module } from '@nestjs/common';
import { InspectionController } from './inspection.controller';
import { InspectionService } from './inspection.service';
import { PrismaModule } from '../../common/prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [InspectionController],
providers: [InspectionService],
exports: [InspectionService],
})
export class InspectionModule {}
...@@ -6,14 +6,14 @@ const mkdir = promisify(fs.mkdir); ...@@ -6,14 +6,14 @@ const mkdir = promisify(fs.mkdir);
const writeFile = promisify(fs.writeFile); const writeFile = promisify(fs.writeFile);
/** /**
* Saves uploaded maintenance photos to the file system * Saves uploaded inspection photos to the file system
* @param files Array of uploaded files * @param files Array of uploaded files
* @param maintenanceId The ID of the maintenance record * @param inspectionId The ID of the inspection record
* @returns Array of saved file paths * @returns Array of saved file paths
*/ */
export async function saveMaintenancePhotos( export async function saveInspectionPhotos(
files: Express.Multer.File[], files: Express.Multer.File[],
maintenanceId: number, inspectionId: number,
): Promise<string[]> { ): Promise<string[]> {
if (!files || files.length === 0) { if (!files || files.length === 0) {
return []; return [];
...@@ -21,12 +21,12 @@ export async function saveMaintenancePhotos( ...@@ -21,12 +21,12 @@ export async function saveMaintenancePhotos(
const uploadDir = const uploadDir =
process.env.NODE_ENV === 'production' process.env.NODE_ENV === 'production'
? `/home/api-cellnex/public_html/uploads/maintenance/${maintenanceId}` ? `/home/api-cellnex/public_html/uploads/inspection/${inspectionId}`
: path.join( : path.join(
process.cwd(), process.cwd(),
'uploads', 'uploads',
'maintenance', 'inspection',
maintenanceId.toString(), inspectionId.toString(),
); );
// Create directory if it doesn't exist // Create directory if it doesn't exist
...@@ -45,7 +45,7 @@ export async function saveMaintenancePhotos( ...@@ -45,7 +45,7 @@ export async function saveMaintenancePhotos(
try { try {
await writeFile(filePath, file.buffer); await writeFile(filePath, file.buffer);
savedPaths.push(`/uploads/maintenance/${maintenanceId}/${filename}`); savedPaths.push(`/uploads/inspection/${inspectionId}/${filename}`);
} catch (error) { } catch (error) {
console.error(`Error saving file ${filename}:`, error); console.error(`Error saving file ${filename}:`, error);
throw new Error(`Failed to save file ${filename}: ${error.message}`); throw new Error(`Failed to save file ${filename}: ${error.message}`);
......
export * from './create-maintenance.dto';
export * from './find-maintenance.dto';
export * from './maintenance-response.dto';
export * from './maintenance-response-option.enum';
import { Module } from '@nestjs/common';
import { MaintenanceController } from './maintenance.controller';
import { MaintenanceService } from './maintenance.service';
import { PrismaModule } from '../../common/prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [MaintenanceController],
providers: [MaintenanceService],
exports: [MaintenanceService],
})
export class MaintenanceModule {}
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