feat: inventory

This commit is contained in:
louis 2024-03-27 14:11:11 +08:00
parent cab022af0a
commit ce54da72c9
12 changed files with 263 additions and 88 deletions

View File

@ -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);

View File

@ -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 // 无库存
}

View File

@ -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);
} }
// 不允许多端登录 // 不允许多端登录

View File

@ -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();
} }

View File

@ -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')

View File

@ -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()

View File

@ -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;
} }

View File

@ -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}`;

View File

@ -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()

View File

@ -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[]>;
} }

View File

@ -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}`;
}
} }

View File

@ -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',
@ -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',