feat: 乙方公司管理

This commit is contained in:
louis 2024-03-04 16:34:54 +08:00
parent ba5e47b766
commit 944b5adbff
11 changed files with 340 additions and 13 deletions

View File

@ -29,6 +29,7 @@ import { VehicleUsageModule } from './modules/vehicle-usage/vehicle-usage.module
import { VehicleUsageController } from './modules/vehicle-usage/vehicle-usage.controller'; import { VehicleUsageController } from './modules/vehicle-usage/vehicle-usage.controller';
import { VehicleUsageService } from './modules/vehicle-usage/vehicle-usage.service'; import { VehicleUsageService } from './modules/vehicle-usage/vehicle-usage.service';
import { MaterialsInventoryModule } from './modules/materials_inventory/materials_inventory.module'; import { MaterialsInventoryModule } from './modules/materials_inventory/materials_inventory.module';
import { CompanyModule } from './modules/company/company.module';
@Module({ @Module({
imports: [ imports: [
@ -61,7 +62,9 @@ import { MaterialsInventoryModule } from './modules/materials_inventory/material
VehicleUsageModule, VehicleUsageModule,
MaterialsInventoryModule MaterialsInventoryModule,
CompanyModule
], ],
providers: [ providers: [
{ provide: APP_FILTER, useClass: AllExceptionsFilter }, { provide: APP_FILTER, useClass: AllExceptionsFilter },

View File

@ -1,6 +1,14 @@
// 字典项status
export enum DictTypeStatusEnum { export enum DictTypeStatusEnum {
/** 启用 */ /** 启用 */
ENABLE = 1, ENABLE = 1,
/** 禁用 */ /** 禁用 */
DISABLE = 0 DISABLE = 0
} }
// 业务模块枚举
export enum BusinessModuleEnum {
CONTRACT = 1,
MATERIALS_INVENTORY = 2,
COMPANY = 3
}

View File

@ -0,0 +1,79 @@
import {
Body,
Controller,
Get,
Query,
Put,
Delete,
Post,
BadRequestException
} from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Perm, definePermission } from '../auth/decorators/permission.decorator';
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
import { CompanyService } from './company.service';
import { ApiResult } from '~/common/decorators/api-result.decorator';
import { CompanyEntity } from './company.entity';
import { CompanyDto, CompanyQueryDto, CompanyUpdateDto } from './company.dto';
import { IdParam } from '~/common/decorators/id-param.decorator';
export const permissions = definePermission('app:company', {
LIST: 'list',
CREATE: 'create',
READ: 'read',
UPDATE: 'update',
DELETE: 'delete'
} as const);
@ApiTags('Company - 公司')
@ApiSecurityAuth()
@Controller('company')
export class CompanyController {
constructor(private companyService: CompanyService) {}
@Get()
@ApiOperation({ summary: '获取公司列表' })
@ApiResult({ type: [CompanyEntity], isPage: true })
@Perm(permissions.LIST)
async list(@Query() dto: CompanyQueryDto) {
return this.companyService.findAll(dto);
}
@Get(':id')
@ApiOperation({ summary: '获取公司信息' })
@ApiResult({ type: CompanyDto })
@Perm(permissions.READ)
async info(@IdParam() id: number) {
return this.companyService.info(id);
}
@Post()
@ApiOperation({ summary: '新增公司' })
@Perm(permissions.CREATE)
async create(@Body() dto: CompanyDto): Promise<void> {
await this.companyService.create(dto);
}
@Put(':id')
@ApiOperation({ summary: '更新公司' })
@Perm(permissions.UPDATE)
async update(@IdParam() id: number, @Body() dto: CompanyUpdateDto): Promise<void> {
await this.companyService.update(id, dto);
}
@Delete(':id')
@ApiOperation({ summary: '删除公司' })
@Perm(permissions.DELETE)
async delete(@IdParam() id: number): Promise<void> {
await this.companyService.delete(id);
}
@Put('unlink-attachments/:id')
@ApiOperation({ summary: '附件解除关联' })
@Perm(permissions.UPDATE)
async unlinkAttachments(
@IdParam() id: number,
@Body() { fileIds }: CompanyUpdateDto
): Promise<void> {
await this.companyService.unlinkAttachments(id, fileIds);
}
}

View File

@ -0,0 +1,39 @@
import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger';
import {
IsArray,
IsDate,
IsDateString,
IsIn,
IsInt,
IsNumber,
IsOptional,
IsString,
Matches,
MinLength
} from 'class-validator';
import { PagerDto } from '~/common/dto/pager.dto';
import { Storage } from '../tools/storage/storage.entity';
import { IsUnique } from '~/shared/database/constraints/unique.constraint';
import { CompanyEntity } from './company.entity';
export class CompanyDto {
@ApiProperty({ description: '公司名称' })
@IsUnique(CompanyEntity, { message: '已存在同名公司' })
@IsString()
name: string;
@ApiProperty({ description: '附件' })
files: Storage[];
}
export class CompanyUpdateDto extends PartialType(CompanyDto) {
@ApiProperty({ description: '附件' })
@IsOptional()
@IsArray()
fileIds: number[];
}
export class CompanyQueryDto extends IntersectionType(
PagerDto<CompanyDto>,
PartialType(CompanyDto)
) {}

View File

@ -0,0 +1,29 @@
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm';
import { CommonEntity } from '~/common/entity/common.entity';
import { Storage } from '../tools/storage/storage.entity';
@Entity({ name: 'company' })
export class CompanyEntity extends CommonEntity {
@Column({
name: 'name',
type: 'varchar',
unique: true,
length: 255,
comment: '公司名称'
})
@ApiProperty({ description: '公司名称' })
name: string;
@Column({ name: 'is_delete', type: 'tinyint', default: 0, comment: '是否删除' })
@ApiProperty({ description: '删除状态0未删除1已删除' })
isDelete: number;
@ManyToMany(() => Storage, storage => storage.companys)
@JoinTable({
name: 'company_storage',
joinColumn: { name: 'company_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'file_id', referencedColumnName: 'id' }
})
files: Relation<Storage[]>;
}

View File

@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { CompanyController } from './company.controller';
import { CompanyService } from './company.service';
import { CompanyEntity } from './company.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
import { StorageModule } from '../tools/storage/storage.module';
import { DatabaseModule } from '~/shared/database/database.module';
@Module({
imports: [TypeOrmModule.forFeature([CompanyEntity]), StorageModule, DatabaseModule],
controllers: [CompanyController],
providers: [CompanyService]
})
export class CompanyModule {}

View File

@ -0,0 +1,127 @@
import { Injectable } from '@nestjs/common';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { CompanyEntity } from './company.entity';
import { EntityManager, Like, Repository } from 'typeorm';
import { CompanyDto, CompanyQueryDto, CompanyUpdateDto } from './company.dto';
import { Pagination } from '~/helper/paginate/pagination';
import { paginate } from '~/helper/paginate';
import { Storage } from '../tools/storage/storage.entity';
import { BusinessException } from '~/common/exceptions/biz.exception';
import { ErrorEnum } from '~/constants/error-code.constant';
import { fieldSearch } from '~/shared/database/field-search';
@Injectable()
export class CompanyService {
constructor(
@InjectEntityManager() private entityManager: EntityManager,
@InjectRepository(CompanyEntity)
private companyRepository: Repository<CompanyEntity>,
@InjectRepository(Storage)
private storageRepository: Repository<Storage>
) {}
/**
*
*/
async findAll({
page,
pageSize,
...fields
}: CompanyQueryDto): Promise<Pagination<CompanyEntity>> {
const queryBuilder = this.companyRepository
.createQueryBuilder('company')
.leftJoin('company.files', 'files')
.addSelect(['files.id', 'files.path'])
.where(fieldSearch(fields))
.andWhere('company.isDelete = 0');
return paginate<CompanyEntity>(queryBuilder, {
page,
pageSize
});
}
/**
*
*/
async create(dto: CompanyDto): Promise<void> {
await this.companyRepository.insert(dto);
}
/**
*
*/
async update(id: number, { fileIds, ...data }: Partial<CompanyUpdateDto>): Promise<void> {
await this.entityManager.transaction(async manager => {
await manager.update(CompanyEntity, id, {
...data
});
const company = await this.companyRepository
.createQueryBuilder('company')
.leftJoinAndSelect('company.files', 'files')
.where('company.id = :id', { id })
.getOne();
if (fileIds?.length) {
const count = await this.storageRepository
.createQueryBuilder('storage')
.where('storage.id in(:fileIds)', { fileIds })
.getCount();
if (count !== fileIds?.length) {
throw new BusinessException(ErrorEnum.STORAGE_NOT_FOUND);
}
// 附件要批量更新
await manager
.createQueryBuilder()
.relation(CompanyEntity, 'files')
.of(id)
.addAndRemove(fileIds, company.files);
}
});
}
/**
*
*/
async delete(id: number): Promise<void> {
// 合同比较重要,做逻辑删除
await this.companyRepository.update(id, { isDelete: 1 });
}
/**
*
*/
async info(id: number) {
const info = await this.companyRepository
.createQueryBuilder('company')
.where({
id
})
.andWhere('company.isDelete = 0')
.getOne();
return info;
}
/**
*
* @param id ID
* @param fileIds ID
*/
async unlinkAttachments(id: number, fileIds: number[]) {
await this.entityManager.transaction(async manager => {
const company = await this.companyRepository
.createQueryBuilder('company')
.leftJoinAndSelect('company.files', 'files')
.where('company.id = :id', { id })
.getOne();
const linkedFiles = company.files
.map(item => item.id)
.filter(item => !fileIds.includes(item));
// 附件要批量更新
await manager
.createQueryBuilder()
.relation(CompanyEntity, 'files')
.of(id)
.addAndRemove(linkedFiles, company.files);
});
}
}

View File

@ -9,6 +9,7 @@ import { paginate } from '~/helper/paginate';
import { Storage } from '../tools/storage/storage.entity'; import { Storage } from '../tools/storage/storage.entity';
import { BusinessException } from '~/common/exceptions/biz.exception'; import { BusinessException } from '~/common/exceptions/biz.exception';
import { ErrorEnum } from '~/constants/error-code.constant'; import { ErrorEnum } from '~/constants/error-code.constant';
import { fieldSearch } from '~/shared/database/field-search';
@Injectable() @Injectable()
export class ContractService { export class ContractService {
@ -19,27 +20,20 @@ export class ContractService {
@InjectRepository(Storage) @InjectRepository(Storage)
private storageRepository: Repository<Storage> private storageRepository: Repository<Storage>
) {} ) {}
/** /**
* *
*/ */
async findAll({ async findAll({
page, page,
pageSize, pageSize,
contractNumber, ...fields
title,
type,
status
}: ContractQueryDto): Promise<Pagination<ContractEntity>> { }: ContractQueryDto): Promise<Pagination<ContractEntity>> {
const queryBuilder = this.contractRepository const queryBuilder = this.contractRepository
.createQueryBuilder('contract') .createQueryBuilder('contract')
.leftJoin('contract.files', 'files') .leftJoin('contract.files', 'files')
.addSelect(['files.id', 'files.path']) .addSelect(['files.id', 'files.path'])
.where({ .where(fieldSearch(fields))
...(contractNumber ? { contractNumber: Like(`%${contractNumber}%`) } : null),
...(title ? { title: Like(`%${title}%`) } : null),
...(isNumber(type) ? { type } : null),
...(isNumber(status) ? { status } : null)
})
.andWhere('contract.isDelete = 0'); .andWhere('contract.isDelete = 0');
return paginate<ContractEntity>(queryBuilder, { return paginate<ContractEntity>(queryBuilder, {

View File

@ -192,7 +192,7 @@ export class MaterialsInventoryEntity extends CommonEntity {
@ApiProperty({ description: '删除状态0未删除1已删除' }) @ApiProperty({ description: '删除状态0未删除1已删除' })
isDelete: number; isDelete: number;
@ManyToMany(() => Storage, storage => storage.contracts) @ManyToMany(() => Storage, storage => storage.materialsInventories)
@JoinTable({ @JoinTable({
name: 'materials_inventory_storage', name: 'materials_inventory_storage',
joinColumn: { name: 'materials_inventory_id', referencedColumnName: 'id' }, joinColumn: { name: 'materials_inventory_id', referencedColumnName: 'id' },

View File

@ -2,7 +2,9 @@ import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
import { Column, Entity, ManyToMany, Relation } from 'typeorm'; import { Column, Entity, ManyToMany, Relation } from 'typeorm';
import { CommonEntity } from '~/common/entity/common.entity'; import { CommonEntity } from '~/common/entity/common.entity';
import { CompanyEntity } from '~/modules/company/company.entity';
import { ContractEntity } from '~/modules/contract/contract.entity'; import { ContractEntity } from '~/modules/contract/contract.entity';
import { MaterialsInventoryEntity } from '~/modules/materials_inventory/materials_inventory.entity';
@Entity({ name: 'tool_storage' }) @Entity({ name: 'tool_storage' })
export class Storage extends CommonEntity { export class Storage extends CommonEntity {
@ -42,4 +44,12 @@ export class Storage extends CommonEntity {
@ApiHideProperty() @ApiHideProperty()
@ManyToMany(() => ContractEntity, contract => contract.files) @ManyToMany(() => ContractEntity, contract => contract.files)
contracts: Relation<ContractEntity[]>; contracts: Relation<ContractEntity[]>;
@ApiHideProperty()
@ManyToMany(() => CompanyEntity, company => company.files)
companys: Relation<CompanyEntity[]>;
@ApiHideProperty()
@ManyToMany(() => MaterialsInventoryEntity, materialsInventories => materialsInventories.files)
materialsInventories: Relation<MaterialsInventoryEntity[]>;
} }

View File

@ -0,0 +1,24 @@
import { isNumber } from 'lodash';
import { Like, ObjectLiteral, ObjectType } from 'typeorm';
export const fieldSearch = <T>(entity: Partial<T>): ObjectLiteral => {
let result = {};
for (let key in entity) {
if (entity.hasOwnProperty(key)) {
switch (typeof entity[key]) {
case 'number':
result = { ...result, ...(isNumber(entity[key]) ? { [key]: entity[key] } : null) };
break;
case 'string':
result = { ...result, ...(entity[key] ? { [key]: Like(`%${entity[key]}%`) } : null) };
break;
case 'boolean':
result = { ...result, ...(entity[key] === true ? { [key]: 1 } : { [key]: 0 }) };
break;
default:
result = { ...result, ...(entity[key] ? { [key]: entity[key] } : null) };
break;
}
}
}
return result;
};