251210:1709 Frontend: reeactor organization and run build
Spec Validation / validate-markdown (push) Has been cancelled
Spec Validation / validate-diagrams (push) Has been cancelled
Spec Validation / check-todos (push) Has been cancelled

This commit is contained in:
admin
2025-12-10 17:09:11 +07:00
parent aa96cd90e3
commit c8a0f281ef
140 changed files with 3780 additions and 1473 deletions
@@ -19,6 +19,7 @@ import {
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';
@@ -38,11 +39,10 @@ export class ContractController {
@Get()
@ApiOperation({
summary: 'Get All Contracts (Optional: filter by projectId)',
summary: 'Get All Contracts (Search & Filter)',
})
@ApiQuery({ name: 'projectId', required: false, type: Number })
findAll(@Query('projectId') projectId?: number) {
return this.contractService.findAll(projectId);
findAll(@Query() query: SearchContractDto) {
return this.contractService.findAll(query);
}
@Get(':id')
@@ -4,7 +4,7 @@ import {
ConflictException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from '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';
@@ -29,17 +29,51 @@ export class ContractService {
return this.contractRepo.save(contract);
}
async findAll(projectId?: number) {
const query = this.contractRepo
.createQueryBuilder('c')
.leftJoinAndSelect('c.project', 'p')
.orderBy('c.contractCode', 'ASC');
async findAll(params?: any) {
const { search, projectId, page = 1, limit = 100 } = params || {};
const skip = (page - 1) * limit;
if (projectId) {
query.where('c.projectId = :projectId', { projectId });
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}%`) });
}
return query.getMany();
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) {
@@ -20,6 +20,10 @@ export class CreateOrganizationDto {
@Length(1, 255)
organizationName!: string;
@ApiProperty({ example: 1, required: false })
@IsOptional()
roleId?: number;
@ApiProperty({ example: true, required: false })
@IsOptional()
@IsBoolean()
@@ -0,0 +1,30 @@
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;
}
@@ -0,0 +1,30 @@
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;
}
@@ -12,6 +12,9 @@ export class Organization extends BaseEntity {
@Column({ name: 'organization_name', length: 255 })
organizationName!: string;
@Column({ name: 'role_id', nullable: true })
roleId?: number;
@Column({ name: 'is_active', default: true })
isActive!: boolean;
}
@@ -6,6 +6,7 @@ import {
Patch,
Param,
Delete,
Query,
UseGuards,
ParseIntPipe,
} from '@nestjs/common';
@@ -13,6 +14,7 @@ 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';
@@ -32,8 +34,8 @@ export class OrganizationController {
@Get()
@ApiOperation({ summary: 'Get All Organizations' })
findAll() {
return this.orgService.findAll();
findAll(@Query() query: SearchOrganizationDto) {
return this.orgService.findAll(query);
}
@Get(':id')
@@ -4,7 +4,7 @@ import {
ConflictException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from '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';
@@ -29,10 +29,40 @@ export class OrganizationService {
return this.orgRepo.save(org);
}
async findAll() {
return this.orgRepo.find({
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) {
@@ -63,7 +63,7 @@ export class ProjectService {
);
}
query.orderBy('project.created_at', 'DESC');
query.orderBy('project.createdAt', 'DESC');
query.skip(skip).take(limit);
const [items, total] = await query.getManyAndCount();