将现有WMS系统改造为生产可用的标准化三方仓储管理系统,解决所有架构问题,实现前后端完美联动。
Docker配置重构 (docker-compose.yml)
- ✅ 移除不存在的wms-gateway和odoo服务
- ✅ 统一端口: 后端8080, 前端3000, 数据库5432
- ✅ 添加Redis缓存服务
- ✅ 配置健康检查和依赖管理
- ✅ 统一数据库为PostgreSQL wms_db
后端配置标准化 (application.yml)
- ✅ 统一context-path为
/api/v1 - ✅ 统一端口为
8080 - ✅ 添加环境变量支持
- ✅ 配置Redis、JWT、业务参数
- ✅ 创建docker环境配置文件
前端配置标准化 (api.ts)
- ✅ 统一API_BASE_URL为
http://localhost:8080/api/v1 - ✅ 修复入库接口路径:
/inbound/orders/* - ✅ 修复出库接口路径:
/outbound/orders/*
InboundController.java - 修改所有路径
@RequestMapping("/inbound") // 保持不变
public class InboundController {
// GET /api/v1/inbound/orders?current=1&size=20&tenantId=1
@GetMapping("/orders")
public Result<Page<InboundOrder>> getOrdersByPage(...)
// GET /api/v1/inbound/orders/{id}
@GetMapping("/orders/{id}")
// GET /api/v1/inbound/orders/no/{orderNo}
@GetMapping("/orders/no/{orderNo}")
// POST /api/v1/inbound/orders
@PostMapping("/orders")
// PUT /api/v1/inbound/orders/{id}
@PutMapping("/orders/{id}")
// DELETE /api/v1/inbound/orders/{id}
@DeleteMapping("/orders/{id}")
// POST /api/v1/inbound/orders/{id}/confirm
@PostMapping("/orders/{id}/confirm")
// POST /api/v1/inbound/orders/{id}/receive
@PostMapping("/orders/{id}/receive")
// GET /api/v1/inbound/orders/{orderId}/lines
@GetMapping("/orders/{orderId}/lines")
// POST /api/v1/inbound/orders/{id}/complete
@PostMapping("/orders/{id}/complete")
// POST /api/v1/inbound/orders/{id}/cancel
@PostMapping("/orders/{id}/cancel")
}OutboundController.java - 需要新增或修改的方法
@RequestMapping("/outbound")
public class OutboundController {
@GetMapping("/orders") // 分页查询
@GetMapping("/orders/{id}") // 详情查询
@GetMapping("/orders/no/{orderNo}") // 单号查询
@PostMapping("/orders") // 创建
@PutMapping("/orders/{id}") // 更新
@DeleteMapping("/orders/{id}") // 删除
@PostMapping("/orders/{id}/confirm") // 确认
@PostMapping("/orders/{id}/allocate") // 分配库存
@PostMapping("/orders/{id}/start-picking") // 开始拣货
@PostMapping("/orders/{id}/complete-picking") // 完成拣货
@PostMapping("/orders/{id}/start-checking") // 开始复核
@PostMapping("/orders/{id}/complete-checking") // 完成复核
@PostMapping("/orders/{id}/ship") // 发货
@PostMapping("/orders/{id}/cancel") // 取消
@GetMapping("/orders/{orderId}/lines") // 获取明细
}其他Controller需统一检查:
- WarehouseController
- LocationController
- ProductController
- CustomerController
- SupplierController
- 等所有Controller
所有Controller返回统一使用:
public class Result<T> {
private Integer code; // 200成功, 其他失败
private String message;
private T data;
private Long timestamp;
}新增表结构 (database/schema/02_inventory.sql)
-- 库存预留表
CREATE TABLE wms.wms_inventory_reservation (
id BIGSERIAL PRIMARY KEY,
reservation_no VARCHAR(50) UNIQUE NOT NULL,
reservation_type VARCHAR(20) NOT NULL, -- ORDER, TRANSFER, LOCK
business_no VARCHAR(50),
business_id BIGINT,
warehouse_id BIGINT NOT NULL,
location_id BIGINT,
inventory_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
batch_no VARCHAR(50),
reserved_quantity DECIMAL(15,3) NOT NULL,
used_quantity DECIMAL(15,3) DEFAULT 0,
remaining_quantity DECIMAL(15,3),
status VARCHAR(20) NOT NULL, -- ACTIVE, USED, RELEASED, EXPIRED
reservation_time TIMESTAMP NOT NULL,
expiry_time TIMESTAMP,
release_time TIMESTAMP,
operator_id BIGINT,
operator_name VARCHAR(50),
remark TEXT,
tenant_id BIGINT NOT NULL DEFAULT 1,
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_reservation_inventory ON wms.wms_inventory_reservation(inventory_id, status);
CREATE INDEX idx_reservation_business ON wms.wms_inventory_reservation(business_no, business_id);
CREATE INDEX idx_reservation_expiry ON wms.wms_inventory_reservation(expiry_time, status);新增Service (InventoryReservationService.java)
public interface InventoryReservationService {
// 创建预留
InventoryReservation reserve(ReservationRequest request);
// 使用预留
void use(Long reservationId, BigDecimal quantity);
// 释放预留
void release(Long reservationId);
// 过期预留自动释放(定时任务)
void autoReleaseExpired();
// 查询预留
List<InventoryReservation> findByBusinessNo(String businessNo);
}OutboundService集成
- 创建出库单时自动预留库存
- 拣货完成时使用预留
- 取消出库单时释放预留
新增表结构 (database/schema/03_inbound.sql)
-- ASN预约收货通知表
CREATE TABLE wms.wms_asn (
id BIGSERIAL PRIMARY KEY,
asn_no VARCHAR(50) UNIQUE NOT NULL,
warehouse_id BIGINT NOT NULL,
supplier_id BIGINT,
customer_id BIGINT,
asn_type VARCHAR(20) NOT NULL, -- PURCHASE, RETURN, TRANSFER
source_no VARCHAR(50),
appointment_date DATE NOT NULL,
appointment_time_from TIME,
appointment_time_to TIME,
contact_person VARCHAR(50),
contact_phone VARCHAR(20),
vehicle_no VARCHAR(20),
driver_name VARCHAR(50),
driver_phone VARCHAR(20),
total_quantity DECIMAL(15,3),
total_volume DECIMAL(15,3),
total_weight DECIMAL(15,3),
status VARCHAR(20) NOT NULL, -- SUBMITTED, CONFIRMED, ARRIVED, RECEIVING, COMPLETED, CANCELLED
dock_door VARCHAR(20),
arrival_time TIMESTAMP,
start_receive_time TIMESTAMP,
finish_receive_time TIMESTAMP,
remark TEXT,
tenant_id BIGINT NOT NULL DEFAULT 1,
created_by BIGINT,
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_by BIGINT,
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_asn_date ON wms.wms_asn(warehouse_id, appointment_date, status);ASN流程:
客户提交ASN → 仓库确认 → 到货签到 → 自动创建入库单 → 收货上架
在InboundService中增加:
public class ReceiveDifference {
private Long lineId;
private BigDecimal plannedQty; // 计划数量
private BigDecimal receivedQty; // 实收数量
private BigDecimal differenceQty; // 差异数量
private String differenceType; // MORE多收, LESS少收, DAMAGE破损
private String differenceReason; // 差异原因
private String handleMethod; // 处理方式: ACCEPT接受, REJECT拒收, REPORT上报
}新建PDAController (services/wms-basic-service/src/main/java/com/wms/basic/controller/PDAController.java)
@RestController
@RequestMapping("/pda")
public class PDAController {
// PDA登录
@PostMapping("/auth/login")
public Result<PDASession> login(@RequestBody PDALoginRequest request);
// 扫描条码解析
@PostMapping("/barcode/scan")
public Result<BarcodeInfo> scanBarcode(@RequestBody ScanRequest request);
// 查询产品信息
@GetMapping("/products/{code}")
public Result<ProductInfo> getProductByCode(@PathVariable String code);
// 查询库位信息
@GetMapping("/locations/{code}")
public Result<LocationInfo> getLocationByCode(@PathVariable String code);
// 查询库存
@PostMapping("/inventory/query")
public Result<List<InventoryInfo>> queryInventory(@RequestBody QueryRequest request);
// === 入库操作 ===
// 获取待收货任务
@GetMapping("/inbound/tasks")
public Result<List<InboundTask>> getInboundTasks();
// 扫码收货
@PostMapping("/inbound/receive")
public Result<ReceiveResult> receive(@RequestBody ReceiveRequest request);
// 扫码上架
@PostMapping("/inbound/putaway")
public Result<PutawayResult> putaway(@RequestBody PutawayRequest request);
// === 出库操作 ===
// 获取拣货任务
@GetMapping("/outbound/picking-tasks")
public Result<List<PickingTask>> getPickingTasks();
// 扫码拣货
@PostMapping("/outbound/pick")
public Result<PickResult> pick(@RequestBody PickRequest request);
// 扫码复核
@PostMapping("/outbound/check")
public Result<CheckResult> check(@RequestBody CheckRequest request);
// === 移库操作 ===
// 获取移库任务
@GetMapping("/transfer/tasks")
public Result<List<TransferTask>> getTransferTasks();
// 扫码移库
@PostMapping("/transfer/move")
public Result<MoveResult> move(@RequestBody MoveRequest request);
// === 盘点操作 ===
// 获取盘点任务
@GetMapping("/stocktaking/tasks")
public Result<List<StocktakingTask>> getStocktakingTasks();
// 扫码盘点
@PostMapping("/stocktaking/count")
public Result<CountResult> count(@RequestBody CountRequest request);
// 提交盘点结果
@PostMapping("/stocktaking/submit")
public Result<Void> submitStocktaking(@RequestBody SubmitRequest request);
}技术选型:
- H5: React + Vite + Ant Design Mobile
- 原生: React Native 或 Flutter
核心页面:
/pda/login # 登录页
/pda/home # 主菜单
/pda/scan # 扫码页(通用)
/pda/inbound # 入库管理
/pda/inbound/receive # 收货
/pda/inbound/putaway # 上架
/pda/outbound # 出库管理
/pda/outbound/pick # 拣货
/pda/outbound/check # 复核
/pda/transfer # 移库
/pda/stocktaking # 盘点
/pda/query # 查询
扫码功能实现:
// 使用html5-qrcode库
import { Html5Qrcode } from "html5-qrcode";
const scanner = new Html5Qrcode("reader");
scanner.start(
{ facingMode: "environment" },
{ fps: 10, qrbox: 250 },
(decodedText) => {
// 扫码成功,调用后端API解析
pda.scanBarcode(decodedText);
}
);WaveService增强
/**
* 智能波次算法
* 根据订单特征自动分波
*/
public class SmartWaveStrategy {
/**
* 按区域分波
* 同一拣货区域的订单分到一个波次
*/
public List<WaveOrder> allocateByZone(List<OutboundOrder> orders);
/**
* 按客户分波
* 同一客户的多个订单合并
*/
public List<WaveOrder> allocateByCustomer(List<OutboundOrder> orders);
/**
* 按时效分波
* 紧急订单优先,时效相同的合并
*/
public List<WaveOrder> allocateByPriority(List<OutboundOrder> orders);
/**
* 混合策略
* 综合考虑区域、时效、订单量
*/
public List<WaveOrder> allocateBySmart(List<OutboundOrder> orders);
}拣货路径优化
/**
* 拣货路径优化算法
* 基于TSP(旅行商问题)求解最短拣货路径
*/
public class PickingPathOptimizer {
/**
* S型路径: 按货架顺序依次拣货
*/
public List<PickingTask> optimizeBySType(List<Location> locations);
/**
* Z型路径: 之字形穿越货架
*/
public List<PickingTask> optimizeByZType(List<Location> locations);
/**
* 最短路径: 使用遗传算法计算
*/
public List<PickingTask> optimizeByShortestPath(List<Location> locations);
}PrintController.java
@RestController
@RequestMapping("/print")
public class PrintController {
// 打印入库标签
@PostMapping("/inbound/label")
public Result<String> printInboundLabel(@RequestBody PrintRequest request);
// 打印拣货单
@PostMapping("/picking/list")
public Result<String> printPickingList(@RequestBody PrintRequest request);
// 打印发货标签
@PostMapping("/shipping/label")
public Result<String> printShippingLabel(@RequestBody PrintRequest request);
// 打印库位标签
@PostMapping("/location/label")
public Result<String> printLocationLabel(@RequestBody PrintRequest request);
}打印模板设计(使用Jasper Reports或iText)
templates/
├── inbound_label.jrxml # 入库标签
├── picking_list.jrxml # 拣货单
├── shipping_label.jrxml # 发货标签
└── location_label.jrxml # 库位标签
新增表 (wms_billing_rule)
CREATE TABLE wms.wms_billing_rule (
id BIGSERIAL PRIMARY KEY,
rule_code VARCHAR(50) UNIQUE NOT NULL,
rule_name VARCHAR(100) NOT NULL,
customer_id BIGINT, -- 客户ID(NULL表示通用规则)
rule_type VARCHAR(20) NOT NULL, -- STORAGE-仓储费, INBOUND-入库费, OUTBOUND-出库费, OPERATION-作业费
billing_method VARCHAR(20), -- FIXED-固定, QUANTITY-按数量, VOLUME-按体积, WEIGHT-按重量, DAYS-按天数
unit_price DECIMAL(15,2),
min_charge DECIMAL(15,2), -- 最低收费
free_period INT, -- 免费期(天)
calculation_formula TEXT, -- 计费公式
enabled BOOLEAN DEFAULT TRUE,
effective_date DATE,
expiry_date DATE,
tenant_id BIGINT NOT NULL DEFAULT 1
);计费公式示例:
仓储费 = (体积 × 单价 × 天数) + (超出免费期天数 × 超期单价)
入库费 = 件数 × 单价 + (体积 > 阈值 ? 超大件费 : 0)
出库费 = 件数 × 单价 + 拣货费 + 包装费
新建模块 (services/wms-portal)
wms-portal/
├── pages/
│ ├── login/ # 客户登录
│ ├── dashboard/ # 客户仪表板
│ ├── inventory/ # 库存查询
│ ├── orders/ # 订单查询
│ ├── inbound/ # 入库管理
│ ├── outbound/ # 出库管理
│ ├── reports/ # 报表查询
│ └── billing/ # 账单查询
功能清单:
- 实时库存查询
- 入库单查询与创建
- 出库单查询与创建
- 库龄分析
- 账单明细查询
- 数据报表导出
新增视图 (database/views/inventory_aging.sql)
CREATE VIEW wms.v_inventory_aging AS
SELECT
i.id,
i.product_id,
p.product_code,
p.product_name,
i.warehouse_id,
i.batch_no,
i.available_quantity,
i.created_time,
CURRENT_DATE - i.created_time::date AS aging_days,
CASE
WHEN CURRENT_DATE - i.created_time::date <= 30 THEN '0-30天'
WHEN CURRENT_DATE - i.created_time::date <= 60 THEN '31-60天'
WHEN CURRENT_DATE - i.created_time::date <= 90 THEN '61-90天'
WHEN CURRENT_DATE - i.created_time::date <= 180 THEN '91-180天'
ELSE '180天以上'
END AS aging_range
FROM wms.wms_inventory i
JOIN wms.wms_product p ON i.product_id = p.id
WHERE i.available_quantity > 0;-- 入库单索引
CREATE INDEX idx_inbound_order_status ON wms.wms_inbound_order(warehouse_id, status, created_time);
CREATE INDEX idx_inbound_order_supplier ON wms.wms_inbound_order(supplier_id, created_time);
CREATE INDEX idx_inbound_line_product ON wms.wms_inbound_order_line(product_id, status);
-- 出库单索引
CREATE INDEX idx_outbound_order_status ON wms.wms_outbound_order(warehouse_id, status, created_time);
CREATE INDEX idx_outbound_order_customer ON wms.wms_outbound_order(customer_id, created_time);
CREATE INDEX idx_outbound_line_product ON wms.wms_outbound_order_line(product_id, status);
-- 库存索引
CREATE INDEX idx_inventory_product ON wms.wms_inventory(product_id, warehouse_id, status);
CREATE INDEX idx_inventory_location ON wms.wms_inventory(location_id, status);
CREATE INDEX idx_inventory_batch ON wms.wms_inventory(batch_no, product_id);
CREATE INDEX idx_inventory_available ON wms.wms_inventory(product_id, available_quantity)
WHERE available_quantity > 0;
-- 库存事务索引
CREATE INDEX idx_inventory_transaction_time ON wms.wms_inventory_transaction(transaction_time);
CREATE INDEX idx_inventory_transaction_product ON wms.wms_inventory_transaction(product_id, transaction_time);
-- 波次索引
CREATE INDEX idx_wave_status ON wms.wms_wave_order(warehouse_id, status, wave_date);-- 按月分区历史订单表
CREATE TABLE wms.wms_inbound_order_history (
LIKE wms.wms_inbound_order INCLUDING ALL
) PARTITION BY RANGE (created_time);
-- 创建分区
CREATE TABLE wms.wms_inbound_order_2025_01
PARTITION OF wms.wms_inbound_order_history
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
-- 自动归档策略(6个月前数据)
CREATE OR REPLACE FUNCTION archive_old_orders()
RETURNS void AS $$
BEGIN
INSERT INTO wms.wms_inbound_order_history
SELECT * FROM wms.wms_inbound_order
WHERE created_time < CURRENT_DATE - INTERVAL '6 months';
DELETE FROM wms.wms_inbound_order
WHERE created_time < CURRENT_DATE - INTERVAL '6 months';
END;
$$ LANGUAGE plpgsql;- 修改所有后端Controller路径
- 测试前后端联调
- 更新API文档
- 实现库存预留锁定机制
- 实现ASN预约收货
- 完善收货差异处理
- 单元测试
- 开发PDA后端API
- 开发PDA H5前端
- 集成扫码功能
- 现场测试
- 实现智能波次算法
- 优化拣货路径
- 开发打印服务
- 设计打印模板
- 实现计费规则引擎
- 开发客户门户
- 实现库龄分析
- 添加索引
- 配置分区
- 全功能测试
- 性能测试
- Docker镜像构建
- 生产环境部署
- 数据迁移
- 用户培训
后端:
services/wms-basic-service/src/main/java/com/wms/basic/
├── entity/
│ ├── InventoryReservation.java # 库存预留实体
│ ├── ASN.java # ASN实体
│ ├── ASNLine.java # ASN明细
│ ├── BillingRule.java # 计费规则
│ └── BillingRecord.java # 计费记录
├── repository/
│ ├── InventoryReservationRepository.java
│ ├── ASNRepository.java
│ └── BillingRuleRepository.java
├── service/
│ ├── InventoryReservationService.java # 预留服务
│ ├── ASNService.java # ASN服务
│ ├── BillingService.java # 计费服务
│ └── PickingPathOptimizer.java # 路径优化
├── controller/
│ ├── PDAController.java # PDA接口
│ ├── PrintController.java # 打印接口
│ └── PortalController.java # 客户门户接口
└── dto/
├── ReservationRequest.java
├── ASNRequest.java
└── PDA相关DTO...
数据库:
database/schema/
├── 02_inventory_enhancement.sql # 库存增强
├── 03_inbound_asn.sql # ASN表
├── 11_billing.sql # 计费模块
├── 12_indexes.sql # 索引优化
└── 13_partitions.sql # 分区配置
前端PDA(可选独立项目):
services/wms-pda/
├── public/
├── src/
│ ├── pages/ # 页面组件
│ ├── components/ # 通用组件
│ ├── services/ # API调用
│ ├── utils/ # 工具函数
│ └── App.tsx
├── package.json
└── vite.config.ts
- 前后端接口100%匹配,无404错误
- 入库流程完整:ASN→收货→上架
- 出库流程完整:预留→拣货→复核→发货
- PDA扫码功能正常
- 打印服务正常输出
- 计费规则计算准确
- 接口响应时间 < 500ms (P95)
- 并发用户数 >= 100
- 数据库查询使用索引
- 事务处理无死锁
- 7x24小时运行无崩溃
- 异常处理覆盖完整
- 日志记录完善
- 可回滚升级
- 数据备份: 改造前务必全量备份数据库
- 灰度发布: 建议先在测试环境验证,再逐步切换生产
- 向后兼容: 保留旧接口一段时间,避免客户端无法访问
- 文档同步: 及时更新API文档和用户手册
- 培训计划: 提前培训仓库操作人员使用新功能
文档版本: v1.0 创建日期: 2025-10-07 维护团队: WMS开发团队