251211:1314 Frontend: reeactor Admin panel
This commit is contained in:
@@ -1,70 +0,0 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
UseGuards,
|
||||
ParseIntPipe,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiBearerAuth,
|
||||
ApiQuery,
|
||||
} from '@nestjs/swagger';
|
||||
import { ContractService } from './contract.service.js';
|
||||
import { CreateContractDto } from './dto/create-contract.dto.js';
|
||||
import { UpdateContractDto } from './dto/update-contract.dto.js';
|
||||
import { SearchContractDto } from './dto/search-contract.dto.js';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard.js';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator.js';
|
||||
|
||||
@ApiTags('Contracts')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('contracts')
|
||||
export class ContractController {
|
||||
constructor(private readonly contractService: ContractService) {}
|
||||
|
||||
@Post()
|
||||
@RequirePermission('master_data.manage')
|
||||
@ApiOperation({ summary: 'Create Contract' })
|
||||
create(@Body() dto: CreateContractDto) {
|
||||
return this.contractService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({
|
||||
summary: 'Get All Contracts (Search & Filter)',
|
||||
})
|
||||
findAll(@Query() query: SearchContractDto) {
|
||||
return this.contractService.findAll(query);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get Contract by ID' })
|
||||
findOne(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.contractService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@RequirePermission('master_data.manage')
|
||||
@ApiOperation({ summary: 'Update Contract' })
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() dto: UpdateContractDto
|
||||
) {
|
||||
return this.contractService.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RequirePermission('master_data.manage')
|
||||
@ApiOperation({ summary: 'Delete Contract' })
|
||||
remove(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.contractService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
ConflictException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, Like } from 'typeorm';
|
||||
import { Contract } from './entities/contract.entity';
|
||||
import { CreateContractDto } from './dto/create-contract.dto.js';
|
||||
import { UpdateContractDto } from './dto/update-contract.dto.js';
|
||||
|
||||
@Injectable()
|
||||
export class ContractService {
|
||||
constructor(
|
||||
@InjectRepository(Contract)
|
||||
private readonly contractRepo: Repository<Contract>
|
||||
) {}
|
||||
|
||||
async create(dto: CreateContractDto) {
|
||||
const existing = await this.contractRepo.findOne({
|
||||
where: { contractCode: dto.contractCode },
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException(
|
||||
`Contract Code "${dto.contractCode}" already exists`
|
||||
);
|
||||
}
|
||||
const contract = this.contractRepo.create(dto);
|
||||
return this.contractRepo.save(contract);
|
||||
}
|
||||
|
||||
async findAll(params?: any) {
|
||||
const { search, projectId, page = 1, limit = 100 } = params || {};
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const findOptions: any = {
|
||||
relations: ['project'],
|
||||
order: { contractCode: 'ASC' },
|
||||
skip,
|
||||
take: limit,
|
||||
where: [],
|
||||
};
|
||||
|
||||
const searchConditions = [];
|
||||
if (search) {
|
||||
searchConditions.push({ contractCode: Like(`%${search}%`) });
|
||||
searchConditions.push({ contractName: Like(`%${search}%`) });
|
||||
}
|
||||
|
||||
if (projectId) {
|
||||
// Combine project filter with search if exists
|
||||
if (searchConditions.length > 0) {
|
||||
findOptions.where = searchConditions.map((cond) => ({
|
||||
...cond,
|
||||
projectId,
|
||||
}));
|
||||
} else {
|
||||
findOptions.where = { projectId };
|
||||
}
|
||||
} else {
|
||||
if (searchConditions.length > 0) {
|
||||
findOptions.where = searchConditions;
|
||||
} else {
|
||||
delete findOptions.where; // No filters
|
||||
}
|
||||
}
|
||||
|
||||
const [data, total] = await this.contractRepo.findAndCount(findOptions);
|
||||
|
||||
return {
|
||||
data,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const contract = await this.contractRepo.findOne({
|
||||
where: { id },
|
||||
relations: ['project'],
|
||||
});
|
||||
if (!contract) throw new NotFoundException(`Contract ID ${id} not found`);
|
||||
return contract;
|
||||
}
|
||||
|
||||
async update(id: number, dto: UpdateContractDto) {
|
||||
const contract = await this.findOne(id);
|
||||
Object.assign(contract, dto);
|
||||
return this.contractRepo.save(contract);
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const contract = await this.findOne(id);
|
||||
// Schema doesn't have deleted_at for Contract either.
|
||||
return this.contractRepo.remove(contract);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import {
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
IsBoolean,
|
||||
IsOptional,
|
||||
Length,
|
||||
IsInt,
|
||||
IsDateString,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class CreateContractDto {
|
||||
@ApiProperty({ example: 1 })
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
projectId!: number;
|
||||
|
||||
@ApiProperty({ example: 'C-001' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(1, 50)
|
||||
contractCode!: string;
|
||||
|
||||
@ApiProperty({ example: 'Main Construction Contract' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(1, 255)
|
||||
contractName!: string;
|
||||
|
||||
@ApiProperty({ example: 'Description of the contract', required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@ApiProperty({ example: '2024-01-01', required: false })
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
startDate?: string; // Receive as string, TypeORM handles date
|
||||
|
||||
@ApiProperty({ example: '2025-12-31', required: false })
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
endDate?: string;
|
||||
|
||||
@ApiProperty({ example: true, required: false })
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import {
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
IsBoolean,
|
||||
IsOptional,
|
||||
Length,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class CreateOrganizationDto {
|
||||
@ApiProperty({ example: 'ITD' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(1, 20)
|
||||
organizationCode!: string;
|
||||
|
||||
@ApiProperty({ example: 'Italian-Thai Development' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(1, 255)
|
||||
organizationName!: string;
|
||||
|
||||
@ApiProperty({ example: 1, required: false })
|
||||
@IsOptional()
|
||||
roleId?: number;
|
||||
|
||||
@ApiProperty({ example: true, required: false })
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { IsOptional, IsString, IsInt, Min } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class SearchContractDto {
|
||||
@ApiPropertyOptional({ description: 'Search term (code or name)' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Filter by Project ID' })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
projectId?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Page number', default: 1 })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Type(() => Number)
|
||||
page?: number = 1;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Items per page', default: 100 })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Type(() => Number)
|
||||
limit?: number = 100;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { IsOptional, IsString, IsInt, Min } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class SearchOrganizationDto {
|
||||
@ApiPropertyOptional({ description: 'Search term (code or name)' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Filter by Role ID' })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
roleId?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Page number', default: 1 })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Type(() => Number)
|
||||
page?: number = 1;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Items per page', default: 100 })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Type(() => Number)
|
||||
limit?: number = 100;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateContractDto } from './create-contract.dto.js';
|
||||
|
||||
export class UpdateContractDto extends PartialType(CreateContractDto) {}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateOrganizationDto } from './create-organization.dto.js';
|
||||
|
||||
export class UpdateOrganizationDto extends PartialType(CreateOrganizationDto) {}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Contract } from './contract.entity';
|
||||
import { Organization } from './organization.entity';
|
||||
|
||||
@Entity('contract_organizations')
|
||||
export class ContractOrganization {
|
||||
@PrimaryColumn({ name: 'contract_id' })
|
||||
contractId!: number;
|
||||
|
||||
@PrimaryColumn({ name: 'organization_id' })
|
||||
organizationId!: number;
|
||||
|
||||
@Column({ name: 'role_in_contract', nullable: true, length: 100 })
|
||||
roleInContract?: string;
|
||||
|
||||
// Relation ไปยัง Contract
|
||||
@ManyToOne(() => Contract, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'contract_id' })
|
||||
contract?: Contract;
|
||||
|
||||
// Relation ไปยัง Organization
|
||||
@ManyToOne(() => Organization, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'organization_id' })
|
||||
organization?: Organization;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Project } from './project.entity';
|
||||
|
||||
@Entity('contracts')
|
||||
export class Contract extends BaseEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column({ name: 'project_id' })
|
||||
projectId!: number;
|
||||
|
||||
@Column({ name: 'contract_code', unique: true, length: 50 })
|
||||
contractCode!: string;
|
||||
|
||||
@Column({ name: 'contract_name', length: 255 })
|
||||
contractName!: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column({ name: 'start_date', type: 'date', nullable: true })
|
||||
startDate?: Date;
|
||||
|
||||
@Column({ name: 'end_date', type: 'date', nullable: true })
|
||||
endDate?: Date;
|
||||
|
||||
@Column({ name: 'is_active', default: true })
|
||||
isActive!: boolean;
|
||||
|
||||
// Relation
|
||||
@ManyToOne(() => Project)
|
||||
@JoinColumn({ name: 'project_id' })
|
||||
project?: Project;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
|
||||
@Entity('organizations')
|
||||
export class Organization extends BaseEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column({ name: 'organization_code', unique: true, length: 20 })
|
||||
organizationCode!: string;
|
||||
|
||||
@Column({ name: 'organization_name', length: 255 })
|
||||
organizationName!: string;
|
||||
|
||||
@Column({ name: 'role_id', nullable: true })
|
||||
roleId?: number;
|
||||
|
||||
@Column({ name: 'is_active', default: true })
|
||||
isActive!: boolean;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Entity, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Project } from './project.entity';
|
||||
import { Organization } from './organization.entity';
|
||||
import { Organization } from '../../organization/entities/organization.entity';
|
||||
|
||||
@Entity('project_organizations')
|
||||
export class ProjectOrganization {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Contract } from './contract.entity';
|
||||
import { Contract } from '../../contract/entities/contract.entity';
|
||||
|
||||
@Entity('projects')
|
||||
export class Project extends BaseEntity {
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
Query,
|
||||
UseGuards,
|
||||
ParseIntPipe,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { OrganizationService } from './organization.service.js';
|
||||
import { CreateOrganizationDto } from './dto/create-organization.dto.js';
|
||||
import { UpdateOrganizationDto } from './dto/update-organization.dto.js';
|
||||
import { SearchOrganizationDto } from './dto/search-organization.dto.js';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard.js';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator.js';
|
||||
|
||||
@ApiTags('Organizations')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('organizations')
|
||||
export class OrganizationController {
|
||||
constructor(private readonly orgService: OrganizationService) {}
|
||||
|
||||
@Post()
|
||||
@RequirePermission('master_data.manage')
|
||||
@ApiOperation({ summary: 'Create Organization' })
|
||||
create(@Body() dto: CreateOrganizationDto) {
|
||||
return this.orgService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Get All Organizations' })
|
||||
findAll(@Query() query: SearchOrganizationDto) {
|
||||
return this.orgService.findAll(query);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get Organization by ID' })
|
||||
findOne(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.orgService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@RequirePermission('master_data.manage')
|
||||
@ApiOperation({ summary: 'Update Organization' })
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() dto: UpdateOrganizationDto
|
||||
) {
|
||||
return this.orgService.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RequirePermission('master_data.manage')
|
||||
@ApiOperation({ summary: 'Delete Organization' })
|
||||
remove(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.orgService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
ConflictException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, Like } from 'typeorm';
|
||||
import { Organization } from './entities/organization.entity';
|
||||
import { CreateOrganizationDto } from './dto/create-organization.dto.js';
|
||||
import { UpdateOrganizationDto } from './dto/update-organization.dto.js';
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationService {
|
||||
constructor(
|
||||
@InjectRepository(Organization)
|
||||
private readonly orgRepo: Repository<Organization>
|
||||
) {}
|
||||
|
||||
async create(dto: CreateOrganizationDto) {
|
||||
const existing = await this.orgRepo.findOne({
|
||||
where: { organizationCode: dto.organizationCode },
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException(
|
||||
`Organization Code "${dto.organizationCode}" already exists`
|
||||
);
|
||||
}
|
||||
const org = this.orgRepo.create(dto);
|
||||
return this.orgRepo.save(org);
|
||||
}
|
||||
|
||||
async findAll(params?: any) {
|
||||
const { search, page = 1, limit = 100 } = params || {};
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Use findAndCount for safer, standard TypeORM queries
|
||||
const findOptions: any = {
|
||||
order: { organizationCode: 'ASC' },
|
||||
skip,
|
||||
take: limit,
|
||||
};
|
||||
|
||||
if (search) {
|
||||
findOptions.where = [
|
||||
{ organizationCode: Like(`%${search}%`) },
|
||||
{ organizationName: Like(`%${search}%`) },
|
||||
];
|
||||
}
|
||||
|
||||
// Debug logging
|
||||
console.log(
|
||||
'[OrganizationService] Finding all with options:',
|
||||
JSON.stringify(findOptions)
|
||||
);
|
||||
|
||||
const [data, total] = await this.orgRepo.findAndCount(findOptions);
|
||||
console.log(`[OrganizationService] Found ${total} organizations`);
|
||||
|
||||
return {
|
||||
data,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const org = await this.orgRepo.findOne({ where: { id } });
|
||||
if (!org) throw new NotFoundException(`Organization ID ${id} not found`);
|
||||
return org;
|
||||
}
|
||||
|
||||
async update(id: number, dto: UpdateOrganizationDto) {
|
||||
const org = await this.findOne(id);
|
||||
Object.assign(org, dto);
|
||||
return this.orgRepo.save(org);
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const org = await this.findOne(id);
|
||||
// Hard delete or Soft delete? Schema doesn't have deleted_at for Organization, but let's check.
|
||||
// Schema says: created_at, updated_at. No deleted_at.
|
||||
// So hard delete.
|
||||
return this.orgRepo.remove(org);
|
||||
}
|
||||
}
|
||||
@@ -2,32 +2,21 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ProjectService } from './project.service.js';
|
||||
import { ProjectController } from './project.controller.js';
|
||||
import { OrganizationService } from './organization.service.js';
|
||||
import { OrganizationController } from './organization.controller.js';
|
||||
import { ContractService } from './contract.service.js';
|
||||
import { ContractController } from './contract.controller.js';
|
||||
|
||||
import { Project } from './entities/project.entity';
|
||||
import { Organization } from './entities/organization.entity';
|
||||
import { Contract } from './entities/contract.entity';
|
||||
import { ProjectOrganization } from './entities/project-organization.entity';
|
||||
import { ContractOrganization } from './entities/contract-organization.entity';
|
||||
// Modules
|
||||
import { UserModule } from '../user/user.module';
|
||||
import { OrganizationModule } from '../organization/organization.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
Project,
|
||||
Organization,
|
||||
Contract,
|
||||
ProjectOrganization,
|
||||
ContractOrganization,
|
||||
]),
|
||||
TypeOrmModule.forFeature([Project, ProjectOrganization]),
|
||||
UserModule,
|
||||
OrganizationModule,
|
||||
],
|
||||
controllers: [ProjectController, OrganizationController, ContractController],
|
||||
providers: [ProjectService, OrganizationService, ContractService],
|
||||
exports: [ProjectService, OrganizationService, ContractService],
|
||||
controllers: [ProjectController],
|
||||
providers: [ProjectService],
|
||||
exports: [ProjectService],
|
||||
})
|
||||
export class ProjectModule {}
|
||||
|
||||
@@ -2,12 +2,12 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { ProjectService } from './project.service';
|
||||
import { Project } from './entities/project.entity';
|
||||
import { Organization } from './entities/organization.entity';
|
||||
import { OrganizationService } from '../organization/organization.service';
|
||||
|
||||
describe('ProjectService', () => {
|
||||
let service: ProjectService;
|
||||
let mockProjectRepository: Record<string, jest.Mock>;
|
||||
let mockOrganizationRepository: Record<string, jest.Mock>;
|
||||
let mockOrganizationService: Record<string, jest.Mock>;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockProjectRepository = {
|
||||
@@ -27,9 +27,8 @@ describe('ProjectService', () => {
|
||||
})),
|
||||
};
|
||||
|
||||
mockOrganizationRepository = {
|
||||
find: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
mockOrganizationService = {
|
||||
findAllActive: jest.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -40,8 +39,8 @@ describe('ProjectService', () => {
|
||||
useValue: mockProjectRepository,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(Organization),
|
||||
useValue: mockOrganizationRepository,
|
||||
provide: OrganizationService,
|
||||
useValue: mockOrganizationService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
@@ -66,7 +65,7 @@ describe('ProjectService', () => {
|
||||
.createQueryBuilder()
|
||||
.getManyAndCount.mockResolvedValue([mockProjects, 1]);
|
||||
|
||||
const result = await service.findAll({});
|
||||
const result = await service.findAll({ page: 1, limit: 10 });
|
||||
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.meta).toBeDefined();
|
||||
@@ -76,11 +75,11 @@ describe('ProjectService', () => {
|
||||
describe('findAllOrganizations', () => {
|
||||
it('should return all organizations', async () => {
|
||||
const mockOrgs = [{ organization_id: 1, name: 'Test Org' }];
|
||||
mockOrganizationRepository.find.mockResolvedValue(mockOrgs);
|
||||
mockOrganizationService.findAllActive.mockResolvedValue(mockOrgs);
|
||||
|
||||
const result = await service.findAllOrganizations();
|
||||
|
||||
expect(mockOrganizationRepository.find).toHaveBeenCalled();
|
||||
expect(mockOrganizationService.findAllActive).toHaveBeenCalled();
|
||||
expect(result).toEqual(mockOrgs);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Repository, Like } from 'typeorm';
|
||||
|
||||
// Entities
|
||||
import { Project } from './entities/project.entity';
|
||||
import { Organization } from './entities/organization.entity';
|
||||
import { OrganizationService } from '../organization/organization.service';
|
||||
|
||||
// DTOs
|
||||
import { CreateProjectDto } from './dto/create-project.dto.js';
|
||||
@@ -23,8 +23,7 @@ export class ProjectService {
|
||||
constructor(
|
||||
@InjectRepository(Project)
|
||||
private projectRepository: Repository<Project>,
|
||||
@InjectRepository(Organization)
|
||||
private organizationRepository: Repository<Organization>
|
||||
private organizationService: OrganizationService
|
||||
) {}
|
||||
|
||||
// --- CRUD Operations ---
|
||||
@@ -123,9 +122,6 @@ export class ProjectService {
|
||||
// --- Organization Helper ---
|
||||
|
||||
async findAllOrganizations() {
|
||||
return this.organizationRepository.find({
|
||||
where: { isActive: true },
|
||||
order: { organizationCode: 'ASC' },
|
||||
});
|
||||
return this.organizationService.findAllActive();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user