feat: inventory
This commit is contained in:
parent
cab022af0a
commit
ce54da72c9
|
@ -26,7 +26,7 @@ export class BusinessException extends HttpException {
|
||||||
code,
|
code,
|
||||||
message
|
message
|
||||||
}),
|
}),
|
||||||
HttpStatus.OK
|
HttpStatus.BAD_REQUEST
|
||||||
);
|
);
|
||||||
|
|
||||||
this.errorCode = Number(code);
|
this.errorCode = Number(code);
|
||||||
|
|
|
@ -21,8 +21,9 @@ export enum MaterialsInOrOutEnum {
|
||||||
|
|
||||||
// 系统参数key
|
// 系统参数key
|
||||||
export enum ParamConfigEnum {
|
export enum ParamConfigEnum {
|
||||||
InventoryNumberPrefixIn = 'inventory_number_prefix_in',
|
InventoryNumberPrefix = 'inventory_number_prefix',
|
||||||
InventoryNumberPrefixOut = 'inventory_number_prefix_out',
|
InventoryInOutNumberPrefixIn = 'inventory_inout_number_prefix_in',
|
||||||
|
InventoryInOutNumberPrefixOut = 'inventory_inout_number_prefix_out',
|
||||||
ProductNumberPrefix = 'product_number_prefix'
|
ProductNumberPrefix = 'product_number_prefix'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,3 +33,10 @@ export enum ContractStatusEnum {
|
||||||
Approved = 1, // 已通过
|
Approved = 1, // 已通过
|
||||||
Rejected = 2 // 已拒绝
|
Rejected = 2 // 已拒绝
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 库存查询剩余咋黄台
|
||||||
|
export enum HasInventoryStatusEnum {
|
||||||
|
All = 0, // 全部
|
||||||
|
Yes = 1, // 有库存
|
||||||
|
No = 2 // 无库存
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
import { ExecutionContext, HttpException, HttpStatus, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { FastifyRequest } from 'fastify';
|
import { FastifyRequest } from 'fastify';
|
||||||
|
@ -72,7 +72,7 @@ export class JwtAuthGuard extends AuthGuard(AuthStrategy.JWT) {
|
||||||
const pv = await this.authService.getPasswordVersionByUid(request.user.uid);
|
const pv = await this.authService.getPasswordVersionByUid(request.user.uid);
|
||||||
if (pv !== `${request.user.pv}`) {
|
if (pv !== `${request.user.pv}`) {
|
||||||
// 密码版本不一致,登录期间已更改过密码
|
// 密码版本不一致,登录期间已更改过密码
|
||||||
throw new BusinessException(ErrorEnum.INVALID_LOGIN);
|
throw new HttpException(ErrorEnum.INVALID_LOGIN,HttpStatus.UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不允许多端登录
|
// 不允许多端登录
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BaseService {
|
export class BaseService {
|
||||||
generateInventoryNumber(): string {
|
generateInventoryInOutNumber(): string {
|
||||||
// Generate a random inventory number
|
// Generate a random inventory number
|
||||||
return Math.floor(Math.random() * 1000000).toString();
|
return Math.floor(Math.random() * 1000000).toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,11 @@ import { MaterialsInOutService } from './materials_in_out.service';
|
||||||
import { MaterialsInOutEntity } from './materials_in_out.entity';
|
import { MaterialsInOutEntity } from './materials_in_out.entity';
|
||||||
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
|
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
|
||||||
import { definePermission, Perm } from '~/modules/auth/decorators/permission.decorator';
|
import { definePermission, Perm } from '~/modules/auth/decorators/permission.decorator';
|
||||||
import { MaterialsInOutQueryDto, MaterialsInOutDto, MaterialsInOutUpdateDto } from './materials_in_out.dto';
|
import {
|
||||||
|
MaterialsInOutQueryDto,
|
||||||
|
MaterialsInOutDto,
|
||||||
|
MaterialsInOutUpdateDto
|
||||||
|
} from './materials_in_out.dto';
|
||||||
|
|
||||||
export const permissions = definePermission('materials_inventory:history_in_out', {
|
export const permissions = definePermission('materials_inventory:history_in_out', {
|
||||||
LIST: 'list',
|
LIST: 'list',
|
||||||
|
@ -40,8 +44,8 @@ export class MaterialsInOutController {
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: '新增原材料出入库记录' })
|
@ApiOperation({ summary: '新增原材料出入库记录' })
|
||||||
@Perm(permissions.CREATE)
|
@Perm(permissions.CREATE)
|
||||||
async create(@Body() dto: MaterialsInOutDto): Promise<void> {
|
async create(@Body() dto: MaterialsInOutDto): Promise<number> {
|
||||||
await this.materialsInOutService.create(dto);
|
return this.materialsInOutService.create(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
|
|
|
@ -29,14 +29,14 @@ export class MaterialsInOutDto {
|
||||||
projectId?: number;
|
projectId?: number;
|
||||||
|
|
||||||
@ApiProperty({ description: '产品Id' })
|
@ApiProperty({ description: '产品Id' })
|
||||||
@ValidateIf(o => !o.inventoryNumber)
|
@ValidateIf(o => !o.inventoryInOutNumber)
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
productId: number;
|
productId: number;
|
||||||
|
|
||||||
@ApiProperty({ description: '原材料库存编号' })
|
@ApiProperty({ description: '原材料库存编号' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
inventoryNumber: string;
|
inventoryInOutNumber: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '库存id(产品和单价双主键决定一条库存)' })
|
@ApiProperty({ description: '库存id(产品和单价双主键决定一条库存)' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
@ -80,6 +80,11 @@ export class MaterialsInOutDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
issuanceNumber: string;
|
issuanceNumber: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '库存位置' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
position: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@ApiProperty({ description: '备注' })
|
@ApiProperty({ description: '备注' })
|
||||||
|
@ -98,11 +103,12 @@ export class MaterialsInOutQueryDto extends PagerDto<MaterialsInOutQueryDto> {
|
||||||
// @IsString()
|
// @IsString()
|
||||||
@Transform(params => {
|
@Transform(params => {
|
||||||
// 开始和结束时间用的是一天的开始和一天的结束的时分秒
|
// 开始和结束时间用的是一天的开始和一天的结束的时分秒
|
||||||
const date = params.value;
|
return params.value
|
||||||
return [
|
? [
|
||||||
date ? `${formatToDate(dayjs(date).startOf('month'))} 00:00:00` : null,
|
params.value[0] ? `${formatToDate(params.value[0], 'YYYY-MM-DD')} 00:00:00` : null,
|
||||||
date ? `${formatToDate(dayjs(date).endOf('month'))} 23:59:59` : null
|
params.value[1] ? `${formatToDate(params.value[1], 'YYYY-MM-DD')} 23:59:59` : null
|
||||||
];
|
]
|
||||||
|
: [];
|
||||||
})
|
})
|
||||||
time?: string[];
|
time?: string[];
|
||||||
|
|
||||||
|
@ -129,7 +135,7 @@ export class MaterialsInOutQueryDto extends PagerDto<MaterialsInOutQueryDto> {
|
||||||
@ApiProperty({ description: '原材料库存编号' })
|
@ApiProperty({ description: '原材料库存编号' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
inventoryNumber?: string;
|
inventoryInOutNumber?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|
|
@ -19,16 +19,17 @@ import { ProductEntity } from '~/modules/product/product.entity';
|
||||||
import { ProjectEntity } from '~/modules/project/project.entity';
|
import { ProjectEntity } from '~/modules/project/project.entity';
|
||||||
import { ParamConfigEntity } from '~/modules/system/param-config/param-config.entity';
|
import { ParamConfigEntity } from '~/modules/system/param-config/param-config.entity';
|
||||||
import { Storage } from '~/modules/tools/storage/storage.entity';
|
import { Storage } from '~/modules/tools/storage/storage.entity';
|
||||||
|
import { MaterialsInventoryEntity } from '../materials_inventory.entity';
|
||||||
@Entity({ name: 'materials_in_out' })
|
@Entity({ name: 'materials_in_out' })
|
||||||
export class MaterialsInOutEntity extends CommonEntity {
|
export class MaterialsInOutEntity extends CommonEntity {
|
||||||
@Column({
|
@Column({
|
||||||
name: 'inventory_number',
|
name: 'inventory_inout_number',
|
||||||
type: 'varchar',
|
type: 'varchar',
|
||||||
length: 50,
|
length: 50,
|
||||||
comment: '原材料库存编号'
|
comment: '原材料出入库编号'
|
||||||
})
|
})
|
||||||
@ApiProperty({ description: '原材料库存编号' })
|
@ApiProperty({ description: '原材料出入库编号' })
|
||||||
inventoryNumber: string;
|
inventoryInOutNumber: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'product_id',
|
name: 'product_id',
|
||||||
|
@ -38,6 +39,14 @@ export class MaterialsInOutEntity extends CommonEntity {
|
||||||
@ApiProperty({ description: '产品' })
|
@ApiProperty({ description: '产品' })
|
||||||
productId: number;
|
productId: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'inventory_id',
|
||||||
|
type: 'int',
|
||||||
|
comment: '库存'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '库存' })
|
||||||
|
inventoryId: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'in_or_out',
|
name: 'in_or_out',
|
||||||
type: 'tinyint',
|
type: 'tinyint',
|
||||||
|
@ -127,4 +136,8 @@ export class MaterialsInOutEntity extends CommonEntity {
|
||||||
inverseJoinColumn: { name: 'file_id', referencedColumnName: 'id' }
|
inverseJoinColumn: { name: 'file_id', referencedColumnName: 'id' }
|
||||||
})
|
})
|
||||||
files: Relation<Storage[]>;
|
files: Relation<Storage[]>;
|
||||||
|
|
||||||
|
@ManyToOne(() => MaterialsInventoryEntity)
|
||||||
|
@JoinColumn({ name: 'inventory_id' })
|
||||||
|
inventory: MaterialsInventoryEntity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,26 +43,7 @@ export class MaterialsInOutService {
|
||||||
isCreateOut,
|
isCreateOut,
|
||||||
...ext
|
...ext
|
||||||
}: MaterialsInOutQueryDto): Promise<Pagination<MaterialsInOutEntity>> {
|
}: MaterialsInOutQueryDto): Promise<Pagination<MaterialsInOutEntity>> {
|
||||||
const sqb = this.materialsInOutRepository
|
const sqb = this.buildSearchQuery()
|
||||||
.createQueryBuilder('materialsInOut')
|
|
||||||
.leftJoin('materialsInOut.files', 'files')
|
|
||||||
.leftJoin('materialsInOut.project', 'project')
|
|
||||||
.leftJoin('materialsInOut.product', 'product')
|
|
||||||
.leftJoin('product.unit', 'unit')
|
|
||||||
.leftJoin('product.files', 'productFiles')
|
|
||||||
.leftJoin('product.company', 'company')
|
|
||||||
.addSelect([
|
|
||||||
'files.id',
|
|
||||||
'files.path',
|
|
||||||
'project.name',
|
|
||||||
'product.name',
|
|
||||||
'product.productSpecification',
|
|
||||||
'product.productNumber',
|
|
||||||
'productFiles.id',
|
|
||||||
'productFiles.path',
|
|
||||||
'unit.label',
|
|
||||||
'company.name'
|
|
||||||
])
|
|
||||||
.where(fieldSearch(ext))
|
.where(fieldSearch(ext))
|
||||||
.andWhere('materialsInOut.isDelete = 0')
|
.andWhere('materialsInOut.isDelete = 0')
|
||||||
.addOrderBy('materialsInOut.createdAt', 'DESC');
|
.addOrderBy('materialsInOut.createdAt', 'DESC');
|
||||||
|
@ -85,25 +66,63 @@ export class MaterialsInOutService {
|
||||||
return pageData;
|
return pageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildSearchQuery() {
|
||||||
|
return this.materialsInOutRepository
|
||||||
|
.createQueryBuilder('materialsInOut')
|
||||||
|
.leftJoin('materialsInOut.files', 'files')
|
||||||
|
.leftJoin('materialsInOut.project', 'project')
|
||||||
|
.leftJoin('materialsInOut.product', 'product')
|
||||||
|
.leftJoin('materialsInOut.inventory', 'inventory')
|
||||||
|
.leftJoin('product.unit', 'unit')
|
||||||
|
.leftJoin('product.files', 'productFiles')
|
||||||
|
.leftJoin('product.company', 'company')
|
||||||
|
.addSelect([
|
||||||
|
'inventory.id',
|
||||||
|
'inventory.position',
|
||||||
|
'files.id',
|
||||||
|
'files.path',
|
||||||
|
'project.name',
|
||||||
|
'product.name',
|
||||||
|
'product.productSpecification',
|
||||||
|
'product.productNumber',
|
||||||
|
'productFiles.id',
|
||||||
|
'productFiles.path',
|
||||||
|
'unit.label',
|
||||||
|
'company.name'
|
||||||
|
]);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 新增
|
* 新增
|
||||||
*/
|
*/
|
||||||
async create(dto: MaterialsInOutDto): Promise<void> {
|
async create(dto: MaterialsInOutDto): Promise<number> {
|
||||||
let { inOrOut, inventoryNumber, projectId, inventoryId } = dto;
|
let {
|
||||||
inventoryNumber = await this.generateInventoryNumber(inOrOut);
|
inOrOut,
|
||||||
|
inventoryInOutNumber,
|
||||||
|
projectId,
|
||||||
|
inventoryId,
|
||||||
|
position,
|
||||||
|
unitPrice,
|
||||||
|
quantity,
|
||||||
|
productId
|
||||||
|
} = dto;
|
||||||
|
inventoryInOutNumber = await this.generateInventoryInOutNumber(inOrOut);
|
||||||
|
let newRecordId;
|
||||||
await this.entityManager.transaction(async manager => {
|
await this.entityManager.transaction(async manager => {
|
||||||
// 1.生成出入库记录
|
delete dto.position;
|
||||||
const { productId, quantity, unitPrice } = await manager.save(MaterialsInOutEntity, {
|
// 1.更新增减库存
|
||||||
...this.materialsInOutRepository.create(dto),
|
const inventoryEntity = await (
|
||||||
inventoryNumber
|
|
||||||
});
|
|
||||||
// 2.更新增减库存
|
|
||||||
await (
|
|
||||||
Object.is(inOrOut, MaterialsInOrOutEnum.In)
|
Object.is(inOrOut, MaterialsInOrOutEnum.In)
|
||||||
? this.materialsInventoryService.inInventory
|
? this.materialsInventoryService.inInventory.bind(this.materialsInventoryService)
|
||||||
: this.materialsInventoryService.outInventory
|
: this.materialsInventoryService.outInventory.bind(this.materialsInventoryService)
|
||||||
)({ productId, quantity, unitPrice, projectId, inventoryId }, manager);
|
)({ productId, quantity, unitPrice, projectId, inventoryId, position }, manager);
|
||||||
|
// 2.生成出入库记录
|
||||||
|
const { id } = await manager.save(MaterialsInOutEntity, {
|
||||||
|
...this.materialsInOutRepository.create({ ...dto, inventoryId: inventoryEntity?.id }),
|
||||||
|
inventoryInOutNumber
|
||||||
|
});
|
||||||
|
newRecordId = id;
|
||||||
});
|
});
|
||||||
|
return newRecordId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,8 +157,8 @@ export class MaterialsInOutService {
|
||||||
}
|
}
|
||||||
await (
|
await (
|
||||||
Object.is(data.inOrOut, MaterialsInOrOutEnum.In)
|
Object.is(data.inOrOut, MaterialsInOrOutEnum.In)
|
||||||
? this.materialsInventoryService.inInventory
|
? this.materialsInventoryService.inInventory.bind(this.materialsInventoryService)
|
||||||
: this.materialsInventoryService.outInventory
|
: this.materialsInventoryService.outInventory.bind(this.materialsInventoryService)
|
||||||
)(
|
)(
|
||||||
{
|
{
|
||||||
productId: entity.productId,
|
productId: entity.productId,
|
||||||
|
@ -189,7 +208,6 @@ export class MaterialsInOutService {
|
||||||
manager
|
manager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 完成所有业务逻辑后,更新出入库记录
|
// 完成所有业务逻辑后,更新出入库记录
|
||||||
await manager.update(MaterialsInOutEntity, id, {
|
await manager.update(MaterialsInOutEntity, id, {
|
||||||
...data
|
...data
|
||||||
|
@ -232,8 +250,8 @@ export class MaterialsInOutService {
|
||||||
// 更新库存
|
// 更新库存
|
||||||
await (
|
await (
|
||||||
Object.is(entity.inOrOut, MaterialsInOrOutEnum.In)
|
Object.is(entity.inOrOut, MaterialsInOrOutEnum.In)
|
||||||
? this.materialsInventoryService.outInventory
|
? this.materialsInventoryService.outInventory.bind(this.materialsInventoryService)
|
||||||
: this.materialsInventoryService.inInventory
|
: this.materialsInventoryService.inInventory.bind(this.materialsInventoryService)
|
||||||
)(
|
)(
|
||||||
{
|
{
|
||||||
productId: entity.productId,
|
productId: entity.productId,
|
||||||
|
@ -253,8 +271,7 @@ export class MaterialsInOutService {
|
||||||
* 获取单个出入库信息
|
* 获取单个出入库信息
|
||||||
*/
|
*/
|
||||||
async info(id: number) {
|
async info(id: number) {
|
||||||
const info = await this.materialsInOutRepository
|
const info = await this.buildSearchQuery()
|
||||||
.createQueryBuilder('materialsInOut')
|
|
||||||
.where({
|
.where({
|
||||||
id
|
id
|
||||||
})
|
})
|
||||||
|
@ -288,30 +305,30 @@ export class MaterialsInOutService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成库存单号
|
* 生成库存出入库单号
|
||||||
* @returns 库存单号
|
* @returns 库存出入库单号
|
||||||
*/
|
*/
|
||||||
async generateInventoryNumber(inOrOut: MaterialsInOrOutEnum = MaterialsInOrOutEnum.In) {
|
async generateInventoryInOutNumber(inOrOut: MaterialsInOrOutEnum = MaterialsInOrOutEnum.In) {
|
||||||
const prefix =
|
const prefix =
|
||||||
(
|
(
|
||||||
await this.paramConfigRepository.findOne({
|
await this.paramConfigRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
key: inOrOut
|
key: inOrOut
|
||||||
? ParamConfigEnum.InventoryNumberPrefixOut
|
? ParamConfigEnum.InventoryInOutNumberPrefixOut
|
||||||
: ParamConfigEnum.InventoryNumberPrefixIn
|
: ParamConfigEnum.InventoryInOutNumberPrefixIn
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)?.value || '';
|
)?.value || '';
|
||||||
const lastMaterial = await this.materialsInOutRepository
|
const lastMaterial = await this.materialsInOutRepository
|
||||||
.createQueryBuilder('materialsInOut')
|
.createQueryBuilder('materialsInOut')
|
||||||
.select(
|
.select(
|
||||||
`MAX(CAST(REPLACE(materialsInOut.inventoryNumber, '${prefix}', '') AS UNSIGNED))`,
|
`MAX(CAST(REPLACE(materialsInOut.inventoryInOutNumber, '${prefix}', '') AS UNSIGNED))`,
|
||||||
'maxInventoryNumber'
|
'maxInventoryInOutNumber'
|
||||||
)
|
)
|
||||||
.where('materialsInOut.inOrOut = :inOrOut', { inOrOut })
|
.where('materialsInOut.inOrOut = :inOrOut', { inOrOut })
|
||||||
.getRawOne();
|
.getRawOne();
|
||||||
const lastNumber = lastMaterial.maxInventoryNumber
|
const lastNumber = lastMaterial.maxInventoryInOutNumber
|
||||||
? parseInt(lastMaterial.maxInventoryNumber.replace(prefix, ''))
|
? parseInt(lastMaterial.maxInventoryInOutNumber.replace(prefix, ''))
|
||||||
: 0;
|
: 0;
|
||||||
const newNumber = lastNumber + 1 < 1000 ? 1000 : lastNumber + 1;
|
const newNumber = lastNumber + 1 < 1000 ? 1000 : lastNumber + 1;
|
||||||
return `${prefix}${newNumber}`;
|
return `${prefix}${newNumber}`;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsDate,
|
IsDate,
|
||||||
IsDateString,
|
IsDateString,
|
||||||
|
IsEnum,
|
||||||
IsIn,
|
IsIn,
|
||||||
IsInt,
|
IsInt,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
|
@ -16,6 +17,7 @@ import { Storage } from '../tools/storage/storage.entity';
|
||||||
import { Transform } from 'class-transformer';
|
import { Transform } from 'class-transformer';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { formatToDate } from '~/utils';
|
import { formatToDate } from '~/utils';
|
||||||
|
import { HasInventoryStatusEnum } from '~/constants/enum';
|
||||||
|
|
||||||
export class MaterialsInventoryDto {}
|
export class MaterialsInventoryDto {}
|
||||||
|
|
||||||
|
@ -23,7 +25,22 @@ export class MaterialsInventoryUpdateDto extends PartialType(MaterialsInventoryD
|
||||||
export class MaterialsInventoryQueryDto extends IntersectionType(
|
export class MaterialsInventoryQueryDto extends IntersectionType(
|
||||||
PagerDto<MaterialsInventoryDto>,
|
PagerDto<MaterialsInventoryDto>,
|
||||||
PartialType(MaterialsInventoryDto)
|
PartialType(MaterialsInventoryDto)
|
||||||
) {}
|
) {
|
||||||
|
@ApiProperty({ description: '产品名' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
product: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '关键字' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
keyword: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品名' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(HasInventoryStatusEnum)
|
||||||
|
isHasInventory: HasInventoryStatusEnum;
|
||||||
|
}
|
||||||
export class MaterialsInventoryExportDto {
|
export class MaterialsInventoryExportDto {
|
||||||
@ApiProperty({ description: '项目' })
|
@ApiProperty({ description: '项目' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
|
||||||
import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, Relation } from 'typeorm';
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
|
JoinTable,
|
||||||
|
ManyToMany,
|
||||||
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
|
Relation
|
||||||
|
} from 'typeorm';
|
||||||
import { CommonEntity } from '~/common/entity/common.entity';
|
import { CommonEntity } from '~/common/entity/common.entity';
|
||||||
import { ProductEntity } from '../product/product.entity';
|
import { ProductEntity } from '../product/product.entity';
|
||||||
import { ProjectEntity } from '../project/project.entity';
|
import { ProjectEntity } from '../project/project.entity';
|
||||||
|
import { MaterialsInOutEntity } from './in_out/materials_in_out.entity';
|
||||||
|
|
||||||
@Entity({ name: 'materials_inventory' })
|
@Entity({ name: 'materials_inventory' })
|
||||||
export class MaterialsInventoryEntity extends CommonEntity {
|
export class MaterialsInventoryEntity extends CommonEntity {
|
||||||
|
@ -22,6 +32,16 @@ export class MaterialsInventoryEntity extends CommonEntity {
|
||||||
@ApiProperty({ description: '产品' })
|
@ApiProperty({ description: '产品' })
|
||||||
productId: number;
|
productId: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'position',
|
||||||
|
type: 'varchar',
|
||||||
|
length: 255,
|
||||||
|
nullable: true,
|
||||||
|
comment: '库存位置'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '库存位置' })
|
||||||
|
position: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'quantity',
|
name: 'quantity',
|
||||||
type: 'int',
|
type: 'int',
|
||||||
|
@ -57,4 +77,17 @@ export class MaterialsInventoryEntity extends CommonEntity {
|
||||||
@ManyToOne(() => ProductEntity)
|
@ManyToOne(() => ProductEntity)
|
||||||
@JoinColumn({ name: 'product_id' })
|
@JoinColumn({ name: 'product_id' })
|
||||||
product: ProductEntity;
|
product: ProductEntity;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'inventory_number',
|
||||||
|
type: 'varchar',
|
||||||
|
length: 50,
|
||||||
|
comment: '库存编号'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '库存编号' })
|
||||||
|
inventoryNumber: string;
|
||||||
|
|
||||||
|
@ApiHideProperty()
|
||||||
|
@OneToMany(() => MaterialsInOutEntity, inout => inout.inventory)
|
||||||
|
materialsInOuts: Relation<MaterialsInOutEntity[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,15 @@ import dayjs from 'dayjs';
|
||||||
import { MaterialsInOutEntity } from './in_out/materials_in_out.entity';
|
import { MaterialsInOutEntity } from './in_out/materials_in_out.entity';
|
||||||
import { fieldSearch } from '~/shared/database/field-search';
|
import { fieldSearch } from '~/shared/database/field-search';
|
||||||
import { groupBy, sum, uniqBy } from 'lodash';
|
import { groupBy, sum, uniqBy } from 'lodash';
|
||||||
import { MaterialsInOrOutEnum } from '~/constants/enum';
|
import { HasInventoryStatusEnum, MaterialsInOrOutEnum, ParamConfigEnum } from '~/constants/enum';
|
||||||
import { ProjectEntity } from '../project/project.entity';
|
import { ProjectEntity } from '../project/project.entity';
|
||||||
import { calcNumber } from '~/utils';
|
import { calcNumber } from '~/utils';
|
||||||
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 { ParamConfigEntity } from '../system/param-config/param-config.entity';
|
||||||
|
import { isDefined } from 'class-validator';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MaterialsInventoryService {
|
export class MaterialsInventoryService {
|
||||||
const;
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectEntityManager() private entityManager: EntityManager,
|
@InjectEntityManager() private entityManager: EntityManager,
|
||||||
@InjectRepository(MaterialsInventoryEntity)
|
@InjectRepository(MaterialsInventoryEntity)
|
||||||
|
@ -31,7 +32,9 @@ export class MaterialsInventoryService {
|
||||||
@InjectRepository(MaterialsInOutEntity)
|
@InjectRepository(MaterialsInOutEntity)
|
||||||
private materialsInOutRepository: Repository<MaterialsInOutEntity>,
|
private materialsInOutRepository: Repository<MaterialsInOutEntity>,
|
||||||
@InjectRepository(ProjectEntity)
|
@InjectRepository(ProjectEntity)
|
||||||
private projectRepository: Repository<ProjectEntity>
|
private projectRepository: Repository<ProjectEntity>,
|
||||||
|
@InjectRepository(ParamConfigEntity)
|
||||||
|
private paramConfigRepository: Repository<ParamConfigEntity>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -236,7 +239,7 @@ export class MaterialsInventoryService {
|
||||||
);
|
);
|
||||||
number++;
|
number++;
|
||||||
sheet.addRow([
|
sheet.addRow([
|
||||||
`${inRecord.inventoryNumber || ''}`,
|
`${inRecord.inventoryInOutNumber || ''}`,
|
||||||
inRecord.product.company.name || '',
|
inRecord.product.company.name || '',
|
||||||
inRecord.product.name || '',
|
inRecord.product.name || '',
|
||||||
inRecord.product.unit.label || '',
|
inRecord.product.unit.label || '',
|
||||||
|
@ -312,7 +315,10 @@ export class MaterialsInventoryService {
|
||||||
*/
|
*/
|
||||||
async findAll({
|
async findAll({
|
||||||
page,
|
page,
|
||||||
pageSize
|
pageSize,
|
||||||
|
product,
|
||||||
|
keyword,
|
||||||
|
isHasInventory
|
||||||
}: MaterialsInventoryQueryDto): Promise<Pagination<MaterialsInventoryEntity>> {
|
}: MaterialsInventoryQueryDto): Promise<Pagination<MaterialsInventoryEntity>> {
|
||||||
const queryBuilder = this.materialsInventoryRepository
|
const queryBuilder = this.materialsInventoryRepository
|
||||||
.createQueryBuilder('materialsInventory')
|
.createQueryBuilder('materialsInventory')
|
||||||
|
@ -322,13 +328,34 @@ export class MaterialsInventoryService {
|
||||||
.leftJoin('product.company', 'company')
|
.leftJoin('product.company', 'company')
|
||||||
.addSelect([
|
.addSelect([
|
||||||
'project.name',
|
'project.name',
|
||||||
|
'project.id',
|
||||||
|
'unit.id',
|
||||||
'unit.label',
|
'unit.label',
|
||||||
|
'company.id',
|
||||||
'company.name',
|
'company.name',
|
||||||
|
'product.id',
|
||||||
'product.name',
|
'product.name',
|
||||||
'product.productSpecification',
|
'product.productSpecification',
|
||||||
'product.productNumber'
|
'product.productNumber'
|
||||||
])
|
])
|
||||||
.where('materialsInventory.isDelete = 0');
|
.where('materialsInventory.isDelete = 0');
|
||||||
|
if (product) {
|
||||||
|
queryBuilder.andWhere('product.name like :product', { product: `%${product}%` });
|
||||||
|
}
|
||||||
|
if (keyword) {
|
||||||
|
queryBuilder.andWhere(
|
||||||
|
'(materialsInventory.inventoryNumber like :keyword or product.name like :keyword or product.productNumber like :keyword or product.productSpecification like :keyword)',
|
||||||
|
{
|
||||||
|
keyword: `%${keyword}%`
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isHasInventory == HasInventoryStatusEnum.Yes) {
|
||||||
|
queryBuilder.andWhere('materialsInventory.quantity > 0');
|
||||||
|
}
|
||||||
|
if (isHasInventory == HasInventoryStatusEnum.No) {
|
||||||
|
queryBuilder.andWhere('materialsInventory.quantity = 0');
|
||||||
|
}
|
||||||
return paginate<MaterialsInventoryEntity>(queryBuilder, {
|
return paginate<MaterialsInventoryEntity>(queryBuilder, {
|
||||||
page,
|
page,
|
||||||
pageSize
|
pageSize
|
||||||
|
@ -361,6 +388,7 @@ export class MaterialsInventoryService {
|
||||||
*/
|
*/
|
||||||
async inInventory(
|
async inInventory(
|
||||||
data: {
|
data: {
|
||||||
|
position?: string;
|
||||||
projectId: number;
|
projectId: number;
|
||||||
productId: number;
|
productId: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
@ -368,8 +396,15 @@ export class MaterialsInventoryService {
|
||||||
changedUnitPrice?: number;
|
changedUnitPrice?: number;
|
||||||
},
|
},
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
): Promise<void> {
|
): Promise<MaterialsInventoryEntity> {
|
||||||
const { projectId, productId, quantity: inQuantity, unitPrice, changedUnitPrice } = data;
|
const {
|
||||||
|
projectId,
|
||||||
|
productId,
|
||||||
|
quantity: inQuantity,
|
||||||
|
unitPrice,
|
||||||
|
changedUnitPrice,
|
||||||
|
position
|
||||||
|
} = data;
|
||||||
|
|
||||||
const exsitedInventory = await manager.findOne(MaterialsInventoryEntity, {
|
const exsitedInventory = await manager.findOne(MaterialsInventoryEntity, {
|
||||||
where: { projectId, productId, unitPrice }, // 根据项目,产品,价格查出之前的实时库存情况
|
where: { projectId, productId, unitPrice }, // 根据项目,产品,价格查出之前的实时库存情况
|
||||||
|
@ -378,13 +413,16 @@ export class MaterialsInventoryService {
|
||||||
|
|
||||||
// 若不存在库存,直接新增库存
|
// 若不存在库存,直接新增库存
|
||||||
if (!exsitedInventory) {
|
if (!exsitedInventory) {
|
||||||
await manager.insert(MaterialsInventoryEntity, {
|
const inventoryNumber = await this.generateInventoryNumber();
|
||||||
|
const { raw } = await manager.insert(MaterialsInventoryEntity, {
|
||||||
projectId,
|
projectId,
|
||||||
productId,
|
productId,
|
||||||
unitPrice,
|
unitPrice,
|
||||||
|
inventoryNumber,
|
||||||
|
position,
|
||||||
quantity: inQuantity
|
quantity: inQuantity
|
||||||
});
|
});
|
||||||
return;
|
return manager.findOne(MaterialsInventoryEntity, { where: { id: raw.insertId } });
|
||||||
}
|
}
|
||||||
// 若该项目存在库存,则该项目该产品的库存增加
|
// 若该项目存在库存,则该项目该产品的库存增加
|
||||||
let { quantity, id } = exsitedInventory;
|
let { quantity, id } = exsitedInventory;
|
||||||
|
@ -396,6 +434,8 @@ export class MaterialsInventoryService {
|
||||||
quantity: newQuantity,
|
quantity: newQuantity,
|
||||||
unitPrice: changedUnitPrice || undefined
|
unitPrice: changedUnitPrice || undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return manager.findOne(MaterialsInventoryEntity, { where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -407,14 +447,22 @@ export class MaterialsInventoryService {
|
||||||
data: {
|
data: {
|
||||||
quantity: number;
|
quantity: number;
|
||||||
inventoryId?: number;
|
inventoryId?: number;
|
||||||
|
productId: number;
|
||||||
|
unitPrice?: number;
|
||||||
},
|
},
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
): Promise<void> {
|
): Promise<MaterialsInventoryEntity> {
|
||||||
const { quantity: outQuantity, inventoryId } = data;
|
const { quantity: outQuantity, inventoryId, productId, unitPrice } = data;
|
||||||
|
let searchPayload: any = {};
|
||||||
|
if (inventoryId) {
|
||||||
|
searchPayload.id = inventoryId;
|
||||||
|
} else {
|
||||||
|
// 删除出入库记录时,需要根据产品ID和价格查找库存
|
||||||
|
searchPayload = { productId, unitPrice };
|
||||||
|
}
|
||||||
// 开启悲观行锁,防止脏读和修改
|
// 开启悲观行锁,防止脏读和修改
|
||||||
const inventory = await manager.findOne(MaterialsInventoryEntity, {
|
const inventory = await manager.findOne(MaterialsInventoryEntity, {
|
||||||
where: { id: inventoryId },
|
where: searchPayload,
|
||||||
lock: { mode: 'pessimistic_write' }
|
lock: { mode: 'pessimistic_write' }
|
||||||
});
|
});
|
||||||
// 检查库存剩余
|
// 检查库存剩余
|
||||||
|
@ -430,6 +478,8 @@ export class MaterialsInventoryService {
|
||||||
await manager.update(MaterialsInventoryEntity, id, {
|
await manager.update(MaterialsInventoryEntity, id, {
|
||||||
quantity: newQuantity
|
quantity: newQuantity
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return manager.findOne(MaterialsInventoryEntity, { where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -467,4 +517,31 @@ export class MaterialsInventoryService {
|
||||||
.getOne();
|
.getOne();
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成库存编号
|
||||||
|
* @returns 库存编号
|
||||||
|
*/
|
||||||
|
async generateInventoryNumber() {
|
||||||
|
const prefix =
|
||||||
|
(
|
||||||
|
await this.paramConfigRepository.findOne({
|
||||||
|
where: {
|
||||||
|
key: ParamConfigEnum.InventoryNumberPrefix
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)?.value || '';
|
||||||
|
const lastInventory = await this.materialsInventoryRepository
|
||||||
|
.createQueryBuilder('materials_inventory')
|
||||||
|
.select(
|
||||||
|
`MAX(CAST(REPLACE(materials_inventory.inventoryNumber, '${prefix}', '') AS UNSIGNED))`,
|
||||||
|
'maxInventoryNumber'
|
||||||
|
)
|
||||||
|
.getRawOne();
|
||||||
|
const lastNumber = lastInventory.maxInventoryNumber
|
||||||
|
? parseInt(lastInventory.maxInventoryNumber.replace(prefix, ''))
|
||||||
|
: 0;
|
||||||
|
const newNumber = lastNumber + 1 < 1000 ? 1000 : lastNumber + 1;
|
||||||
|
return `${prefix}${newNumber}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import pinyin from 'pinyin';
|
||||||
import { DictItemEntity } from '../system/dict-item/dict-item.entity';
|
import { DictItemEntity } from '../system/dict-item/dict-item.entity';
|
||||||
@Entity({ name: 'product' })
|
@Entity({ name: 'product' })
|
||||||
export class ProductEntity extends CommonEntity {
|
export class ProductEntity extends CommonEntity {
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'product_number',
|
name: 'product_number',
|
||||||
type: 'varchar',
|
type: 'varchar',
|
||||||
|
@ -35,7 +34,7 @@ export class ProductEntity extends CommonEntity {
|
||||||
})
|
})
|
||||||
@ApiProperty({ description: '产品名称' })
|
@ApiProperty({ description: '产品名称' })
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'product_specification',
|
name: 'product_specification',
|
||||||
type: 'varchar',
|
type: 'varchar',
|
||||||
|
@ -45,6 +44,7 @@ export class ProductEntity extends CommonEntity {
|
||||||
})
|
})
|
||||||
@ApiProperty({ description: '产品规格', nullable: true })
|
@ApiProperty({ description: '产品规格', nullable: true })
|
||||||
productSpecification?: string;
|
productSpecification?: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'remark',
|
name: 'remark',
|
||||||
type: 'varchar',
|
type: 'varchar',
|
||||||
|
|
Loading…
Reference in New Issue