nuevas correciones al formulario y esquema base de datos para osp

This commit is contained in:
2026-02-25 12:17:33 -04:00
parent a88cf94adb
commit f910aea3cc
15 changed files with 4889 additions and 731 deletions

View File

@@ -108,10 +108,19 @@ export class MinioService implements OnModuleInit {
async delete(objectName: string): Promise<void> {
try {
await this.minioClient.removeObject(this.bucketName, objectName);
this.logger.log(`Object "${objectName}" deleted successfully.`);
// Ensure we don't have a leading slash which can cause issues with removeObject
const cleanedName = objectName.startsWith('/')
? objectName.slice(1)
: objectName;
await this.minioClient.removeObject(this.bucketName, cleanedName);
this.logger.log(
`Object "${cleanedName}" deleted successfully from bucket "${this.bucketName}".`,
);
} catch (error: any) {
this.logger.error(`Error deleting file: ${error.message}`);
this.logger.error(
`Error deleting file "${objectName}": ${error.message}`,
);
// We don't necessarily want to throw if the file is already gone
}
}

View File

@@ -0,0 +1,9 @@
ALTER TABLE "training_surveys" ADD COLUMN "internal_distribution_zone" text;--> statement-breakpoint
ALTER TABLE "training_surveys" ADD COLUMN "is_exporting" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "training_surveys" ADD COLUMN "external_country" text;--> statement-breakpoint
ALTER TABLE "training_surveys" ADD COLUMN "external_city" text;--> statement-breakpoint
ALTER TABLE "training_surveys" ADD COLUMN "external_description" text;--> statement-breakpoint
ALTER TABLE "training_surveys" ADD COLUMN "external_quantity" text;--> statement-breakpoint
ALTER TABLE "training_surveys" ADD COLUMN "external_unit" text;--> statement-breakpoint
ALTER TABLE "training_surveys" ADD COLUMN "women_count" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
ALTER TABLE "training_surveys" ADD COLUMN "men_count" integer DEFAULT 0 NOT NULL;

View File

@@ -0,0 +1,5 @@
DROP INDEX "training_surveys_index_00";--> statement-breakpoint
ALTER TABLE "training_surveys" ADD COLUMN "coor_full_name" text NOT NULL;--> statement-breakpoint
CREATE INDEX "training_surveys_index_00" ON "training_surveys" USING btree ("coor_full_name");--> statement-breakpoint
ALTER TABLE "training_surveys" DROP COLUMN "firstname";--> statement-breakpoint
ALTER TABLE "training_surveys" DROP COLUMN "lastname";

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -155,6 +155,20 @@
"when": 1771901546945,
"tag": "0021_warm_machine_man",
"breakpoints": true
},
{
"idx": 22,
"version": "7",
"when": 1772031518006,
"tag": "0022_nervous_dragon_lord",
"breakpoints": true
},
{
"idx": 23,
"version": "7",
"when": 1772032122473,
"tag": "0023_sticky_slayback",
"breakpoints": true
}
]
}

View File

