Initial commit: ERP system with advance verification fixes
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user