anexado guardar en minio y cambios generales en la interfaz de osp

This commit is contained in:
2026-02-24 11:00:50 -04:00
parent fed90d9ff1
commit c70e146ce2
22 changed files with 5139 additions and 696 deletions

View File

@@ -17,3 +17,10 @@ DATABASE_URL="postgresql://postgres:local**@localhost:5432/caja_ahorro" #url con
MAIL_HOST=gmail
MAIL_USERNAME=
MAIL_PASSWORD=
MINIO_ENDPOINT=
MINIO_PORT=
MINIO_ACCESS_KEY=
MINIO_SECRET_KEY=
MINIO_BUCKET=
MINIO_USE_SSL=

View File

@@ -44,6 +44,7 @@
"drizzle-orm": "0.40.0",
"express": "5.1.0",
"joi": "17.13.3",
"minio": "^8.0.6",
"moment": "2.30.1",
"path-to-regexp": "8.2.0",
"pg": "8.13.3",

View File

@@ -10,16 +10,17 @@ import { ConfigModule } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import { JwtModule } from '@nestjs/jwt';
import { ThrottlerGuard } from '@nestjs/throttler';
import { MinioModule } from './common/minio/minio.module';
import { DrizzleModule } from './database/drizzle.module';
import { AuthModule } from './features/auth/auth.module';
import { ConfigurationsModule } from './features/configurations/configurations.module';
import { LocationModule } from './features/location/location.module'
import { InventoryModule } from './features/inventory/inventory.module';
import { LocationModule } from './features/location/location.module';
import { MailModule } from './features/mail/mail.module';
import { RolesModule } from './features/roles/roles.module';
import { UserRolesModule } from './features/user-roles/user-roles.module';
import { SurveysModule } from './features/surveys/surveys.module';
import { InventoryModule } from './features/inventory/inventory.module';
import { TrainingModule } from './features/training/training.module';
import { UserRolesModule } from './features/user-roles/user-roles.module';
@Module({
providers: [
@@ -51,6 +52,7 @@ import { TrainingModule } from './features/training/training.module';
NodeMailerModule,
LoggerModule,
ThrottleModule,
MinioModule,
UsersModule,
AuthModule,
MailModule,
@@ -61,7 +63,7 @@ import { TrainingModule } from './features/training/training.module';
SurveysModule,
LocationModule,
InventoryModule,
TrainingModule
TrainingModule,
],
})
export class AppModule { }
export class AppModule {}

View File

@@ -14,6 +14,12 @@ interface EnvVars {
MAIL_HOST: string;
MAIL_USERNAME: string;
MAIL_PASSWORD: string;
MINIO_ENDPOINT: string;
MINIO_PORT: number;
MINIO_ACCESS_KEY: string;
MINIO_SECRET_KEY: string;
MINIO_BUCKET: string;
MINIO_USE_SSL: boolean;
}
const envsSchema = joi
@@ -30,6 +36,12 @@ const envsSchema = joi
MAIL_HOST: joi.string(),
MAIL_USERNAME: joi.string(),
MAIL_PASSWORD: joi.string(),
MINIO_ENDPOINT: joi.string().required(),
MINIO_PORT: joi.number().required(),
MINIO_ACCESS_KEY: joi.string().required(),
MINIO_SECRET_KEY: joi.string().required(),
MINIO_BUCKET: joi.string().required(),
MINIO_USE_SSL: joi.boolean().default(false),
})
.unknown(true);
@@ -54,4 +66,10 @@ export const envs = {
mail_host: envVars.MAIL_HOST,
mail_username: envVars.MAIL_USERNAME,
mail_password: envVars.MAIL_PASSWORD,
minio_endpoint: envVars.MINIO_ENDPOINT,
minio_port: envVars.MINIO_PORT,
minio_access_key: envVars.MINIO_ACCESS_KEY,
minio_secret_key: envVars.MINIO_SECRET_KEY,
minio_bucket: envVars.MINIO_BUCKET,
minio_use_ssl: envVars.MINIO_USE_SSL,
};

View File

@@ -0,0 +1,9 @@
import { Global, Module } from '@nestjs/common';
import { MinioService } from './minio.service';
@Global()
@Module({
providers: [MinioService],
exports: [MinioService],
})
export class MinioModule {}

View File

