[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:
parent
60782a3cac
commit
24c2931209
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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')
|
||||
|
||||
75
src/modules/contracts/entities/contract-partida.entity.ts
Normal file
75
src/modules/contracts/entities/contract-partida.entity.ts
Normal 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;
|
||||
}
|
||||
@ -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')
|
||||
|
||||
@ -8,3 +8,4 @@
|
||||
export * from './contract.entity';
|
||||
export * from './subcontractor.entity';
|
||||
export * from './contract-addendum.entity';
|
||||
export * from './contract-partida.entity';
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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> {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user