Initial commit: ERP system with advance verification fixes

This commit is contained in:
System Administrator
2026-03-25 23:55:36 +07:00
commit 563ca12d76
5920 changed files with 828689 additions and 0 deletions
@@ -0,0 +1,45 @@
import { IsString, IsNumber, IsDate, IsOptional, IsUUID, IsDecimal } from 'class-validator';
export class CreateProjectDto {
@IsString()
name: string;
@IsUUID()
@IsOptional()
customer_id: string;
@IsString()
contract_number: string;
@IsNumber()
contract_amount: number;
@IsString()
currency: string;
@IsNumber()
rmb_equivalent: number;
@IsDate()
@IsOptional()
start_date: Date;
@IsDate()
@IsOptional()
end_date: Date;
@IsString()
status: string;
@IsString()
@IsOptional()
settlement_type: string; // 'lump_sum' 或 'unit_price'
@IsNumber()
@IsOptional()
estimated_quantity: number;
@IsNumber()
@IsOptional()
unit_price: number;
}
@@ -0,0 +1,2 @@
export * from './create-project.dto';
export * from './update-project.dto';
@@ -0,0 +1,51 @@
import { IsString, IsNumber, IsDate, IsOptional, IsUUID } from 'class-validator';
export class UpdateProjectDto {
@IsString()
@IsOptional()
name: string;
@IsUUID()
@IsOptional()
customer_id: string;
@IsString()
@IsOptional()
contract_number: string;
@IsNumber()
@IsOptional()
contract_amount: number;
@IsString()
@IsOptional()
currency: string;
@IsNumber()
@IsOptional()
rmb_equivalent: number;
@IsDate()
@IsOptional()
start_date: Date;
@IsDate()
@IsOptional()
end_date: Date;
@IsString()
@IsOptional()
status: string;
@IsString()
@IsOptional()
settlement_type: string; // 'lump_sum' 或 'unit_price'
@IsNumber()
@IsOptional()
estimated_quantity: number;
@IsNumber()
@IsOptional()
unit_price: number;
}
@@ -0,0 +1,77 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm';
import { Customer } from '../customers/customer.entity';
import { Contract } from '../contracts/contract.entity';
import { Purchase } from '../purchases/purchase.entity';
import { Subcontract } from '../subcontracts/subcontract.entity';
import { FinancialRecord } from '../finance/financial-record.entity';
import { ExpenseClaim } from '../finance/expense-claim.entity';
import { AdvancePayment } from '../finance/advance-payment.entity';
@Entity('projects')
export class Project {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 255, nullable: false })
name: string;
@Column({ type: 'uuid', nullable: true })
customer_id: string;
@ManyToOne(() => Customer, customer => customer.projects)
customer: Customer;
@Column({ type: 'varchar', length: 100, unique: true, nullable: false })
contract_number: string;
@Column({ type: 'decimal', precision: 18, scale: 2, nullable: false })
contract_amount: number;
@Column({ type: 'varchar', length: 10, nullable: false })
currency: string;
@Column({ type: 'decimal', precision: 18, scale: 2, nullable: false })
rmb_equivalent: number;
@Column({ type: 'date', nullable: true })
start_date: Date;
@Column({ type: 'date', nullable: true })
end_date: Date;
@Column({ type: 'varchar', length: 50, nullable: false })
status: string;
@Column({ type: 'varchar', length: 20, nullable: true })
settlement_type: string; // 'lump_sum' 或 'unit_price'
@Column({ type: 'decimal', precision: 18, scale: 2, nullable: true })
estimated_quantity: number;
@Column({ type: 'decimal', precision: 18, scale: 2, nullable: true })
unit_price: number;
@CreateDateColumn()
created_at: Date;
@UpdateDateColumn()
updated_at: Date;
@OneToMany(() => Contract, contract => contract.project)
contracts: Contract[];
@OneToMany(() => Purchase, purchase => purchase.project)
purchases: Purchase[];
@OneToMany(() => Subcontract, subcontract => subcontract.project)
subcontracts: Subcontract[];
@OneToMany(() => FinancialRecord, financialRecord => financialRecord.project)
financial_records: FinancialRecord[];
@OneToMany(() => ExpenseClaim, expenseClaim => expenseClaim.project)
expense_claims: ExpenseClaim[];
@OneToMany(() => AdvancePayment, advancePayment => advancePayment.project)
advance_payments: AdvancePayment[];
}
@@ -0,0 +1,43 @@
import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common';
import { ProjectsService } from './projects.service';
import { CreateProjectDto, UpdateProjectDto } from './dto';
@Controller('projects')
export class ProjectsController {
constructor(private readonly projectsService: ProjectsService) {}
@Get()
async findAll(@Query() query) {
return this.projectsService.findAll(query);
}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.projectsService.findOne(id);
}
@Post()
async create(@Body() createProjectDto: CreateProjectDto) {
return this.projectsService.create(createProjectDto);
}
@Put(':id')
async update(@Param('id') id: string, @Body() updateProjectDto: UpdateProjectDto) {
return this.projectsService.update(id, updateProjectDto);
}
@Delete(':id')
async remove(@Param('id') id: string) {
return this.projectsService.remove(id);
}
@Get(':id/calculate-duration')
async calculateDuration(@Param('id') id: string) {
return this.projectsService.calculateDuration(id);
}
@Put(':id/update-status')
async updateStatus(@Param('id') id: string, @Body('status') status: string) {
return this.projectsService.updateStatus(id, status);
}
}
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Project } from './project.entity';
import { ProjectsController } from './projects.controller';
import { ProjectsService } from './projects.service';
@Module({
imports: [TypeOrmModule.forFeature([Project])],
controllers: [ProjectsController],
providers: [ProjectsService],
exports: [ProjectsService],
})
export class ProjectsModule {}
@@ -0,0 +1,90 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Project } from './project.entity';
import { CreateProjectDto, UpdateProjectDto } from './dto';
@Injectable()
export class ProjectsService {
constructor(
@InjectRepository(Project)
private projectsRepository: Repository<Project>,
) {}
async findAll(query) {
const { page = 1, limit = 10, status, customer_id } = query;
const skip = (page - 1) * limit;
const queryBuilder = this.projectsRepository.createQueryBuilder('project');
if (status) {
queryBuilder.where('project.status = :status', { status });
}
if (customer_id) {
queryBuilder.where('project.customer_id = :customer_id', { customer_id });
}
const [projects, total] = await queryBuilder
.leftJoinAndSelect('project.customer', 'customer')
.skip(skip)
.take(limit)
.getManyAndCount();
return {
projects,
total,
page,
limit,
};
}
async findOne(id: string) {
const project = await this.projectsRepository.findOne({
where: { id },
relations: ['customer', 'contracts', 'purchases', 'subcontracts'],
});
if (!project) {
throw new NotFoundException(`Project with ID ${id} not found`);
}
return project;
}
async create(createProjectDto: CreateProjectDto) {
const project = this.projectsRepository.create(createProjectDto);
return this.projectsRepository.save(project);
}
async update(id: string, updateProjectDto: UpdateProjectDto) {
const project = await this.findOne(id);
Object.assign(project, updateProjectDto);
return this.projectsRepository.save(project);
}
async remove(id: string) {
const project = await this.findOne(id);
return this.projectsRepository.remove(project);
}
async calculateDuration(id: string) {
const project = await this.findOne(id);
if (!project.start_date || !project.end_date) {
return { message: 'Start date or end date is not set' };
}
const start = new Date(project.start_date);
const end = new Date(project.end_date);
const duration = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
return { duration_days: duration };
}
async updateStatus(id: string, status: string) {
const project = await this.findOne(id);
project.status = status;
return this.projectsRepository.save(project);
}
}