feat: implement order candidate 1.0.0-rc.1
This commit is contained in:
8
.cool-saas-candidate.json
Normal file
8
.cool-saas-candidate.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"moduleCode": "order",
|
||||||
|
"candidateVersion": "1.0.0-rc.1",
|
||||||
|
"buildRevision": 1,
|
||||||
|
"buildChecksum": "5cf032fdac2b0403fbcad8b44b326a29141ba556bbbb9710ac9329b5d7c031dd",
|
||||||
|
"schemaInputChecksum": "1b3eec01e5b04880166e20e8e6df70b79f227129ea9165e38cfbb925be3c2d23",
|
||||||
|
"branchName": "release/1.0.0-rc.1"
|
||||||
|
}
|
||||||
1
src/saas/generated/index.ts
Normal file
1
src/saas/generated/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './order';
|
||||||
1176
src/saas/generated/order/contracts.ts
Normal file
1176
src/saas/generated/order/contracts.ts
Normal file
File diff suppressed because it is too large
Load Diff
5
src/saas/generated/order/index.ts
Normal file
5
src/saas/generated/order/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export * from './models';
|
||||||
|
export * from './contracts';
|
||||||
|
export * from './saas-actions';
|
||||||
|
export * from './saas-events';
|
||||||
|
export * from './module';
|
||||||
100
src/saas/generated/order/models.ts
Normal file
100
src/saas/generated/order/models.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// Generated by cool saas-model generate. DO NOT EDIT.
|
||||||
|
import {
|
||||||
|
SaasReadModel,
|
||||||
|
SaasTableModel,
|
||||||
|
defineSaasReadModel,
|
||||||
|
defineSaasTableModel
|
||||||
|
} from '@cool-midway/module-runtime';
|
||||||
|
|
||||||
|
export interface OrdersRow extends Record<string, unknown> {
|
||||||
|
id: string;
|
||||||
|
order_no: string;
|
||||||
|
goods_id: string;
|
||||||
|
quantity: number;
|
||||||
|
unit_price: string;
|
||||||
|
amount: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Orders extends SaasTableModel<OrdersRow> {
|
||||||
|
static readonly definition = defineSaasTableModel({
|
||||||
|
"kind": "table",
|
||||||
|
"moduleCode": "order",
|
||||||
|
"revision": 1,
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"schemaInputChecksum": "1b3eec01e5b04880166e20e8e6df70b79f227129ea9165e38cfbb925be3c2d23",
|
||||||
|
"tableCode": "orders",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"code": "id",
|
||||||
|
"type": "bigint",
|
||||||
|
"nullable": false,
|
||||||
|
"primary": true,
|
||||||
|
"readable": true,
|
||||||
|
"writable": false,
|
||||||
|
"filterable": true,
|
||||||
|
"sortable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "order_no",
|
||||||
|
"type": "varchar",
|
||||||
|
"nullable": false,
|
||||||
|
"primary": false,
|
||||||
|
"readable": true,
|
||||||
|
"writable": true,
|
||||||
|
"filterable": true,
|
||||||
|
"sortable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "goods_id",
|
||||||
|
"type": "bigint",
|
||||||
|
"nullable": false,
|
||||||
|
"primary": false,
|
||||||
|
"readable": true,
|
||||||
|
"writable": true,
|
||||||
|
"filterable": true,
|
||||||
|
"sortable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "quantity",
|
||||||
|
"type": "int",
|
||||||
|
"nullable": false,
|
||||||
|
"primary": false,
|
||||||
|
"readable": true,
|
||||||
|
"writable": true,
|
||||||
|
"filterable": true,
|
||||||
|
"sortable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "unit_price",
|
||||||
|
"type": "decimal",
|
||||||
|
"nullable": false,
|
||||||
|
"primary": false,
|
||||||
|
"readable": true,
|
||||||
|
"writable": true,
|
||||||
|
"filterable": true,
|
||||||
|
"sortable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "amount",
|
||||||
|
"type": "decimal",
|
||||||
|
"nullable": false,
|
||||||
|
"primary": false,
|
||||||
|
"readable": true,
|
||||||
|
"writable": true,
|
||||||
|
"filterable": true,
|
||||||
|
"sortable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "status",
|
||||||
|
"type": "varchar",
|
||||||
|
"nullable": false,
|
||||||
|
"primary": false,
|
||||||
|
"readable": true,
|
||||||
|
"writable": true,
|
||||||
|
"filterable": true,
|
||||||
|
"sortable": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
12
src/saas/generated/order/module.ts
Normal file
12
src/saas/generated/order/module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Generated by cool saas-model generate. DO NOT EDIT.
|
||||||
|
import { defineGeneratedSaasModule } from '@cool-midway/module-runtime';
|
||||||
|
|
||||||
|
export const GeneratedSaasModule = defineGeneratedSaasModule({
|
||||||
|
"formatVersion": 2,
|
||||||
|
"moduleCode": "order",
|
||||||
|
"revision": 1,
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"schemaInputChecksum": "1b3eec01e5b04880166e20e8e6df70b79f227129ea9165e38cfbb925be3c2d23",
|
||||||
|
"runtimeContractChecksum": "3d025d1b634c413449eb91164fb1f859a388d8930d2de112980a606b591befce",
|
||||||
|
"implementationManifestChecksum": "f25647d2dd90e14d6226683d2ff67ba32132b2dfdeb861015805240a96d4635b"
|
||||||
|
});
|
||||||
218
src/saas/generated/order/saas-actions.ts
Normal file
218
src/saas/generated/order/saas-actions.ts
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
// Generated by cool saas-model generate. DO NOT EDIT.
|
||||||
|
import { defineInjectedSaasAction } from '@cool-midway/module-runtime';
|
||||||
|
|
||||||
|
export type DemoGoodsBatchInfoInput = {
|
||||||
|
ids: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DemoGoodsBatchInfoOutput = {
|
||||||
|
list: Record<string, unknown>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DemoGoodsReleaseStockInput = {
|
||||||
|
goodsId: number;
|
||||||
|
orderNo: string;
|
||||||
|
quantity: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DemoGoodsReleaseStockOutput = {
|
||||||
|
currentStock: number;
|
||||||
|
goodsId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DemoGoodsReserveStockInput = {
|
||||||
|
goodsId: number;
|
||||||
|
quantity: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DemoGoodsReserveStockOutput = {
|
||||||
|
goodsId: number;
|
||||||
|
remainingStock: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SaasActions = {
|
||||||
|
Demo: {
|
||||||
|
GoodsBatchInfo: defineInjectedSaasAction<DemoGoodsBatchInfoInput, DemoGoodsBatchInfoOutput>({
|
||||||
|
"kind": "action",
|
||||||
|
"name": "goods.batchInfo",
|
||||||
|
"access": "read",
|
||||||
|
"actionName": "goods.batchInfo",
|
||||||
|
"idempotent": false,
|
||||||
|
"moduleCode": "demo",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ids"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ids": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"callerPolicy": {
|
||||||
|
"kind": "module",
|
||||||
|
"allowedModules": [
|
||||||
|
"order"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contractKind": "saas.action.import",
|
||||||
|
"outputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"list"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"inputChecksum": "b2e0c5fd68adc75e2687c6a1ce01b8270240b42df6511efd6d922ea3242bc264",
|
||||||
|
"actionChecksum": "3978a7ba9a3e0f91842031fd323ec63b3816eef68cb8dc82ffad6d187de8b7ea",
|
||||||
|
"outputChecksum": "fe644424ef6a9e462b56edc9f70f48bb218783c69469469d4310de48589a0df5",
|
||||||
|
"contractVersion": 1,
|
||||||
|
"providerReleaseId": 38,
|
||||||
|
"consumerModuleCode": "order",
|
||||||
|
"providerModuleCode": "demo",
|
||||||
|
"providerReleaseVersion": "1.0.0-rc.1"
|
||||||
|
}),
|
||||||
|
GoodsReleaseStock: defineInjectedSaasAction<DemoGoodsReleaseStockInput, DemoGoodsReleaseStockOutput>({
|
||||||
|
"kind": "action",
|
||||||
|
"name": "goods.releaseStock",
|
||||||
|
"access": "write",
|
||||||
|
"actionName": "goods.releaseStock",
|
||||||
|
"idempotent": true,
|
||||||
|
"moduleCode": "demo",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"goodsId",
|
||||||
|
"quantity",
|
||||||
|
"orderNo"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"goodsId": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"orderNo": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 64,
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"callerPolicy": {
|
||||||
|
"kind": "module",
|
||||||
|
"allowedModules": [
|
||||||
|
"order"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contractKind": "saas.action.import",
|
||||||
|
"outputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"goodsId",
|
||||||
|
"currentStock"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"goodsId": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"currentStock": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"inputChecksum": "c363d259b5108d069650a2554309eb3eb165d98723596595958e00acf8410abe",
|
||||||
|
"actionChecksum": "63423abe8557075caf668a91795f95a45195759a6b82e755d8af51ddfc71996e",
|
||||||
|
"outputChecksum": "a632b9f89518c3c17861454b74173a4880d5e8482639d3ff7f761aee4d061bd9",
|
||||||
|
"contractVersion": 1,
|
||||||
|
"commandEventType": "saas.workflow.demo.goods.releaseStock",
|
||||||
|
"providerReleaseId": 38,
|
||||||
|
"consumerModuleCode": "order",
|
||||||
|
"providerModuleCode": "demo",
|
||||||
|
"providerReleaseVersion": "1.0.0-rc.1",
|
||||||
|
"commandEventContractChecksum": "18a727b91cf9410c3b7ac14e3a8799dbbfa8937f84667bfe7e31e21e662396be"
|
||||||
|
}),
|
||||||
|
GoodsReserveStock: defineInjectedSaasAction<DemoGoodsReserveStockInput, DemoGoodsReserveStockOutput>({
|
||||||
|
"kind": "action",
|
||||||
|
"name": "goods.reserveStock",
|
||||||
|
"access": "write",
|
||||||
|
"actionName": "goods.reserveStock",
|
||||||
|
"idempotent": true,
|
||||||
|
"moduleCode": "demo",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"goodsId",
|
||||||
|
"quantity"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"goodsId": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"callerPolicy": {
|
||||||
|
"kind": "module",
|
||||||
|
"allowedModules": [
|
||||||
|
"order"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contractKind": "saas.action.import",
|
||||||
|
"outputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"goodsId",
|
||||||
|
"remainingStock"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"goodsId": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"remainingStock": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"inputChecksum": "6fbfa4d2324c4c0bdc621cbfd256353284e27495f59f47231b7d897b82a4e7b6",
|
||||||
|
"actionChecksum": "773465c93790e8df9119149e1efb4c4d8d2510298a24af7aa825662540ff37d5",
|
||||||
|
"outputChecksum": "545bd8b0bf6eb8670b21497236038187ee0f1df1544a318b16a399458fcb796e",
|
||||||
|
"contractVersion": 1,
|
||||||
|
"commandEventType": "saas.workflow.demo.goods.reserveStock",
|
||||||
|
"providerReleaseId": 38,
|
||||||
|
"consumerModuleCode": "order",
|
||||||
|
"providerModuleCode": "demo",
|
||||||
|
"providerReleaseVersion": "1.0.0-rc.1",
|
||||||
|
"commandEventContractChecksum": "ffbecad3a3d63b8bf92fde701dfaa11c8502a2c5f9f6bbb89b755e214024c3ae"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
25
src/saas/generated/order/saas-events.ts
Normal file
25
src/saas/generated/order/saas-events.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Generated by cool saas-model generate. DO NOT EDIT.
|
||||||
|
import { defineSaasEventHandler } from '@cool-midway/module-runtime';
|
||||||
|
import type { SaasEventEnvelope } from '@cool-midway/module-runtime';
|
||||||
|
|
||||||
|
export type DemoGoodsStockReservedV1Payload = {
|
||||||
|
goodsId: number;
|
||||||
|
quantity: number;
|
||||||
|
remainingStock: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DemoGoodsStockReservedV1Event = SaasEventEnvelope<DemoGoodsStockReservedV1Payload>;
|
||||||
|
|
||||||
|
export const SaasEvents = {
|
||||||
|
Demo: {
|
||||||
|
GoodsStockReservedV1: defineSaasEventHandler({
|
||||||
|
"kind": "event_handler",
|
||||||
|
"eventType": "demo.goods.stockReserved",
|
||||||
|
"eventVersion": 1,
|
||||||
|
"eventContractChecksum": "4d8a47d64f7d9fa43e0253228d05a2bb28db7efe3181e96d58dbff2163b9b752",
|
||||||
|
"sourceModule": "demo",
|
||||||
|
"tables": [],
|
||||||
|
"maxAttempts": 10
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
1022
src/saas/generated/order/saas-implementation.expected.json
Normal file
1022
src/saas/generated/order/saas-implementation.expected.json
Normal file
File diff suppressed because it is too large
Load Diff
2390
src/saas/generated/order/saas-schema.lock.json
Normal file
2390
src/saas/generated/order/saas-schema.lock.json
Normal file
File diff suppressed because it is too large
Load Diff
181
src/service/orders.ts
Normal file
181
src/service/orders.ts
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import { Provide } from '@midwayjs/core';
|
||||||
|
import {
|
||||||
|
InjectSaasAction,
|
||||||
|
InjectSaasCrud,
|
||||||
|
ModuleRuntimeError,
|
||||||
|
SaasAction,
|
||||||
|
type SaasActionCaller,
|
||||||
|
type SaasCrudCaller,
|
||||||
|
SaasCrudService,
|
||||||
|
SaasCrudServiceBase,
|
||||||
|
} from '@cool-midway/module-runtime';
|
||||||
|
import {
|
||||||
|
Actions,
|
||||||
|
Crud,
|
||||||
|
SaasActions,
|
||||||
|
type OrdersCancelOrderInput,
|
||||||
|
type OrdersCancelOrderOutput,
|
||||||
|
type OrdersCreateOrderInput,
|
||||||
|
type OrdersCreateOrderOutput,
|
||||||
|
type OrdersDetailWithGoodsInput,
|
||||||
|
type OrdersDetailWithGoodsOutput,
|
||||||
|
} from '../saas/generated/order';
|
||||||
|
|
||||||
|
type GoodsRecord = Record<string, unknown>;
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@SaasCrudService(Crud.Orders)
|
||||||
|
export class OrdersService extends SaasCrudServiceBase<typeof Crud.Orders> {
|
||||||
|
@InjectSaasCrud(Crud.Orders)
|
||||||
|
private readonly orders!: SaasCrudCaller<typeof Crud.Orders>;
|
||||||
|
|
||||||
|
@InjectSaasAction(SaasActions.Demo.GoodsBatchInfo)
|
||||||
|
private readonly batchInfo!: SaasActionCaller<
|
||||||
|
typeof SaasActions.Demo.GoodsBatchInfo
|
||||||
|
>;
|
||||||
|
|
||||||
|
@InjectSaasAction(SaasActions.Demo.GoodsReserveStock)
|
||||||
|
private readonly reserveStock!: SaasActionCaller<
|
||||||
|
typeof SaasActions.Demo.GoodsReserveStock
|
||||||
|
>;
|
||||||
|
|
||||||
|
@InjectSaasAction(SaasActions.Demo.GoodsReleaseStock)
|
||||||
|
private readonly releaseStock!: SaasActionCaller<
|
||||||
|
typeof SaasActions.Demo.GoodsReleaseStock
|
||||||
|
>;
|
||||||
|
|
||||||
|
@SaasAction(Actions.OrdersCreateOrder)
|
||||||
|
async createOrder(
|
||||||
|
input: OrdersCreateOrderInput
|
||||||
|
): Promise<OrdersCreateOrderOutput> {
|
||||||
|
const goods = await this.requireGoods(input.goodsId);
|
||||||
|
if (integer(goods.status, '商品状态') !== 1) {
|
||||||
|
throw conflict('商品未上架,不能下单');
|
||||||
|
}
|
||||||
|
|
||||||
|
const unitPrice = decimal(goods.price, '商品价格');
|
||||||
|
const amount = multiplyDecimal(unitPrice, input.quantity);
|
||||||
|
|
||||||
|
// The order row is kept in the outer managed transaction. If the remote
|
||||||
|
// stock reservation fails, Runtime rolls this local CRUD mutation back.
|
||||||
|
const order = await this.orders.add({
|
||||||
|
order_no: input.orderNo,
|
||||||
|
goods_id: String(input.goodsId),
|
||||||
|
quantity: input.quantity,
|
||||||
|
unit_price: unitPrice,
|
||||||
|
amount,
|
||||||
|
status: 'pending',
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.reserveStock({
|
||||||
|
goodsId: input.goodsId,
|
||||||
|
quantity: input.quantity,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: safeInteger(order.id, '订单 ID'),
|
||||||
|
orderNo: order.order_no,
|
||||||
|
amount: finiteNumber(order.amount, '订单金额'),
|
||||||
|
status: order.status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@SaasAction(Actions.OrdersDetailWithGoods)
|
||||||
|
async detailWithGoods(
|
||||||
|
input: OrdersDetailWithGoodsInput
|
||||||
|
): Promise<OrdersDetailWithGoodsOutput> {
|
||||||
|
const order = await this.requireOrder(input.id);
|
||||||
|
const goods = await this.requireGoods(safeInteger(order.goods_id, '商品 ID'));
|
||||||
|
return { order, goods };
|
||||||
|
}
|
||||||
|
|
||||||
|
@SaasAction(Actions.OrdersCancelOrder)
|
||||||
|
async cancelOrder(
|
||||||
|
input: OrdersCancelOrderInput
|
||||||
|
): Promise<OrdersCancelOrderOutput> {
|
||||||
|
const order = await this.requireOrder(input.id);
|
||||||
|
if (order.status === 'cancelled') {
|
||||||
|
return { id: input.id, status: 'cancelled', restoredStock: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = await this.orders.update({
|
||||||
|
id: order.id,
|
||||||
|
status: 'cancelled',
|
||||||
|
});
|
||||||
|
if (update.affected === 0) {
|
||||||
|
return { id: input.id, status: 'cancelled', restoredStock: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const goodsId = safeInteger(order.goods_id, '商品 ID');
|
||||||
|
await this.releaseStock({
|
||||||
|
goodsId,
|
||||||
|
quantity: order.quantity,
|
||||||
|
orderNo: order.order_no,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: input.id,
|
||||||
|
status: 'cancelled',
|
||||||
|
restoredStock: order.quantity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async requireOrder(id: number) {
|
||||||
|
const order = await this.orders.info({ id: String(id) });
|
||||||
|
if (!order) throw conflict(`订单不存在: ${id}`);
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async requireGoods(goodsId: number): Promise<GoodsRecord> {
|
||||||
|
const result = await this.batchInfo({ ids: [goodsId] });
|
||||||
|
const goods = result.list.find(item => String(item.id) === String(goodsId));
|
||||||
|
if (!goods) throw conflict(`商品不存在: ${goodsId}`);
|
||||||
|
return goods;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decimal(value: unknown, label: string): string {
|
||||||
|
const normalized = String(value ?? '').trim();
|
||||||
|
if (!/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(normalized)) {
|
||||||
|
throw conflict(`${label}非法`);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function multiplyDecimal(value: string, multiplier: number): string {
|
||||||
|
const negative = value.startsWith('-');
|
||||||
|
const unsigned = negative ? value.slice(1) : value;
|
||||||
|
const [integerPart, fractionPart = ''] = unsigned.split('.');
|
||||||
|
const scale = fractionPart.length;
|
||||||
|
const digits = BigInt(`${integerPart}${fractionPart}`);
|
||||||
|
const product = digits * BigInt(multiplier);
|
||||||
|
const padded = product.toString().padStart(scale + 1, '0');
|
||||||
|
const absolute = scale
|
||||||
|
? `${padded.slice(0, -scale)}.${padded.slice(-scale)}`
|
||||||
|
: padded;
|
||||||
|
return negative && product !== BigInt(0) ? `-${absolute}` : absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
function integer(value: unknown, label: string): number {
|
||||||
|
const normalized = Number(value);
|
||||||
|
if (!Number.isInteger(normalized)) throw conflict(`${label}非法`);
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeInteger(value: unknown, label: string): number {
|
||||||
|
const normalized = integer(value, label);
|
||||||
|
if (!Number.isSafeInteger(normalized) || normalized < 1) {
|
||||||
|
throw conflict(`${label}超出安全范围`);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function finiteNumber(value: unknown, label: string): number {
|
||||||
|
const normalized = Number(value);
|
||||||
|
if (!Number.isFinite(normalized)) throw conflict(`${label}非法`);
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function conflict(message: string) {
|
||||||
|
return new ModuleRuntimeError('MODULE_ACTION_INPUT_INVALID', message, 409);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user