wms-module
npx skills add https://github.com/rytass/utils --skill wms-module
Agent 安装分布
Skill 文档
WMS Base NestJS Module (åå²ç®¡ç模çµ)
Overview
@rytass/wms-base-nestjs-module æä¾ NestJS åå²ç®¡ç系統çåºç¤æ¨¡çµï¼æ¯æ´å²ä½æ¨¹ççµæ§ãç©æç®¡çã庫åç°å追蹤ãè¨å®ç®¡çåå庫å°ååè½ã
Quick Start
å®è£
npm install @rytass/wms-base-nestjs-module
Peer Dependencies:
@nestjs/common^9.0.0 || ^10.0.0@nestjs/typeorm^9.0.0 || ^10.0.0typeorm^0.3.0
åºæ¬è¨å®
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WMSBaseModule } from '@rytass/wms-base-nestjs-module';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
// ... database config
}),
WMSBaseModule.forRoot({
allowNegativeStock: false, // é è¨: false, ç¦æ¢è² 庫å
}),
],
})
export class AppModule {}
é忥è¨å®
import { WMSBaseModule, WMSBaseModuleAsyncOptions } from '@rytass/wms-base-nestjs-module';
import { ConfigService } from '@nestjs/config';
@Module({
imports: [
// useFactory æ¹å¼
WMSBaseModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
allowNegativeStock: config.get('WMS_ALLOW_NEGATIVE_STOCK', false),
}),
}),
// æ useClass æ¹å¼ï¼ä½¿ç¨èªè¨ Factoryï¼
// WMSBaseModule.forRootAsync({
// useClass: WMSConfigFactory,
// }),
// æ useExisting æ¹å¼ï¼éç¨ç¾æ Factoryï¼
// WMSBaseModule.forRootAsync({
// useExisting: ExistingWMSConfigFactory,
// }),
],
})
export class AppModule {}
Core Entities
æ¨¡çµæä¾ä»¥ä¸åºç¤ Entityï¼çå¯ééç¹¼æ¿æ´å±ï¼
LocationEntity (å²ä½)
import { LocationEntity } from '@rytass/wms-base-nestjs-module';
// åºç¤çµæ§ - Table: 'locations'
@Entity('locations')
@TableInheritance({ column: { type: 'varchar', name: 'entityName' } })
@Tree('materialized-path')
class LocationEntity {
@PrimaryColumn({ type: 'varchar' })
id: string;
@TreeChildren()
children: LocationEntity[];
@TreeParent()
parent: LocationEntity;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn()
deletedAt: Date | null; // Soft delete
}
ä½¿ç¨ TypeORM
@Tree('materialized-path')坦使¨¹ççµæ§
MaterialEntity (ç©æ)
import { MaterialEntity } from '@rytass/wms-base-nestjs-module';
// Table: 'materials'
@Entity('materials')
@TableInheritance({ column: { type: 'varchar', name: 'entityName' } })
class MaterialEntity {
@PrimaryColumn({ type: 'varchar' })
id: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn()
deletedAt: Date | null; // Soft delete
@OneToMany(() => BatchEntity, batch => batch.material)
batches: Relation<BatchEntity[]>;
@OneToMany(() => StockEntity, stock => stock.material)
stocks: Relation<StockEntity[]>;
}
BatchEntity (æ¹æ¬¡)
import { BatchEntity } from '@rytass/wms-base-nestjs-module';
// Table: 'batches'
// è¤å主éµ: id + materialId
@Entity('batches')
class BatchEntity {
@PrimaryColumn('varchar')
id: string;
@PrimaryColumn('varchar')
materialId: string;
@ManyToOne(() => MaterialEntity, material => material.batches)
@JoinColumn({ name: 'materialId', referencedColumnName: 'id' })
material: Relation<MaterialEntity>;
@OneToMany(() => StockEntity, stock => stock.batch)
stocks: Relation<StockEntity[]>;
}
StockEntity (庫åç°å)
import { StockEntity } from '@rytass/wms-base-nestjs-module';
// Table: 'stocks'
@Entity('stocks')
@TableInheritance({ column: { type: 'varchar', name: 'entityName' } })
class StockEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar' })
materialId: string;
@Column({ type: 'varchar' })
batchId: string;
@Column({ type: 'varchar' })
locationId: string;
@Column({ type: 'varchar' })
orderId: string;
@Column({ type: 'numeric' })
quantity: number; // æ£æ¸çºå
¥åº«ï¼è² æ¸çºåºåº«
@ManyToOne(() => MaterialEntity, material => material.stocks)
@JoinColumn({ name: 'materialId', referencedColumnName: 'id' })
material: Relation<MaterialEntity>;
@ManyToOne(() => BatchEntity, batch => batch.stocks)
@JoinColumn([
{ name: 'materialId', referencedColumnName: 'materialId' },
{ name: 'batchId', referencedColumnName: 'id' },
])
batch: Relation<BatchEntity>;
@ManyToOne(() => OrderEntity, order => order.stocks)
@JoinColumn({ name: 'orderId', referencedColumnName: 'id' })
order: Relation<OrderEntity>;
@CreateDateColumn()
createdAt: Date;
}
OrderEntity (è¨å®)
import { OrderEntity } from '@rytass/wms-base-nestjs-module';
// Table: 'orders'
@Entity('orders')
@TableInheritance({ column: { type: 'varchar', name: 'entityName' } })
class OrderEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@OneToMany(() => StockEntity, stock => stock.order)
stocks: Relation<StockEntity>[];
}
WarehouseMapEntity (å庫å°å)
注æï¼
WarehouseMapServiceæå¾ index.ts å°åºï¼å¯ç´æ¥ importãä½WarehouseMapEntityãMapRangeTypeãMapRangeColorãMapDataçé¡åç®åæªå¾ index.ts å°åºï¼éç´æ¥å¾åå§ç¢¼è·¯å¾ import æèªè¡å®ç¾©ã
// WarehouseMapService å¯ç´æ¥ import
import { WarehouseMapService } from '@rytass/wms-base-nestjs-module';
// WarehouseMapEntity éå¾åå§ç¢¼è·¯å¾ importï¼è¥å¿
è¦ï¼
// Table: 'warehouse_maps'
@Entity('warehouse_maps')
class WarehouseMapEntity {
@PrimaryColumn('varchar') // å°æ locationId
id: string;
@Column({ type: 'jsonb' })
mapData: MapData;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
Services
LocationService (å²ä½æå)
import { LocationService, LocationEntity } from '@rytass/wms-base-nestjs-module';
@Injectable()
export class WarehouseService {
constructor(private readonly locationService: LocationService) {}
// 建ç«å²ä½
async createLocation() {
// å»ºç«æ ¹å²ä½
const warehouse = await this.locationService.create({
id: 'WAREHOUSE-A',
});
// 建ç«åå²ä½
const zone = await this.locationService.create({
id: 'ZONE-A1',
parentId: 'WAREHOUSE-A',
});
return zone;
}
// å°åå²ä½ (Soft Delete)
// 注æ: åªæåº«åçº 0 çå²ä½æè½å°å
async archiveLocation(id: string) {
await this.locationService.archive(id);
// æé£åææåå²ä½ä¸èµ·å°å
}
// è§£é¤å°å
async unArchiveLocation(id: string) {
const location = await this.locationService.unArchive(id);
return location;
}
}
MaterialService (ç©ææå)
import { MaterialService, MaterialEntity } from '@rytass/wms-base-nestjs-module';
@Injectable()
export class ProductService {
constructor(private readonly materialService: MaterialService) {}
// 建ç«ç©æ
async createMaterial() {
const material = await this.materialService.create({
id: 'SKU-001',
});
return material;
}
}
StockService (庫åæå)
import {
StockService,
StockFindDto,
StockFindAllDto,
StockSorter,
} from '@rytass/wms-base-nestjs-module';
@Injectable()
export class InventoryService {
constructor(private readonly stockService: StockService) {}
// æ¥è©¢åº«åæ¸é (åå³å 總æ¸é)
// find() å¯é¸å³å
¥ manager 忏ä¾äº¤æä½¿ç¨
async getStockQuantity(locationId: string, materialId: string): Promise<number> {
return this.stockService.find({
locationIds: [locationId],
materialIds: [materialId],
exactLocationMatch: true, // åªæ¥è©¢è©²å²ä½ï¼ä¸å
å«åå²ä½
});
}
// æ¥è©¢å²ä½æ¨¹ä¸ç總庫å (å
嫿æåå²ä½)
async getTotalStock(locationId: string): Promise<number> {
return this.stockService.find({
locationIds: [locationId],
// exactLocationMatch: false (é è¨) - å
嫿æåå²ä½
});
}
// æ¥è©¢åº«åç°åè¨é
async getTransactionLogs(options: StockFindAllDto) {
return this.stockService.findTransactions({
locationIds: ['WAREHOUSE-A'],
materialIds: ['SKU-001'],
batchIds: ['BATCH-001'],
offset: 0,
limit: 20, // æå¤§ 100
sorter: StockSorter.CREATED_AT_DESC,
});
// åå³: StockCollectionDto { transactionLogs, total, offset, limit }
}
}
StockFindDto 忏:
| 忏 | é¡å | 說æ |
|---|---|---|
locationIds |
string[] |
å²ä½ ID å表 |
materialIds |
string[] |
ç©æ ID å表 |
batchIds |
string[] |
æ¹æ¬¡ ID å表 |
exactLocationMatch |
boolean |
true: åªæ¥è©¢æå®å²ä½ï¼false (é è¨): å
å«åå²ä½ |
StockFindAllDto é¡å¤åæ¸:
| 忏 | é¡å | é è¨å¼ | 說æ |
|---|---|---|---|
offset |
number |
0 |
åé åç§» |
limit |
number |
20 |
æ¯é çæ¸ (æå¤§ 100) |
sorter |
StockSorter |
CREATED_AT_DESC |
æåºæ¹å¼ |
StockCollectionDto åå³çµæ§:
interface StockCollectionDto {
transactionLogs: StockTransactionLogDto[];
total: number;
offset: number;
limit: number;
}
// æ³åçæ¬ï¼æé¤éè¯æ¬ä½
type StockTransactionLogDto<Stock extends StockEntity = StockEntity> = Omit<
Stock,
'material' | 'batch' | 'location' | 'order'
>;
// 實éå
嫿¬ä½ï¼ä»¥é è¨ StockEntity çºä¾ï¼:
// { id, materialId, batchId, locationId, orderId, quantity, createdAt }
// 注æ: åå§ç¢¼ Omit äº 'location'ï¼ä½ StockEntity 實é䏿²æ location relation
// ï¼åªæ locationId æ¬ä½ï¼ï¼éæ¯é²ç¦¦æ§è¨è¨
OrderService (è¨å®æå)
import {
OrderService,
OrderEntity,
OrderCreateDto,
BatchCreateDto,
} from '@rytass/wms-base-nestjs-module';
import { Entity, Column } from 'typeorm';
// æ´å±è¨å® Entity
@Entity('custom_orders')
export class CustomOrderEntity extends OrderEntity {
@Column()
orderNumber: string;
@Column()
type: 'INBOUND' | 'OUTBOUND';
}
@Injectable()
export class OrderManagementService {
constructor(private readonly orderService: OrderService) {}
// 建ç«å
¥åº«è¨å®
async createInboundOrder() {
const order = await this.orderService.createOrder(CustomOrderEntity, {
order: {
orderNumber: 'IN-2024-001',
type: 'INBOUND',
},
batches: [
{
id: 'BATCH-001',
materialId: 'SKU-001',
locationId: 'ZONE-A1',
quantity: 100, // æ£æ¸: å
¥åº«
},
],
});
return order;
}
// 建ç«åºåº«è¨å®
async createOutboundOrder() {
const order = await this.orderService.createOrder(CustomOrderEntity, {
order: {
orderNumber: 'OUT-2024-001',
type: 'OUTBOUND',
},
batches: [
{
id: 'BATCH-001',
materialId: 'SKU-001',
locationId: 'ZONE-A1',
quantity: -50, // è² æ¸: åºåº«
},
],
});
return order;
// è¥ allowNegativeStock: false ä¸åº«åä¸è¶³ï¼ææåº StockQuantityNotEnoughError
}
// æª¢æ¥æ¯å¦å¯å»ºç«åº«åç°å
async canCreateStock(batch: BatchCreateDto): Promise<boolean> {
return this.orderService.canCreateStock(batch);
}
}
BatchCreateDto:
interface BatchCreateDto {
id: string;
materialId: string;
locationId: string;
quantity: number;
}
OrderCreateDto:
type OrderDto<O extends OrderEntity = OrderEntity> = DeepPartial<Omit<O, 'stocks'>>;
type OrderCreateDto<O extends OrderEntity = OrderEntity> = {
order: OrderDto<O>;
batches: BatchCreateDto[];
};
WarehouseMapService (å庫å°åæå)
注æï¼
WarehouseMapServiceæå¾ index.ts å°åºï¼å¯ç´æ¥ importãä½MapRangeTypeåMapRangeColorç®åæªå¾ index.ts å°åºï¼éèªè¡å®ç¾©æç´æ¥ä½¿ç¨å串å¼ã
import { WarehouseMapService } from '@rytass/wms-base-nestjs-module';
// MapRangeType/MapRangeColor å¯ç´æ¥ä½¿ç¨å䏲弿èªè¡å®ç¾© enum
// å¯èªè¡å®ç¾© enum æä½¿ç¨å串常æ¸
enum MapRangeType {
RECTANGLE = 'RECTANGLE',
POLYGON = 'POLYGON',
}
enum MapRangeColor {
RED = 'RED',
YELLOW = 'YELLOW',
GREEN = 'GREEN',
BLUE = 'BLUE',
BLACK = 'BLACK',
}
@Injectable()
export class MapService {
constructor(private readonly warehouseMapService: WarehouseMapService) {}
// æ´æ°å庫å°å
async updateMap(locationId: string) {
const map = await this.warehouseMapService.updateMap(
locationId,
// backgrounds: èæ¯åç
[
{
id: 'bg-1',
filename: 'warehouse-floor.png',
x: 0,
y: 0,
width: 1000,
height: 800,
},
],
// ranges: å忍è¨
[
// ç©å½¢åå
{
id: 'zone-a1',
type: MapRangeType.RECTANGLE,
color: MapRangeColor.GREEN,
x: 100,
y: 100,
width: 200,
height: 150,
},
// å¤éå½¢åå
{
id: 'zone-special',
type: MapRangeType.POLYGON,
color: MapRangeColor.YELLOW,
points: [
{ x: 400, y: 100 },
{ x: 500, y: 100 },
{ x: 550, y: 200 },
{ x: 400, y: 200 },
],
},
],
);
return map;
}
// åå¾å°åè³æ
async getMap(locationId: string) {
return this.warehouseMapService.getMapById(locationId);
// è¥ä¸åå¨åå³: { id, backgrounds: [], ranges: [] }
}
// åªé¤å°å
async deleteMap(locationId: string) {
await this.warehouseMapService.deleteMapById(locationId);
}
}
Custom Entities (æ´å± Entity)
éé forRoot æ forRootAsync å³å
¥èªè¨ Entityï¼
import {
WMSBaseModule,
LocationEntity,
MaterialEntity,
StockEntity,
BatchEntity,
OrderEntity,
} from '@rytass/wms-base-nestjs-module';
import { Entity, Column } from 'typeorm';
// æ´å±å²ä½
@Entity('custom_locations')
export class CustomLocationEntity extends LocationEntity {
@Column()
name: string;
@Column({ nullable: true })
description: string;
}
// æ´å±ç©æ
@Entity('custom_materials')
export class CustomMaterialEntity extends MaterialEntity {
@Column()
name: string;
@Column()
sku: string;
}
// 模çµè¨å®
@Module({
imports: [
WMSBaseModule.forRoot({
locationEntity: CustomLocationEntity,
materialEntity: CustomMaterialEntity,
stockEntity: StockEntity, // 使ç¨é è¨
batchEntity: BatchEntity, // 使ç¨é è¨
orderEntity: OrderEntity, // 使ç¨é è¨
allowNegativeStock: false,
}),
],
})
export class AppModule {}
Data Types
MapData
interface MapData {
id: string;
backgrounds: MapBackground[];
ranges: (MapRectangleRange | MapPolygonRange)[];
}
interface MapBackground {
id: string;
filename: string;
x: number;
y: number;
height: number;
width: number;
}
interface MapRange {
id: string;
type: MapRangeType;
color: string;
}
interface MapRectangleRange extends MapRange {
x: number;
y: number;
width: number;
height: number;
}
interface MapPolygonRange extends MapRange {
points: MapPolygonRangePoint[];
}
interface MapPolygonRangePoint {
x: number;
y: number;
}
Enums
enum MapRangeType {
RECTANGLE = 'RECTANGLE',
POLYGON = 'POLYGON',
}
enum MapRangeColor {
RED = 'RED',
YELLOW = 'YELLOW',
GREEN = 'GREEN',
BLUE = 'BLUE',
BLACK = 'BLACK',
}
enum StockSorter {
CREATED_AT_DESC = 'CREATED_AT_DESC',
CREATED_AT_ASC = 'CREATED_AT_ASC',
}
Error Handling
import {
LocationNotFoundError,
LocationCannotArchiveError,
LocationAlreadyExistedError,
StockQuantityNotEnoughError,
} from '@rytass/wms-base-nestjs-module';
é¯èª¤ä»£ç¢¼è¡¨:
| Code | Error | Description |
|---|---|---|
| 100 | LocationNotFoundError | å²ä½ä¸åå¨ |
| 101 | LocationCannotArchiveError | å²ä½ç¡æ³å°å (庫åä¸çº 0) |
| 102 | LocationAlreadyExistedError | å²ä½å·²åå¨ |
| 200 | StockQuantityNotEnoughError | åº«åæ¸éä¸è¶³ |
@Injectable()
export class SafeLocationService {
constructor(private readonly locationService: LocationService) {}
async safeArchive(id: string) {
try {
await this.locationService.archive(id);
} catch (error) {
if (error instanceof LocationCannotArchiveError) {
throw new Error('ç¡æ³å°åå²ä½ï¼è«å
æ¸
空庫å');
}
if (error instanceof LocationNotFoundError) {
throw new Error('å²ä½ä¸åå¨');
}
throw error;
}
}
}
Configuration Options
interface WMSBaseModuleOptions {
// èªè¨ Entity (ççºé¸å¡«)
stockEntity?: new () => StockEntity;
locationEntity?: new () => LocationEntity;
materialEntity?: new () => MaterialEntity;
batchEntity?: new () => BatchEntity;
orderEntity?: new () => OrderEntity;
warehouseMapEntity?: new () => WarehouseMapEntity;
// é¸é
allowNegativeStock?: boolean; // é è¨: false
}
// Async Options
interface WMSBaseModuleAsyncOptions {
imports?: ModuleMetadata['imports'];
inject?: InjectionToken[];
useFactory?: (...args: any[]) => Promise<WMSBaseModuleOptions> | WMSBaseModuleOptions;
useClass?: Type<WMSBaseModuleOptionsFactory>;
useExisting?: Type<WMSBaseModuleOptionsFactory>;
}
// Options Factory Interface
interface WMSBaseModuleOptionsFactory {
createWMSBaseModuleOptions(): Promise<WMSBaseModuleOptions> | WMSBaseModuleOptions;
}
Symbol Tokens
å¯ç¨æ¼ä¾è³´æ³¨å ¥ç Symbol Tokensï¼
import {
// Repository Tokens
RESOLVED_TREE_LOCATION_REPO, // TreeRepository<LocationEntity>
RESOLVED_MATERIAL_REPO, // Repository<MaterialEntity>
RESOLVED_BATCH_REPO, // Repository<BatchEntity>
RESOLVED_ORDER_REPO, // Repository<OrderEntity>
RESOLVED_STOCK_REPO, // Repository<StockEntity>
RESOLVED_WAREHOUSE_MAP_REPO, // Repository<WarehouseMapEntity>
// Entity Provider Tokens
PROVIDE_LOCATION_ENTITY,
PROVIDE_MATERIAL_ENTITY,
PROVIDE_BATCH_ENTITY,
PROVIDE_ORDER_ENTITY,
PROVIDE_STOCK_ENTITY,
PROVIDE_WAREHOUSE_MAP_ENTITY,
// Options Tokens
WMS_MODULE_OPTIONS, // WMSBaseModuleOptions
ALLOW_NEGATIVE_STOCK, // boolean
} from '@rytass/wms-base-nestjs-module';
// 使ç¨ç¯ä¾
@Injectable()
export class CustomService {
constructor(
@Inject(RESOLVED_TREE_LOCATION_REPO)
private readonly locationRepo: TreeRepository<LocationEntity>,
@Inject(RESOLVED_STOCK_REPO)
private readonly stockRepo: Repository<StockEntity>,
@Inject(ALLOW_NEGATIVE_STOCK)
private readonly allowNegativeStock: boolean,
) {}
}
Complete Example
import { Module, Injectable } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import {
WMSBaseModule,
LocationService,
MaterialService,
StockService,
OrderService,
OrderEntity,
StockSorter,
} from '@rytass/wms-base-nestjs-module';
import { Entity, Column } from 'typeorm';
// èªè¨è¨å®
@Entity('warehouse_orders')
export class WarehouseOrderEntity extends OrderEntity {
@Column()
orderNumber: string;
@Column()
type: 'INBOUND' | 'OUTBOUND' | 'TRANSFER';
@Column({ nullable: true })
note: string;
}
// å岿å
@Injectable()
export class WarehouseManagementService {
constructor(
private readonly locationService: LocationService,
private readonly materialService: MaterialService,
private readonly stockService: StockService,
private readonly orderService: OrderService,
) {}
// åå§åååº«çµæ§
async initializeWarehouse() {
const warehouse = await this.locationService.create({ id: 'WH-001' });
const zoneA = await this.locationService.create({ id: 'WH-001-A', parentId: 'WH-001' });
const zoneB = await this.locationService.create({ id: 'WH-001-B', parentId: 'WH-001' });
return { warehouse, zoneA, zoneB };
}
// å
¥åº«ä½æ¥
async inbound(materialId: string, locationId: string, quantity: number, batchId: string) {
// 確ä¿ç©æåå¨
await this.materialService.create({ id: materialId });
// 建ç«å
¥åº«è¨å®
const order = await this.orderService.createOrder(WarehouseOrderEntity, {
order: {
orderNumber: `IN-${Date.now()}`,
type: 'INBOUND',
},
batches: [{
id: batchId,
materialId,
locationId,
quantity, // æ£æ¸
}],
});
return order;
}
// åºåº«ä½æ¥
async outbound(materialId: string, locationId: string, quantity: number, batchId: string) {
// 檢æ¥åº«å
const stock = await this.stockService.find({
locationIds: [locationId],
materialIds: [materialId],
batchIds: [batchId],
exactLocationMatch: true,
});
if (stock < quantity) {
throw new Error(`庫åä¸è¶³: ç¾æ ${stock}, éè¦ ${quantity}`);
}
// 建ç«åºåº«è¨å®
const order = await this.orderService.createOrder(WarehouseOrderEntity, {
order: {
orderNumber: `OUT-${Date.now()}`,
type: 'OUTBOUND',
},
batches: [{
id: batchId,
materialId,
locationId,
quantity: -quantity, // è² æ¸
}],
});
return order;
}
// æ¥è©¢åº«å
async getInventory(locationId: string) {
const total = await this.stockService.find({ locationIds: [locationId] });
const logs = await this.stockService.findTransactions({
locationIds: [locationId],
limit: 10,
sorter: StockSorter.CREATED_AT_DESC,
});
return { total, recentLogs: logs.transactionLogs };
}
}
// 模çµ
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
database: 'wms',
entities: [WarehouseOrderEntity],
synchronize: true,
}),
WMSBaseModule.forRoot({
orderEntity: WarehouseOrderEntity,
allowNegativeStock: false,
}),
],
providers: [WarehouseManagementService],
})
export class WarehouseModule {}
Troubleshooting
è² åº«åé¯èª¤
妿 allowNegativeStock: falseï¼åºåº«æ¸éè¶
éåº«åææåº StockQuantityNotEnoughErrorã
å
æ¥è©¢åº«ååå·è¡åºåº«æä½ã
交æå¤±æ
createOrder 使ç¨è³æåº«äº¤æï¼ä»»ä½æ¹æ¬¡å¤±æé½æå滾æ´åè¨å®ã
ç¢ºä¿æææ¹æ¬¡è³ææ£ç¢ºåæäº¤ã
åä½ç¡æ³å°å
ç¶å試å°åä¸åä»æåº«åçåä½æï¼ææåº LocationCannotArchiveErrorã
éå
å°åº«åç§»åºï¼åºåº«æèª¿æ¥ï¼å¾æè½å°ååä½ã
åä½å·²åå¨é¯èª¤
建ç«å²ä½æï¼è¥ ID å·²åå¨ï¼å«å·²å°åçï¼ï¼ææåº LocationAlreadyExistedErrorã
å¯ä»¥å
è§£é¤å°å (unArchive) æä½¿ç¨ä¸åç IDã