feat: 重做出入库逻辑
This commit is contained in:
parent
77247c140c
commit
cab022af0a
|
@ -13,7 +13,7 @@ SWAGGER_PATH = api-docs
|
||||||
SWAGGER_VERSION = 1.0
|
SWAGGER_VERSION = 1.0
|
||||||
|
|
||||||
# db
|
# db
|
||||||
DB_HOST = 127.0.0.1
|
DB_HOST = 192.168.60.39
|
||||||
DB_PORT = 13307
|
DB_PORT = 13307
|
||||||
DB_DATABASE = hxoa
|
DB_DATABASE = hxoa
|
||||||
DB_USERNAME = root
|
DB_USERNAME = root
|
||||||
|
@ -23,7 +23,7 @@ DB_LOGGING = "all"
|
||||||
|
|
||||||
# redis
|
# redis
|
||||||
REDIS_PORT = 6379
|
REDIS_PORT = 6379
|
||||||
REDIS_HOST = 127.0.0.1
|
REDIS_HOST = 192.168.60.39
|
||||||
REDIS_PASSWORD = 123456
|
REDIS_PASSWORD = 123456
|
||||||
REDIS_DB = 0
|
REDIS_DB = 0
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,9 @@ export enum MaterialsInOrOutEnum {
|
||||||
|
|
||||||
// 系统参数key
|
// 系统参数key
|
||||||
export enum ParamConfigEnum {
|
export enum ParamConfigEnum {
|
||||||
MaterialsInOutPrefix = 'materials_in_out_prefix'
|
InventoryNumberPrefixIn = 'inventory_number_prefix_in',
|
||||||
|
InventoryNumberPrefixOut = 'inventory_number_prefix_out',
|
||||||
|
ProductNumberPrefix = 'product_number_prefix'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 合同审核状态
|
// 合同审核状态
|
||||||
|
|
|
@ -60,4 +60,5 @@ export enum ErrorEnum {
|
||||||
// Inventory
|
// Inventory
|
||||||
INVENTORY_INSUFFICIENT = '1408:库存不足',
|
INVENTORY_INSUFFICIENT = '1408:库存不足',
|
||||||
MATERIALS_IN_OUT_NOT_FOUND = '1409:出入库信息不存在',
|
MATERIALS_IN_OUT_NOT_FOUND = '1409:出入库信息不存在',
|
||||||
|
MATERIALS_IN_OUT_UNIT_PRICE_CANNOT_BE_MODIFIED = '1410:该价格的产品已经出库,单价不允许修改。若有疑问,请联系管理员'
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,11 +46,6 @@ export class AuthController {
|
||||||
@ApiSecurityAuth()
|
@ApiSecurityAuth()
|
||||||
@ApiOperation({ summary: '屏幕解锁,使用密码和token' })
|
@ApiOperation({ summary: '屏幕解锁,使用密码和token' })
|
||||||
@ApiResult({ type: LoginToken })
|
@ApiResult({ type: LoginToken })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async unlock(@Body() dto: LoginDto, @AuthUser() user: IAuthUser): Promise<Boolean> {
|
async unlock(@Body() dto: LoginDto, @AuthUser() user: IAuthUser): Promise<Boolean> {
|
||||||
await this.authService.unlock(user.uid, dto.password);
|
await this.authService.unlock(user.uid, dto.password);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -13,7 +13,8 @@ import {
|
||||||
IsString,
|
IsString,
|
||||||
Matches,
|
Matches,
|
||||||
MinLength,
|
MinLength,
|
||||||
ValidateIf
|
ValidateIf,
|
||||||
|
isNumber
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { PagerDto } from '~/common/dto/pager.dto';
|
import { PagerDto } from '~/common/dto/pager.dto';
|
||||||
|
@ -37,6 +38,11 @@ export class MaterialsInOutDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
inventoryNumber: string;
|
inventoryNumber: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '库存id(产品和单价双主键决定一条库存)' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
inventoryId: number;
|
||||||
|
|
||||||
@ApiProperty({ description: '单位(字典)' })
|
@ApiProperty({ description: '单位(字典)' })
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|
|
@ -39,7 +39,7 @@ export class MaterialsInOutEntity extends CommonEntity {
|
||||||
productId: number;
|
productId: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'inOrOut',
|
name: 'in_or_out',
|
||||||
type: 'tinyint',
|
type: 'tinyint',
|
||||||
comment: '入库或出库'
|
comment: '入库或出库'
|
||||||
})
|
})
|
||||||
|
|
|
@ -49,12 +49,17 @@ export class MaterialsInOutService {
|
||||||
.leftJoin('materialsInOut.project', 'project')
|
.leftJoin('materialsInOut.project', 'project')
|
||||||
.leftJoin('materialsInOut.product', 'product')
|
.leftJoin('materialsInOut.product', 'product')
|
||||||
.leftJoin('product.unit', 'unit')
|
.leftJoin('product.unit', 'unit')
|
||||||
|
.leftJoin('product.files', 'productFiles')
|
||||||
.leftJoin('product.company', 'company')
|
.leftJoin('product.company', 'company')
|
||||||
.addSelect([
|
.addSelect([
|
||||||
'files.id',
|
'files.id',
|
||||||
'files.path',
|
'files.path',
|
||||||
'project.name',
|
'project.name',
|
||||||
'product.name',
|
'product.name',
|
||||||
|
'product.productSpecification',
|
||||||
|
'product.productNumber',
|
||||||
|
'productFiles.id',
|
||||||
|
'productFiles.path',
|
||||||
'unit.label',
|
'unit.label',
|
||||||
'company.name'
|
'company.name'
|
||||||
])
|
])
|
||||||
|
@ -84,21 +89,8 @@ export class MaterialsInOutService {
|
||||||
* 新增
|
* 新增
|
||||||
*/
|
*/
|
||||||
async create(dto: MaterialsInOutDto): Promise<void> {
|
async create(dto: MaterialsInOutDto): Promise<void> {
|
||||||
let { inOrOut, inventoryNumber, projectId } = dto;
|
let { inOrOut, inventoryNumber, projectId, inventoryId } = dto;
|
||||||
if (Object.is(inOrOut, MaterialsInOrOutEnum.In)) {
|
inventoryNumber = await this.generateInventoryNumber(inOrOut);
|
||||||
// 入库
|
|
||||||
inventoryNumber = await this.generateInventoryNumber();
|
|
||||||
} else {
|
|
||||||
// 出库
|
|
||||||
const inRecord = await this.materialsInOutRepository.findOne({
|
|
||||||
where: {
|
|
||||||
inventoryNumber
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const { productId } = inRecord;
|
|
||||||
dto.productId = productId;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.entityManager.transaction(async manager => {
|
await this.entityManager.transaction(async manager => {
|
||||||
// 1.生成出入库记录
|
// 1.生成出入库记录
|
||||||
const { productId, quantity, unitPrice } = await manager.save(MaterialsInOutEntity, {
|
const { productId, quantity, unitPrice } = await manager.save(MaterialsInOutEntity, {
|
||||||
|
@ -110,7 +102,7 @@ export class MaterialsInOutService {
|
||||||
Object.is(inOrOut, MaterialsInOrOutEnum.In)
|
Object.is(inOrOut, MaterialsInOrOutEnum.In)
|
||||||
? this.materialsInventoryService.inInventory
|
? this.materialsInventoryService.inInventory
|
||||||
: this.materialsInventoryService.outInventory
|
: this.materialsInventoryService.outInventory
|
||||||
)({ productId, quantity, unitPrice, projectId }, manager);
|
)({ productId, quantity, unitPrice, projectId, inventoryId }, manager);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,9 +117,41 @@ export class MaterialsInOutService {
|
||||||
},
|
},
|
||||||
lock: { mode: 'pessimistic_write' }
|
lock: { mode: 'pessimistic_write' }
|
||||||
});
|
});
|
||||||
await manager.update(MaterialsInOutEntity, id, {
|
|
||||||
...data
|
// 修改入库记录的价格,会直接更改库存实际价格.
|
||||||
|
// 1.如果有了出库记录,不允许修改入库价格。
|
||||||
|
if (
|
||||||
|
Object.is(data.inOrOut, MaterialsInOrOutEnum.In) &&
|
||||||
|
isDefined(data.unitPrice) &&
|
||||||
|
Math.abs(Number(data.unitPrice) - Number(entity.unitPrice)) !== 0
|
||||||
|
) {
|
||||||
|
const outEntity = await manager.findOne(MaterialsInOutEntity, {
|
||||||
|
where: {
|
||||||
|
unitPrice: entity.unitPrice,
|
||||||
|
inOrOut: MaterialsInOrOutEnum.Out,
|
||||||
|
projectId: entity.projectId,
|
||||||
|
productId: entity.productId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
if (isDefined(outEntity)) {
|
||||||
|
throw new BusinessException(ErrorEnum.MATERIALS_IN_OUT_UNIT_PRICE_CANNOT_BE_MODIFIED);
|
||||||
|
}
|
||||||
|
await (
|
||||||
|
Object.is(data.inOrOut, MaterialsInOrOutEnum.In)
|
||||||
|
? this.materialsInventoryService.inInventory
|
||||||
|
: this.materialsInventoryService.outInventory
|
||||||
|
)(
|
||||||
|
{
|
||||||
|
productId: entity.productId,
|
||||||
|
quantity: 0,
|
||||||
|
unitPrice: entity.unitPrice,
|
||||||
|
projectId: entity.projectId,
|
||||||
|
changedUnitPrice: data.unitPrice
|
||||||
|
},
|
||||||
|
manager
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let changedQuantity = 0;
|
let changedQuantity = 0;
|
||||||
if (isDefined(data.quantity) && entity.quantity !== data.quantity) {
|
if (isDefined(data.quantity) && entity.quantity !== data.quantity) {
|
||||||
if (entity.inOrOut === MaterialsInOrOutEnum.In) {
|
if (entity.inOrOut === MaterialsInOrOutEnum.In) {
|
||||||
|
@ -165,6 +189,12 @@ export class MaterialsInOutService {
|
||||||
manager
|
manager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 完成所有业务逻辑后,更新出入库记录
|
||||||
|
await manager.update(MaterialsInOutEntity, id, {
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
|
||||||
if (fileIds?.length) {
|
if (fileIds?.length) {
|
||||||
const count = await this.storageRepository
|
const count = await this.storageRepository
|
||||||
.createQueryBuilder('storage')
|
.createQueryBuilder('storage')
|
||||||
|
@ -208,7 +238,7 @@ export class MaterialsInOutService {
|
||||||
{
|
{
|
||||||
productId: entity.productId,
|
productId: entity.productId,
|
||||||
quantity: entity.quantity,
|
quantity: entity.quantity,
|
||||||
unitPrice: undefined,
|
unitPrice: entity.unitPrice,
|
||||||
projectId: entity.projectId
|
projectId: entity.projectId
|
||||||
},
|
},
|
||||||
manager
|
manager
|
||||||
|
@ -261,12 +291,14 @@ export class MaterialsInOutService {
|
||||||
* 生成库存单号
|
* 生成库存单号
|
||||||
* @returns 库存单号
|
* @returns 库存单号
|
||||||
*/
|
*/
|
||||||
async generateInventoryNumber() {
|
async generateInventoryNumber(inOrOut: MaterialsInOrOutEnum = MaterialsInOrOutEnum.In) {
|
||||||
const prefix =
|
const prefix =
|
||||||
(
|
(
|
||||||
await this.paramConfigRepository.findOne({
|
await this.paramConfigRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
key: ParamConfigEnum.MaterialsInOutPrefix
|
key: inOrOut
|
||||||
|
? ParamConfigEnum.InventoryNumberPrefixOut
|
||||||
|
: ParamConfigEnum.InventoryNumberPrefixIn
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)?.value || '';
|
)?.value || '';
|
||||||
|
@ -276,6 +308,7 @@ export class MaterialsInOutService {
|
||||||
`MAX(CAST(REPLACE(materialsInOut.inventoryNumber, '${prefix}', '') AS UNSIGNED))`,
|
`MAX(CAST(REPLACE(materialsInOut.inventoryNumber, '${prefix}', '') AS UNSIGNED))`,
|
||||||
'maxInventoryNumber'
|
'maxInventoryNumber'
|
||||||
)
|
)
|
||||||
|
.where('materialsInOut.inOrOut = :inOrOut', { inOrOut })
|
||||||
.getRawOne();
|
.getRawOne();
|
||||||
const lastNumber = lastMaterial.maxInventoryNumber
|
const lastNumber = lastMaterial.maxInventoryNumber
|
||||||
? parseInt(lastMaterial.maxInventoryNumber.replace(prefix, ''))
|
? parseInt(lastMaterial.maxInventoryNumber.replace(prefix, ''))
|
||||||
|
|
|
@ -62,7 +62,15 @@ export class MaterialsInventoryService {
|
||||||
.leftJoin('mio.product', 'product')
|
.leftJoin('mio.product', 'product')
|
||||||
.leftJoin('product.unit', 'unit')
|
.leftJoin('product.unit', 'unit')
|
||||||
.leftJoin('product.company', 'company')
|
.leftJoin('product.company', 'company')
|
||||||
.addSelect(['project.id', 'project.name', 'product.name', 'unit.label', 'company.name'])
|
.addSelect([
|
||||||
|
'project.id',
|
||||||
|
'project.name',
|
||||||
|
'unit.label',
|
||||||
|
'company.name',
|
||||||
|
'product.name',
|
||||||
|
'product.productSpecification',
|
||||||
|
'product.productNumber'
|
||||||
|
])
|
||||||
.where({
|
.where({
|
||||||
time: MoreThan(time[0])
|
time: MoreThan(time[0])
|
||||||
})
|
})
|
||||||
|
@ -99,7 +107,7 @@ export class MaterialsInventoryService {
|
||||||
sheet.getCell('A2').value = `日期:${dayjs(time[0]).format('YYYY年M月')}`;
|
sheet.getCell('A2').value = `日期:${dayjs(time[0]).format('YYYY年M月')}`;
|
||||||
// 设置表头
|
// 设置表头
|
||||||
const headers = [
|
const headers = [
|
||||||
'库存编号',
|
'出入库单号',
|
||||||
'公司名称',
|
'公司名称',
|
||||||
'产品名称',
|
'产品名称',
|
||||||
'单位',
|
'单位',
|
||||||
|
@ -185,7 +193,10 @@ export class MaterialsInventoryService {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupedData = groupBy<MaterialsInOutEntity>(currentMonthProjectData, 'inventoryNumber');
|
const groupedData = groupBy<MaterialsInOutEntity>(
|
||||||
|
currentMonthProjectData,
|
||||||
|
record => `${record.productId}-${record.unitPrice}`
|
||||||
|
);
|
||||||
let number = 0;
|
let number = 0;
|
||||||
const groupedInventories = groupBy(
|
const groupedInventories = groupBy(
|
||||||
currentProjectInventories,
|
currentProjectInventories,
|
||||||
|
@ -309,7 +320,14 @@ export class MaterialsInventoryService {
|
||||||
.leftJoin('materialsInventory.product', 'product')
|
.leftJoin('materialsInventory.product', 'product')
|
||||||
.leftJoin('product.unit', 'unit')
|
.leftJoin('product.unit', 'unit')
|
||||||
.leftJoin('product.company', 'company')
|
.leftJoin('product.company', 'company')
|
||||||
.addSelect(['project.name', 'product.name', 'unit.label', 'company.name'])
|
.addSelect([
|
||||||
|
'project.name',
|
||||||
|
'unit.label',
|
||||||
|
'company.name',
|
||||||
|
'product.name',
|
||||||
|
'product.productSpecification',
|
||||||
|
'product.productNumber'
|
||||||
|
])
|
||||||
.where('materialsInventory.isDelete = 0');
|
.where('materialsInventory.isDelete = 0');
|
||||||
return paginate<MaterialsInventoryEntity>(queryBuilder, {
|
return paginate<MaterialsInventoryEntity>(queryBuilder, {
|
||||||
page,
|
page,
|
||||||
|
@ -337,6 +355,7 @@ export class MaterialsInventoryService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 产品入库后计算最新库存
|
* 产品入库后计算最新库存
|
||||||
|
* 请注意。产品库存需要根据产品id和价格双主键存储。因为产品价格会变化,需要分开统计。
|
||||||
* @param data 传入项目ID,产品ID和入库数量和单价
|
* @param data 传入项目ID,产品ID和入库数量和单价
|
||||||
* @param manager 传入事务对象防止开启多重事务
|
* @param manager 传入事务对象防止开启多重事务
|
||||||
*/
|
*/
|
||||||
|
@ -346,13 +365,14 @@ export class MaterialsInventoryService {
|
||||||
productId: number;
|
productId: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
unitPrice?: number;
|
unitPrice?: number;
|
||||||
|
changedUnitPrice?: number;
|
||||||
},
|
},
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { projectId, productId, quantity: inQuantity, unitPrice } = data;
|
const { projectId, productId, quantity: inQuantity, unitPrice, changedUnitPrice } = data;
|
||||||
|
|
||||||
const exsitedInventory = await manager.findOne(MaterialsInventoryEntity, {
|
const exsitedInventory = await manager.findOne(MaterialsInventoryEntity, {
|
||||||
where: { projectId, productId }, // 查出某个项目的某个产品的库存情况
|
where: { projectId, productId, unitPrice }, // 根据项目,产品,价格查出之前的实时库存情况
|
||||||
lock: { mode: 'pessimistic_write' } // 开启悲观行锁,防止脏读和修改
|
lock: { mode: 'pessimistic_write' } // 开启悲观行锁,防止脏读和修改
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -373,29 +393,28 @@ export class MaterialsInventoryService {
|
||||||
throw new Error('库存数量不合法');
|
throw new Error('库存数量不合法');
|
||||||
}
|
}
|
||||||
await manager.update(MaterialsInventoryEntity, id, {
|
await manager.update(MaterialsInventoryEntity, id, {
|
||||||
quantity: newQuantity
|
quantity: newQuantity,
|
||||||
|
unitPrice: changedUnitPrice || undefined
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 产品出库
|
* 产品出库
|
||||||
* @param data 传入产品id和入库数量和单价
|
* @param data 传入库存ID(一定存在。)
|
||||||
* @param manager 传入事务对象防止开启多重事务
|
* @param manager 传入事务对象防止开启多重事务
|
||||||
*/
|
*/
|
||||||
async outInventory(
|
async outInventory(
|
||||||
data: {
|
data: {
|
||||||
projectId: number;
|
|
||||||
productId: number;
|
|
||||||
quantity: number;
|
quantity: number;
|
||||||
unitPrice?: number;
|
inventoryId?: number;
|
||||||
},
|
},
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { projectId, productId, quantity: outQuantity } = data;
|
const { quantity: outQuantity, inventoryId } = data;
|
||||||
|
|
||||||
// 开启悲观行锁,防止脏读和修改
|
// 开启悲观行锁,防止脏读和修改
|
||||||
const inventory = await manager.findOne(MaterialsInventoryEntity, {
|
const inventory = await manager.findOne(MaterialsInventoryEntity, {
|
||||||
where: { projectId, productId },
|
where: { id: inventoryId },
|
||||||
lock: { mode: 'pessimistic_write' }
|
lock: { mode: 'pessimistic_write' }
|
||||||
});
|
});
|
||||||
// 检查库存剩余
|
// 检查库存剩余
|
||||||
|
@ -406,26 +425,41 @@ export class MaterialsInventoryService {
|
||||||
let { quantity, id } = inventory;
|
let { quantity, id } = inventory;
|
||||||
const newQuantity = calcNumber(quantity || 0, outQuantity || 0, 'subtract');
|
const newQuantity = calcNumber(quantity || 0, outQuantity || 0, 'subtract');
|
||||||
if (isNaN(newQuantity)) {
|
if (isNaN(newQuantity)) {
|
||||||
throw new Error('库存数量不合法');
|
throw new Error('库存数量不足。请检查库存或重新操作。');
|
||||||
}
|
}
|
||||||
await manager.update(MaterialsInventoryEntity, id, {
|
await manager.update(MaterialsInventoryEntity, id, {
|
||||||
quantity: newQuantity
|
quantity: newQuantity
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除
|
* 删除
|
||||||
*/
|
*/
|
||||||
async delete(id: number): Promise<void> {
|
async delete(id: number): Promise<void> {
|
||||||
// 合同比较重要,做逻辑删除
|
// 比较重要,做逻辑删除
|
||||||
await this.materialsInventoryRepository.update(id, { isDelete: 1 });
|
await this.materialsInventoryRepository.update(id, { isDelete: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取单个合同信息
|
* 获取某个价格的某个商品库存信息
|
||||||
*/
|
*/
|
||||||
async info(id: number) {
|
async info(id: number) {
|
||||||
const info = await this.materialsInventoryRepository
|
const info = await this.materialsInventoryRepository
|
||||||
.createQueryBuilder('materialsInventory')
|
.createQueryBuilder('materialsInventory')
|
||||||
|
.leftJoin('materialsInventory.project', 'project')
|
||||||
|
.leftJoin('materialsInventory.product', 'product')
|
||||||
|
.leftJoin('product.unit', 'unit')
|
||||||
|
.leftJoin('product.company', 'company')
|
||||||
|
.addSelect([
|
||||||
|
'project.name',
|
||||||
|
'project.id',
|
||||||
|
'product.id',
|
||||||
|
'product.name',
|
||||||
|
'unit.label',
|
||||||
|
'company.name',
|
||||||
|
'product.productSpecification',
|
||||||
|
'product.productNumber'
|
||||||
|
])
|
||||||
.where({
|
.where({
|
||||||
id
|
id
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,6 +8,12 @@ export class ProductDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品规格' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
productSpecification: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '产品备注' })
|
@ApiProperty({ description: '产品备注' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -47,4 +53,10 @@ export class ProductQueryDto extends IntersectionType(
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '关键字(名字/编号/规格)' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
keyword?: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,16 @@ 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({
|
||||||
|
name: 'product_number',
|
||||||
|
type: 'varchar',
|
||||||
|
length: 255,
|
||||||
|
comment: '产品编号'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '产品编号' })
|
||||||
|
productNumber: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'name',
|
name: 'name',
|
||||||
type: 'varchar',
|
type: 'varchar',
|
||||||
|
@ -26,6 +36,15 @@ export class ProductEntity extends CommonEntity {
|
||||||
@ApiProperty({ description: '产品名称' })
|
@ApiProperty({ description: '产品名称' })
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'product_specification',
|
||||||
|
type: 'varchar',
|
||||||
|
nullable: true,
|
||||||
|
length: 255,
|
||||||
|
comment: '产品规格'
|
||||||
|
})
|
||||||
|
@ApiProperty({ description: '产品规格', nullable: true })
|
||||||
|
productSpecification?: string;
|
||||||
@Column({
|
@Column({
|
||||||
name: 'remark',
|
name: 'remark',
|
||||||
type: 'varchar',
|
type: 'varchar',
|
||||||
|
|
|
@ -4,9 +4,10 @@ import { ProductService } from './product.service';
|
||||||
import { ProductEntity } from './product.entity';
|
import { ProductEntity } from './product.entity';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { StorageModule } from '../tools/storage/storage.module';
|
import { StorageModule } from '../tools/storage/storage.module';
|
||||||
|
import { ParamConfigModule } from '../system/param-config/param-config.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([ProductEntity]), StorageModule],
|
imports: [TypeOrmModule.forFeature([ProductEntity]), StorageModule, ParamConfigModule],
|
||||||
controllers: [ProductController],
|
controllers: [ProductController],
|
||||||
providers: [ProductService]
|
providers: [ProductService]
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { Storage } from '../tools/storage/storage.entity';
|
||||||
import { BusinessException } from '~/common/exceptions/biz.exception';
|
import { BusinessException } from '~/common/exceptions/biz.exception';
|
||||||
import { ErrorEnum } from '~/constants/error-code.constant';
|
import { ErrorEnum } from '~/constants/error-code.constant';
|
||||||
import { fieldSearch } from '~/shared/database/field-search';
|
import { fieldSearch } from '~/shared/database/field-search';
|
||||||
|
import { ParamConfigEnum } from '~/constants/enum';
|
||||||
|
import { ParamConfigEntity } from '../system/param-config/param-config.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProductService {
|
export class ProductService {
|
||||||
|
@ -17,7 +19,9 @@ export class ProductService {
|
||||||
@InjectRepository(ProductEntity)
|
@InjectRepository(ProductEntity)
|
||||||
private productRepository: Repository<ProductEntity>,
|
private productRepository: Repository<ProductEntity>,
|
||||||
@InjectRepository(Storage)
|
@InjectRepository(Storage)
|
||||||
private storageRepository: Repository<Storage>
|
private storageRepository: Repository<Storage>,
|
||||||
|
@InjectRepository(ParamConfigEntity)
|
||||||
|
private paramConfigRepository: Repository<ParamConfigEntity>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,7 +32,7 @@ export class ProductService {
|
||||||
pageSize,
|
pageSize,
|
||||||
...fields
|
...fields
|
||||||
}: ProductQueryDto): Promise<Pagination<ProductEntity>> {
|
}: ProductQueryDto): Promise<Pagination<ProductEntity>> {
|
||||||
const { company: companyName, ...ext } = fields;
|
const { company: companyName, keyword, ...ext } = fields;
|
||||||
const sqb = this.productRepository
|
const sqb = this.productRepository
|
||||||
.createQueryBuilder('product')
|
.createQueryBuilder('product')
|
||||||
.leftJoin('product.files', 'files')
|
.leftJoin('product.files', 'files')
|
||||||
|
@ -45,6 +49,16 @@ export class ProductService {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (keyword) {
|
||||||
|
//关键字模糊查询product的name,productNumber,productSpecification
|
||||||
|
sqb.andWhere(
|
||||||
|
'(product.name like :keyword or product.productNumber like :keyword or product.productSpecification like :keyword)',
|
||||||
|
{
|
||||||
|
keyword: `%${keyword}%`
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
return paginate<ProductEntity>(sqb, {
|
return paginate<ProductEntity>(sqb, {
|
||||||
page,
|
page,
|
||||||
pageSize
|
pageSize
|
||||||
|
@ -62,7 +76,9 @@ export class ProductService {
|
||||||
if (isExsit) {
|
if (isExsit) {
|
||||||
throw new BusinessException(ErrorEnum.PRODUCT_EXIST);
|
throw new BusinessException(ErrorEnum.PRODUCT_EXIST);
|
||||||
}
|
}
|
||||||
await this.productRepository.insert(this.productRepository.create(dto));
|
await this.productRepository.insert(
|
||||||
|
this.productRepository.create({ ...dto, productNumber: await this.generateProductNumber() })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,11 +107,7 @@ export class ProductService {
|
||||||
throw new BusinessException(ErrorEnum.STORAGE_NOT_FOUND);
|
throw new BusinessException(ErrorEnum.STORAGE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
// 附件要批量插入
|
// 附件要批量插入
|
||||||
await manager
|
await manager.createQueryBuilder().relation(ProductEntity, 'files').of(id).add(fileIds);
|
||||||
.createQueryBuilder()
|
|
||||||
.relation(ProductEntity, 'files')
|
|
||||||
.of(id)
|
|
||||||
.add(fileIds);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -146,4 +158,31 @@ export class ProductService {
|
||||||
.addAndRemove(linkedFiles, product.files);
|
.addAndRemove(linkedFiles, product.files);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成产品编号
|
||||||
|
* @returns 产品编号
|
||||||
|
*/
|
||||||
|
async generateProductNumber(): Promise<string> {
|
||||||
|
const prefix =
|
||||||
|
(
|
||||||
|
await this.paramConfigRepository.findOne({
|
||||||
|
where: {
|
||||||
|
key: ParamConfigEnum.ProductNumberPrefix
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)?.value || '';
|
||||||
|
const lastProduct = await this.productRepository
|
||||||
|
.createQueryBuilder('product')
|
||||||
|
.select(
|
||||||
|
`MAX(CAST(REPLACE(COALESCE(product.product_number, ''), '${prefix}', '') AS UNSIGNED))`,
|
||||||
|
'productNumber'
|
||||||
|
)
|
||||||
|
.getRawOne();
|
||||||
|
const lastNumber = lastProduct.productNumber
|
||||||
|
? parseInt(lastProduct.productNumber.replace(prefix, ''))
|
||||||
|
: 0;
|
||||||
|
const newNumber = lastNumber + 1 < 1000 ? 1000 : lastNumber + 1;
|
||||||
|
return `${prefix}${newNumber}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue