667 lines
22 KiB
TypeScript
667 lines
22 KiB
TypeScript
import { Provide } from '@midwayjs/core';
|
|
import { Between, Like, Repository, LessThan, MoreThan, In } from 'typeorm';
|
|
import { Stock } from '../entity/stock.entity';
|
|
import { StockRecord } from '../entity/stock_record.entity';
|
|
import { paginate } from '../utils/paginate.util';
|
|
import { Product } from '../entity/product.entity';
|
|
import {
|
|
CreatePurchaseOrderDTO,
|
|
CreateStockPointDTO,
|
|
QueryPointDTO,
|
|
QueryPurchaseOrderDTO,
|
|
QueryStockDTO,
|
|
QueryStockRecordDTO,
|
|
UpdatePurchaseOrderDTO,
|
|
UpdateStockDTO,
|
|
UpdateStockPointDTO,
|
|
} from '../dto/stock.dto';
|
|
import { StockPoint } from '../entity/stock_point.entity';
|
|
import { PurchaseOrder } from '../entity/purchase_order.entity';
|
|
import { PurchaseOrderItem } from '../entity/purchase_order_item.entity';
|
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
|
import {
|
|
PurchaseOrderStatus,
|
|
StockRecordOperationType,
|
|
} from '../enums/base.enum';
|
|
import { User } from '../entity/user.entity';
|
|
import dayjs = require('dayjs');
|
|
import { Transfer } from '../entity/transfer.entity';
|
|
import { TransferItem } from '../entity/transfer_item.entity';
|
|
import { Area } from '../entity/area.entity';
|
|
|
|
@Provide()
|
|
export class StockService {
|
|
@InjectEntityModel(StockPoint)
|
|
stockPointModel: Repository<StockPoint>;
|
|
|
|
@InjectEntityModel(Stock)
|
|
stockModel: Repository<Stock>;
|
|
|
|
@InjectEntityModel(StockRecord)
|
|
stockRecordModel: Repository<StockRecord>;
|
|
|
|
@InjectEntityModel(PurchaseOrder)
|
|
purchaseOrderModel: Repository<PurchaseOrder>;
|
|
|
|
@InjectEntityModel(PurchaseOrderItem)
|
|
purchaseOrderItemModel: Repository<PurchaseOrderItem>;
|
|
|
|
@InjectEntityModel(Transfer)
|
|
transferModel: Repository<Transfer>;
|
|
|
|
@InjectEntityModel(TransferItem)
|
|
transferItemModel: Repository<TransferItem>;
|
|
|
|
@InjectEntityModel(Area)
|
|
areaModel: Repository<Area>;
|
|
|
|
async createStockPoint(data: CreateStockPointDTO) {
|
|
const { areas: areaCodes, ...restData } = data;
|
|
const stockPoint = new StockPoint();
|
|
Object.assign(stockPoint, restData);
|
|
|
|
if (areaCodes && areaCodes.length > 0) {
|
|
const areas = await this.areaModel.findBy({ code: In(areaCodes) });
|
|
stockPoint.areas = areas;
|
|
} else {
|
|
stockPoint.areas = [];
|
|
}
|
|
|
|
await this.stockPointModel.save(stockPoint);
|
|
}
|
|
|
|
async updateStockPoint(id: number, data: UpdateStockPointDTO) {
|
|
const { areas: areaCodes, ...restData } = data;
|
|
const pointToUpdate = await this.stockPointModel.findOneBy({ id });
|
|
if (!pointToUpdate) {
|
|
throw new Error(`仓库点 ID ${id} 不存在`);
|
|
}
|
|
|
|
Object.assign(pointToUpdate, restData);
|
|
|
|
if (areaCodes !== undefined) {
|
|
if (areaCodes.length > 0) {
|
|
const areas = await this.areaModel.findBy({ code: In(areaCodes) });
|
|
pointToUpdate.areas = areas;
|
|
} else {
|
|
pointToUpdate.areas = [];
|
|
}
|
|
}
|
|
|
|
await this.stockPointModel.save(pointToUpdate);
|
|
}
|
|
|
|
async getStockPoints(query: QueryPointDTO) {
|
|
const { current = 1, pageSize = 10 } = query;
|
|
return await paginate(this.stockPointModel, {
|
|
pagination: { current, pageSize },
|
|
relations: ['areas'],
|
|
});
|
|
}
|
|
|
|
async getAllStockPoints(): Promise<StockPoint[]> {
|
|
return await this.stockPointModel.find({ relations: ['areas'] });
|
|
}
|
|
|
|
async delStockPoints(id: number) {
|
|
const point = await this.stockPointModel.findOneBy({ id });
|
|
if (!point) {
|
|
throw new Error(`库存点 ID ${id} 不存在`);
|
|
}
|
|
await this.stockRecordModel.delete({ stockPointId: id });
|
|
await this.stockPointModel.softDelete(id);
|
|
}
|
|
|
|
async createPurchaseOrder(data: CreatePurchaseOrderDTO) {
|
|
const { stockPointId, expectedArrivalTime, status, items, note } = data;
|
|
const now = dayjs().format('YYYY-MM-DD');
|
|
const count = await this.purchaseOrderModel.count({
|
|
where: {
|
|
createdAt: Between(
|
|
dayjs(`${now} 00:00:00`).toDate(),
|
|
dayjs(`${now} 23:59:59`).toDate()
|
|
),
|
|
},
|
|
});
|
|
const orderNumber = `${now.replace(/-/g, '')}P0${count + 1}`;
|
|
const purchaseOrder = await this.purchaseOrderModel.save({
|
|
stockPointId,
|
|
orderNumber,
|
|
expectedArrivalTime,
|
|
status,
|
|
note,
|
|
});
|
|
items.forEach(item => (item.purchaseOrderId = purchaseOrder.id));
|
|
await this.purchaseOrderItemModel.save(items);
|
|
}
|
|
|
|
async updatePurchaseOrder(id: number, data: UpdatePurchaseOrderDTO) {
|
|
const purchaseOrder = await this.purchaseOrderModel.findOneBy({ id });
|
|
if (!purchaseOrder) throw new Error(`采购订单 ID ${id} 不存在`);
|
|
if (purchaseOrder.status === 'received')
|
|
throw new Error(`采购订单 ID ${id} 已到达,无法修改`);
|
|
const { stockPointId, expectedArrivalTime, status, items, note } = data;
|
|
purchaseOrder.stockPointId = stockPointId;
|
|
purchaseOrder.expectedArrivalTime = expectedArrivalTime;
|
|
purchaseOrder.status = status;
|
|
purchaseOrder.note = note;
|
|
this.purchaseOrderModel.save(purchaseOrder);
|
|
|
|
const dbItems = await this.purchaseOrderItemModel.find({
|
|
where: { purchaseOrderId: id },
|
|
});
|
|
const ids = new Set(items.map(v => String(v.id)));
|
|
const toDelete = dbItems.filter(
|
|
dbVariation => !ids.has(String(dbVariation.id))
|
|
);
|
|
if (toDelete.length > 0) {
|
|
const idsToDelete = toDelete.map(v => v.id);
|
|
await this.purchaseOrderItemModel.delete(idsToDelete);
|
|
}
|
|
for (const item of items) {
|
|
if (item.id) {
|
|
await this.purchaseOrderItemModel.update(item.id, item);
|
|
} else {
|
|
item.purchaseOrderId = id;
|
|
await this.purchaseOrderItemModel.save(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
async getPurchaseOrders(query: QueryPurchaseOrderDTO) {
|
|
const { current = 1, pageSize = 10, orderNumber, stockPointId } = query;
|
|
|
|
const where: any = {};
|
|
if (orderNumber) where.orderNumber = Like(`%${orderNumber}%`);
|
|
if (stockPointId) where.stockPointId = Like(`%${stockPointId}%`);
|
|
|
|
return await paginate(
|
|
this.purchaseOrderModel
|
|
.createQueryBuilder('purchase_order')
|
|
.leftJoinAndSelect(
|
|
StockPoint,
|
|
'stock_point',
|
|
'purchase_order.stockPointId = stock_point.id'
|
|
)
|
|
.leftJoin(
|
|
qb =>
|
|
qb
|
|
.select([
|
|
'poi.purchaseOrderId AS purchaseOrderId',
|
|
"JSON_ARRAYAGG(JSON_OBJECT('id', poi.id, 'name', poi.name,'sku', poi.sku, 'quantity', poi.quantity, 'price', poi.price)) AS items",
|
|
])
|
|
.from(PurchaseOrderItem, 'poi')
|
|
.groupBy('poi.purchaseOrderId'),
|
|
'items',
|
|
'items.purchaseOrderId = purchase_order.id'
|
|
)
|
|
.select([
|
|
'purchase_order.*',
|
|
'stock_point.name as stockPointName',
|
|
'items.items',
|
|
])
|
|
.where(where)
|
|
.orderBy('createdAt', 'DESC'),
|
|
{
|
|
pagination: { current, pageSize },
|
|
}
|
|
);
|
|
}
|
|
|
|
// 检查指定 SKU 是否在任一仓库有库存(数量大于 0)
|
|
async hasStockBySku(sku: string): Promise<boolean> {
|
|
const count = await this.stockModel
|
|
.createQueryBuilder('stock')
|
|
.where('stock.sku = :sku', { sku })
|
|
.andWhere('stock.quantity > 0')
|
|
.getCount();
|
|
return count > 0;
|
|
}
|
|
|
|
async delPurchaseOrder(id: number) {
|
|
const purchaseOrder = await this.purchaseOrderModel.findOneBy({ id });
|
|
if (!purchaseOrder) throw new Error(`采购订单 ID ${id} 不存在`);
|
|
if (purchaseOrder.status === 'received')
|
|
throw new Error(`采购订单 ID ${id} 已到达,无法删除`);
|
|
await this.purchaseOrderItemModel.delete({ purchaseOrderId: id });
|
|
await this.purchaseOrderModel.delete({ id });
|
|
}
|
|
|
|
async receivePurchaseOrder(id: number, userId: number) {
|
|
const purchaseOrder = await this.purchaseOrderModel.findOneBy({ id });
|
|
if (!purchaseOrder) throw new Error(`采购订单 ID ${id} 不存在`);
|
|
if (purchaseOrder.status === 'received')
|
|
throw new Error(`采购订单 ID ${id} 已到达,不要重复操作`);
|
|
const items = await this.purchaseOrderItemModel.find({
|
|
where: { purchaseOrderId: id },
|
|
});
|
|
for (const item of items) {
|
|
const updateStock = new UpdateStockDTO();
|
|
updateStock.stockPointId = purchaseOrder.stockPointId;
|
|
updateStock.sku = item.sku;
|
|
updateStock.quantityChange = item.quantity;
|
|
updateStock.operationType = StockRecordOperationType.IN;
|
|
updateStock.operatorId = userId;
|
|
updateStock.note = '采购入库';
|
|
await this.updateStock(updateStock);
|
|
}
|
|
purchaseOrder.status = PurchaseOrderStatus.RECEIVED;
|
|
await this.purchaseOrderModel.save(purchaseOrder);
|
|
}
|
|
|
|
async getPurchaseOrder(orderNumber: string) {
|
|
const sql = `
|
|
SELECT poi.* FROM purchase_order po
|
|
left join purchase_order_item poi on po.id = poi.purchaseOrderId
|
|
WHERE po.orderNumber = '${orderNumber}'
|
|
`
|
|
const data = await this.stockModel.query(sql);
|
|
return data;
|
|
}
|
|
|
|
// 获取库存列表
|
|
async getStocks(query: QueryStockDTO) {
|
|
const { current = 1, pageSize = 10, name, sku } = query;
|
|
const nameKeywords = name
|
|
? name.split(' ').filter(Boolean)
|
|
: [];
|
|
|
|
let queryBuilder = this.stockModel
|
|
.createQueryBuilder('stock')
|
|
.select([
|
|
// 'stock.id as id',
|
|
'stock.sku as sku',
|
|
'product.name as name',
|
|
'product.nameCn as nameCn',
|
|
'JSON_ARRAYAGG(JSON_OBJECT("id", stock.stockPointId, "quantity", stock.quantity)) as stockPoint',
|
|
'MIN(stock.updatedAt) as updatedAt',
|
|
'MAX(stock.createdAt) as createdAt',
|
|
])
|
|
.leftJoin(Product, 'product', 'product.sku = stock.sku')
|
|
.groupBy('stock.sku')
|
|
.addGroupBy('product.name')
|
|
.addGroupBy('product.nameCn');
|
|
let totalQueryBuilder = this.stockModel
|
|
.createQueryBuilder('stock')
|
|
.select('COUNT(DISTINCT stock.sku)', 'count')
|
|
.leftJoin(Product, 'product', 'product.sku = stock.sku');
|
|
if (sku || nameKeywords.length) {
|
|
const conditions = [];
|
|
if (sku) {
|
|
conditions.push(`stock.sku LIKE :sku`);
|
|
}
|
|
if (nameKeywords.length) {
|
|
nameKeywords.forEach((name, index) => {
|
|
conditions.push(`product.name LIKE :name${index}`);
|
|
});
|
|
}
|
|
const whereClause = conditions.join(' OR ');
|
|
queryBuilder.andWhere(`(${whereClause})`, {
|
|
sku: `%${sku}%`,
|
|
...nameKeywords.reduce((acc, name, index) => ({ ...acc, [`name${index}`]: `%${name}%` }), {}),
|
|
});
|
|
totalQueryBuilder.andWhere(`(${whereClause})`, {
|
|
sku: `%${sku}%`,
|
|
...nameKeywords.reduce((acc, name, index) => ({ ...acc, [`name${index}`]: `%${name}%` }), {}),
|
|
});
|
|
}
|
|
if (query.order) {
|
|
const sortFieldMap: Record<string, string> = {
|
|
name: 'product.name',
|
|
sku: 'stock.sku',
|
|
updatedAt: 'updatedAt',
|
|
createdAt: 'createdAt',
|
|
};
|
|
let isFirstSort = true;
|
|
Object.entries(query.order).forEach(([field, direction]) => {
|
|
const orderDirection = direction === 'asc' ? 'ASC' : 'DESC';
|
|
if (field.startsWith('point_')) {
|
|
const pointId = field.split('_')[1];
|
|
const sortExpr = `SUM(CASE WHEN stock.stockPointId = :pointId THEN stock.quantity ELSE 0 END)`;
|
|
const sortAlias = `pointSort_${pointId}`;
|
|
queryBuilder
|
|
.addSelect(sortExpr, sortAlias)
|
|
.setParameter('pointId', Number(pointId));
|
|
if (isFirstSort) {
|
|
queryBuilder.orderBy(sortAlias, orderDirection);
|
|
isFirstSort = false;
|
|
} else {
|
|
queryBuilder.addOrderBy(sortAlias, orderDirection);
|
|
}
|
|
} else {
|
|
const actualSortField = sortFieldMap[field] || field;
|
|
if (isFirstSort) {
|
|
queryBuilder.orderBy(actualSortField, orderDirection);
|
|
isFirstSort = false;
|
|
} else {
|
|
queryBuilder.addOrderBy(actualSortField, orderDirection);
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
// 默认按产品名称排序
|
|
queryBuilder.orderBy('product.name', 'ASC');
|
|
}
|
|
|
|
const items = await queryBuilder
|
|
.offset((current - 1) * pageSize)
|
|
.limit(pageSize)
|
|
.getRawMany();
|
|
const totalResult = await totalQueryBuilder.getRawOne();
|
|
const total = parseInt(totalResult.count, 10);
|
|
|
|
const transfer = await this.transferModel
|
|
.createQueryBuilder('t')
|
|
.select(['ti.sku as sku', 'SUM(ti.quantity) as quantity'])
|
|
.leftJoin(TransferItem, 'ti', 'ti.transferId = t.id')
|
|
.where('!t.isArrived and !t.isCancel and !t.isLost')
|
|
.groupBy('ti.sku')
|
|
.getRawMany();
|
|
|
|
for (const item of items) {
|
|
item.inTransitQuantity =
|
|
transfer.find(t => t.sku === item.sku)?.quantity || 0;
|
|
}
|
|
|
|
return {
|
|
items,
|
|
total,
|
|
current,
|
|
pageSize,
|
|
};
|
|
}
|
|
|
|
async getStocksBySkus(skus: string[]) {
|
|
if (!skus || skus.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const stocks = await this.stockModel
|
|
.createQueryBuilder('stock')
|
|
.select('stock.sku', 'sku')
|
|
.addSelect('SUM(stock.quantity)', 'totalQuantity')
|
|
.where('stock.sku IN (:...skus)', { skus })
|
|
.groupBy('stock.sku')
|
|
.getRawMany();
|
|
|
|
return stocks;
|
|
}
|
|
|
|
// 更新库存
|
|
async updateStock(data: UpdateStockDTO) {
|
|
const {
|
|
stockPointId,
|
|
sku,
|
|
quantityChange,
|
|
operationType,
|
|
operatorId,
|
|
note,
|
|
} = data;
|
|
|
|
const stock = await this.stockModel.findOneBy({
|
|
stockPointId,
|
|
sku,
|
|
});
|
|
if (!stock) {
|
|
// 如果库存不存在,则直接新增
|
|
const newStock = this.stockModel.create({
|
|
stockPointId,
|
|
sku,
|
|
quantity: operationType === 'in' ? quantityChange : -quantityChange,
|
|
});
|
|
await this.stockModel.save(newStock);
|
|
} else {
|
|
// 更新库存
|
|
stock.quantity +=
|
|
operationType === 'in' ? quantityChange : -quantityChange;
|
|
// if (stock.quantity < 0) {
|
|
// throw new Error('库存不足,无法完成操作');
|
|
// }
|
|
await this.stockModel.save(stock);
|
|
}
|
|
|
|
// 记录库存变更日志
|
|
const stockRecord = this.stockRecordModel.create({
|
|
stockPointId,
|
|
sku,
|
|
operationType,
|
|
quantityChange,
|
|
operatorId,
|
|
note,
|
|
});
|
|
await this.stockRecordModel.save(stockRecord);
|
|
}
|
|
|
|
// 获取库存记录
|
|
async getStockRecords(query: QueryStockRecordDTO) {
|
|
const {
|
|
current = 1,
|
|
pageSize = 10,
|
|
stockPointId,
|
|
sku,
|
|
name,
|
|
operationType,
|
|
startDate,
|
|
endDate,
|
|
} = query;
|
|
|
|
const where: any = {};
|
|
if (stockPointId) where.stockPointId = stockPointId;
|
|
if (sku) where.sku = sku;
|
|
if (operationType) where.operationType = operationType;
|
|
if (startDate) where.createdAt = MoreThan(startDate);
|
|
if (endDate) where.createdAt = LessThan(endDate);
|
|
if (startDate && endDate) where.createdAt = Between(startDate, endDate);
|
|
const queryBuilder = this.stockRecordModel
|
|
.createQueryBuilder('stock_record')
|
|
.leftJoin(Product, 'product', 'product.sku = stock_record.sku')
|
|
.leftJoin(User, 'user', 'stock_record.operatorId = user.id')
|
|
.leftJoin(StockPoint, 'sp', 'sp.id = stock_record.stockPointId')
|
|
.select([
|
|
'stock_record.*',
|
|
'product.name as productName',
|
|
'user.username as operatorName',
|
|
'sp.name as stockPointName',
|
|
])
|
|
.where(where);
|
|
if (name)
|
|
queryBuilder.andWhere('product.name LIKE :name', {
|
|
name: `%${name}%`,
|
|
});
|
|
const items = await queryBuilder
|
|
.orderBy('stock_record.createdAt', 'DESC')
|
|
.skip((current - 1) * pageSize)
|
|
.take(pageSize)
|
|
.getRawMany();
|
|
const total = await queryBuilder.getCount();
|
|
return {
|
|
items,
|
|
total,
|
|
current,
|
|
pageSize,
|
|
};
|
|
}
|
|
|
|
async createTransfer(data: Record<string, any>, userId: number) {
|
|
const { sourceStockPointId, destStockPointId, sendAt, items, note } = data;
|
|
// for (const item of items) {
|
|
// const stock = await this.stockModel.findOneBy({
|
|
// stockPointId: sourceStockPointId,
|
|
// sku: item.sku,
|
|
// });
|
|
// if (!stock || stock.quantity < item.quantity)
|
|
// throw new Error(`${item.productName} 库存不足`);
|
|
// }
|
|
const now = dayjs().format('YYYY-MM-DD');
|
|
const count = await this.transferModel.count({
|
|
where: {
|
|
createdAt: Between(
|
|
dayjs(`${now} 00:00:00`).toDate(),
|
|
dayjs(`${now} 23:59:59`).toDate()
|
|
),
|
|
},
|
|
});
|
|
const orderNumber = `${now.replace(/-/g, '')}P0${count + 1}`;
|
|
const transfer = await this.transferModel.save({
|
|
sourceStockPointId,
|
|
destStockPointId,
|
|
orderNumber,
|
|
sendAt,
|
|
note,
|
|
});
|
|
for (const item of items) {
|
|
item.transferId = transfer.id;
|
|
const updateStock = new UpdateStockDTO();
|
|
updateStock.stockPointId = sourceStockPointId;
|
|
updateStock.sku = item.sku;
|
|
updateStock.quantityChange = item.quantity;
|
|
updateStock.operationType = StockRecordOperationType.OUT;
|
|
updateStock.operatorId = userId;
|
|
updateStock.note = `调拨${transfer.orderNumber} 出库`;
|
|
await this.updateStock(updateStock);
|
|
}
|
|
await this.transferItemModel.save(items);
|
|
}
|
|
|
|
async getTransfers(query: Record<string, any>) {
|
|
const {
|
|
current = 1,
|
|
pageSize = 10,
|
|
orderNumber,
|
|
sourceStockPointId,
|
|
destStockPointId,
|
|
} = query;
|
|
|
|
const where: any = {};
|
|
if (orderNumber) where.orderNumber = Like(`%${orderNumber}%`);
|
|
if (sourceStockPointId) where.sourceStockPointId = sourceStockPointId;
|
|
if (destStockPointId) where.destStockPointId = destStockPointId;
|
|
|
|
return await paginate(
|
|
this.transferModel
|
|
.createQueryBuilder('t')
|
|
.leftJoinAndSelect(StockPoint, 'sp', 't.sourceStockPointId = sp.id')
|
|
.leftJoinAndSelect(StockPoint, 'sp1', 't.destStockPointId = sp1.id')
|
|
.leftJoin(
|
|
qb =>
|
|
qb
|
|
.select([
|
|
'ti.transferId AS transferId',
|
|
"JSON_ARRAYAGG(JSON_OBJECT('id', ti.id, 'productName', ti.productName,'sku', ti.sku, 'quantity', ti.quantity)) AS items",
|
|
])
|
|
.from(TransferItem, 'ti')
|
|
.groupBy('ti.transferId'),
|
|
'items',
|
|
'items.transferId = t.id'
|
|
)
|
|
.select([
|
|
't.*',
|
|
'sp.name as sourceStockPointName',
|
|
'sp1.name as destStockPointName',
|
|
'items.items',
|
|
])
|
|
.where(where)
|
|
.orderBy('createdAt', 'DESC'),
|
|
{
|
|
pagination: { current, pageSize },
|
|
}
|
|
);
|
|
}
|
|
|
|
async cancelTransfer(id: number, userId: number) {
|
|
const transfer = await this.transferModel.findOneBy({ id });
|
|
if (!transfer) throw new Error(`调拨 ID ${id} 不存在`);
|
|
if (transfer.isArrived) throw new Error(`调拨 ID ${id} 已到达,无法取消`);
|
|
const items = await this.transferItemModel.find({
|
|
where: { transferId: id },
|
|
});
|
|
for (const item of items) {
|
|
const updateStock = new UpdateStockDTO();
|
|
updateStock.stockPointId = transfer.sourceStockPointId;
|
|
updateStock.sku = item.sku;
|
|
updateStock.quantityChange = item.quantity;
|
|
updateStock.operationType = StockRecordOperationType.IN;
|
|
updateStock.operatorId = userId;
|
|
updateStock.note = `取消调拨${transfer.orderNumber} 入库`;
|
|
await this.updateStock(updateStock);
|
|
}
|
|
transfer.isCancel = true;
|
|
await this.transferModel.save(transfer);
|
|
}
|
|
|
|
async receiveTransfer(id: number, userId: number) {
|
|
const transfer = await this.transferModel.findOneBy({ id });
|
|
if (!transfer) throw new Error(`调拨 ID ${id} 不存在`);
|
|
if (transfer.isCancel) throw new Error(`调拨 ID ${id} 已取消`);
|
|
if (transfer.isArrived)
|
|
throw new Error(`调拨 ID ${id} 已到达,不要重复操作`);
|
|
const items = await this.transferItemModel.find({
|
|
where: { transferId: id },
|
|
});
|
|
for (const item of items) {
|
|
const updateStock = new UpdateStockDTO();
|
|
updateStock.stockPointId = transfer.destStockPointId;
|
|
updateStock.sku = item.sku;
|
|
updateStock.quantityChange = item.quantity;
|
|
updateStock.operationType = StockRecordOperationType.IN;
|
|
updateStock.operatorId = userId;
|
|
updateStock.note = `调拨${transfer.orderNumber} 入库`;
|
|
await this.updateStock(updateStock);
|
|
}
|
|
transfer.isArrived = true;
|
|
transfer.arriveAt = new Date();
|
|
await this.transferModel.save(transfer);
|
|
}
|
|
async lostTransfer(id: number) {
|
|
const transfer = await this.transferModel.findOneBy({ id });
|
|
if (!transfer) throw new Error(`调拨 ID ${id} 不存在`);
|
|
if (transfer.isCancel) throw new Error(`调拨 ID ${id} 已取消`);
|
|
if (transfer.isArrived)
|
|
throw new Error(`调拨 ID ${id} 已到达,不要重复操作`);
|
|
transfer.isLost = true;
|
|
await this.transferModel.save(transfer);
|
|
}
|
|
|
|
async updateTransfer(id: number, data: Record<string, any>, userId: number) {
|
|
const { sourceStockPointId, destStockPointId, items, note } = data;
|
|
const transfer = await this.transferModel.findOneBy({ id });
|
|
if (!transfer) throw new Error(`调拨单 ID ${id} 不存在`);
|
|
if (transfer.isCancel) throw new Error(`调拨单 ID ${id} 已取消`);
|
|
if (transfer.isArrived) throw new Error(`调拨单 ID ${id} 已到达`);
|
|
|
|
const dbItems = await this.transferItemModel.find({
|
|
where: { transferId: id },
|
|
});
|
|
for (const item of dbItems) {
|
|
item.transferId = transfer.id;
|
|
const updateStock = new UpdateStockDTO();
|
|
updateStock.stockPointId = sourceStockPointId;
|
|
updateStock.sku = item.sku;
|
|
updateStock.quantityChange = item.quantity;
|
|
updateStock.operationType = StockRecordOperationType.IN;
|
|
updateStock.operatorId = userId;
|
|
updateStock.note = `调拨调整 ${transfer.orderNumber} 入库`;
|
|
await this.updateStock(updateStock);
|
|
}
|
|
await this.transferItemModel.delete(dbItems.map(v => v.id));
|
|
|
|
for (const item of items) {
|
|
item.transferId = transfer.id;
|
|
const updateStock = new UpdateStockDTO();
|
|
updateStock.stockPointId = sourceStockPointId;
|
|
updateStock.sku = item.sku;
|
|
updateStock.quantityChange = item.quantity;
|
|
updateStock.operationType = StockRecordOperationType.OUT;
|
|
updateStock.operatorId = userId;
|
|
updateStock.note = `调拨调整${transfer.orderNumber} 出库`;
|
|
await this.updateStock(updateStock);
|
|
await this.transferItemModel.save(item);
|
|
}
|
|
|
|
transfer.sourceStockPointId = sourceStockPointId;
|
|
transfer.destStockPointId = destStockPointId;
|
|
transfer.note = note;
|
|
await this.transferModel.save(transfer);
|
|
}
|
|
}
|