wms-module
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWMS Base NestJS Module (倉儲管理模組)
WMS Base NestJS Module(仓储管理模块)
Overview
概述
@rytass/wms-base-nestjs-module@rytass/wms-base-nestjs-moduleQuick Start
快速开始
安裝
安装
bash
npm install @rytass/wms-base-nestjs-modulePeer Dependencies:
- ^9.0.0 || ^10.0.0
@nestjs/common - ^9.0.0 || ^10.0.0
@nestjs/typeorm - ^0.3.0
typeorm
bash
npm install @rytass/wms-base-nestjs-module对等依赖:
- ^9.0.0 || ^10.0.0
@nestjs/common - ^9.0.0 || ^10.0.0
@nestjs/typeorm - ^0.3.0
typeorm
基本設定
基本设置
typescript
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 {}typescript
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WMSBaseModule } from '@rytass/wms-base-nestjs-module';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
// ... 数据库配置
}),
WMSBaseModule.forRoot({
allowNegativeStock: false, // 默认: false,禁止负库存
}),
],
})
export class AppModule {}非同步設定
异步设置
typescript
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 {}typescript
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,皆可透過繼承擴展:
模块提供以下基础Entity,均可通过继承扩展:
LocationEntity (儲位)
LocationEntity(储位)
typescript
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')
typescript
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; // 软删除
}使用TypeORM实现树状结构@Tree('materialized-path')
MaterialEntity (物料)
MaterialEntity(物料)
typescript
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[]>;
}typescript
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; // 软删除
@OneToMany(() => BatchEntity, batch => batch.material)
batches: Relation<BatchEntity[]>;
@OneToMany(() => StockEntity, stock => stock.material)
stocks: Relation<StockEntity[]>;
}BatchEntity (批次)
BatchEntity(批次)
typescript
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[]>;
}typescript
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 (庫存異動)
StockEntity(库存异动)
typescript
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;
}typescript
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 (訂單)
OrderEntity(订单)
typescript
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>[];
}typescript
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 (倉庫地圖)
WarehouseMapEntity(仓库地图)
注意:有從 index.ts 導出,可直接 import。但WarehouseMapService、WarehouseMapEntity、MapRangeType、MapRangeColor等類型目前未從 index.ts 導出,需直接從原始碼路徑 import 或自行定義。MapData
typescript
// 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;
}注意:已从index.ts导出,可直接import。但WarehouseMapService、WarehouseMapEntity、MapRangeType、MapRangeColor等类型目前未从index.ts导出,需直接从源码路径import或自行定义。MapData
typescript
// 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 (儲位服務)
LocationService(储位服务)
typescript
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;
}
}typescript
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;
}
// 归档储位 (软删除)
// 注意: 只有库存为0的储位才能归档
async archiveLocation(id: string) {
await this.locationService.archive(id);
// 会连同所有子储位一起归档
}
// 取消归档
async unArchiveLocation(id: string) {
const location = await this.locationService.unArchive(id);
return location;
}
}MaterialService (物料服務)
MaterialService(物料服务)
typescript
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;
}
}typescript
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 (庫存服務)
StockService(库存服务)
typescript
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 參數:
| 參數 | 類型 | 說明 |
|---|---|---|
| | 儲位 ID 列表 |
| | 物料 ID 列表 |
| | 批次 ID 列表 |
| | |
StockFindAllDto 額外參數:
| 參數 | 類型 | 預設值 | 說明 |
|---|---|---|---|
| | | 分頁偏移 |
| | | 每頁筆數 (最大 100) |
| | | 排序方式 |
StockCollectionDto 回傳結構:
typescript
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 欄位),這是防禦性設計typescript
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 参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| | 储位ID列表 |
| | 物料ID列表 |
| | 批次ID列表 |
| | |
StockFindAllDto 额外参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| | | 分页偏移 |
| | | 每页笔数 (最大100) |
| | | 排序方式 |
StockCollectionDto 返回结构:
typescript
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 (訂單服務)
OrderService(订单服务)
typescript
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:
typescript
interface BatchCreateDto {
id: string;
materialId: string;
locationId: string;
quantity: number;
}OrderCreateDto:
typescript
type OrderDto<O extends OrderEntity = OrderEntity> = DeepPartial<Omit<O, 'stocks'>>;
type OrderCreateDto<O extends OrderEntity = OrderEntity> = {
order: OrderDto<O>;
batches: BatchCreateDto[];
};typescript
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:
typescript
interface BatchCreateDto {
id: string;
materialId: string;
locationId: string;
quantity: number;
}OrderCreateDto:
typescript
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。但WarehouseMapService和MapRangeType目前未從 index.ts 導出,需自行定義或直接使用字串值。MapRangeColor
typescript
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);
}
}注意:已从index.ts导出,可直接import。但WarehouseMapService和MapRangeType目前未从index.ts导出,需自行定义或直接使用字符串值。MapRangeColor
typescript
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)
自定义实体(扩展Entity)
透過 或 傳入自訂 Entity:
forRootforRootAsynctypescript
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 {}通过或传入自定义Entity:
forRootforRootAsynctypescript
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
MapData
typescript
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;
}typescript
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
枚举
typescript
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',
}typescript
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
错误处理
typescript
import {
LocationNotFoundError,
LocationCannotArchiveError,
LocationAlreadyExistedError,
StockQuantityNotEnoughError,
} from '@rytass/wms-base-nestjs-module';錯誤代碼表:
| Code | Error | Description |
|---|---|---|
| 100 | LocationNotFoundError | 儲位不存在 |
| 101 | LocationCannotArchiveError | 儲位無法封存 (庫存不為 0) |
| 102 | LocationAlreadyExistedError | 儲位已存在 |
| 200 | StockQuantityNotEnoughError | 庫存數量不足 |
typescript
@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;
}
}
}typescript
import {
LocationNotFoundError,
LocationCannotArchiveError,
LocationAlreadyExistedError,
StockQuantityNotEnoughError,
} from '@rytass/wms-base-nestjs-module';错误代码表:
| 代码 | 错误类型 | 描述 |
|---|---|---|
| 100 | LocationNotFoundError | 储位不存在 |
| 101 | LocationCannotArchiveError | 储位无法归档 (库存不为0) |
| 102 | LocationAlreadyExistedError | 储位已存在 |
| 200 | StockQuantityNotEnoughError | 库存数量不足 |
typescript
@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
配置选项
typescript
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;
}typescript
interface WMSBaseModuleOptions {
// 自定义Entity(均为可选)
stockEntity?: new () => StockEntity;
locationEntity?: new () => LocationEntity;
materialEntity?: new () => MaterialEntity;
batchEntity?: new () => BatchEntity;
orderEntity?: new () => OrderEntity;
warehouseMapEntity?: new () => WarehouseMapEntity;
// 选项
allowNegativeStock?: boolean; // 默认: false
}
// 异步选项
interface WMSBaseModuleAsyncOptions {
imports?: ModuleMetadata['imports'];
inject?: InjectionToken[];
useFactory?: (...args: any[]) => Promise<WMSBaseModuleOptions> | WMSBaseModuleOptions;
useClass?: Type<WMSBaseModuleOptionsFactory>;
useExisting?: Type<WMSBaseModuleOptionsFactory>;
}
// 选项工厂接口
interface WMSBaseModuleOptionsFactory {
createWMSBaseModuleOptions(): Promise<WMSBaseModuleOptions> | WMSBaseModuleOptions;
}Symbol Tokens
符号令牌
可用於依賴注入的 Symbol Tokens:
typescript
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,
) {}
}可用于依赖注入的Symbol Tokens:
typescript
import {
// 仓库令牌
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提供器令牌
PROVIDE_LOCATION_ENTITY,
PROVIDE_MATERIAL_ENTITY,
PROVIDE_BATCH_ENTITY,
PROVIDE_ORDER_ENTITY,
PROVIDE_STOCK_ENTITY,
PROVIDE_WAREHOUSE_MAP_ENTITY,
// 选项令牌
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
完整示例
typescript
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 {}typescript
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: falseStockQuantityNotEnoughError如果,出库数量超过库存会抛出。
先查询库存再执行出库操作。
allowNegativeStock: falseStockQuantityNotEnoughError交易失敗
交易失败
createOrdercreateOrder倉位無法封存
储位无法归档
當嘗試封存一個仍有庫存的倉位時,會拋出 。
需先將庫存移出(出庫或調撥)後才能封存倉位。
LocationCannotArchiveError当尝试归档一个仍有库存的储位时,会抛出。
需先将库存移出(出库或调拨)后才能归档储位。
LocationCannotArchiveError倉位已存在錯誤
储位已存在错误
建立儲位時,若 ID 已存在(含已封存的),會拋出 。
可以先解除封存 () 或使用不同的 ID。
LocationAlreadyExistedErrorunArchive创建储位时,若ID已存在(含已归档的),会抛出。
可以先取消归档()或使用不同的ID。
LocationAlreadyExistedErrorunArchive