correciones al formulario osp
This commit is contained in:
9
apps/api/src/database/migrations/0014_deep_meteorite.sql
Normal file
9
apps/api/src/database/migrations/0014_deep_meteorite.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE "training_surveys" ADD COLUMN "infrastructure_mt2" text DEFAULT '' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" ADD COLUMN "has_transport" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" ADD COLUMN "structure_type" text DEFAULT '' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" ADD COLUMN "is_open_space" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" ADD COLUMN "equipment_list" jsonb DEFAULT '[]'::jsonb NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" ADD COLUMN "production_list" jsonb DEFAULT '[]'::jsonb NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" ADD COLUMN "product_list" jsonb DEFAULT '[]'::jsonb NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" ADD COLUMN "internal_distribution_list" jsonb DEFAULT '[]'::jsonb NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" ADD COLUMN "external_distribution_list" jsonb DEFAULT '[]'::jsonb NOT NULL;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "training_surveys" ALTER COLUMN "photo1" DROP NOT NULL;
|
||||
36
apps/api/src/database/migrations/0016_silent_tag.sql
Normal file
36
apps/api/src/database/migrations/0016_silent_tag.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
ALTER TABLE "training_surveys" DROP CONSTRAINT "training_surveys_coor_state_states_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP CONSTRAINT "training_surveys_coor_municipality_municipalities_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP CONSTRAINT "training_surveys_coor_parish_parishes_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" ALTER COLUMN "general_observations" DROP NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" ALTER COLUMN "paralysis_reason" DROP NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "financial_requirement_description";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "producer_count";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "product_count";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "product_description";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "installed_capacity";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "operational_capacity";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "coor_state";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "coor_municipality";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "coor_parish";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "types_of_equipment";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "equipment_count";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "equipment_description";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "raw_material";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "material_type";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "raw_material_count";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "product_count_daily";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "product_count_weekly";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "product_count_monthly";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "prod_description_internal";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "internal_count";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "external_count";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "prod_description_external";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "country";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "city";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "men_count";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "women_count";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "internal_distribution_list";--> statement-breakpoint
|
||||
ALTER TABLE "training_surveys" DROP COLUMN "external_distribution_list";
|
||||
2222
apps/api/src/database/migrations/meta/0014_snapshot.json
Normal file
2222
apps/api/src/database/migrations/meta/0014_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2222
apps/api/src/database/migrations/meta/0015_snapshot.json
Normal file
2222
apps/api/src/database/migrations/meta/0015_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1994
apps/api/src/database/migrations/meta/0016_snapshot.json
Normal file
1994
apps/api/src/database/migrations/meta/0016_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -99,6 +99,27 @@
|
||||
"when": 1769629815868,
|
||||
"tag": "0013_cuddly_night_nurse",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 14,
|
||||
"version": "7",
|
||||
"when": 1769646908602,
|
||||
"tag": "0014_deep_meteorite",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 15,
|
||||
"version": "7",
|
||||
"when": 1769648728698,
|
||||
"tag": "0015_concerned_wild_pack",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 16,
|
||||
"version": "7",
|
||||
"when": 1769653021994,
|
||||
"tag": "0016_silent_tag",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -46,12 +46,14 @@ export const answersSurveys = t.pgTable(
|
||||
export const trainingSurveys = t.pgTable(
|
||||
'training_surveys',
|
||||
{
|
||||
// Datos basicos
|
||||
// === 1. IDENTIFICADORES Y DATOS DE VISITA ===
|
||||
id: t.serial('id').primaryKey(),
|
||||
firstname: t.text('firstname').notNull(),
|
||||
lastname: t.text('lastname').notNull(),
|
||||
visitDate: t.timestamp('visit_date').notNull(),
|
||||
// ubicacion
|
||||
coorPhone: t.text('coor_phone'),
|
||||
|
||||
// === 2. UBICACIÓN (Claves Foráneas - Nullables) ===
|
||||
state: t
|
||||
.integer('state')
|
||||
.references(() => states.id, { onDelete: 'set null' }),
|
||||
@@ -61,93 +63,89 @@ export const trainingSurveys = t.pgTable(
|
||||
parish: t
|
||||
.integer('parish')
|
||||
.references(() => parishes.id, { onDelete: 'set null' }),
|
||||
siturCodeCommune: t.text('situr_code_commune').notNull(),
|
||||
|
||||
// === 3. DATOS DE LA OSP (Organización Socioproductiva) ===
|
||||
ospType: t.text('osp_type').notNull(), // UPF, EPS, etc.
|
||||
ecoSector: t.text('eco_sector').notNull().default(''),
|
||||
productiveSector: t.text('productive_sector').notNull().default(''),
|
||||
centralProductiveActivity: t
|
||||
.text('central_productive_activity')
|
||||
.notNull()
|
||||
.default(''),
|
||||
mainProductiveActivity: t
|
||||
.text('main_productive_activity')
|
||||
.notNull()
|
||||
.default(''),
|
||||
productiveActivity: t.text('productive_activity').notNull(),
|
||||
ospRif: t.text('osp_rif').notNull(),
|
||||
ospName: t.text('osp_name').notNull(),
|
||||
companyConstitutionYear: t.integer('company_constitution_year').notNull(),
|
||||
currentStatus: t.text('current_status').notNull().default('ACTIVA'),
|
||||
infrastructureMt2: t.text('infrastructure_mt2').notNull().default(''),
|
||||
hasTransport: t.boolean('has_transport').notNull().default(false),
|
||||
structureType: t.text('structure_type').notNull().default(''),
|
||||
isOpenSpace: t.boolean('is_open_space').notNull().default(false),
|
||||
paralysisReason: t.text('paralysis_reason'),
|
||||
equipmentList: t.jsonb('equipment_list').notNull().default([]),
|
||||
productionList: t.jsonb('production_list').notNull().default([]),
|
||||
productList: t.jsonb('product_list').notNull().default([]),
|
||||
ospAddress: t.text('osp_address').notNull(),
|
||||
ospGoogleMapsLink: t.text('osp_google_maps_link').notNull().default(''),
|
||||
communeName: t.text('commune_name').notNull().default(''),
|
||||
siturCodeCommune: t.text('situr_code_commune').notNull(),
|
||||
communeRif: t.text('commune_rif').notNull().default(''),
|
||||
communeSpokespersonName: t.text('commune_spokesperson_name').notNull().default(''),
|
||||
communeSpokespersonCedula: t.text('commune_spokesperson_cedula').notNull().default(''),
|
||||
communeSpokespersonRif: t.text('commune_spokesperson_rif').notNull().default(''),
|
||||
communeSpokespersonPhone: t.text('commune_spokesperson_phone').notNull().default(''),
|
||||
communeSpokespersonName: t
|
||||
.text('commune_spokesperson_name')
|
||||
.notNull()
|
||||
.default(''),
|
||||
communeSpokespersonCedula: t
|
||||
.text('commune_spokesperson_cedula')
|
||||
.notNull()
|
||||
.default(''),
|
||||
communeSpokespersonRif: t
|
||||
.text('commune_spokesperson_rif')
|
||||
.notNull()
|
||||
.default(''),
|
||||
communeSpokespersonPhone: t
|
||||
.text('commune_spokesperson_phone')
|
||||
.notNull()
|
||||
.default(''),
|
||||
communeEmail: t.text('commune_email').notNull().default(''),
|
||||
communalCouncil: t.text('communal_council').notNull(),
|
||||
siturCodeCommunalCouncil: t.text('situr_code_communal_council').notNull(),
|
||||
communalCouncilRif: t.text('communal_council_rif').notNull().default(''),
|
||||
communalCouncilSpokespersonName: t.text('communal_council_spokesperson_name').notNull().default(''),
|
||||
communalCouncilSpokespersonCedula: t.text('communal_council_spokesperson_cedula').notNull().default(''),
|
||||
communalCouncilSpokespersonRif: t.text('communal_council_spokesperson_rif').notNull().default(''),
|
||||
communalCouncilSpokespersonPhone: t.text('communal_council_spokesperson_phone').notNull().default(''),
|
||||
communalCouncilEmail: t.text('communal_council_email').notNull().default(''),
|
||||
ospGoogleMapsLink: t.text('osp_google_maps_link').notNull().default(''),
|
||||
// datos del OSP (ORGANIZACIÓN SOCIOPRODUCTIVA)
|
||||
ospName: t.text('osp_name').notNull(),
|
||||
ospAddress: t.text('osp_address').notNull(),
|
||||
ospRif: t.text('osp_rif').notNull(),
|
||||
ospType: t.text('osp_type').notNull(),
|
||||
productiveActivity: t.text('productive_activity').notNull(),
|
||||
financialRequirementDescription: t
|
||||
.text('financial_requirement_description')
|
||||
communalCouncilSpokespersonName: t
|
||||
.text('communal_council_spokesperson_name')
|
||||
.notNull()
|
||||
.default(''),
|
||||
communalCouncilSpokespersonCedula: t
|
||||
.text('communal_council_spokesperson_cedula')
|
||||
.notNull()
|
||||
.default(''),
|
||||
communalCouncilSpokespersonRif: t
|
||||
.text('communal_council_spokesperson_rif')
|
||||
.notNull()
|
||||
.default(''),
|
||||
communalCouncilSpokespersonPhone: t
|
||||
.text('communal_council_spokesperson_phone')
|
||||
.notNull()
|
||||
.default(''),
|
||||
communalCouncilEmail: t
|
||||
.text('communal_council_email')
|
||||
.notNull()
|
||||
.default(''),
|
||||
currentStatus: t.text('current_status').notNull().default('ACTIVA'),
|
||||
companyConstitutionYear: t.integer('company_constitution_year').notNull(),
|
||||
producerCount: t.integer('producer_count').notNull(),
|
||||
productCount: t.integer('product_count').notNull().default(0),
|
||||
productDescription: t.text('product_description').notNull(),
|
||||
installedCapacity: t.text('installed_capacity').notNull(),
|
||||
operationalCapacity: t.text('operational_capacity').notNull(),
|
||||
// datos del responsable
|
||||
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(),
|
||||
ospResponsiblePhone: t.text('osp_responsible_phone').notNull(),
|
||||
ospResponsibleEmail: t.text('osp_responsible_email').notNull(),
|
||||
civilState: t.text('civil_state').notNull(),
|
||||
familyBurden: t.integer('family_burden').notNull(),
|
||||
numberOfChildren: t.integer('number_of_children').notNull(),
|
||||
// datos adicionales
|
||||
generalObservations: t.text('general_observations').notNull(),
|
||||
paralysisReason: t.text('paralysis_reason').notNull(),
|
||||
// fotos
|
||||
photo1: t.text('photo1').notNull(),
|
||||
generalObservations: t.text('general_observations'),
|
||||
photo1: t.text('photo1'),
|
||||
photo2: t.text('photo2'),
|
||||
photo3: t.text('photo3'),
|
||||
// nuevos campos coordinacion
|
||||
coorState: t
|
||||
.integer('coor_state')
|
||||
.references(() => states.id, { onDelete: 'set null' }),
|
||||
coorMunicipality: t
|
||||
.integer('coor_municipality')
|
||||
.references(() => municipalities.id, { onDelete: 'set null' }),
|
||||
coorParish: t
|
||||
.integer('coor_parish')
|
||||
.references(() => parishes.id, { onDelete: 'set null' }),
|
||||
coorPhone: t.text('coor_phone'),
|
||||
// sectores
|
||||
ecoSector: t.text('eco_sector').notNull().default(''),
|
||||
productiveSector: t.text('productive_sector').notNull().default(''),
|
||||
centralProductiveActivity: t.text('central_productive_activity').notNull().default(''),
|
||||
mainProductiveActivity: t.text('main_productive_activity').notNull().default(''),
|
||||
// equipamiento
|
||||
typesOfEquipment: t.text('types_of_equipment').notNull().default(''),
|
||||
equipmentCount: t.integer('equipment_count').notNull().default(0),
|
||||
equipmentDescription: t.text('equipment_description').notNull().default(''),
|
||||
// materia prima
|
||||
rawMaterial: t.text('raw_material').notNull().default(''),
|
||||
materialType: t.text('material_type').notNull().default(''),
|
||||
rawMaterialCount: t.integer('raw_material_count').notNull().default(0),
|
||||
// conteo de productos
|
||||
productCountDaily: t.integer('product_count_daily').notNull().default(0),
|
||||
productCountWeekly: t.integer('product_count_weekly').notNull().default(0),
|
||||
productCountMonthly: t.integer('product_count_monthly').notNull().default(0),
|
||||
// nuevos campos adicionales
|
||||
prodDescriptionInternal: t.text('prod_description_internal').notNull().default(''),
|
||||
internalCount: t.integer('internal_count').notNull().default(0),
|
||||
externalCount: t.integer('external_count').notNull().default(0),
|
||||
prodDescriptionExternal: t.text('prod_description_external').notNull().default(''),
|
||||
country: t.text('country').notNull().default(''),
|
||||
city: t.text('city').notNull().default(''),
|
||||
menCount: t.integer('men_count').notNull().default(0),
|
||||
womenCount: t.integer('women_count').notNull().default(0),
|
||||
...timestamps,
|
||||
},
|
||||
(trainingSurveys) => ({
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsDateString, IsInt, IsOptional, IsString } from 'class-validator';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsDateString,
|
||||
IsInt,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateTrainingDto {
|
||||
// === 1. DATOS BÁSICOS ===
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
firstname: string;
|
||||
@@ -12,7 +21,25 @@ export class CreateTrainingDto {
|
||||
|
||||
@ApiProperty()
|
||||
@IsDateString()
|
||||
visitDate: string;
|
||||
visitDate: string; // Llega como string ISO "2024-11-11T10:00"
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
coorPhone?: string;
|
||||
|
||||
// === 2. DATOS OSP ===
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospName: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospRif: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospType: string; // 'UPF', etc.
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@@ -20,24 +47,108 @@ export class CreateTrainingDto {
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
financialRequirementDescription?: string;
|
||||
currentStatus: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
@Type(() => Number) // Convierte "2017" -> 2017
|
||||
companyConstitutionYear: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
state: number;
|
||||
ospAddress: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
ospGoogleMapsLink?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
infrastructureMt2?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
structureType?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === 'true' || value === true) // Convierte "false" -> false
|
||||
hasTransport?: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === 'true' || value === true)
|
||||
isOpenSpace?: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
paralysisReason?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
generalObservations?: string;
|
||||
|
||||
// === 3. SECTORES ===
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ecoSector: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
productiveSector: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
centralProductiveActivity: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
mainProductiveActivity: string;
|
||||
|
||||
// === 4. DATOS RESPONSABLE ===
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospResponsibleFullname: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospResponsibleCedula: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospResponsibleRif: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospResponsiblePhone: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospResponsibleEmail: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
civilState: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
municipality: number;
|
||||
@Type(() => Number) // Convierte "3" -> 3
|
||||
familyBurden: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
parish: number;
|
||||
@Type(() => Number)
|
||||
numberOfChildren: number;
|
||||
|
||||
// === 5. COMUNA Y CONSEJO COMUNAL ===
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
siturCodeCommune: string;
|
||||
@@ -102,217 +213,51 @@ export class CreateTrainingDto {
|
||||
@IsString()
|
||||
communalCouncilEmail: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospGoogleMapsLink: string;
|
||||
// === 6. LISTAS (Arrays JSON) ===
|
||||
// Reciben un string JSON '[{...}]' y lo convierten a Objeto JS real
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospName: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospAddress: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospRif: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospType: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
currentStatus: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
companyConstitutionYear: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
producerCount: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
productCount: number;
|
||||
@IsArray()
|
||||
@Transform(({ value }) => {
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
})
|
||||
equipmentList?: any[];
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
productDescription: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
installedCapacity: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
operationalCapacity: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospResponsibleFullname: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospResponsibleCedula: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospResponsibleRif: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospResponsiblePhone: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ospResponsibleEmail: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
civilState: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
familyBurden: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
numberOfChildren: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
generalObservations: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
photo1?: string;
|
||||
@IsArray()
|
||||
@Transform(({ value }) => {
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
})
|
||||
productionList?: any[];
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
photo2?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
photo3?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
paralysisReason: string;
|
||||
|
||||
// nuevos campos coordinacion
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
coorState?: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
coorMunicipality?: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
coorParish?: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
coorPhone?: string;
|
||||
|
||||
// sectores
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ecoSector: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
productiveSector: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
centralProductiveActivity: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
mainProductiveActivity: string;
|
||||
|
||||
// equipamiento
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
typesOfEquipment: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
equipmentCount: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
equipmentDescription: string;
|
||||
|
||||
// materia prima
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
rawMaterial: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
materialType: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
rawMaterialCount: number;
|
||||
|
||||
// conteo de productos
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
productCountDaily: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
productCountWeekly: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
productCountMonthly: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
prodDescriptionInternal: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
internalCount: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
externalCount: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
prodDescriptionExternal: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
country: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
city: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
menCount: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
womenCount: number;
|
||||
@IsArray()
|
||||
@Transform(({ value }) => {
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
})
|
||||
productList?: any[];
|
||||
}
|
||||
|
||||
@@ -74,95 +74,121 @@ export class TrainingService {
|
||||
|
||||
const filters: SQL[] = [];
|
||||
|
||||
if (startDate) {
|
||||
if (startDate)
|
||||
filters.push(gte(trainingSurveys.visitDate, new Date(startDate)));
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
if (endDate)
|
||||
filters.push(lte(trainingSurveys.visitDate, new Date(endDate)));
|
||||
}
|
||||
|
||||
if (stateId) {
|
||||
filters.push(eq(trainingSurveys.state, stateId));
|
||||
}
|
||||
|
||||
if (municipalityId) {
|
||||
if (stateId) filters.push(eq(trainingSurveys.state, stateId));
|
||||
if (municipalityId)
|
||||
filters.push(eq(trainingSurveys.municipality, municipalityId));
|
||||
}
|
||||
|
||||
if (parishId) {
|
||||
filters.push(eq(trainingSurveys.parish, parishId));
|
||||
}
|
||||
|
||||
if (ospType) {
|
||||
filters.push(eq(trainingSurveys.ospType, ospType));
|
||||
}
|
||||
if (parishId) filters.push(eq(trainingSurveys.parish, parishId));
|
||||
if (ospType) filters.push(eq(trainingSurveys.ospType, ospType));
|
||||
|
||||
const whereCondition = filters.length > 0 ? and(...filters) : undefined;
|
||||
|
||||
const totalOspsResult = await this.drizzle
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition);
|
||||
const totalOsps = Number(totalOspsResult[0].count);
|
||||
// Ejecutamos todas las consultas en paralelo con Promise.all para mayor velocidad
|
||||
const [
|
||||
totalOspsResult,
|
||||
totalProducersResult,
|
||||
totalProductsResult, // Nuevo: Calculado desde el JSON
|
||||
statusDistribution,
|
||||
activityDistribution,
|
||||
typeDistribution,
|
||||
stateDistribution,
|
||||
yearDistribution,
|
||||
] = await Promise.all([
|
||||
// 1. Total OSPs
|
||||
this.drizzle
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition),
|
||||
|
||||
const totalProducersResult = await this.drizzle
|
||||
.select({ sum: sql<number>`sum(${trainingSurveys.producerCount})` })
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition);
|
||||
const totalProducers = Number(totalProducersResult[0].sum || 0);
|
||||
// 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
|
||||
)
|
||||
)
|
||||
`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition),
|
||||
|
||||
const statusDistribution = await this.drizzle
|
||||
.select({
|
||||
name: trainingSurveys.currentStatus,
|
||||
value: sql<number>`count(*)`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition)
|
||||
.groupBy(trainingSurveys.currentStatus);
|
||||
// 3. NUEVO: Total Productos (Contamos el largo del array JSON productList)
|
||||
this.drizzle
|
||||
.select({
|
||||
sum: sql<number>`sum(jsonb_array_length(${trainingSurveys.productList}))`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition),
|
||||
|
||||
const activityDistribution = await this.drizzle
|
||||
.select({
|
||||
name: trainingSurveys.productiveActivity,
|
||||
value: sql<number>`count(*)`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition)
|
||||
.groupBy(trainingSurveys.productiveActivity);
|
||||
// 4. Distribución por Estatus
|
||||
this.drizzle
|
||||
.select({
|
||||
name: trainingSurveys.currentStatus,
|
||||
value: sql<number>`count(*)`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition)
|
||||
.groupBy(trainingSurveys.currentStatus),
|
||||
|
||||
const typeDistribution = await this.drizzle
|
||||
.select({
|
||||
name: trainingSurveys.ospType,
|
||||
value: sql<number>`count(*)`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition)
|
||||
.groupBy(trainingSurveys.ospType);
|
||||
// 5. Distribución por Actividad
|
||||
this.drizzle
|
||||
.select({
|
||||
name: trainingSurveys.productiveActivity,
|
||||
value: sql<number>`count(*)`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition)
|
||||
.groupBy(trainingSurveys.productiveActivity),
|
||||
|
||||
// New Aggregations
|
||||
const stateDistribution = await this.drizzle
|
||||
.select({
|
||||
name: states.name,
|
||||
value: sql<number>`count(${trainingSurveys.id})`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.leftJoin(states, eq(trainingSurveys.state, states.id))
|
||||
.where(whereCondition)
|
||||
.groupBy(states.name);
|
||||
// 6. Distribución por Tipo
|
||||
this.drizzle
|
||||
.select({
|
||||
name: trainingSurveys.ospType,
|
||||
value: sql<number>`count(*)`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition)
|
||||
.groupBy(trainingSurveys.ospType),
|
||||
|
||||
const yearDistribution = await this.drizzle
|
||||
.select({
|
||||
name: sql<string>`cast(${trainingSurveys.companyConstitutionYear} as text)`,
|
||||
value: sql<number>`count(*)`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition)
|
||||
.groupBy(trainingSurveys.companyConstitutionYear)
|
||||
.orderBy(trainingSurveys.companyConstitutionYear);
|
||||
// 7. Distribución por Estado (CORREGIDO con COALESCE)
|
||||
this.drizzle
|
||||
.select({
|
||||
// Si states.name es NULL, devuelve 'Sin Asignar'
|
||||
name: sql<string>`COALESCE(${states.name}, 'Sin Asignar')`,
|
||||
value: sql<number>`count(${trainingSurveys.id})`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.leftJoin(states, eq(trainingSurveys.state, states.id))
|
||||
.where(whereCondition)
|
||||
// Importante: Agrupar también por el resultado del COALESCE o por states.name
|
||||
.groupBy(states.name),
|
||||
|
||||
// 8. Distribución por Año
|
||||
this.drizzle
|
||||
.select({
|
||||
name: sql<string>`cast(${trainingSurveys.companyConstitutionYear} as text)`,
|
||||
value: sql<number>`count(*)`,
|
||||
})
|
||||
.from(trainingSurveys)
|
||||
.where(whereCondition)
|
||||
.groupBy(trainingSurveys.companyConstitutionYear)
|
||||
.orderBy(trainingSurveys.companyConstitutionYear),
|
||||
]);
|
||||
|
||||
return {
|
||||
totalOsps,
|
||||
totalProducers,
|
||||
totalOsps: Number(totalOspsResult[0]?.count || 0),
|
||||
totalProducers: Number(totalProducersResult[0]?.sum || 0),
|
||||
totalProducts: Number(totalProductsResult[0]?.sum || 0), // Dato extraído del JSON
|
||||
|
||||
statusDistribution: statusDistribution.map((item) => ({
|
||||
...item,
|
||||
value: Number(item.value),
|
||||
@@ -239,16 +265,30 @@ export class TrainingService {
|
||||
createTrainingDto: CreateTrainingDto,
|
||||
files: Express.Multer.File[],
|
||||
) {
|
||||
// 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, ...rest } = createTrainingDto;
|
||||
|
||||
const [newRecord] = await this.drizzle
|
||||
.insert(trainingSurveys)
|
||||
.values({
|
||||
...createTrainingDto,
|
||||
visitDate: new Date(createTrainingDto.visitDate),
|
||||
photo1: photoPaths[0] || '',
|
||||
photo2: photoPaths[1] || null,
|
||||
photo3: photoPaths[2] || null,
|
||||
// Insertamos el resto de datos planos y las listas (arrays)
|
||||
...rest,
|
||||
|
||||
// Conversión de fecha
|
||||
visitDate: new Date(visitDate),
|
||||
|
||||
// 3. Asignar fotos de forma segura
|
||||
photo1: photoPaths[0] ?? null,
|
||||
photo2: photoPaths[1] ?? null,
|
||||
photo3: photoPaths[2] ?? null,
|
||||
|
||||
// NOTA: Como las columnas state, municipality, etc. en la BD
|
||||
// tienen "onDelete: set null" o son nullables, al no pasarlas aquí,
|
||||
// Postgres automáticamente las guardará como NULL.
|
||||
})
|
||||
.returning();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user