@@ -48,8 +48,7 @@ export const trainingSurveys = t.pgTable(
{
// === 1. IDENTIFICADORES Y DATOS DE VISITA ===
id: t.serial('id').primaryKey(),
firstname: t.text('firstname').notNull(),
lastname: t.text('lastname').notNull(),
coorFullName: t.text('coor_full_name').notNull(),
visitDate: t.timestamp('visit_date').notNull(),
coorPhone: t.text('coor_phone'),
@@ -133,6 +132,20 @@ export const trainingSurveys = t.pgTable(
familyBurden: t.integer('family_burden'),
numberOfChildren: t.integer('number_of_children'),
generalObservations: t.text('general_observations'),
// === 4. DATOS DE DISTRIBUCIÓN Y EXPORTACIÓN ===
internalDistributionZone: t.text('internal_distribution_zone'),
isExporting: t.boolean('is_exporting').notNull().default(false),
externalCountry: t.text('external_country'),
externalCity: t.text('external_city'),
externalDescription: t.text('external_description'),
externalQuantity: t.text('external_quantity'),
externalUnit: t.text('external_unit'),
// === 5. MANO DE OBRA ===
womenCount: t.integer('women_count').notNull().default(0),
menCount: t.integer('men_count').notNull().default(0),
// Fotos
photo1: t.text('photo1'),
photo2: t.text('photo2'),
@@ -149,7 +162,7 @@ export const trainingSurveys = t.pgTable(
(trainingSurveys) => ({
trainingSurveysIndex: t
.index('training_surveys_index_00')
.on(trainingSurveys.firstname),
.on(trainingSurveys.coorFullName),
}),
);

View File

@@ -2,7 +2,6 @@ import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import {
IsArray,
IsBoolean,
IsDateString,
IsEmail,
IsInt,
@@ -15,11 +14,7 @@ export class CreateTrainingDto {
// === 1. DATOS BÁSICOS ===
@ApiProperty()
@IsString()
firstname: string;
@ApiProperty()
@IsString()
lastname: string;
coorFullName: string;
@ApiProperty()
@IsDateString()
@@ -77,16 +72,14 @@ export class CreateTrainingDto {
structureType?: string;
@ApiProperty()
@IsBoolean()
@IsString()
@IsOptional()
@Transform(({ value }) => value === 'true' || value === true) // Convierte "false" -> false
hasTransport?: boolean;
hasTransport?: string;
@ApiProperty()
@IsBoolean()
@IsString()
@IsOptional()
@Transform(({ value }) => value === 'true' || value === true)
isOpenSpace?: boolean;
isOpenSpace?: string;
@ApiProperty()
@IsString()
@@ -209,7 +202,56 @@ export class CreateTrainingDto {
@IsEmail()
communalCouncilEmail?: string;
// === 6. LISTAS (Arrays JSON) ===
// === 6. DISTRIBUCIÓN Y EXPORTACIÓN ===
@ApiProperty()
@IsString()
@IsOptional()
internalDistributionZone?: string;
@ApiProperty()
@IsString()
@IsOptional()
isExporting?: string;
@ApiProperty()
@IsString()
@IsOptional()
externalCountry?: string;
@ApiProperty()
@IsString()
@IsOptional()
externalCity?: string;
@ApiProperty()
@IsString()
@IsOptional()
externalDescription?: string;
@ApiProperty()
@IsString()
@IsOptional()
externalQuantity?: string;
@ApiProperty()
@IsString()
@IsOptional()
externalUnit?: string;
// === 7. MANO DE OBRA ===
@ApiProperty()
@IsInt()
@IsOptional()
@Type(() => Number)
womenCount?: number;
@ApiProperty()
@IsInt()
@IsOptional()
@Type(() => Number)
menCount?: number;
// === 8. LISTAS (Arrays JSON) ===
// Reciben un string JSON '[{...}]' y lo convierten a Objeto JS real
@ApiProperty()

View File

@@ -107,17 +107,7 @@ export class TrainingService {
// 2. Total Productores (Columna plana que mantuviste)
this.drizzle
.select({
sum: sql<number>`
SUM(
(
SELECT SUM(
COALESCE((item->>'menCount')::int, 0) +
COALESCE((item->>'womenCount')::int, 0)
)
FROM jsonb_array_elements(${trainingSurveys.productList}) as item
)
)
`,
sum: sql<number>`SUM(${trainingSurveys.womenCount} + ${trainingSurveys.menCount})`,
})
.from(trainingSurveys)
.where(whereCondition),
@@ -244,20 +234,25 @@ export class TrainingService {
private async deleteFile(fileUrl: string) {
if (!fileUrl) return;
// 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('/');
// If it's a full URL, we need to extract the part after the bucket name
if (fileUrl.startsWith('http')) {
const url = new URL(fileUrl);
const pathname = url.pathname; // /bucket/folder/filename
const parts = pathname.split('/').filter(Boolean); // ['bucket', 'folder', 'filename']
await this.minioService.delete(objectName);
// The first part is the bucket name, the rest is the object name
if (parts.length >= 2) {
const objectName = parts.slice(1).join('/');
await this.minioService.delete(objectName);
return;
}
}
// If it's not a URL or doesn't match the expected format, pass it as is
await this.minioService.delete(fileUrl);
} catch (error) {
// If it's not a valid URL, maybe it's just the object name stored from before
// Fallback if URL parsing fails
await this.minioService.delete(fileUrl);
}
}
@@ -292,6 +287,9 @@ export class TrainingService {
state: Number(state) ?? null,
municipality: Number(municipality) ?? null,
parish: Number(parish) ?? null,
hasTransport: rest.hasTransport === 'true' ? true : false,
isOpenSpace: rest.isOpenSpace === 'true' ? true : false,
isExporting: rest.isExporting === 'true' ? true : false,
createdBy: userId,
updatedBy: userId,
})
@@ -359,6 +357,12 @@ export class TrainingService {
// actualizamos el id del usuario que actualizo el registro
updateData.updatedBy = userId;
updateData.hasTransport =
updateTrainingDto.hasTransport === 'true' ? true : false;
updateData.isOpenSpace =
updateTrainingDto.isOpenSpace === 'true' ? true : false;
updateData.isExporting =
updateTrainingDto.isExporting === 'true' ? true : false;
const [updatedRecord] = await this.drizzle
.update(trainingSurveys)
@@ -402,8 +406,7 @@ export class TrainingService {
// const records = await this.drizzle
// .select({
// firstname: trainingSurveys.firstname,
// lastname: trainingSurveys.lastname,
// coorFullName: trainingSurveys.coorFullName,
// visitDate: trainingSurveys.visitDate,
// stateName: states.name,
// municipalityName: municipalities.name,
@@ -451,8 +454,7 @@ export class TrainingService {
// const dateStr = date.toLocaleDateString('es-VE');
// const timeStr = date.toLocaleTimeString('es-VE');
// sheet.cell(`A${currentRow}`).value(record.firstname);
// sheet.cell(`B${currentRow}`).value(record.lastname);
// sheet.cell(`A${currentRow}`).value(record.coorFullName);
// sheet.cell(`C${currentRow}`).value(dateStr);
// sheet.cell(`D${currentRow}`).value(timeStr);
// sheet.cell(`E${currentRow}`).value(record.stateName || '');