[MAI-012] feat: Complete contracts module with schema fixes

- Fix 3 entities schema from 'contracts' to 'construction' to match DDL
- Create ContractPartida entity for contrato_partidas table
- Fix ContractService and SubcontractorService to use local ServiceContext
- Fix ContractController and SubcontractorController pagination structure
- Update entities/index.ts to export ContractPartida

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-01-25 09:08:31 -06:00
parent 60782a3cac
commit 24c2931209
9 changed files with 105 additions and 29 deletions

View File

@ -17,7 +17,11 @@ import { AuthService } from '../../auth/services/auth.service';
import { User } from '../../core/entities/user.entity';
import { Tenant } from '../../core/entities/tenant.entity';
import { RefreshToken } from '../../auth/entities/refresh-token.entity';
import { ServiceContext } from '../../../shared/services/base.service';
interface ServiceContext {
tenantId: string;
userId?: string;
}
export function createContractController(dataSource: DataSource): Router {
const router = Router();
@ -69,7 +73,7 @@ export function createContractController(dataSource: DataSource): Router {
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const result = await service.findWithFilters(getContext(req), filters, page, limit);
res.status(200).json({ success: true, data: result.data, pagination: result.meta });
res.status(200).json({ success: true, data: result.data, pagination: { total: result.total, page: result.page, limit: result.limit } });
} catch (error) {
next(error);
}

View File

@ -16,7 +16,11 @@ import { AuthService } from '../../auth/services/auth.service';
import { User } from '../../core/entities/user.entity';
import { Tenant } from '../../core/entities/tenant.entity';
import { RefreshToken } from '../../auth/entities/refresh-token.entity';
import { ServiceContext } from '../../../shared/services/base.service';
interface ServiceContext {
tenantId: string;
userId?: string;
}
export function createSubcontractorController(dataSource: DataSource): Router {
const router = Router();
@ -65,7 +69,7 @@ export function createSubcontractorController(dataSource: DataSource): Router {
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const result = await service.findWithFilters(getContext(req), filters, page, limit);
res.status(200).json({ success: true, data: result.data, pagination: result.meta });
res.status(200).json({ success: true, data: result.data, pagination: { total: result.total, page: result.page, limit: result.limit } });
} catch (error) {
next(error);
}

View File

@ -23,7 +23,7 @@ import { Contract } from './contract.entity';
export type AddendumType = 'extension' | 'amount_increase' | 'amount_decrease' | 'scope_change' | 'termination' | 'other';
export type AddendumStatus = 'draft' | 'review' | 'approved' | 'rejected';
@Entity({ schema: 'contracts', name: 'contract_addendums' })
@Entity({ schema: 'construction', name: 'contrato_addendas' })
@Index(['tenantId', 'addendumNumber'], { unique: true })
export class ContractAddendum {
@PrimaryGeneratedColumn('uuid')

View File

@ -0,0 +1,75 @@
/**
* ContractPartida Entity
* Partidas/lineas de contratos con subcontratistas
*
* @module Contracts
* @table construction.contrato_partidas
*/
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { Tenant } from '../../core/entities/tenant.entity';
import { User } from '../../core/entities/user.entity';
import { Contract } from './contract.entity';
@Entity({ schema: 'construction', name: 'contrato_partidas' })
export class ContractPartida {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ name: 'tenant_id', type: 'uuid' })
tenantId: string;
@Column({ name: 'contrato_id', type: 'uuid' })
contractId: string;
@Column({ name: 'concepto_id', type: 'uuid' })
conceptoId: string;
@Column({ type: 'decimal', precision: 12, scale: 4, default: 0 })
quantity: number;
@Column({ name: 'unit_price', type: 'decimal', precision: 12, scale: 4, default: 0 })
unitPrice: number;
@Column({ name: 'total_amount', type: 'decimal', precision: 14, scale: 2, insert: false, update: false })
totalAmount: number;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt: Date;
@Column({ name: 'created_by', type: 'uuid', nullable: true })
createdById: string;
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
updatedAt: Date;
@Column({ name: 'updated_by', type: 'uuid', nullable: true })
updatedById: string;
@Column({ name: 'deleted_at', type: 'timestamptz', nullable: true })
deletedAt: Date;
@Column({ name: 'deleted_by', type: 'uuid', nullable: true })
deletedById: string;
// Relations
@ManyToOne(() => Tenant)
@JoinColumn({ name: 'tenant_id' })
tenant: Tenant;
@ManyToOne(() => Contract, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'contrato_id' })
contract: Contract;
@ManyToOne(() => User)
@JoinColumn({ name: 'created_by' })
createdBy: User;
}

