feat: 原材料出入库记录模块
This commit is contained in:
parent
944b5adbff
commit
47b7015c16
|
@ -30,6 +30,7 @@ import { VehicleUsageController } from './modules/vehicle-usage/vehicle-usage.co
|
||||||
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';
|
import { CompanyModule } from './modules/company/company.module';
|
||||||
|
import { ProductModule } from './modules/product/product.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -64,7 +65,9 @@ import { CompanyModule } from './modules/company/company.module';
|
||||||
|
|
||||||
MaterialsInventoryModule,
|
MaterialsInventoryModule,
|
||||||
|
|
||||||
CompanyModule
|
CompanyModule,
|
||||||
|
|
||||||
|
ProductModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: APP_FILTER, useClass: AllExceptionsFilter },
|
{ provide: APP_FILTER, useClass: AllExceptionsFilter },
|
||||||
|
|
|
@ -12,3 +12,9 @@ export enum BusinessModuleEnum {
|
||||||
MATERIALS_INVENTORY = 2,
|
MATERIALS_INVENTORY = 2,
|
||||||
COMPANY = 3
|
COMPANY = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 原材料出库或者入库
|
||||||
|
export enum MaterialsInOrOutEnum {
|
||||||
|
In,
|
||||||
|
Out
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Body, Controller, Delete, Get, Post, Put, Query } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { ApiResult } from '~/common/decorators/api-result.decorator';
|
||||||
|
import { IdParam } from '~/common/decorators/id-param.decorator';
|
||||||
|
import { MaterialsInOutService } from './materials_in_out.service';
|
||||||
|
import { MaterialsInOutEntity } from './materials_in_out.entity';
|
||||||
|
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
|
||||||
|
import { definePermission, Perm } from '~/modules/auth/decorators/permission.decorator';
|
||||||
|
import { MaterialsInOutQueryDto, MaterialsInOutDto, MaterialsInOutUpdateDto } from './materials_in_out.dto';
|
||||||
|
|
||||||
|
export const permissions = definePermission('materials_inventory:history_in_out', {
|
||||||
|
LIST: 'list',
|
||||||
|
CREATE: 'create',
|
||||||
|
READ: 'read',
|
||||||
|
UPDATE: 'update',
|
||||||
|
DELETE: 'delete'
|
||||||
|
} as const);
|
||||||
|
|
||||||
|
@ApiTags('Materials In Out History - 原材料出入库管理')
|
||||||
|
@ApiSecurityAuth()
|
||||||
|
@Controller('materials-in-out')
|
||||||
|
export class MaterialsInOutController {
|
||||||
|
constructor(private miService: MaterialsInOutService) {}
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: '获取原材料盘点列表' })
|
||||||
|
@ApiResult({ type: [MaterialsInOutEntity], isPage: true })
|
||||||
|
@Perm(permissions.LIST)
|
||||||
|
async list(@Query() dto: MaterialsInOutQueryDto) {
|
||||||
|
return this.miService.findAll(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: '获取原材料盘点信息' })
|
||||||
|
@ApiResult({ type: MaterialsInOutDto })
|
||||||
|
@Perm(permissions.READ)
|
||||||
|
async info(@IdParam() id: number) {
|
||||||
|
return this.miService.info(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@ApiOperation({ summary: '新增原材料盘点' })
|
||||||
|
@Perm(permissions.CREATE)
|
||||||
|
async create(@Body() dto: MaterialsInOutDto): Promise<void> {
|
||||||
|
await this.miService.create(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id')
|
||||||
|
@ApiOperation({ summary: '更新原材料盘点' })
|
||||||
|
@Perm(permissions.UPDATE)
|
||||||
|
async update(@IdParam() id: number, @Body() dto: MaterialsInOutUpdateDto): Promise<void> {
|
||||||
|
await this.miService.update(id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@ApiOperation({ summary: '删除原材料盘点' })
|
||||||
|
@Perm(permissions.DELETE)
|
||||||
|
async delete(@IdParam() id: number): Promise<void> {
|
||||||
|
await this.miService.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('unlink-attachments/:id')
|
||||||
|
@ApiOperation({ summary: '附件解除关联' })
|
||||||
|
@Perm(permissions.UPDATE)
|
||||||
|
async unlinkAttachments(
|
||||||
|
@IdParam() id: number,
|
||||||
|
@Body() { fileIds }: MaterialsInOutUpdateDto
|
||||||
|
): Promise<void> {
|
||||||
|
await this.miService.unlinkAttachments(id, fileIds);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
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 '~/modules/tools/storage/storage.entity';
|
||||||
|
|
||||||
|
export class MaterialsInOutDto {
|
||||||
|
@ApiProperty({ description: '附件' })
|
||||||
|
files: Storage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MaterialsInOutUpdateDto extends PartialType(MaterialsInOutDto) {
|
||||||
|
@ApiProperty({ description: '附件' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
fileIds: number[];
|
||||||
|
}
|
||||||
|
export class MaterialsInOutQueryDto extends IntersectionType(
|
||||||
|
PagerDto<MaterialsInOutDto>,
|
||||||
|
PartialType(MaterialsInOutDto)
|
||||||
|
) {}
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm';
|
||||||
|
import { CommonEntity } from '~/common/entity/common.entity';
|
||||||
|
import { MaterialsInOrOutEnum } from '~/constants/enum';
|
||||||
|
import { Storage } from '~/modules/tools/storage/storage.entity';
|
||||||
|
|
||||||
|
@Entity({ name: 'materials_in_out' })
|
||||||
|
export class MaterialsInOutEntity extends CommonEntity {
|
||||||
|
@Column({
|
||||||
|
name: 'product',
|
||||||
|
type: 'int',
|
||||||
|
comment: '产品名称'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '产品名称' })
|
||||||
|
product: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'unit',
|
||||||
|
type: 'int',
|
||||||
|
comment: '单位(字典)'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '单位(字典)' })
|
||||||
|
unit: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'inOrOut',
|
||||||
|
type: 'tinyint',
|
||||||
|
comment: '入库或出库'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '入库或出库 0:入库 1:出库' })
|
||||||
|
inOrOut: MaterialsInOrOutEnum;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'inventory_time',
|
||||||
|
type: 'date',
|
||||||
|
nullable: true,
|
||||||
|
comment: '时间'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '时间' })
|
||||||
|
time: Date;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'quantity',
|
||||||
|
type: 'int',
|
||||||
|
default: 0,
|
||||||
|
comment: '数量'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '数量' })
|
||||||
|
quantity: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'unit_price',
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 10,
|
||||||
|
default: 0,
|
||||||
|
scale: 2,
|
||||||
|
comment: '单价'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '入库单价' })
|
||||||
|
unitPrice: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'amount',
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 10,
|
||||||
|
default: 0,
|
||||||
|
scale: 2,
|
||||||
|
comment: '金额'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '金额' })
|
||||||
|
amount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'agent', type: 'varchar', length: 50, comment: '经办人', nullable: true })
|
||||||
|
@ApiProperty({ description: '经办人' })
|
||||||
|
agent: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'issuance_number',
|
||||||
|
type: 'varchar',
|
||||||
|
length: 100,
|
||||||
|
comment: '领料单号'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '领料单号' })
|
||||||
|
issuanceNumber: string;
|
||||||
|
|
||||||
|
@Column({ name: 'remark', type: 'varchar', length: 255, comment: '备注', nullable: true })
|
||||||
|
@ApiProperty({ description: '备注' })
|
||||||
|
remark: string;
|
||||||
|
|
||||||
|
@Column({ name: 'project', type: 'varchar', length: 255, comment: '项目', nullable: false })
|
||||||
|
@ApiProperty({ description: '项目' })
|
||||||
|
project: string;
|
||||||
|
|
||||||
|
@Column({ name: 'is_delete', type: 'tinyint', default: 0, comment: '是否删除' })
|
||||||
|
@ApiProperty({ description: '删除状态:0未删除,1已删除' })
|
||||||
|
isDelete: number;
|
||||||
|
|
||||||
|
@ManyToMany(() => Storage, storage => storage.materialsInOut)
|
||||||
|
@JoinTable({
|
||||||
|
name: 'materials_in_out_storage',
|
||||||
|
joinColumn: { name: 'materials_in_out_id', referencedColumnName: 'id' },
|
||||||
|
inverseJoinColumn: { name: 'file_id', referencedColumnName: 'id' }
|
||||||
|
})
|
||||||
|
files: Relation<Storage[]>;
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { EntityManager, Repository } from 'typeorm';
|
||||||
|
import { Pagination } from '~/helper/paginate/pagination';
|
||||||
|
import { BusinessException } from '~/common/exceptions/biz.exception';
|
||||||
|
import { ErrorEnum } from '~/constants/error-code.constant';
|
||||||
|
import { paginate } from '~/helper/paginate';
|
||||||
|
import { Storage } from '~/modules/tools/storage/storage.entity';
|
||||||
|
import { MaterialsInOutQueryDto, MaterialsInOutDto, MaterialsInOutUpdateDto } from './materials_in_out.dto';
|
||||||
|
import { MaterialsInOutEntity } from './materials_in_out.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MaterialsInOutService {
|
||||||
|
constructor(
|
||||||
|
@InjectEntityManager() private entityManager: EntityManager,
|
||||||
|
@InjectRepository(MaterialsInOutEntity)
|
||||||
|
private materialsInventoryRepository: Repository<MaterialsInOutEntity>,
|
||||||
|
@InjectRepository(Storage)
|
||||||
|
private storageRepository: Repository<Storage>
|
||||||
|
) {}
|
||||||
|
/**
|
||||||
|
* 查询所有出入库记录
|
||||||
|
*/
|
||||||
|
async findAll({
|
||||||
|
page,
|
||||||
|
pageSize
|
||||||
|
// materialsInventoryNumber,
|
||||||
|
// title,
|
||||||
|
// type,
|
||||||
|
// status
|
||||||
|
}: MaterialsInOutQueryDto): Promise<Pagination<MaterialsInOutEntity>> {
|
||||||
|
const queryBuilder = this.materialsInventoryRepository
|
||||||
|
.createQueryBuilder('materialsInventory')
|
||||||
|
.leftJoin('materialsInventory.files', 'files')
|
||||||
|
.addSelect(['files.id', 'files.path'])
|
||||||
|
// .where({
|
||||||
|
// ...(materialsInventoryNumber ? { materialsInventoryNumber: Like(`%${materialsInventoryNumber}%`) } : null),
|
||||||
|
// ...(title ? { title: Like(`%${title}%`) } : null),
|
||||||
|
// ...(isNumber(type) ? { type } : null),
|
||||||
|
// ...(isNumber(status) ? { status } : null)
|
||||||
|
// })
|
||||||
|
.andWhere('materialsInventory.isDelete = 0');
|
||||||
|
|
||||||
|
return paginate<MaterialsInOutEntity>(queryBuilder, {
|
||||||
|
page,
|
||||||
|
pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
async create(dto: MaterialsInOutDto): Promise<void> {
|
||||||
|
await this.materialsInventoryRepository.insert(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新
|
||||||
|
*/
|
||||||
|
async update(id: number, { fileIds, ...data }: Partial<MaterialsInOutUpdateDto>): Promise<void> {
|
||||||
|
await this.entityManager.transaction(async manager => {
|
||||||
|
await manager.update(MaterialsInOutEntity, id, {
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
const materialsInventory = await this.materialsInventoryRepository
|
||||||
|
.createQueryBuilder('materialsInventory')
|
||||||
|
.leftJoinAndSelect('materialsInventory.files', 'files')
|
||||||
|
.where('materialsInventory.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(MaterialsInOutEntity, 'files')
|
||||||
|
.of(id)
|
||||||
|
.addAndRemove(fileIds, materialsInventory.files);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*/
|
||||||
|
async delete(id: number): Promise<void> {
|
||||||
|
// 合同比较重要,做逻辑删除
|
||||||
|
await this.materialsInventoryRepository.update(id, { isDelete: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个合同信息
|
||||||
|
*/
|
||||||
|
async info(id: number) {
|
||||||
|
const info = await this.materialsInventoryRepository
|
||||||
|
.createQueryBuilder('materialsInventory')
|
||||||
|
.where({
|
||||||
|
id
|
||||||
|
})
|
||||||
|
.andWhere('materialsInventory.isDelete = 0')
|
||||||
|
.getOne();
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解除附件关联
|
||||||
|
* @param id 合同ID
|
||||||
|
* @param fileIds 附件ID
|
||||||
|
*/
|
||||||
|
async unlinkAttachments(id: number, fileIds: number[]) {
|
||||||
|
await this.entityManager.transaction(async manager => {
|
||||||
|
const materialsInventory = await this.materialsInventoryRepository
|
||||||
|
.createQueryBuilder('materialsInventory')
|
||||||
|
.leftJoinAndSelect('materialsInventory.files', 'files')
|
||||||
|
.where('materialsInventory.id = :id', { id })
|
||||||
|
.getOne();
|
||||||
|
const linkedFiles = materialsInventory.files
|
||||||
|
.map(item => item.id)
|
||||||
|
.filter(item => !fileIds.includes(item));
|
||||||
|
// 附件要批量更新
|
||||||
|
await manager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.relation(MaterialsInOutEntity, 'files')
|
||||||
|
.of(id)
|
||||||
|
.addAndRemove(linkedFiles, materialsInventory.files);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,14 +57,4 @@ export class MaterialsInventoryController {
|
||||||
async delete(@IdParam() id: number): Promise<void> {
|
async delete(@IdParam() id: number): Promise<void> {
|
||||||
await this.miService.delete(id);
|
await this.miService.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put('unlink-attachments/:id')
|
|
||||||
@ApiOperation({ summary: '附件解除关联' })
|
|
||||||
@Perm(permissions.UPDATE)
|
|
||||||
async unlinkAttachments(
|
|
||||||
@IdParam() id: number,
|
|
||||||
@Body() { fileIds }: ContractUpdateDto
|
|
||||||
): Promise<void> {
|
|
||||||
await this.miService.unlinkAttachments(id, fileIds);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,17 +14,9 @@ import {
|
||||||
import { PagerDto } from '~/common/dto/pager.dto';
|
import { PagerDto } from '~/common/dto/pager.dto';
|
||||||
import { Storage } from '../tools/storage/storage.entity';
|
import { Storage } from '../tools/storage/storage.entity';
|
||||||
|
|
||||||
export class MaterialsInventoryDto {
|
export class MaterialsInventoryDto {}
|
||||||
@ApiProperty({ description: '附件' })
|
|
||||||
files: Storage[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MaterialsInventoryUpdateDto extends PartialType(MaterialsInventoryDto) {
|
export class MaterialsInventoryUpdateDto extends PartialType(MaterialsInventoryDto) {}
|
||||||
@ApiProperty({ description: '附件' })
|
|
||||||
@IsOptional()
|
|
||||||
@IsArray()
|
|
||||||
fileIds: number[];
|
|
||||||
}
|
|
||||||
export class MaterialsInventoryQueryDto extends IntersectionType(
|
export class MaterialsInventoryQueryDto extends IntersectionType(
|
||||||
PagerDto<MaterialsInventoryDto>,
|
PagerDto<MaterialsInventoryDto>,
|
||||||
PartialType(MaterialsInventoryDto)
|
PartialType(MaterialsInventoryDto)
|
||||||
|
|
|
@ -191,12 +191,4 @@ export class MaterialsInventoryEntity extends CommonEntity {
|
||||||
@Column({ name: 'is_delete', type: 'tinyint', default: 0, comment: '是否删除' })
|
@Column({ name: 'is_delete', type: 'tinyint', default: 0, comment: '是否删除' })
|
||||||
@ApiProperty({ description: '删除状态:0未删除,1已删除' })
|
@ApiProperty({ description: '删除状态:0未删除,1已删除' })
|
||||||
isDelete: number;
|
isDelete: number;
|
||||||
|
|
||||||
@ManyToMany(() => Storage, storage => storage.materialsInventories)
|
|
||||||
@JoinTable({
|
|
||||||
name: 'materials_inventory_storage',
|
|
||||||
joinColumn: { name: 'materials_inventory_id', referencedColumnName: 'id' },
|
|
||||||
inverseJoinColumn: { name: 'file_id', referencedColumnName: 'id' }
|
|
||||||
})
|
|
||||||
files: Relation<Storage[]>;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
|
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
|
||||||
import { MaterialsInventoryEntity } from './materials_inventory.entity';
|
import { MaterialsInventoryEntity } from './materials_inventory.entity';
|
||||||
import { Storage } from '../tools/storage/storage.entity';
|
|
||||||
import { EntityManager, Repository } from 'typeorm';
|
import { EntityManager, Repository } from 'typeorm';
|
||||||
import {
|
import {
|
||||||
MaterialsInventoryDto,
|
MaterialsInventoryDto,
|
||||||
|
@ -9,8 +8,7 @@ import {
|
||||||
MaterialsInventoryUpdateDto
|
MaterialsInventoryUpdateDto
|
||||||
} from './materials_inventory.dto';
|
} from './materials_inventory.dto';
|
||||||
import { Pagination } from '~/helper/paginate/pagination';
|
import { Pagination } from '~/helper/paginate/pagination';
|
||||||
import { BusinessException } from '~/common/exceptions/biz.exception';
|
|
||||||
import { ErrorEnum } from '~/constants/error-code.constant';
|
|
||||||
import { paginate } from '~/helper/paginate';
|
import { paginate } from '~/helper/paginate';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -18,31 +16,17 @@ export class MaterialsInventoryService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectEntityManager() private entityManager: EntityManager,
|
@InjectEntityManager() private entityManager: EntityManager,
|
||||||
@InjectRepository(MaterialsInventoryEntity)
|
@InjectRepository(MaterialsInventoryEntity)
|
||||||
private materialsInventoryRepository: Repository<MaterialsInventoryEntity>,
|
private materialsInventoryRepository: Repository<MaterialsInventoryEntity>
|
||||||
@InjectRepository(Storage)
|
|
||||||
private storageRepository: Repository<Storage>
|
|
||||||
) {}
|
) {}
|
||||||
/**
|
/**
|
||||||
* 列举所有角色:除去超级管理员
|
* 查询所有盘点信息
|
||||||
*/
|
*/
|
||||||
async findAll({
|
async findAll({
|
||||||
page,
|
page,
|
||||||
pageSize
|
pageSize
|
||||||
// materialsInventoryNumber,
|
|
||||||
// title,
|
|
||||||
// type,
|
|
||||||
// status
|
|
||||||
}: MaterialsInventoryQueryDto): Promise<Pagination<MaterialsInventoryEntity>> {
|
}: MaterialsInventoryQueryDto): Promise<Pagination<MaterialsInventoryEntity>> {
|
||||||
const queryBuilder = this.materialsInventoryRepository
|
const queryBuilder = this.materialsInventoryRepository
|
||||||
.createQueryBuilder('materialsInventory')
|
.createQueryBuilder('materialsInventory')
|
||||||
.leftJoin('materialsInventory.files', 'files')
|
|
||||||
.addSelect(['files.id', 'files.path'])
|
|
||||||
// .where({
|
|
||||||
// ...(materialsInventoryNumber ? { materialsInventoryNumber: Like(`%${materialsInventoryNumber}%`) } : null),
|
|
||||||
// ...(title ? { title: Like(`%${title}%`) } : null),
|
|
||||||
// ...(isNumber(type) ? { type } : null),
|
|
||||||
// ...(isNumber(status) ? { status } : null)
|
|
||||||
// })
|
|
||||||
.andWhere('materialsInventory.isDelete = 0');
|
.andWhere('materialsInventory.isDelete = 0');
|
||||||
|
|
||||||
return paginate<MaterialsInventoryEntity>(queryBuilder, {
|
return paginate<MaterialsInventoryEntity>(queryBuilder, {
|
||||||
|
@ -61,34 +45,11 @@ export class MaterialsInventoryService {
|
||||||
/**
|
/**
|
||||||
* 更新
|
* 更新
|
||||||
*/
|
*/
|
||||||
async update(
|
async update(id: number, data: Partial<MaterialsInventoryUpdateDto>): Promise<void> {
|
||||||
id: number,
|
|
||||||
{ fileIds, ...data }: Partial<MaterialsInventoryUpdateDto>
|
|
||||||
): Promise<void> {
|
|
||||||
await this.entityManager.transaction(async manager => {
|
await this.entityManager.transaction(async manager => {
|
||||||
await manager.update(MaterialsInventoryEntity, id, {
|
await manager.update(MaterialsInventoryEntity, id, {
|
||||||
...data
|
...data
|
||||||
});
|
});
|
||||||
const materialsInventory = await this.materialsInventoryRepository
|
|
||||||
.createQueryBuilder('materialsInventory')
|
|
||||||
.leftJoinAndSelect('materialsInventory.files', 'files')
|
|
||||||
.where('materialsInventory.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(MaterialsInventoryEntity, 'files')
|
|
||||||
.of(id)
|
|
||||||
.addAndRemove(fileIds, materialsInventory.files);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,28 +74,4 @@ export class MaterialsInventoryService {
|
||||||
.getOne();
|
.getOne();
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 解除附件关联
|
|
||||||
* @param id 合同ID
|
|
||||||
* @param fileIds 附件ID
|
|
||||||
*/
|
|
||||||
async unlinkAttachments(id: number, fileIds: number[]) {
|
|
||||||
await this.entityManager.transaction(async manager => {
|
|
||||||
const materialsInventory = await this.materialsInventoryRepository
|
|
||||||
.createQueryBuilder('materialsInventory')
|
|
||||||
.leftJoinAndSelect('materialsInventory.files', 'files')
|
|
||||||
.where('materialsInventory.id = :id', { id })
|
|
||||||
.getOne();
|
|
||||||
const linkedFiles = materialsInventory.files
|
|
||||||
.map(item => item.id)
|
|
||||||
.filter(item => !fileIds.includes(item));
|
|
||||||
// 附件要批量更新
|
|
||||||
await manager
|
|
||||||
.createQueryBuilder()
|
|
||||||
.relation(MaterialsInventoryEntity, 'files')
|
|
||||||
.of(id)
|
|
||||||
.addAndRemove(linkedFiles, materialsInventory.files);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { ProductService } from './product.service';
|
||||||
|
import { ApiResult } from '~/common/decorators/api-result.decorator';
|
||||||
|
import { ProductEntity } from './product.entity';
|
||||||
|
import { ProductDto, ProductQueryDto, ProductUpdateDto } from './product.dto';
|
||||||
|
import { IdParam } from '~/common/decorators/id-param.decorator';
|
||||||
|
export const permissions = definePermission('app:product', {
|
||||||
|
LIST: 'list',
|
||||||
|
CREATE: 'create',
|
||||||
|
READ: 'read',
|
||||||
|
UPDATE: 'update',
|
||||||
|
DELETE: 'delete'
|
||||||
|
} as const);
|
||||||
|
|
||||||
|
@ApiTags('Product - 产品')
|
||||||
|
@ApiSecurityAuth()
|
||||||
|
@Controller('product')
|
||||||
|
export class ProductController {
|
||||||
|
constructor(private productService: ProductService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: '获取产品列表' })
|
||||||
|
@ApiResult({ type: [ProductEntity], isPage: true })
|
||||||
|
@Perm(permissions.LIST)
|
||||||
|
async list(@Query() dto: ProductQueryDto) {
|
||||||
|
return this.productService.findAll(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: '获取产品信息' })
|
||||||
|
@ApiResult({ type: ProductDto })
|
||||||
|
@Perm(permissions.READ)
|
||||||
|
async info(@IdParam() id: number) {
|
||||||
|
return this.productService.info(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@ApiOperation({ summary: '新增产品' })
|
||||||
|
@Perm(permissions.CREATE)
|
||||||
|
async create(@Body() dto: ProductDto): Promise<void> {
|
||||||
|
await this.productService.create(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id')
|
||||||
|
@ApiOperation({ summary: '更新产品' })
|
||||||
|
@Perm(permissions.UPDATE)
|
||||||
|
async update(@IdParam() id: number, @Body() dto: ProductUpdateDto): Promise<void> {
|
||||||
|
await this.productService.update(id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@ApiOperation({ summary: '删除产品' })
|
||||||
|
@Perm(permissions.DELETE)
|
||||||
|
async delete(@IdParam() id: number): Promise<void> {
|
||||||
|
await this.productService.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('unlink-attachments/:id')
|
||||||
|
@ApiOperation({ summary: '附件解除关联' })
|
||||||
|
@Perm(permissions.UPDATE)
|
||||||
|
async unlinkAttachments(
|
||||||
|
@IdParam() id: number,
|
||||||
|
@Body() { fileIds }: ProductUpdateDto
|
||||||
|
): Promise<void> {
|
||||||
|
await this.productService.unlinkAttachments(id, fileIds);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 { ProductEntity } from './product.entity';
|
||||||
|
|
||||||
|
export class ProductDto {
|
||||||
|
@ApiProperty({ description: '产品名称' })
|
||||||
|
@IsUnique(ProductEntity, { message: '已存在同名产品' })
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '附件' })
|
||||||
|
files: Storage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProductUpdateDto extends PartialType(ProductDto) {
|
||||||
|
@ApiProperty({ description: '附件' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
fileIds: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProductQueryDto extends IntersectionType(
|
||||||
|
PagerDto<ProductDto>,
|
||||||
|
PartialType(ProductDto)
|
||||||
|
) {}
|
|
@ -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: 'product' })
|
||||||
|
export class ProductEntity 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.products)
|
||||||
|
@JoinTable({
|
||||||
|
name: 'product_storage',
|
||||||
|
joinColumn: { name: 'product_id', referencedColumnName: 'id' },
|
||||||
|
inverseJoinColumn: { name: 'file_id', referencedColumnName: 'id' }
|
||||||
|
})
|
||||||
|
files: Relation<Storage[]>;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ProductController } from './product.controller';
|
||||||
|
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],
|
||||||
|
controllers: [ProductController],
|
||||||
|
providers: [ProductService]
|
||||||
|
})
|
||||||
|
export class ProductModule {}
|
|
@ -0,0 +1,127 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { ProductEntity } from './product.entity';
|
||||||
|
import { EntityManager, Like, Repository } from 'typeorm';
|
||||||
|
import { ProductDto, ProductQueryDto, ProductUpdateDto } from './product.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 ProductService {
|
||||||
|
constructor(
|
||||||
|
@InjectEntityManager() private entityManager: EntityManager,
|
||||||
|
@InjectRepository(ProductEntity)
|
||||||
|
private productRepository: Repository<ProductEntity>,
|
||||||
|
@InjectRepository(Storage)
|
||||||
|
private storageRepository: Repository<Storage>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有产品
|
||||||
|
*/
|
||||||
|
async findAll({
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
...fields
|
||||||
|
}: ProductQueryDto): Promise<Pagination<ProductEntity>> {
|
||||||
|
const queryBuilder = this.productRepository
|
||||||
|
.createQueryBuilder('product')
|
||||||
|
.leftJoin('product.files', 'files')
|
||||||
|
.addSelect(['files.id', 'files.path'])
|
||||||
|
.where(fieldSearch(fields))
|
||||||
|
.andWhere('product.isDelete = 0');
|
||||||
|
|
||||||
|
return paginate<ProductEntity>(queryBuilder, {
|
||||||
|
page,
|
||||||
|
pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
async create(dto: ProductDto): Promise<void> {
|
||||||
|
await this.productRepository.insert(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新
|
||||||
|
*/
|
||||||
|
async update(id: number, { fileIds, ...data }: Partial<ProductUpdateDto>): Promise<void> {
|
||||||
|
await this.entityManager.transaction(async manager => {
|
||||||
|
await manager.update(ProductEntity, id, {
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
const product = await this.productRepository
|
||||||
|
.createQueryBuilder('product')
|
||||||
|
.leftJoinAndSelect('product.files', 'files')
|
||||||
|
.where('product.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(ProductEntity, 'files')
|
||||||
|
.of(id)
|
||||||
|
.addAndRemove(fileIds, product.files);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*/
|
||||||
|
async delete(id: number): Promise<void> {
|
||||||
|
// 合同比较重要,做逻辑删除
|
||||||
|
await this.productRepository.update(id, { isDelete: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个合同信息
|
||||||
|
*/
|
||||||
|
async info(id: number) {
|
||||||
|
const info = await this.productRepository
|
||||||
|
.createQueryBuilder('product')
|
||||||
|
.where({
|
||||||
|
id
|
||||||
|
})
|
||||||
|
.andWhere('product.isDelete = 0')
|
||||||
|
.getOne();
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解除附件关联
|
||||||
|
* @param id 合同ID
|
||||||
|
* @param fileIds 附件ID
|
||||||
|
*/
|
||||||
|
async unlinkAttachments(id: number, fileIds: number[]) {
|
||||||
|
await this.entityManager.transaction(async manager => {
|
||||||
|
const product = await this.productRepository
|
||||||
|
.createQueryBuilder('product')
|
||||||
|
.leftJoinAndSelect('product.files', 'files')
|
||||||
|
.where('product.id = :id', { id })
|
||||||
|
.getOne();
|
||||||
|
const linkedFiles = product.files
|
||||||
|
.map(item => item.id)
|
||||||
|
.filter(item => !fileIds.includes(item));
|
||||||
|
// 附件要批量更新
|
||||||
|
await manager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.relation(ProductEntity, 'files')
|
||||||
|
.of(id)
|
||||||
|
.addAndRemove(linkedFiles, product.files);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,9 @@ 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 { CompanyEntity } from '~/modules/company/company.entity';
|
||||||
import { ContractEntity } from '~/modules/contract/contract.entity';
|
import { ContractEntity } from '~/modules/contract/contract.entity';
|
||||||
|
import { MaterialsInOutEntity } from '~/modules/materials_inventory/in_out/materials_in_out.entity';
|
||||||
import { MaterialsInventoryEntity } from '~/modules/materials_inventory/materials_inventory.entity';
|
import { MaterialsInventoryEntity } from '~/modules/materials_inventory/materials_inventory.entity';
|
||||||
|
import { ProductEntity } from '~/modules/product/product.entity';
|
||||||
|
|
||||||
@Entity({ name: 'tool_storage' })
|
@Entity({ name: 'tool_storage' })
|
||||||
export class Storage extends CommonEntity {
|
export class Storage extends CommonEntity {
|
||||||
|
@ -50,6 +52,10 @@ export class Storage extends CommonEntity {
|
||||||
companys: Relation<CompanyEntity[]>;
|
companys: Relation<CompanyEntity[]>;
|
||||||
|
|
||||||
@ApiHideProperty()
|
@ApiHideProperty()
|
||||||
@ManyToMany(() => MaterialsInventoryEntity, materialsInventories => materialsInventories.files)
|
@ManyToMany(() => MaterialsInOutEntity, materialsInOut => materialsInOut.files)
|
||||||
materialsInventories: Relation<MaterialsInventoryEntity[]>;
|
materialsInOut: Relation<MaterialsInOutEntity[]>;
|
||||||
|
|
||||||
|
@ApiHideProperty()
|
||||||
|
@ManyToMany(() => ProductEntity, product => product.files)
|
||||||
|
products: Relation<ProductEntity[]>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue