|
|
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
|
|
SaasCrudCaller,
|
|
|
|
|
SaasCrudService,
|
|
|
|
|
SaasCrudServiceBase,
|
|
|
|
|
SaasInventoryReservation,
|
|
|
|
|
saasWorkflowBusinessId,
|
|
|
|
|
} from '@cool-midway/module-runtime';
|
|
|
|
|
import {
|
|
|
|
|
Actions,
|
|
|
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
|
|
Events,
|
|
|
|
|
type GoodsBatchInfoInput,
|
|
|
|
|
type GoodsBatchInfoOutput,
|
|
|
|
|
type GoodsCrudBeforeChange,
|
|
|
|
|
type GoodsInfoOutput,
|
|
|
|
|
type GoodsReleaseStockInput,
|
|
|
|
|
type GoodsReleaseStockOutput,
|
|
|
|
|
@@ -24,12 +27,25 @@ import {
|
|
|
|
|
@Provide()
|
|
|
|
|
@SaasCrudService(Crud.Goods)
|
|
|
|
|
export class GoodsService extends SaasCrudServiceBase<typeof Crud.Goods> {
|
|
|
|
|
@Inject() private readonly inventory!: SaasInventoryReservation;
|
|
|
|
|
@InjectSaasCrud(Crud.Goods)
|
|
|
|
|
private readonly goods!: SaasCrudCaller<typeof Crud.Goods>;
|
|
|
|
|
|
|
|
|
|
@Inject()
|
|
|
|
|
private readonly saasActionContext!: SaasActionContext;
|
|
|
|
|
|
|
|
|
|
protected async modifyBefore(change: GoodsCrudBeforeChange): Promise<void> {
|
|
|
|
|
if (change.operation === 'delete') {
|
|
|
|
|
await this.inventory.assertNoActiveReservations(change.input.id);
|
|
|
|
|
}
|
|
|
|
|
if (change.operation === 'update' && Object.prototype.hasOwnProperty.call(change.input, 'stock')) {
|
|
|
|
|
const caller = this.saasActionContext.managedCrudCaller;
|
|
|
|
|
if (caller?.kind !== 'business' || !['goods.reserveStock','goods.releaseStock'].includes(caller.actionName) || this.saasActionContext.workflow?.coordinatorModule !== 'order') {
|
|
|
|
|
throw new SaasBusinessReject('STOCK_COMMAND_REQUIRED','现有商品库存只能通过受管库存命令变更');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SaasAction(Actions.GoodsBatchInfo)
|
|
|
|
|
async batchInfo(
|
|
|
|
|
input: GoodsBatchInfoInput
|
|
|
|
|
@@ -46,7 +62,10 @@ export class GoodsService extends SaasCrudServiceBase<typeof Crud.Goods> {
|
|
|
|
|
async reserveStock(
|
|
|
|
|
input: GoodsReserveStockInput
|
|
|
|
|
): Promise<GoodsReserveStockOutput> {
|
|
|
|
|
const goods = await this.requireGoods(input.goodsId);
|
|
|
|
|
const workflow = this.saasActionContext.workflow;
|
|
|
|
|
if (!workflow || workflow.coordinatorModule !== 'order' || workflow.sagaType !== 'order.create@1') throw new SaasBusinessReject('STOCK_RESERVATION_CONFLICT','库存扣减需要订单创建工作流');
|
|
|
|
|
return this.inventory.reserve({reservationId:workflow.sagaId,goodsId:input.goodsId,quantity:input.quantity},async () => {
|
|
|
|
|
const goods = await this.requireGoods(input.goodsId, true);
|
|
|
|
|
if (goods.status !== 1) {
|
|
|
|
|
throw new SaasBusinessReject(
|
|
|
|
|
'GOODS_UNAVAILABLE',
|
|
|
|
|
@@ -69,14 +88,20 @@ export class GoodsService extends SaasCrudServiceBase<typeof Crud.Goods> {
|
|
|
|
|
});
|
|
|
|
|
await this.updateStock(input.goodsId, remainingStock);
|
|
|
|
|
return { goodsId: input.goodsId, remainingStock };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SaasAction(Actions.GoodsReleaseStock)
|
|
|
|
|
async releaseStock(
|
|
|
|
|
input: GoodsReleaseStockInput
|
|
|
|
|
): Promise<GoodsReleaseStockOutput> {
|
|
|
|
|
const goods = await this.requireGoods(input.goodsId);
|
|
|
|
|
const workflow = this.saasActionContext.workflow;
|
|
|
|
|
const reservationId = saasWorkflowBusinessId('order','order.create@1',input.orderNo);
|
|
|
|
|
if (!workflow || workflow.coordinatorModule !== 'order' || !['order.create@1','order.cancel@1'].includes(workflow.sagaType) || (workflow.sagaType === 'order.create@1' && workflow.sagaId !== reservationId)) throw new SaasBusinessReject('STOCK_RESERVATION_CONFLICT','库存释放需要匹配的订单工作流');
|
|
|
|
|
return this.inventory.release({reservationId,goodsId:input.goodsId,quantity:input.quantity},async () => {
|
|
|
|
|
const goods = await this.requireGoods(input.goodsId, true);
|
|
|
|
|
const currentStock = goods.stock + input.quantity;
|
|
|
|
|
if (!Number.isSafeInteger(currentStock)) throw new SaasBusinessReject('STOCK_RESERVATION_CONFLICT','库存数量溢出');
|
|
|
|
|
|
|
|
|
|
this.saasActionContext.raise(Events.DemoGoodsStockReserved, {
|
|
|
|
|
goodsId: input.goodsId,
|
|
|
|
|
@@ -85,6 +110,7 @@ export class GoodsService extends SaasCrudServiceBase<typeof Crud.Goods> {
|
|
|
|
|
});
|
|
|
|
|
await this.updateStock(input.goodsId, currentStock);
|
|
|
|
|
return { goodsId: input.goodsId, currentStock };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async findGoods(goodsId: number): Promise<GoodsInfoOutput | null> {
|
|
|
|
|
@@ -95,8 +121,10 @@ export class GoodsService extends SaasCrudServiceBase<typeof Crud.Goods> {
|
|
|
|
|
return rows[0] || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async requireGoods(goodsId: number): Promise<GoodsInfoOutput> {
|
|
|
|
|
const goods = await this.findGoods(goodsId);
|
|
|
|
|
private async requireGoods(goodsId: number, lock = false): Promise<GoodsInfoOutput> {
|
|
|
|
|
const goods = lock
|
|
|
|
|
? await this.goods.lock({ id: String(goodsId) })
|
|
|
|
|
: await this.findGoods(goodsId);
|
|
|
|
|
if (!goods) {
|
|
|
|
|
throw new SaasBusinessReject(
|
|
|
|
|
'GOODS_NOT_FOUND',
|
|
|
|
|
|