@@ -0,0 +1,118 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import * as Minio from 'minio';
import { envs } from '../config/envs';
@Injectable()
export class MinioService implements OnModuleInit {
private readonly minioClient: Minio.Client;
private readonly logger = new Logger(MinioService.name);
private readonly bucketName = envs.minio_bucket;
constructor() {
this.minioClient = new Minio.Client({
endPoint: envs.minio_endpoint,
port: envs.minio_port,
useSSL: envs.minio_use_ssl,
accessKey: envs.minio_access_key,
secretKey: envs.minio_secret_key,
});
}
async onModuleInit() {
await this.ensureBucketExists();
}
private async ensureBucketExists() {
// Ejecuta esto siempre al menos una vez para asegurar que sea público
const policy = {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: { AWS: ['*'] },
Action: ['s3:GetObject'],
Resource: [`arn:aws:s3:::${this.bucketName}/*`],
},
],
};
try {
// const bucketExists = await this.minioClient.bucketExists(this.bucketName);
// if (!bucketExists) {
// await this.minioClient.makeBucket(this.bucketName);
// }
await this.minioClient.setBucketPolicy(
this.bucketName,
JSON.stringify(policy),
);
this.logger.log(`Public policy ensured for bucket "${this.bucketName}"`);
} catch (error: any) {
this.logger.error(`Error checking/creating bucket: ${error.message}`);
}
}
async upload(
file: Express.Multer.File,
folder: string = 'general',
): Promise<string> {
const fileName = `${Date.now()}-${Math.round(Math.random() * 1e9)}-${file.originalname.replace(/\s/g, '_')}`;
const objectName = `${folder}/${fileName}`;
try {
await this.minioClient.putObject(
this.bucketName,
objectName,
file.buffer,
file.size,
{
'Content-Type': file.mimetype,
},
);
// Return the URL or the object path.
// Usually, we store the object path and generate a signed URL or use a proxy.
// The user asked for the URL to be stored in the database.
return objectName;
} catch (error: any) {
this.logger.error(`Error uploading file: ${error.message}`);
throw error;
}
}
async getFileUrl(objectName: string): Promise<string> {
try {
// If the bucket is public, we can just return the URL.
// If private, we need a signed URL.
// For simplicity and common use cases in these projects, I'll generate a signed URL with a long expiration
// or assume there is some way to access it.
// But let's use signed URL for 1 week (maximum is 7 days) if needed,
// or just return the object name if the backend handles the serving.
// The user wants the URL stored in the DB.
return await this.minioClient.presignedUrl(
'GET',
this.bucketName,
objectName,
604800,
);
} catch (error: any) {
this.logger.error(`Error getting file URL: ${error.message}`);
throw error;
}
}
getPublicUrl(objectName: string): string {
const protocol = envs.minio_use_ssl ? 'https' : 'http';
return `${protocol}://${envs.minio_endpoint}:${envs.minio_port}/${this.bucketName}/${objectName}`;
}
async delete(objectName: string): Promise<void> {
try {
await this.minioClient.removeObject(this.bucketName, objectName);
this.logger.log(`Object "${objectName}" deleted successfully.`);
} catch (error: any) {
this.logger.error(`Error deleting file: ${error.message}`);
// We don't necessarily want to throw if the file is already gone
}
}
}

View File

@@ -0,0 +1,5 @@
ALTER TABLE "training_surveys" ALTER COLUMN "osp_responsible_rif" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "civil_state" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "osp_responsible_email" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "family_burden" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "number_of_children" DROP NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "training_surveys" ALTER COLUMN "osp_rif" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "osp_name" DROP NOT NULL;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -141,6 +141,20 @@
"when": 1771858973096,
"tag": "0019_cuddly_cobalt_man",
"breakpoints": true
},
{
"idx": 20,
"version": "7",
"when": 1771897944334,
"tag": "0020_certain_bushwacker",
"breakpoints": true
},
{
"idx": 21,
"version": "7",
"when": 1771901546945,
"tag": "0021_warm_machine_man",
"breakpoints": true
}
]
}

View File