View File

@ -25,7 +25,7 @@ export type ContractType = 'client' | 'subcontractor';
export type ContractStatus = 'draft' | 'review' | 'approved' | 'active' | 'completed' | 'terminated';
export type ClientContractType = 'desarrollo' | 'llave_en_mano' | 'administracion';
@Entity({ schema: 'contracts', name: 'contracts' })
@Entity({ schema: 'construction', name: 'contratos' })
@Index(['tenantId', 'contractNumber'], { unique: true })
export class Contract {
@PrimaryGeneratedColumn('uuid')

View File

@ -8,3 +8,4 @@
export * from './contract.entity';
export * from './subcontractor.entity';
export * from './contract-addendum.entity';
export * from './contract-partida.entity';

View File

@ -22,7 +22,7 @@ import { User } from '../../core/entities/user.entity';
export type SubcontractorSpecialty = 'cimentacion' | 'estructura' | 'instalaciones_electricas' | 'instalaciones_hidraulicas' | 'acabados' | 'urbanizacion' | 'carpinteria' | 'herreria' | 'otros';
export type SubcontractorStatus = 'active' | 'inactive' | 'blacklisted';
@Entity({ schema: 'contracts', name: 'subcontractors' })
@Entity({ schema: 'construction', name: 'subcontratistas' })
@Index(['tenantId', 'code'], { unique: true })
@Index(['tenantId', 'rfc'], { unique: true })
export class Subcontractor {

View File

@ -9,7 +9,11 @@
import { Repository, FindOptionsWhere, LessThan } from 'typeorm';
import { Contract, ContractStatus, ContractType } from '../entities/contract.entity';
import { ContractAddendum } from '../entities/contract-addendum.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
interface ServiceContext {
tenantId: string;
userId?: string;
}
export interface CreateContractDto {
projectId?: string;
@ -77,7 +81,7 @@ export class ContractService {
filters: ContractFilters = {},
page: number = 1,
limit: number = 20
): Promise<PaginatedResult<Contract>> {
): Promise<{ data: Contract[]; total: number; page: number; limit: number }> {
const skip = (page - 1) * limit;
const queryBuilder = this.contractRepository
@ -126,15 +130,7 @@ export class ContractService {
const [data, total] = await queryBuilder.getManyAndCount();
return {
data,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
return { data, total, page, limit };
}
async findById(ctx: ServiceContext, id: string): Promise<Contract | null> {

View File

@ -8,7 +8,11 @@
import { Repository, FindOptionsWhere } from 'typeorm';
import { Subcontractor, SubcontractorStatus, SubcontractorSpecialty } from '../entities/subcontractor.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
interface ServiceContext {
tenantId: string;
userId?: string;
}
export interface CreateSubcontractorDto {
businessName: string;
@ -63,7 +67,7 @@ export class SubcontractorService {
filters: SubcontractorFilters = {},
page: number = 1,
limit: number = 20
): Promise<PaginatedResult<Subcontractor>> {
): Promise<{ data: Subcontractor[]; total: number; page: number; limit: number }> {
const skip = (page - 1) * limit;
const queryBuilder = this.subcontractorRepository
@ -98,15 +102,7 @@ export class SubcontractorService {
const [data, total] = await queryBuilder.getManyAndCount();
return {
data,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
return { data, total, page, limit };
}
async findById(ctx: ServiceContext, id: string): Promise<Subcontractor | null> {