diff --git a/src/app.module.ts b/src/app.module.ts index 5e6ab5d..28b5c6d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -25,13 +25,11 @@ import { DatabaseModule } from './shared/database/database.module'; import { SocketModule } from './socket/socket.module'; import { ContractModule } from './modules/contract/contract.module'; -import { VehicleUsageModule } from './modules/vehicle-usage/vehicle-usage.module'; -import { VehicleUsageController } from './modules/vehicle-usage/vehicle-usage.controller'; -import { VehicleUsageService } from './modules/vehicle-usage/vehicle-usage.service'; import { MaterialsInventoryModule } from './modules/materials_inventory/materials_inventory.module'; import { CompanyModule } from './modules/company/company.module'; import { ProductModule } from './modules/product/product.module'; import { ProjectModule } from './modules/project/project.module'; +import { VehicleUsageModule } from './modules/vehicle_usage/vehicle_usage.module'; @Module({ imports: [ @@ -62,9 +60,6 @@ import { ProjectModule } from './modules/project/project.module'; // 合同模块 ContractModule, - // 车辆管理 - VehicleUsageModule, - // 原材料库存 MaterialsInventoryModule, @@ -75,7 +70,10 @@ import { ProjectModule } from './modules/project/project.module'; ProductModule, // 项目管理 - ProjectModule + ProjectModule, + + // 车辆管理 + VehicleUsageModule ], providers: [ { provide: APP_FILTER, useClass: AllExceptionsFilter }, @@ -86,9 +84,8 @@ import { ProjectModule } from './modules/project/project.module'; { provide: APP_INTERCEPTOR, useClass: IdempotenceInterceptor }, { provide: APP_GUARD, useClass: JwtAuthGuard }, - { provide: APP_GUARD, useClass: RbacGuard }, - VehicleUsageService + { provide: APP_GUARD, useClass: RbacGuard } ], - controllers: [VehicleUsageController] + controllers: [] }) export class AppModule {} diff --git a/src/modules/product/product.module.ts b/src/modules/product/product.module.ts index 75ea48f..fdc44e9 100644 --- a/src/modules/product/product.module.ts +++ b/src/modules/product/product.module.ts @@ -4,10 +4,9 @@ import { ProductService } from './product.service'; import { ProductEntity } from './product.entity'; import { TypeOrmModule } from '@nestjs/typeorm'; import { StorageModule } from '../tools/storage/storage.module'; -import { DatabaseModule } from '~/shared/database/database.module'; @Module({ - imports: [TypeOrmModule.forFeature([ProductEntity]), StorageModule, DatabaseModule], + imports: [TypeOrmModule.forFeature([ProductEntity]), StorageModule], controllers: [ProductController], providers: [ProductService] }) diff --git a/src/modules/tools/storage/storage.entity.ts b/src/modules/tools/storage/storage.entity.ts index ae0034a..cc58f42 100644 --- a/src/modules/tools/storage/storage.entity.ts +++ b/src/modules/tools/storage/storage.entity.ts @@ -8,6 +8,7 @@ import { MaterialsInOutEntity } from '~/modules/materials_inventory/in_out/mater import { MaterialsInventoryEntity } from '~/modules/materials_inventory/materials_inventory.entity'; import { ProductEntity } from '~/modules/product/product.entity'; import { ProjectEntity } from '~/modules/project/project.entity'; +import { VehicleUsageEntity } from '~/modules/vehicle_usage/vehicle_usage.entity'; @Entity({ name: 'tool_storage' }) export class Storage extends CommonEntity { @@ -63,4 +64,8 @@ export class Storage extends CommonEntity { @ApiHideProperty() @ManyToMany(() => ProjectEntity, project => project.files) projects: Relation; + + @ApiHideProperty() + @ManyToMany(() => VehicleUsageEntity, vu => vu.files) + vehicleUsage: Relation; } diff --git a/src/modules/vehicle-usage/vehicle-usage.controller.ts b/src/modules/vehicle-usage/vehicle-usage.controller.ts deleted file mode 100644 index 9c63da7..0000000 --- a/src/modules/vehicle-usage/vehicle-usage.controller.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Controller } from '@nestjs/common'; - -@Controller('vehicle-usage') -export class VehicleUsageController {} diff --git a/src/modules/vehicle-usage/vehicle-usage.module.ts b/src/modules/vehicle-usage/vehicle-usage.module.ts deleted file mode 100644 index d50dfc1..0000000 --- a/src/modules/vehicle-usage/vehicle-usage.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Module } from '@nestjs/common'; -import { VehicleUsageService } from './vehicle-usage.service'; -import { VehicleUsageController } from './vehicle-usage.controller'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { VehicleUsageEntity } from './vehicle-usage.entity'; - -@Module({ - imports: [TypeOrmModule.forFeature([VehicleUsageEntity])], - providers: [VehicleUsageService], - controllers: [VehicleUsageController] -}) -export class VehicleUsageModule {} diff --git a/src/modules/vehicle-usage/vehicle-usage.service.ts b/src/modules/vehicle-usage/vehicle-usage.service.ts deleted file mode 100644 index 0ec15a1..0000000 --- a/src/modules/vehicle-usage/vehicle-usage.service.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class VehicleUsageService {} diff --git a/src/modules/vehicle_usage/vehicle_usage.controller.ts b/src/modules/vehicle_usage/vehicle_usage.controller.ts new file mode 100644 index 0000000..59d5f92 --- /dev/null +++ b/src/modules/vehicle_usage/vehicle_usage.controller.ts @@ -0,0 +1,80 @@ +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 { ApiResult } from '~/common/decorators/api-result.decorator'; + +import { VehicleUsageService } from './vehicle_usage.service'; +import { IdParam } from '~/common/decorators/id-param.decorator'; +import { VehicleUsageQueryDto, VehicleUsageDto, VehicleUsageUpdateDto } from './vehicle_usage.dto'; +import { VehicleUsageEntity } from '../vehicle_usage/vehicle_usage.entity'; +export const permissions = definePermission('app:vehicle_usage', { + LIST: 'list', + CREATE: 'create', + READ: 'read', + UPDATE: 'update', + DELETE: 'delete' +} as const); + +@ApiTags('VehicleUsage - 车辆使用') +@ApiSecurityAuth() +@Controller('vehicle-usage') +export class VehicleUsageController { + constructor(private vehicleUsageService: VehicleUsageService) {} + + @Get() + @ApiOperation({ summary: '获取车辆使用列表' }) + @ApiResult({ type: [VehicleUsageEntity], isPage: true }) + @Perm(permissions.LIST) + async list(@Query() dto: VehicleUsageQueryDto) { + return this.vehicleUsageService.findAll(dto); + } + + @Get(':id') + @ApiOperation({ summary: '获取车辆使用信息' }) + @ApiResult({ type: VehicleUsageDto }) + @Perm(permissions.READ) + async info(@IdParam() id: number) { + return this.vehicleUsageService.info(id); + } + + @Post() + @ApiOperation({ summary: '新增车辆使用' }) + @Perm(permissions.CREATE) + async create(@Body() dto: VehicleUsageDto): Promise { + await this.vehicleUsageService.create(dto); + } + + @Put(':id') + @ApiOperation({ summary: '更新车辆使用' }) + @Perm(permissions.UPDATE) + async update(@IdParam() id: number, @Body() dto: VehicleUsageUpdateDto): Promise { + await this.vehicleUsageService.update(id, dto); + } + + @Delete(':id') + @ApiOperation({ summary: '删除车辆使用' }) + @Perm(permissions.DELETE) + async delete(@IdParam() id: number): Promise { + await this.vehicleUsageService.delete(id); + } + + @Put('unlink-attachments/:id') + @ApiOperation({ summary: '附件解除关联' }) + @Perm(permissions.UPDATE) + async unlinkAttachments( + @IdParam() id: number, + @Body() { fileIds }: VehicleUsageUpdateDto + ): Promise { + await this.vehicleUsageService.unlinkAttachments(id, fileIds); + } +} diff --git a/src/modules/vehicle_usage/vehicle_usage.dto.ts b/src/modules/vehicle_usage/vehicle_usage.dto.ts new file mode 100644 index 0000000..15100a1 --- /dev/null +++ b/src/modules/vehicle_usage/vehicle_usage.dto.ts @@ -0,0 +1,72 @@ +import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; +import { PagerDto } from '~/common/dto/pager.dto'; +import { Storage } from '../tools/storage/storage.entity'; + +export class VehicleUsageDto { + @ApiProperty({ description: '年度' }) + @IsNumber() + year: number; + + @ApiProperty({ description: '外出使用的车辆名称(字典)' }) + @IsNumber() + vechicleId: number; + + @ApiProperty({ description: '申请人' }) + @IsString() + applicant: string; + + @ApiProperty({ description: '出行司机', nullable: true }) + @IsOptional() + @IsString() + driver: string; + + @ApiProperty({ description: '当前车辆里程数(KM)' }) + @IsOptional() + @IsNumber() + currentMileage: number; + + @ApiProperty({ description: '预计出行开始时间' }) + @IsOptional() + expectedStartDate: Date; + + @ApiProperty({ description: '预计出行结束时间' }) + @IsOptional() + expectedEndDate: Date; + + @ApiProperty({ description: '使用事由' }) + @IsOptional() + purpose: string; + + @ApiProperty({ description: '实际回司时间' }) + @IsOptional() + actualReturnTime: Date; + + @ApiProperty({ description: '回城车辆里程数(KM)' }) + @IsOptional() + returnMileage: number; + + @ApiProperty({ description: '审核人' }) + @IsOptional() + reviewer: string; + + @ApiProperty({ description: '审核状态:0待审核,1同意,2.不同意(字典)' }) + @IsOptional() + status: number; + + @ApiProperty({ description: '备注' }) + @IsOptional() + remark: string; +} + +export class VehicleUsageUpdateDto extends PartialType(VehicleUsageDto) { + @ApiProperty({ description: '附件' }) + @IsOptional() + @IsArray() + fileIds: number[]; +} + +export class VehicleUsageQueryDto extends IntersectionType( + PagerDto, + PartialType(VehicleUsageDto) +) {} diff --git a/src/modules/vehicle-usage/vehicle-usage.entity.ts b/src/modules/vehicle_usage/vehicle_usage.entity.ts similarity index 76% rename from src/modules/vehicle-usage/vehicle-usage.entity.ts rename to src/modules/vehicle_usage/vehicle_usage.entity.ts index 994fd97..73c5dc9 100644 --- a/src/modules/vehicle-usage/vehicle-usage.entity.ts +++ b/src/modules/vehicle_usage/vehicle_usage.entity.ts @@ -1,6 +1,8 @@ import { ApiHideProperty, ApiProperty } from '@nestjs/swagger'; -import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm'; +import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, Relation } from 'typeorm'; import { CommonEntity } from '~/common/entity/common.entity'; +import { DictItemEntity } from '../system/dict-item/dict-item.entity'; +import { Storage } from '../tools/storage/storage.entity'; @Entity({ name: 'vehicle_usage' }) export class VehicleUsageEntity extends CommonEntity { @@ -8,9 +10,9 @@ export class VehicleUsageEntity extends CommonEntity { @ApiProperty({ description: '年度' }) year: number; - @Column({ name: 'vehicle_license', type: 'int', comment: '外出使用的车辆名称(字典)' }) + @Column({ name: 'vehicle_id', type: 'int', comment: '外出使用的车辆名称(字典)' }) @ApiProperty({ description: '外出使用的车辆名称(字典)' }) - vehicleLicense: number; + vechicleId: number; @Column({ name: 'applicant', @@ -75,4 +77,16 @@ export class VehicleUsageEntity extends CommonEntity { @Column({ name: 'remark', type: 'varchar', length: 255, comment: '备注', nullable: true }) @ApiProperty({ description: '备注' }) remark: string; + + @ManyToOne(() => DictItemEntity) + @JoinColumn({ name: 'vehicle_id' }) + vehicle: DictItemEntity; + + @ManyToMany(() => Storage, storage => storage.vehicleUsage) + @JoinTable({ + name: 'vehicle_usage_storage', + joinColumn: { name: 'vehicle_id', referencedColumnName: 'id' }, + inverseJoinColumn: { name: 'file_id', referencedColumnName: 'id' } + }) + files: Relation; } diff --git a/src/modules/vehicle_usage/vehicle_usage.module.ts b/src/modules/vehicle_usage/vehicle_usage.module.ts new file mode 100644 index 0000000..e446f1e --- /dev/null +++ b/src/modules/vehicle_usage/vehicle_usage.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { VehicleUsageService } from './vehicle_usage.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { VehicleUsageEntity } from './vehicle_usage.entity'; +import { VehicleUsageController } from './vehicle_usage.controller'; +import { StorageModule } from '../tools/storage/storage.module'; + +@Module({ + imports: [TypeOrmModule.forFeature([VehicleUsageEntity]), StorageModule], + providers: [VehicleUsageService], + controllers: [VehicleUsageController] +}) +export class VehicleUsageModule {} diff --git a/src/modules/vehicle_usage/vehicle_usage.service.ts b/src/modules/vehicle_usage/vehicle_usage.service.ts new file mode 100644 index 0000000..2c064b7 --- /dev/null +++ b/src/modules/vehicle_usage/vehicle_usage.service.ts @@ -0,0 +1,140 @@ +import { Injectable } from '@nestjs/common'; +import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; +import { EntityManager, Repository } from 'typeorm'; +import { VehicleUsageEntity } from './vehicle_usage.entity'; +import { Storage } from '../tools/storage/storage.entity'; +import { ErrorEnum } from '~/constants/error-code.constant'; +import { BusinessException } from '~/common/exceptions/biz.exception'; +import { paginate } from '~/helper/paginate'; +import { Pagination } from '~/helper/paginate/pagination'; +import { fieldSearch } from '~/shared/database/field-search'; +import { VehicleUsageQueryDto, VehicleUsageDto } from './vehicle_usage.dto'; +import { VehicleUsageUpdateDto } from './vehicle_usage.dto'; + +@Injectable() +export class VehicleUsageService { + constructor( + @InjectRepository(VehicleUsageEntity) + private vehicleUsageRepository: Repository, + @InjectEntityManager() private entityManager: EntityManager, + @InjectRepository(Storage) + private storageRepository: Repository + ) {} + + /** + * 分页查询所有 + */ + async findAll({ + page, + pageSize, + ...fields + }: VehicleUsageQueryDto): Promise> { + const { ...ext } = fields; + const sqb = this.vehicleUsageRepository + .createQueryBuilder('vehicle_usage') + .leftJoin('vehicle_usage.files', 'files') + .leftJoin('vehicle_usage.vehicle', 'vehicle') + .addSelect(['files.id', 'files.path', 'vehicle.id', 'vehicle.label']) + .where(fieldSearch(ext)) + return paginate(sqb, { + page, + pageSize + }); + } + + /** + * 新增 + */ + async create(dto: VehicleUsageDto): Promise { + // const { name, companyId } = dto; + // const isExsit = await this.vehicleUsageRepository.findOne({ + // where: { name, company: { id: companyId } } + // }); + // if (isExsit) { + // throw new BusinessException(ErrorEnum.PRODUCT_EXIST); + // } + await this.vehicleUsageRepository.insert(this.vehicleUsageRepository.create(dto)); + } + + /** + * 更新 + */ + async update(id: number, { fileIds, ...data }: Partial): Promise { + await this.entityManager.transaction(async manager => { + await manager.update( + VehicleUsageEntity, + id, + this.vehicleUsageRepository.create({ + ...data + }) + ); + const vehicleUsage = await this.vehicleUsageRepository + .createQueryBuilder('vehicle_usage') + .leftJoinAndSelect('vehicle_usage.files', 'files') + .where('vehicle_usage.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(VehicleUsageEntity, 'files') + .of(id) + .addAndRemove(fileIds, vehicleUsage.files); + } + }); + } + + /** + * 删除 + */ + async delete(id: number): Promise { + await this.vehicleUsageRepository.delete(id); + } + + /** + * 获取单个信息 + */ + async info(id: number) { + const info = await this.vehicleUsageRepository + .createQueryBuilder('vehicle_usage') + .leftJoin('vehicle_usage.company', 'company') + .addSelect(['company.name', 'company.id']) + .where({ + id + }) + .andWhere('vehicle_usage.isDelete = 0') + .getOne(); + return info; + } + + /** + * 解除附件关联 + * @param id 记录ID + * @param fileIds 附件ID + */ + async unlinkAttachments(id: number, fileIds: number[]) { + await this.entityManager.transaction(async manager => { + const vehicle_usage = await this.vehicleUsageRepository + .createQueryBuilder('vehicle_usage') + .leftJoinAndSelect('vehicle_usage.files', 'files') + .where('vehicle_usage.id = :id', { id }) + .getOne(); + const linkedFiles = vehicle_usage.files + .map(item => item.id) + .filter(item => !fileIds.includes(item)); + // 附件要批量更新 + await manager + .createQueryBuilder() + .relation(VehicleUsageEntity, 'files') + .of(id) + .addAndRemove(linkedFiles, vehicle_usage.files); + }); + } +}