@@ -77,8 +77,8 @@ export const trainingSurveys = t.pgTable(
.notNull()
.default(''),
productiveActivity: t.text('productive_activity').notNull(),
ospRif: t.text('osp_rif').notNull(),
ospName: t.text('osp_name').notNull(),
ospRif: t.text('osp_rif'),
ospName: t.text('osp_name'),
companyConstitutionYear: t.integer('company_constitution_year').notNull(),
currentStatus: t.text('current_status').notNull().default('ACTIVA'),
infrastructureMt2: t.text('infrastructure_mt2').notNull().default(''),
@@ -98,10 +98,8 @@ export const trainingSurveys = t.pgTable(
.text('commune_spokesperson_name')
.notNull()
.default(''),
communeSpokespersonCedula: t
.text('commune_spokesperson_cedula'),
communeSpokespersonRif: t
.text('commune_spokesperson_rif'),
communeSpokespersonCedula: t.text('commune_spokesperson_cedula'),
communeSpokespersonRif: t.text('commune_spokesperson_rif'),
communeSpokespersonPhone: t
.text('commune_spokesperson_phone')
.notNull()
@@ -114,10 +112,10 @@ export const trainingSurveys = t.pgTable(
.text('communal_council_spokesperson_name')
.notNull()
.default(''),
communalCouncilSpokespersonCedula: t
.text('communal_council_spokesperson_cedula'),
communalCouncilSpokespersonRif: t
.text('communal_council_spokesperson_rif'),
communalCouncilSpokespersonCedula: t.text(
'communal_council_spokesperson_cedula',
),
communalCouncilSpokespersonRif: t.text('communal_council_spokesperson_rif'),
communalCouncilSpokespersonPhone: t
.text('communal_council_spokesperson_phone')
.notNull()
@@ -128,20 +126,24 @@ export const trainingSurveys = t.pgTable(
.default(''),
ospResponsibleFullname: t.text('osp_responsible_fullname').notNull(),
ospResponsibleCedula: t.text('osp_responsible_cedula').notNull(),
ospResponsibleRif: t.text('osp_responsible_rif').notNull(),
civilState: t.text('civil_state').notNull(),
ospResponsibleRif: t.text('osp_responsible_rif'),
civilState: t.text('civil_state'),
ospResponsiblePhone: t.text('osp_responsible_phone').notNull(),
ospResponsibleEmail: t.text('osp_responsible_email').notNull(),
familyBurden: t.integer('family_burden').notNull(),
numberOfChildren: t.integer('number_of_children').notNull(),
ospResponsibleEmail: t.text('osp_responsible_email'),
familyBurden: t.integer('family_burden'),
numberOfChildren: t.integer('number_of_children'),
generalObservations: t.text('general_observations'),
// Fotos
photo1: t.text('photo1'),
photo2: t.text('photo2'),
photo3: t.text('photo3'),
// informacion del usuario que creo y actualizo el registro
createdBy: t.integer('created_by').references(() => users.id, { onDelete: 'cascade' }),
updatedBy: t.integer('updated_by').references(() => users.id, { onDelete: 'cascade' }),
createdBy: t
.integer('created_by')
.references(() => users.id, { onDelete: 'cascade' }),
updatedBy: t
.integer('updated_by')
.references(() => users.id, { onDelete: 'cascade' }),
...timestamps,
},
(trainingSurveys) => ({

View File

@@ -4,9 +4,11 @@ import {
IsArray,
IsBoolean,
IsDateString,
IsEmail,
IsInt,
IsOptional,
IsString,
ValidateIf,
} from 'class-validator';
export class CreateTrainingDto {
@@ -124,6 +126,7 @@ export class CreateTrainingDto {
@ApiProperty()
@IsString()
@IsOptional()
ospResponsibleRif: string;
@ApiProperty()
@@ -131,20 +134,25 @@ export class CreateTrainingDto {
ospResponsiblePhone: string;
@ApiProperty()
@IsString()
ospResponsibleEmail: string;
@IsOptional()
@ValidateIf((o, v) => v !== '' && v !== null && v !== undefined)
@IsEmail()
ospResponsibleEmail?: string;
@ApiProperty()
@IsString()
@IsOptional()
civilState: string;
@ApiProperty()
@IsInt()
@IsOptional()
@Type(() => Number) // Convierte "3" -> 3
familyBurden: number;
@ApiProperty()
@IsInt()
@IsOptional()
@Type(() => Number)
numberOfChildren: number;
@@ -165,14 +173,15 @@ export class CreateTrainingDto {
@IsString()
communeSpokespersonName: string;
@ApiProperty()
@IsString()
communeSpokespersonPhone: string;
@ApiProperty()
@IsOptional()
communeEmail: string;
@ValidateIf((o, v) => v !== '' && v !== null && v !== undefined)
@IsEmail()
communeEmail?: string;
@ApiProperty()
@IsString()
@@ -195,10 +204,10 @@ export class CreateTrainingDto {
communalCouncilSpokespersonPhone: string;
@ApiProperty()
@IsString()
communalCouncilEmail: string;
@IsOptional()
@ValidateIf((o, v) => v !== '' && v !== null && v !== undefined)
@IsEmail()
communalCouncilEmail?: string;
// === 6. LISTAS (Arrays JSON) ===
// Reciben un string JSON '[{...}]' y lo convierten a Objeto JS real
@@ -248,13 +257,11 @@ export class CreateTrainingDto {
})
productList?: any[];
//ubicacion
//ubicacion
@ApiProperty()
@IsString()
state: string;
@ApiProperty()
@IsString()
municipality: string;

View File

@@ -7,12 +7,9 @@ import {
Patch,
Post,
Query,
Res,
Req,
UploadedFiles,
UseInterceptors,
StreamableFile,
Header,
Req
} from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';
import {
@@ -27,30 +24,29 @@ import { CreateTrainingDto } from './dto/create-training.dto';
import { TrainingStatisticsFilterDto } from './dto/training-statistics-filter.dto';
import { UpdateTrainingDto } from './dto/update-training.dto';
import { TrainingService } from './training.service';
import { Public } from '@/common/decorators';
@ApiTags('training')
@Controller('training')
export class TrainingController {
constructor(private readonly trainingService: TrainingService) { }
constructor(private readonly trainingService: TrainingService) {}
@Public()
@Get('export/:id')
@ApiOperation({ summary: 'Export training template' })
@ApiResponse({
status: 200,
description: 'Return training template.',
content: { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { schema: { type: 'string', format: 'binary' } } }
})
@Header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
@Header('Content-Disposition', 'attachment; filename=export_osp.xlsx')
async exportTemplate(@Param('id') id: string) {
if (!Number(id)) {
throw new Error('ID is required');
}
const data = await this.trainingService.exportTemplate(Number(id));
return new StreamableFile(data);
}
// @Public()
// @Get('export/:id')
// @ApiOperation({ summary: 'Export training template' })
// @ApiResponse({
// status: 200,
// description: 'Return training template.',
// content: { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { schema: { type: 'string', format: 'binary' } } }
// })
// @Header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
// @Header('Content-Disposition', 'attachment; filename=export_osp.xlsx')
// async exportTemplate(@Param('id') id: string) {
// if (!Number(id)) {
// throw new Error('ID is required');
// }
// const data = await this.trainingService.exportTemplate(Number(id));
// return new StreamableFile(data);
// }
@Get()
@ApiOperation({
@@ -100,7 +96,11 @@ export class TrainingController {
@UploadedFiles(ImageProcessingPipe) files: Express.Multer.File[],
) {
const userId = (req as any).user?.id;
const data = await this.trainingService.create(createTrainingDto, files, userId);
const data = await this.trainingService.create(
createTrainingDto,
files,
userId,
);
return { message: 'Training record created successfully', data };
}

View File

@@ -1,12 +1,11 @@
import { HttpException, HttpStatus, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { MinioService } from '@/common/minio/minio.service';
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { and, eq, gte, ilike, lte, or, SQL, sql } from 'drizzle-orm';
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import * as fs from 'fs';
import * as path from 'path';
import { DRIZZLE_PROVIDER } from 'src/database/drizzle-provider';
import * as schema from 'src/database/index';
import { municipalities, parishes, states, trainingSurveys } from 'src/database/index';
import XlsxPopulate from 'xlsx-populate';
import { states, trainingSurveys } from 'src/database/index';
import { PaginationDto } from '../../common/dto/pagination.dto';
import { CreateTrainingDto } from './dto/create-training.dto';
import { TrainingStatisticsFilterDto } from './dto/training-statistics-filter.dto';
@@ -16,7 +15,8 @@ import { UpdateTrainingDto } from './dto/update-training.dto';
export class TrainingService {
constructor(
@Inject(DRIZZLE_PROVIDER) private drizzle: NodePgDatabase<typeof schema>,
) { }
private readonly minioService: MinioService,
) {}
async findAll(paginationDto?: PaginationDto) {
const {
@@ -232,33 +232,33 @@ export class TrainingService {
private async saveFiles(files: Express.Multer.File[]): Promise<string[]> {
if (!files || files.length === 0) return [];
const uploadDir = './uploads/training';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const savedPaths: string[] = [];
for (const file of files) {
const fileName = `${Date.now()}-${Math.round(Math.random() * 1e9)}${path.extname(file.originalname)}`;
const filePath = path.join(uploadDir, fileName);
fs.writeFileSync(filePath, file.buffer);
savedPaths.push(`/assets/training/${fileName}`);
const objectName = await this.minioService.upload(file, 'training');
const fileUrl = this.minioService.getPublicUrl(objectName);
savedPaths.push(fileUrl);
}
return savedPaths;
}
private deleteFile(assetPath: string) {
if (!assetPath) return;
// Map /assets/training/filename.webp back to ./uploads/training/filename.webp
const relativePath = assetPath.replace('/assets/training/', '');
const fullPath = path.join('./uploads/training', relativePath);
private async deleteFile(fileUrl: string) {
if (!fileUrl) return;
if (fs.existsSync(fullPath)) {
try {
fs.unlinkSync(fullPath);
} catch (err) {
console.error(`Error deleting file ${fullPath}:`, err);
}
// Extract object name from URL
// URL format: http://endpoint:port/bucket/folder/filename
// Or it could be just the path if we decided that.
// Assuming fileUrl is the full public URL from getPublicUrl
try {
const url = new URL(fileUrl);
const pathname = url.pathname; // /bucket/folder/filename
const parts = pathname.split('/');
// parts[0] is '', parts[1] is bucket, parts[2..] is objectName
const objectName = parts.slice(2).join('/');
await this.minioService.delete(objectName);
} catch (error) {
// If it's not a valid URL, maybe it's just the object name stored from before
await this.minioService.delete(fileUrl);
}
}
@@ -268,11 +268,13 @@ export class TrainingService {
userId: number,
) {
// 1. Guardar fotos
const photoPaths = await this.saveFiles(files);
// 2. Extraer solo visitDate para formatearlo.
// Ya NO extraemos state, municipality, etc. porque no vienen en el DTO.
const { visitDate, state, municipality, parish, ...rest } = createTrainingDto;
const { visitDate, state, municipality, parish, ...rest } =
createTrainingDto;
const [newRecord] = await this.drizzle
.insert(trainingSurveys)
@@ -305,45 +307,48 @@ export class TrainingService {
userId: number,
) {
const currentRecord = await this.findOne(id);
const photoFields = ['photo1', 'photo2', 'photo3'] as const;
const photoPaths = await this.saveFiles(files);
// 1. Guardar fotos nuevas en MinIO
const newFilePaths = await this.saveFiles(files);
const updateData: any = { ...updateTrainingDto };
// Handle photo updates/removals
const photoFields = ['photo1', 'photo2', 'photo3'] as const;
// 1. First, handle explicit deletions (where field is '')
photoFields.forEach((field) => {
if (updateData[field] === '') {
const oldPath = currentRecord[field];
if (oldPath) this.deleteFile(oldPath);
updateData[field] = null;
// 2. Determinar el estado final de las fotos (diff)
// - Si el DTO tiene un valor (URL existente o ''), lo usamos.
// - Si el DTO no tiene el campo (undefined), mantenemos el de la DB.
const finalPhotos: (string | null)[] = photoFields.map((field) => {
const dtoValue = updateData[field];
if (dtoValue !== undefined) {
return dtoValue === '' ? null : dtoValue;
}
return currentRecord[field];
});
// 2. We need to find which slots are currently "available" (null) after deletions
// and which ones have existing URLs that we want to keep.
// Let's determine the final state of the 3 slots.
const finalPhotos: (string | null)[] = [
updateData.photo1 !== undefined ? updateData.photo1 : currentRecord.photo1,
updateData.photo2 !== undefined ? updateData.photo2 : currentRecord.photo2,
updateData.photo3 !== undefined ? updateData.photo3 : currentRecord.photo3,
];
// 3. Fill the available (null) slots with NEW photo paths
if (photoPaths.length > 0) {
let photoPathIdx = 0;
for (let i = 0; i < 3 && photoPathIdx < photoPaths.length; i++) {
// 3. Asignar los nuevos paths subidos a los slots que quedaron vacíos
if (newFilePaths.length > 0) {
let newIdx = 0;
for (let i = 0; i < 3 && newIdx < newFilePaths.length; i++) {
if (!finalPhotos[i]) {
finalPhotos[i] = photoPaths[photoPathIdx];
photoPathIdx++;
finalPhotos[i] = newFilePaths[newIdx];
newIdx++;
}
}
}
// Assign back to updateData
// 4. LIMPIEZA: Borrar de MinIO los archivos que ya no están en ningún slot
const oldPhotos = photoFields
.map((f) => currentRecord[f])
.filter((p): p is string => Boolean(p));
const newPhotosSet = new Set(finalPhotos.filter(Boolean));
for (const oldPath of oldPhotos) {
if (!newPhotosSet.has(oldPath)) {
await this.deleteFile(oldPath);
}
}
// 5. Preparar datos finales para la DB
updateData.photo1 = finalPhotos[0];
updateData.photo2 = finalPhotos[1];
updateData.photo3 = finalPhotos[2];
@@ -368,9 +373,9 @@ export class TrainingService {
const record = await this.findOne(id);
// Delete associated files
if (record.photo1) this.deleteFile(record.photo1);
if (record.photo2) this.deleteFile(record.photo2);
if (record.photo3) this.deleteFile(record.photo3);
if (record.photo1) await this.deleteFile(record.photo1);
if (record.photo2) await this.deleteFile(record.photo2);
if (record.photo3) await this.deleteFile(record.photo3);
const [deletedRecord] = await this.drizzle
.delete(trainingSurveys)
@@ -499,139 +504,182 @@ export class TrainingService {
// return await workbook.outputAsync();
// }
async exportTemplate(id: number) {
// async exportTemplate(id: number) {
// // Validar que el registro exista
// const exist = await this.findOne(id);
// if (!exist) throw new NotFoundException(`No se encontro el registro`);
// Validar que el registro exista
const exist = await this.findOne(id);
if (!exist) throw new NotFoundException(`No se encontro el registro`);
// // Obtener los datos del registro
// const records = await this.drizzle
// .select({
// // id: trainingSurveys.id,
// visitDate: trainingSurveys.visitDate,
// ospName: trainingSurveys.ospName,
// productiveSector: trainingSurveys.productiveSector,
// ospAddress: trainingSurveys.ospAddress,
// ospRif: trainingSurveys.ospRif,
// Obtener los datos del registro
const records = await this.drizzle
.select({
// id: trainingSurveys.id,
visitDate: trainingSurveys.visitDate,
ospName: trainingSurveys.ospName,
productiveSector: trainingSurveys.productiveSector,
ospAddress: trainingSurveys.ospAddress,
ospRif: trainingSurveys.ospRif,
// siturCodeCommune: trainingSurveys.siturCodeCommune,
// communeEmail: trainingSurveys.communeEmail,
// communeRif: trainingSurveys.communeRif,
// communeSpokespersonName: trainingSurveys.communeSpokespersonName,
// communeSpokespersonPhone: trainingSurveys.communeSpokespersonPhone,
siturCodeCommune: trainingSurveys.siturCodeCommune,
communeEmail: trainingSurveys.communeEmail,
communeRif: trainingSurveys.communeRif,
communeSpokespersonName: trainingSurveys.communeSpokespersonName,
communeSpokespersonPhone: trainingSurveys.communeSpokespersonPhone,
// siturCodeCommunalCouncil: trainingSurveys.siturCodeCommunalCouncil,
// communalCouncilRif: trainingSurveys.communalCouncilRif,
// communalCouncilSpokespersonName:
// trainingSurveys.communalCouncilSpokespersonName,
// communalCouncilSpokespersonPhone:
// trainingSurveys.communalCouncilSpokespersonPhone,
siturCodeCommunalCouncil: trainingSurveys.siturCodeCommunalCouncil,
communalCouncilRif: trainingSurveys.communalCouncilRif,
communalCouncilSpokespersonName: trainingSurveys.communalCouncilSpokespersonName,
communalCouncilSpokespersonPhone: trainingSurveys.communalCouncilSpokespersonPhone,
// ospType: trainingSurveys.ospType,
// productiveActivity: trainingSurveys.productiveActivity, // Sector Productivo
// companyConstitutionYear: trainingSurveys.companyConstitutionYear,
// infrastructureMt2: trainingSurveys.infrastructureMt2,
ospType: trainingSurveys.ospType,
productiveActivity: trainingSurveys.productiveActivity, // Sector Productivo
companyConstitutionYear: trainingSurveys.companyConstitutionYear,
infrastructureMt2: trainingSurveys.infrastructureMt2,
// hasTransport: trainingSurveys.hasTransport,
// structureType: trainingSurveys.structureType,
// isOpenSpace: trainingSurveys.isOpenSpace,
hasTransport: trainingSurveys.hasTransport,
structureType: trainingSurveys.structureType,
isOpenSpace: trainingSurveys.isOpenSpace,
// ospResponsibleFullname: trainingSurveys.ospResponsibleFullname,
// ospResponsibleCedula: trainingSurveys.ospResponsibleCedula,
// ospResponsiblePhone: trainingSurveys.ospResponsiblePhone,
ospResponsibleFullname: trainingSurveys.ospResponsibleFullname,
ospResponsibleCedula: trainingSurveys.ospResponsibleCedula,
ospResponsiblePhone: trainingSurveys.ospResponsiblePhone,
// productList: trainingSurveys.productList,
// equipmentList: trainingSurveys.equipmentList,
// productionList: trainingSurveys.productionList,
productList: trainingSurveys.productList,
equipmentList: trainingSurveys.equipmentList,
productionList: trainingSurveys.productionList,
// // photo1: trainingSurveys.photo1
// })
// .from(trainingSurveys)
// .where(eq(trainingSurveys.id, id));
// // .leftJoin(states, eq(trainingSurveys.state, states.id))
// // .leftJoin(municipalities,eq(trainingSurveys.municipality, municipalities.id))
// // .leftJoin(parishes, eq(trainingSurveys.parish, parishes.id))
// photo1: trainingSurveys.photo1
})
.from(trainingSurveys)
.where(eq(trainingSurveys.id, id))
// .leftJoin(states, eq(trainingSurveys.state, states.id))
// .leftJoin(municipalities,eq(trainingSurveys.municipality, municipalities.id))
// .leftJoin(parishes, eq(trainingSurveys.parish, parishes.id))
// let equipmentList: any[] = Array.isArray(records[0].equipmentList)
// ? records[0].equipmentList
// : [];
// let productList: any[] = Array.isArray(records[0].productList)
// ? records[0].productList
// : [];
// let productionList: any[] = Array.isArray(records[0].productionList)
// ? records[0].productionList
// : [];
let equipmentList: any[] = Array.isArray(records[0].equipmentList) ? records[0].equipmentList : [];
let productList: any[] = Array.isArray(records[0].productList) ? records[0].productList : [];
let productionList: any[] = Array.isArray(records[0].productionList) ? records[0].productionList : [];
// console.log('equipmentList', equipmentList);
// console.log('productList', productList);
// console.log('productionList', productionList);
console.log('equipmentList', equipmentList);
console.log('productList', productList);
console.log('productionList', productionList);
// let equipmentListArray: any[] = [];
// let productListArray: any[] = [];
// let productionListArray: any[] = [];
let equipmentListArray: any[] = [];
let productListArray: any[] = [];
let productionListArray: any[] = [];
// const equipmentListCount = equipmentList.length;
// for (let i = 0; i < equipmentListCount; i++) {
// equipmentListArray.push([
// equipmentList[i].machine,
// '',
// equipmentList[i].quantity,
// ]);
// }
const equipmentListCount = equipmentList.length;
for (let i = 0; i < equipmentListCount; i++) {
equipmentListArray.push([equipmentList[i].machine, '', equipmentList[i].quantity]);
}
// const productListCount = productList.length;
// for (let i = 0; i < productListCount; i++) {
// productListArray.push([
// productList[i].productName,
// productList[i].dailyCount,
// productList[i].weeklyCount,
// productList[i].monthlyCount,
// ]);
// }
const productListCount = productList.length;
for (let i = 0; i < productListCount; i++) {
productListArray.push([productList[i].productName, productList[i].dailyCount, productList[i].weeklyCount, productList[i].monthlyCount]);
}
// const productionListCount = productionList.length;
// for (let i = 0; i < productionListCount; i++) {
// productionListArray.push([
// productionList[i].rawMaterial,
// '',
// productionList[i].quantity,
// ]);
// }
const productionListCount = productionList.length;
for (let i = 0; i < productionListCount; i++) {
productionListArray.push([productionList[i].rawMaterial, '', productionList[i].quantity]);
}
// // Ruta de la plantilla
// const templatePath = path.join(
// __dirname,
// 'export_template',
// 'excel.osp.xlsx',
// );
// Ruta de la plantilla
const templatePath = path.join(
__dirname,
'export_template',
'excel.osp.xlsx',
);
// // Cargar la plantilla
// const book = await XlsxPopulate.fromFileAsync(templatePath);
// Cargar la plantilla
const book = await XlsxPopulate.fromFileAsync(templatePath);
// const isoString = records[0].visitDate;
// const dateObj = new Date(isoString);
// const fechaFormateada = dateObj.toLocaleDateString('es-ES');
// const horaFormateada = dateObj.toLocaleTimeString('es-ES', {
// hour: '2-digit',
// minute: '2-digit',
// });
const isoString = records[0].visitDate;
const dateObj = new Date(isoString);
const fechaFormateada = dateObj.toLocaleDateString('es-ES');
const horaFormateada = dateObj.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' });
// // Llenar los datos
// book.sheet(0).cell('A6').value(records[0].productiveSector);
// book.sheet(0).cell('D6').value(records[0].ospName);
// book.sheet(0).cell('L5').value(fechaFormateada);
// book.sheet(0).cell('L6').value(horaFormateada);
// book.sheet(0).cell('B10').value(records[0].ospAddress);
// book.sheet(0).cell('C11').value(records[0].communeEmail);
// book.sheet(0).cell('C12').value(records[0].communeSpokespersonName);
// book.sheet(0).cell('G11').value(records[0].communeRif);
// book.sheet(0).cell('G12').value(records[0].communeSpokespersonPhone);
// book.sheet(0).cell('C13').value(records[0].siturCodeCommune);
// book.sheet(0).cell('G13').value(records[0].siturCodeCommunalCouncil);
// book.sheet(0).cell('G14').value(records[0].communalCouncilRif);
// book.sheet(0).cell('C15').value(records[0].communalCouncilSpokespersonName);
// book
// .sheet(0)
// .cell('G15')
// .value(records[0].communalCouncilSpokespersonPhone);
// book.sheet(0).cell('C16').value(records[0].ospType);
// book.sheet(0).cell('C17').value(records[0].ospName);
// book.sheet(0).cell('C18').value(records[0].productiveActivity);
// book.sheet(0).cell('C19').value('Proveedores');
// book.sheet(0).cell('C20').value(records[0].companyConstitutionYear);
// book.sheet(0).cell('C21').value(records[0].infrastructureMt2);
// book.sheet(0).cell('G17').value(records[0].ospRif);
// Llenar los datos
book.sheet(0).cell('A6').value(records[0].productiveSector);
book.sheet(0).cell('D6').value(records[0].ospName);
book.sheet(0).cell('L5').value(fechaFormateada);
book.sheet(0).cell('L6').value(horaFormateada);
book.sheet(0).cell('B10').value(records[0].ospAddress);
book.sheet(0).cell('C11').value(records[0].communeEmail);
book.sheet(0).cell('C12').value(records[0].communeSpokespersonName);
book.sheet(0).cell('G11').value(records[0].communeRif);
book.sheet(0).cell('G12').value(records[0].communeSpokespersonPhone);
book.sheet(0).cell('C13').value(records[0].siturCodeCommune);
book.sheet(0).cell('G13').value(records[0].siturCodeCommunalCouncil);
book.sheet(0).cell('G14').value(records[0].communalCouncilRif);
book.sheet(0).cell('C15').value(records[0].communalCouncilSpokespersonName);
book.sheet(0).cell('G15').value(records[0].communalCouncilSpokespersonPhone);
book.sheet(0).cell('C16').value(records[0].ospType);
book.sheet(0).cell('C17').value(records[0].ospName);
book.sheet(0).cell('C18').value(records[0].productiveActivity);
book.sheet(0).cell('C19').value('Proveedores');
book.sheet(0).cell('C20').value(records[0].companyConstitutionYear);
book.sheet(0).cell('C21').value(records[0].infrastructureMt2);
book.sheet(0).cell('G17').value(records[0].ospRif);
// book
// .sheet(0)
// .cell(records[0].hasTransport === true ? 'J19' : 'L19')
// .value('X');
// book
// .sheet(0)
// .cell(records[0].structureType === 'CASA' ? 'J20' : 'L20')
// .value('X');
// book
// .sheet(0)
// .cell(records[0].isOpenSpace === true ? 'J21' : 'L21')
// .value('X');
book.sheet(0).cell(records[0].hasTransport === true ? 'J19' : 'L19').value('X');
book.sheet(0).cell(records[0].structureType === 'CASA' ? 'J20' : 'L20').value('X');
book.sheet(0).cell(records[0].isOpenSpace === true ? 'J21' : 'L21').value('X');
// book.sheet(0).cell('A24').value(records[0].ospResponsibleFullname);
// book.sheet(0).cell('C24').value(records[0].ospResponsibleCedula);
// book.sheet(0).cell('E24').value(records[0].ospResponsiblePhone);
book.sheet(0).cell('A24').value(records[0].ospResponsibleFullname);
book.sheet(0).cell('C24').value(records[0].ospResponsibleCedula);
book.sheet(0).cell('E24').value(records[0].ospResponsiblePhone);
// book.sheet(0).cell('J24').value('N Femenino');
// book.sheet(0).cell('L24').value('N Masculino');
// book
// .sheet(0)
// .range(`A28:C${equipmentListCount + 28}`)
// .value(equipmentListArray);
// book
// .sheet(0)
// .range(`E28:G${productionListCount + 28}`)
// .value(productionListArray);
// book
// .sheet(0)
// .range(`I28:L${productListCount + 28}`)
// .value(productListArray);
book.sheet(0).cell('J24').value('N Femenino');
book.sheet(0).cell('L24').value('N Masculino');
book.sheet(0).range(`A28:C${equipmentListCount + 28}`).value(equipmentListArray);
book.sheet(0).range(`E28:G${productionListCount + 28}`).value(productionListArray);
book.sheet(0).range(`I28:L${productListCount + 28}`).value(productListArray);
return book.outputAsync();
}
// return book.outputAsync();
// }
}