feat: 盘点表计算模块
This commit is contained in:
parent
3610d8fdc6
commit
5d9ce4cef2
|
@ -88,6 +88,7 @@
|
||||||
"helmet": "^7.1.0",
|
"helmet": "^7.1.0",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mathjs": "^12.4.0",
|
||||||
"minio": "^7.1.3",
|
"minio": "^7.1.3",
|
||||||
"mysql2": "^3.9.1",
|
"mysql2": "^3.9.1",
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
|
|
|
@ -131,6 +131,9 @@ dependencies:
|
||||||
lodash:
|
lodash:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
|
mathjs:
|
||||||
|
specifier: ^12.4.0
|
||||||
|
version: 12.4.0
|
||||||
minio:
|
minio:
|
||||||
specifier: ^7.1.3
|
specifier: ^7.1.3
|
||||||
version: 7.1.3
|
version: 7.1.3
|
||||||
|
@ -4881,6 +4884,10 @@ packages:
|
||||||
dot-prop: 5.3.0
|
dot-prop: 5.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/complex.js@2.1.1:
|
||||||
|
resolution: {integrity: sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/component-emitter@1.3.1:
|
/component-emitter@1.3.1:
|
||||||
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
|
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -5429,6 +5436,10 @@ packages:
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/decimal.js@10.4.3:
|
||||||
|
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/decode-uri-component@0.2.2:
|
/decode-uri-component@0.2.2:
|
||||||
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
|
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
|
||||||
engines: {node: '>=0.10'}
|
engines: {node: '>=0.10'}
|
||||||
|
@ -5912,6 +5923,10 @@ packages:
|
||||||
/escape-html@1.0.3:
|
/escape-html@1.0.3:
|
||||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||||
|
|
||||||
|
/escape-latex@1.2.0:
|
||||||
|
resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/escape-string-applescript@1.0.0:
|
/escape-string-applescript@1.0.0:
|
||||||
resolution: {integrity: sha512-4/hFwoYaC6TkpDn9A3pTC52zQPArFeXuIfhUtCGYdauTzXVP9H3BDr3oO/QzQehMpLDC7srvYgfwvImPFGfvBA==}
|
resolution: {integrity: sha512-4/hFwoYaC6TkpDn9A3pTC52zQPArFeXuIfhUtCGYdauTzXVP9H3BDr3oO/QzQehMpLDC7srvYgfwvImPFGfvBA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -6662,6 +6677,10 @@ packages:
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/fraction.js@4.3.4:
|
||||||
|
resolution: {integrity: sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fresh@0.5.2:
|
/fresh@0.5.2:
|
||||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
@ -7671,6 +7690,10 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/javascript-natural-sort@0.7.1:
|
||||||
|
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/jest-changed-files@29.7.0:
|
/jest-changed-files@29.7.0:
|
||||||
resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
|
resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
|
||||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
@ -8709,6 +8732,22 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/mathjs@12.4.0:
|
||||||
|
resolution: {integrity: sha512-4Moy0RNjwMSajEkGGxNUyMMC/CZAcl87WBopvNsJWB4E4EFebpTedr+0/rhqmnOSTH3Wu/3WfiWiw6mqiaHxVw==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.23.9
|
||||||
|
complex.js: 2.1.1
|
||||||
|
decimal.js: 10.4.3
|
||||||
|
escape-latex: 1.2.0
|
||||||
|
fraction.js: 4.3.4
|
||||||
|
javascript-natural-sort: 0.7.1
|
||||||
|
seedrandom: 3.0.5
|
||||||
|
tiny-emitter: 2.1.0
|
||||||
|
typed-function: 4.1.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/memfs@3.5.3:
|
/memfs@3.5.3:
|
||||||
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
|
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
|
@ -10861,6 +10900,10 @@ packages:
|
||||||
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
|
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/seedrandom@3.0.5:
|
||||||
|
resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/segmentit@2.0.3:
|
/segmentit@2.0.3:
|
||||||
resolution: {integrity: sha512-7mn2XL3OdTUQ+AhHz7SbgyxLTaQRzTWQNVwiK+UlTO8aePGbSwvKUzTwE4238+OUY9MoR6ksAg35zl8sfTunQQ==}
|
resolution: {integrity: sha512-7mn2XL3OdTUQ+AhHz7SbgyxLTaQRzTWQNVwiK+UlTO8aePGbSwvKUzTwE4238+OUY9MoR6ksAg35zl8sfTunQQ==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
|
@ -11635,6 +11678,10 @@ packages:
|
||||||
/through@2.3.8:
|
/through@2.3.8:
|
||||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||||
|
|
||||||
|
/tiny-emitter@2.1.0:
|
||||||
|
resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tiny-inflate@1.0.3:
|
/tiny-inflate@1.0.3:
|
||||||
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
|
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
|
||||||
|
|
||||||
|
@ -11893,6 +11940,11 @@ packages:
|
||||||
resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==}
|
resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/typed-function@4.1.1:
|
||||||
|
resolution: {integrity: sha512-Pq1DVubcvibmm8bYcMowjVnnMwPVMeh0DIdA8ad8NZY2sJgapANJmiigSUwlt+EgXxpfIv8MWrQXTIzkfYZLYQ==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/typedarray@0.0.6:
|
/typedarray@0.0.6:
|
||||||
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
|
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
|
@ -55,5 +55,8 @@ export enum ErrorEnum {
|
||||||
PRODUCT_EXIST = '1406:产品已存在',
|
PRODUCT_EXIST = '1406:产品已存在',
|
||||||
|
|
||||||
// Contract
|
// Contract
|
||||||
CONTRACT_NUMBER_EXIST = '1407:存在相同的合同编号'
|
CONTRACT_NUMBER_EXIST = '1407:存在相同的合同编号',
|
||||||
|
|
||||||
|
// Inventory 库存不足
|
||||||
|
INVENTORY_INSUFFICIENT = '1408:库存不足'
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BaseService {
|
||||||
|
generateInventoryNumber(): string {
|
||||||
|
// Generate a random inventory number
|
||||||
|
return Math.floor(Math.random() * 1000000).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add more common methods here
|
||||||
|
}
|
|
@ -16,6 +16,8 @@ import { MaterialsInOutEntity } from './materials_in_out.entity';
|
||||||
import { fieldSearch } from '~/shared/database/field-search';
|
import { fieldSearch } from '~/shared/database/field-search';
|
||||||
import { ParamConfigEntity } from '~/modules/system/param-config/param-config.entity';
|
import { ParamConfigEntity } from '~/modules/system/param-config/param-config.entity';
|
||||||
import { MaterialsInOrOutEnum, ParamConfigEnum } from '~/constants/enum';
|
import { MaterialsInOrOutEnum, ParamConfigEnum } from '~/constants/enum';
|
||||||
|
import { MaterialsInventoryEntity } from '../materials_inventory.entity';
|
||||||
|
import { MaterialsInventoryService } from '../materials_inventory.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MaterialsInOutService {
|
export class MaterialsInOutService {
|
||||||
|
@ -26,7 +28,8 @@ export class MaterialsInOutService {
|
||||||
@InjectRepository(Storage)
|
@InjectRepository(Storage)
|
||||||
private storageRepository: Repository<Storage>,
|
private storageRepository: Repository<Storage>,
|
||||||
@InjectRepository(ParamConfigEntity)
|
@InjectRepository(ParamConfigEntity)
|
||||||
private paramConfigRepository: Repository<ParamConfigEntity>
|
private paramConfigRepository: Repository<ParamConfigEntity>,
|
||||||
|
private materialsInventoryService: MaterialsInventoryService
|
||||||
) {}
|
) {}
|
||||||
/**
|
/**
|
||||||
* 查询所有出入库记录
|
* 查询所有出入库记录
|
||||||
|
@ -57,7 +60,7 @@ export class MaterialsInOutService {
|
||||||
.where(fieldSearch(ext))
|
.where(fieldSearch(ext))
|
||||||
.andWhere('materialsInOut.isDelete = 0')
|
.andWhere('materialsInOut.isDelete = 0')
|
||||||
.addOrderBy('materialsInOut.createdAt', 'DESC');
|
.addOrderBy('materialsInOut.createdAt', 'DESC');
|
||||||
|
|
||||||
if (productName) {
|
if (productName) {
|
||||||
sqb.andWhere('product.name like :productName', { productName: `%${productName}%` });
|
sqb.andWhere('product.name like :productName', { productName: `%${productName}%` });
|
||||||
}
|
}
|
||||||
|
@ -82,8 +85,10 @@ export class MaterialsInOutService {
|
||||||
async create(dto: MaterialsInOutDto): Promise<void> {
|
async create(dto: MaterialsInOutDto): Promise<void> {
|
||||||
let { inOrOut, inventoryNumber } = dto;
|
let { inOrOut, inventoryNumber } = dto;
|
||||||
if (inOrOut === MaterialsInOrOutEnum.In) {
|
if (inOrOut === MaterialsInOrOutEnum.In) {
|
||||||
|
// 入库
|
||||||
inventoryNumber = await this.generateInventoryNumber();
|
inventoryNumber = await this.generateInventoryNumber();
|
||||||
} else {
|
} else {
|
||||||
|
// 出库
|
||||||
const inRecord = await this.materialsInOutRepository.findOne({
|
const inRecord = await this.materialsInOutRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
inventoryNumber
|
inventoryNumber
|
||||||
|
@ -92,9 +97,15 @@ export class MaterialsInOutService {
|
||||||
const { productId } = inRecord;
|
const { productId } = inRecord;
|
||||||
dto.productId = productId;
|
dto.productId = productId;
|
||||||
}
|
}
|
||||||
await this.materialsInOutRepository.insert({
|
|
||||||
...this.materialsInOutRepository.create(dto),
|
await this.entityManager.transaction(async manager => {
|
||||||
inventoryNumber
|
// 1.生成出入库记录
|
||||||
|
const { productId, quantity } = await manager.create(MaterialsInOutEntity, {
|
||||||
|
...this.materialsInOutRepository.create(dto),
|
||||||
|
inventoryNumber
|
||||||
|
});
|
||||||
|
// 2.更新库存
|
||||||
|
await this.materialsInventoryService.inInventory({ productId, inQuantity: quantity });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const permissions = definePermission('app:materials_inventory', {
|
||||||
EXPORT: 'export'
|
EXPORT: 'export'
|
||||||
} as const);
|
} as const);
|
||||||
|
|
||||||
@ApiTags('MaterialsI Inventory - 原材料盘点')
|
@ApiTags('MaterialsI Inventory - 原材料库存')
|
||||||
@ApiSecurityAuth()
|
@ApiSecurityAuth()
|
||||||
@Controller('materials-inventory')
|
@Controller('materials-inventory')
|
||||||
export class MaterialsInventoryController {
|
export class MaterialsInventoryController {
|
||||||
|
@ -40,7 +40,7 @@ export class MaterialsInventoryController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ApiOperation({ summary: '获取原材料盘点列表' })
|
@ApiOperation({ summary: '获取原材料库存列表' })
|
||||||
@ApiResult({ type: [MaterialsInventoryEntity], isPage: true })
|
@ApiResult({ type: [MaterialsInventoryEntity], isPage: true })
|
||||||
@Perm(permissions.LIST)
|
@Perm(permissions.LIST)
|
||||||
async list(@Query() dto: MaterialsInventoryQueryDto) {
|
async list(@Query() dto: MaterialsInventoryQueryDto) {
|
||||||
|
@ -48,7 +48,7 @@ export class MaterialsInventoryController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@ApiOperation({ summary: '获取原材料盘点信息' })
|
@ApiOperation({ summary: '获取原材料库存信息' })
|
||||||
@ApiResult({ type: MaterialsInventoryDto })
|
@ApiResult({ type: MaterialsInventoryDto })
|
||||||
@Perm(permissions.READ)
|
@Perm(permissions.READ)
|
||||||
async info(@IdParam() id: number) {
|
async info(@IdParam() id: number) {
|
||||||
|
@ -56,21 +56,21 @@ export class MaterialsInventoryController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: '新增原材料盘点' })
|
@ApiOperation({ summary: '新增原材料库存' })
|
||||||
@Perm(permissions.CREATE)
|
@Perm(permissions.CREATE)
|
||||||
async create(@Body() dto: MaterialsInventoryDto): Promise<void> {
|
async create(@Body() dto: MaterialsInventoryDto): Promise<void> {
|
||||||
await this.miService.create(dto);
|
await this.miService.create(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@ApiOperation({ summary: '更新原材料盘点' })
|
@ApiOperation({ summary: '更新原材料库存' })
|
||||||
@Perm(permissions.UPDATE)
|
@Perm(permissions.UPDATE)
|
||||||
async update(@IdParam() id: number, @Body() dto: MaterialsInventoryUpdateDto): Promise<void> {
|
async update(@IdParam() id: number, @Body() dto: MaterialsInventoryUpdateDto): Promise<void> {
|
||||||
await this.miService.update(id, dto);
|
await this.miService.update(id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@ApiOperation({ summary: '删除原材料盘点' })
|
@ApiOperation({ summary: '删除原材料库存' })
|
||||||
@Perm(permissions.DELETE)
|
@Perm(permissions.DELETE)
|
||||||
async delete(@IdParam() id: number): Promise<void> {
|
async delete(@IdParam() id: number): Promise<void> {
|
||||||
await this.miService.delete(id);
|
await this.miService.delete(id);
|
||||||
|
|
|
@ -1,193 +1,41 @@
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm';
|
import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm';
|
||||||
import { CommonEntity } from '~/common/entity/common.entity';
|
import { CommonEntity } from '~/common/entity/common.entity';
|
||||||
import { Storage } from '../tools/storage/storage.entity';
|
|
||||||
|
|
||||||
@Entity({ name: 'materials_inventory' })
|
@Entity({ name: 'materials_inventory' })
|
||||||
export class MaterialsInventoryEntity extends CommonEntity {
|
export class MaterialsInventoryEntity extends CommonEntity {
|
||||||
@Column({ name: 'company_name', type: 'varchar', length: 255, comment: '公司名称' })
|
|
||||||
@ApiProperty({ description: '公司名称' })
|
|
||||||
companyName: number;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'product',
|
name: 'product_id',
|
||||||
type: 'int',
|
type: 'int',
|
||||||
comment: '产品名称(字典)'
|
comment: '产品'
|
||||||
})
|
})
|
||||||
@ApiProperty({ description: '产品名称(字典)' })
|
@ApiProperty({ description: '产品' })
|
||||||
product: number;
|
productId: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'unit',
|
name: 'quantity',
|
||||||
type: 'int',
|
|
||||||
comment: '单位(字典)'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '单位(字典)' })
|
|
||||||
unit: number;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'previous_inventory_quantity',
|
|
||||||
type: 'int',
|
type: 'int',
|
||||||
default: 0,
|
default: 0,
|
||||||
comment: '之前的库存数量'
|
comment: '库存产品数量'
|
||||||
})
|
})
|
||||||
@ApiProperty({ description: '之前的库存数量' })
|
@ApiProperty({ description: '库存产品数量' })
|
||||||
previousInventoryQuantity: number;
|
quantity: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'previous_unit_price',
|
name: 'unit_price',
|
||||||
type: 'decimal',
|
type: 'decimal',
|
||||||
precision: 10,
|
precision: 10,
|
||||||
default: 0,
|
default: 0,
|
||||||
scale: 2,
|
scale: 2,
|
||||||
comment: '之前的单价'
|
comment: '库存产品单价'
|
||||||
})
|
})
|
||||||
@ApiProperty({ description: '之前的单价' })
|
@ApiProperty({ description: '库存产品单价' })
|
||||||
previousUnitPrice: number;
|
unitPrice: number;
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'previous_amount',
|
|
||||||
type: 'decimal',
|
|
||||||
precision: 10,
|
|
||||||
scale: 2,
|
|
||||||
default: 0,
|
|
||||||
comment: '之前的金额'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '之前的金额' })
|
|
||||||
previousAmount: number;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'inventory_time',
|
|
||||||
type: 'date',
|
|
||||||
nullable: true,
|
|
||||||
comment: '入库时间'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '入库时间' })
|
|
||||||
inventoryTime: Date;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'inventory_quantity',
|
|
||||||
type: 'int',
|
|
||||||
default: 0,
|
|
||||||
comment: '入库数量'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '入库数量' })
|
|
||||||
inventoryQuantity: number;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'inventory_unit_price',
|
|
||||||
type: 'decimal',
|
|
||||||
precision: 10,
|
|
||||||
default: 0,
|
|
||||||
scale: 2,
|
|
||||||
comment: '入库单价'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '入库单价' })
|
|
||||||
inventoryUnitPrice: number;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'inventory_amount',
|
|
||||||
type: 'decimal',
|
|
||||||
precision: 10,
|
|
||||||
default: 0,
|
|
||||||
scale: 2,
|
|
||||||
comment: '入库金额'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '入库金额' })
|
|
||||||
inventoryAmount: number;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'out_time',
|
|
||||||
type: 'date',
|
|
||||||
nullable: true,
|
|
||||||
comment: '出库时间'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '出库时间' })
|
|
||||||
outime: Date;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'out_quantity',
|
|
||||||
type: 'int',
|
|
||||||
default: 0,
|
|
||||||
comment: '出库数量'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '出库数量' })
|
|
||||||
outQuantity: number;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'out_unit_price',
|
|
||||||
type: 'decimal',
|
|
||||||
precision: 10,
|
|
||||||
default: 0,
|
|
||||||
scale: 2,
|
|
||||||
comment: '出库单价'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '出库单价' })
|
|
||||||
outUnitPrice: number;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'out_amount',
|
|
||||||
type: 'decimal',
|
|
||||||
precision: 10,
|
|
||||||
default: 0,
|
|
||||||
scale: 2,
|
|
||||||
comment: '出库金额'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '出库金额' })
|
|
||||||
outAmount: number;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'current_inventory_quantity',
|
|
||||||
type: 'int',
|
|
||||||
default: 0,
|
|
||||||
comment: '现在的结存数量'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '现在的结存数量' })
|
|
||||||
currentInventoryQuantity: number;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'current_unit_price',
|
|
||||||
type: 'decimal',
|
|
||||||
precision: 10,
|
|
||||||
default: 0,
|
|
||||||
scale: 2,
|
|
||||||
comment: '现在的单价'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '现在的单价' })
|
|
||||||
currentUnitPrice: number;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
name: 'current_amount',
|
|
||||||
type: 'decimal',
|
|
||||||
precision: 10,
|
|
||||||
default: 0,
|
|
||||||
scale: 2,
|
|
||||||
comment: '现在的金额'
|
|
||||||
})
|
|
||||||
@ApiProperty({ description: '现在的金额' })
|
|
||||||
currentAmount: 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 })
|
@Column({ name: 'remark', type: 'varchar', length: 255, comment: '备注', nullable: true })
|
||||||
@ApiProperty({ description: '备注' })
|
@ApiProperty({ description: '备注' })
|
||||||
remark: string;
|
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: '是否删除' })
|
@Column({ name: 'is_delete', type: 'tinyint', default: 0, comment: '是否删除' })
|
||||||
@ApiProperty({ description: '删除状态:0未删除,1已删除' })
|
@ApiProperty({ description: '删除状态:0未删除,1已删除' })
|
||||||
isDelete: number;
|
isDelete: number;
|
||||||
|
|
|
@ -18,6 +18,9 @@ import { fieldSearch } from '~/shared/database/field-search';
|
||||||
import { groupBy, uniqBy } from 'lodash';
|
import { groupBy, uniqBy } from 'lodash';
|
||||||
import { MaterialsInOrOutEnum } from '~/constants/enum';
|
import { MaterialsInOrOutEnum } from '~/constants/enum';
|
||||||
import { ProjectEntity } from '../project/project.entity';
|
import { ProjectEntity } from '../project/project.entity';
|
||||||
|
import { calcNumber } from '~/utils';
|
||||||
|
import { BusinessException } from '~/common/exceptions/biz.exception';
|
||||||
|
import { ErrorEnum } from '~/constants/error-code.constant';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MaterialsInventoryService {
|
export class MaterialsInventoryService {
|
||||||
const;
|
const;
|
||||||
|
@ -52,7 +55,7 @@ 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', 'product.name', 'unit.label', 'company.name'])
|
||||||
.where(fieldSearch({ time }))
|
.where(fieldSearch({ time }))
|
||||||
.andWhere('mio.isDelete = 0');
|
.andWhere('mio.isDelete = 0');
|
||||||
|
|
||||||
|
@ -262,14 +265,14 @@ export class MaterialsInventoryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增
|
* 新增库存
|
||||||
*/
|
*/
|
||||||
async create(dto: MaterialsInventoryDto): Promise<void> {
|
async create(dto: MaterialsInventoryDto): Promise<void> {
|
||||||
await this.materialsInventoryRepository.insert(dto);
|
await this.materialsInventoryRepository.insert(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新
|
* 更新库存
|
||||||
*/
|
*/
|
||||||
async update(id: number, data: Partial<MaterialsInventoryUpdateDto>): Promise<void> {
|
async update(id: number, data: Partial<MaterialsInventoryUpdateDto>): Promise<void> {
|
||||||
await this.entityManager.transaction(async manager => {
|
await this.entityManager.transaction(async manager => {
|
||||||
|
@ -279,6 +282,79 @@ export class MaterialsInventoryService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品入库
|
||||||
|
*/
|
||||||
|
async inInventory(data: {
|
||||||
|
productId: number;
|
||||||
|
inQuantity: number;
|
||||||
|
unitPrice?: number;
|
||||||
|
}): Promise<void> {
|
||||||
|
const { productId, inQuantity, unitPrice } = data;
|
||||||
|
|
||||||
|
await this.entityManager.transaction(async manager => {
|
||||||
|
const exsitedInventory = await this.materialsInventoryRepository.findOne({
|
||||||
|
where: { productId },
|
||||||
|
lock: { mode: 'pessimistic_write' } // 开启悲观行锁,防止脏读和修改
|
||||||
|
});
|
||||||
|
|
||||||
|
// 若不存在库存,直接新增库存
|
||||||
|
if (!exsitedInventory) {
|
||||||
|
await this.entityManager.transaction(async manager => {
|
||||||
|
if (exsitedInventory) {
|
||||||
|
await manager.insert(MaterialsInventoryEntity, {
|
||||||
|
productId,
|
||||||
|
unitPrice,
|
||||||
|
quantity: inQuantity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 若存在库存,则库存增加
|
||||||
|
let { quantity, id } = exsitedInventory;
|
||||||
|
const newQuantity = calcNumber(quantity || 0, inQuantity || 0, 'add');
|
||||||
|
if (isNaN(newQuantity)) {
|
||||||
|
throw new Error('库存数量不合法');
|
||||||
|
}
|
||||||
|
await manager.update(MaterialsInventoryEntity, id, {
|
||||||
|
quantity: newQuantity
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品入库
|
||||||
|
*/
|
||||||
|
async outInventory(data: { productId: number; outQuantity: number }): Promise<void> {
|
||||||
|
const { productId, outQuantity } = data;
|
||||||
|
|
||||||
|
await this.entityManager.transaction(async manager => {
|
||||||
|
// 开启悲观行锁,防止脏读和修改
|
||||||
|
const inventory = await this.materialsInventoryRepository.findOne({
|
||||||
|
where: { productId },
|
||||||
|
lock: { mode: 'pessimistic_write' }
|
||||||
|
});
|
||||||
|
// 检查库存剩余
|
||||||
|
if (inventory.quantity < outQuantity) {
|
||||||
|
throw new BusinessException(ErrorEnum.INVENTORY_INSUFFICIENT);
|
||||||
|
}
|
||||||
|
// 库存充足,可以出库
|
||||||
|
let { quantity, id } = inventory;
|
||||||
|
const newQuantity = calcNumber(quantity || 0, outQuantity || 0, 'subtract');
|
||||||
|
if (isNaN(newQuantity)) {
|
||||||
|
throw new Error('库存数量不合法');
|
||||||
|
}
|
||||||
|
await manager.update(MaterialsInventoryEntity, id, {
|
||||||
|
quantity: newQuantity
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品出库
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除
|
* 删除
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { customAlphabet, nanoid } from 'nanoid';
|
import { customAlphabet, nanoid } from 'nanoid';
|
||||||
|
|
||||||
import { md5 } from './crypto.util';
|
import { md5 } from './crypto.util';
|
||||||
|
import { add, subtract, multiply, divide, bignumber, BigNumber } from 'mathjs';
|
||||||
|
|
||||||
export function getAvatar(mail: string | undefined) {
|
export function getAvatar(mail: string | undefined) {
|
||||||
if (!mail) return '';
|
if (!mail) return '';
|
||||||
|
@ -53,3 +54,25 @@ export const hashString = function (str, seed = 0) {
|
||||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 使用mathjs进行四则运算,不丢失精度
|
||||||
|
*/
|
||||||
|
export function calcNumber(
|
||||||
|
firstNumber: number,
|
||||||
|
secondNumber: number,
|
||||||
|
option: CalclateOption
|
||||||
|
): number {
|
||||||
|
switch (option) {
|
||||||
|
case 'add':
|
||||||
|
return add(bignumber(firstNumber), bignumber(secondNumber)).toNumber();
|
||||||
|
case 'subtract':
|
||||||
|
return subtract(bignumber(firstNumber), bignumber(secondNumber)).toNumber();
|
||||||
|
// case 'multiply':
|
||||||
|
// return multiply(bignumber(firstNumber), bignumber(secondNumber));
|
||||||
|
// case 'divide':
|
||||||
|
// return divide(bignumber(firstNumber), bignumber(secondNumber));
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type CalclateOption = 'add' | 'subtract' | 'multiply' | 'divide';
|
||||||
|
|
Loading…
Reference in New Issue