本文目录导读:

实现一个PHP项目的货位管理(Warehouse Location Management),核心是 “库位编码” 与 “库存绑定”,就是记录“哪个商品、在哪个货架、哪个层、哪个格子”。
以下是一个从数据库设计到代码实现的完整方案,适用于进销存(WMS)或电商后台系统。
第一阶段:数据库设计
货位管理的核心是三张表:仓库表、货位表、库存表。
-- 1. 仓库表 CREATE TABLE `warehouse` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '仓库名称', `code` varchar(20) NOT NULL COMMENT '仓库编码', `address` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `code` (`code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 2. 货位表 (核心表) CREATE TABLE `location` ( `id` int(11) NOT NULL AUTO_INCREMENT, `warehouse_id` int(11) NOT NULL COMMENT '所属仓库', `code` varchar(50) NOT NULL COMMENT '货位编码,如 A-01-01-01', `area` varchar(50) DEFAULT NULL COMMENT '分区(如 A区、B区、收货区)', `shelf` varchar(50) DEFAULT NULL COMMENT '货架号', `layer` varchar(20) DEFAULT NULL COMMENT '层', `slot` varchar(20) DEFAULT NULL COMMENT '格子', `status` tinyint(1) DEFAULT '0' COMMENT '状态:0空闲,1占用,2锁定,3损坏', `capacity` decimal(10,2) DEFAULT NULL COMMENT '最大容量(体积或重量)', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uniq_code` (`warehouse_id`, `code`), -- 同一仓库下货位编码唯一 KEY `warehouse_id` (`warehouse_id`), FOREIGN KEY (`warehouse_id`) REFERENCES `warehouse` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 3. 库存表 (关联货位) CREATE TABLE `inventory` ( `id` int(11) NOT NULL AUTO_INCREMENT, `product_id` int(11) NOT NULL COMMENT '商品ID', `location_id` int(11) NOT NULL COMMENT '货位ID', `batch_no` varchar(50) DEFAULT NULL COMMENT '批次号', `quantity` int(11) NOT NULL DEFAULT '0' COMMENT '数量', `locked_quantity` int(11) DEFAULT '0' COMMENT '锁定数量(预占)', `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uniq_product_location_batch` (`product_id`, `location_id`, `batch_no`), KEY `location_id` (`location_id`), FOREIGN KEY (`location_id`) REFERENCES `location` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
第二阶段:货位编码规范
推荐使用层级编码,便于系统自动解析和人员现场识别。
格式: 仓库码-分区-货架-层-格子
示例: WH01-A-03-02-01
- WH01:仓库
- A:A区
- 03:3号货架
- 02:第2层
- 01:第1个格子
PHP生成方法:
function generateLocationCode(string $warehouseCode, string $area, string $shelf, string $layer, string $slot): string {
return strtoupper("{$warehouseCode}-{$area}-{$shelf}-{$layer}-{$slot}");
}
// 调用:generateLocationCode('WH01', 'A', '03', '02', '01')
// 结果:WH01-A-03-02-01
第三阶段:核心业务逻辑(PHP实现)
初始化货位(批量创建)
class LocationService {
public function createLocations(array $data): array {
$db = getDB(); // 假设有数据库连接
$result = ['success' => 0, 'errors' => []];
foreach ($data as $loc) {
$code = generateLocationCode(
$loc['warehouse_code'],
$loc['area'],
$loc['shelf'],
$loc['layer'],
$loc['slot']
);
// 检查唯一性
$stmt = $db->prepare("SELECT id FROM location WHERE warehouse_id = ? AND code = ?");
$stmt->execute([$loc['warehouse_id'], $code]);
if ($stmt->fetch()) {
$result['errors'][] = "货位 {$code} 已存在";
continue;
}
// 插入
$insert = $db->prepare(
"INSERT INTO location (warehouse_id, code, area, shelf, layer, slot, status)
VALUES (?, ?, ?, ?, ?, ?, 0)"
);
$insert->execute([
$loc['warehouse_id'], $code, $loc['area'],
$loc['shelf'], $loc['layer'], $loc['slot']
]);
$result['success']++;
}
return $result;
}
}
商品入库(上架到指定货位)
关键点:更新库存表 + 更新货位状态(需用事务)。
class InventoryService {
public function putaway(int $productId, int $locationId, int $quantity, ?string $batchNo) {
$db = getDB();
$db->beginTransaction();
try {
// 1. 检查货位状态
$locStmt = $db->prepare("SELECT status FROM location WHERE id = ? FOR UPDATE");
$locStmt->execute([$locationId]);
$loc = $locStmt->fetch();
if ($loc['status'] != 0) {
throw new \Exception("货位不可用");
}
// 2. 检查是否有库存记录(同一商品+同一货位+同一批次)
$invStmt = $db->prepare(
"SELECT id, quantity FROM inventory
WHERE product_id = ? AND location_id = ? AND (batch_no = ? OR (batch_no IS NULL AND ? IS NULL))
FOR UPDATE"
);
$invStmt->execute([$productId, $locationId, $batchNo, $batchNo]);
$inventory = $invStmt->fetch();
if ($inventory) {
// 已有记录,增加数量
$update = $db->prepare("UPDATE inventory SET quantity = quantity + ? WHERE id = ?");
$update->execute([$quantity, $inventory['id']]);
} else {
// 新记录
$insert = $db->prepare(
"INSERT INTO inventory (product_id, location_id, batch_no, quantity) VALUES (?, ?, ?, ?)"
);
$insert->execute([$productId, $locationId, $batchNo, $quantity]);
}
// 3. 标记货位为占用
$updateLoc = $db->prepare("UPDATE location SET status = 1 WHERE id = ?");
$updateLoc->execute([$locationId]);
$db->commit();
return true;
} catch (\Exception $e) {
$db->rollBack();
throw $e;
}
}
}
查询库存所在货位
class ProductService {
public function getLocationByProduct(int $productId): array {
$db = getDB();
$stmt = $db->prepare(
"SELECT l.code as location_code, l.area, l.shelf, l.layer, l.slot,
i.quantity, i.batch_no
FROM inventory i
JOIN location l ON i.location_id = l.id
WHERE i.product_id = ?
ORDER BY l.code"
);
$stmt->execute([$productId]);
return $stmt->fetchAll();
}
}
货位推荐算法(入库时自动推荐)
class LocationRecommendService {
public function recommendLocation(int $warehouseId, int $productId): ?int {
// 策略:优先推荐同一商品已有库存的货位(合并存放)
// 其次推荐空闲货位(按货位编码排序,从A区开始)
$db = getDB();
// 1. 查找同一商品已经占用的货位
$stmt = $db->prepare(
"SELECT l.id, l.code
FROM inventory i
JOIN location l ON i.location_id = l.id
WHERE l.warehouse_id = ? AND i.product_id = ?
ORDER BY l.code
LIMIT 1"
);
$stmt->execute([$warehouseId, $productId]);
$existing = $stmt->fetch();
if ($existing) {
return $existing['id']; // 合并存放
}
// 2. 如果没有,找空闲货位(按编码排序,从A01开始)
$stmt = $db->prepare(
"SELECT id, code FROM location
WHERE warehouse_id = ? AND status = 0
ORDER BY code
LIMIT 1"
);
$stmt->execute([$warehouseId]);
$free = $stmt->fetch();
return $free ? $free['id'] : null; // null表示仓库已满
}
}
货位可视化(简单列表展示)
// 控制器
public function index(Request $request) {
$warehouseId = $request->get('warehouse_id', 1);
$locations = $this->locationService->getLocationsTree($warehouseId);
echo $this->render('locations/index', ['locations' => $locations]);
}
// Service
public function getLocationsTree(int $warehouseId): array {
$db = getDB();
$stmt = $db->prepare(
"SELECT l.*, i.product_id, i.quantity
FROM location l
LEFT JOIN inventory i ON l.id = i.location_id
WHERE l.warehouse_id = ?
ORDER BY l.area, l.shelf, l.layer, l.slot"
);
$stmt->execute([$warehouseId]);
$rows = $stmt->fetchAll();
// 整理成树形结构,便于前端展示
$tree = [];
foreach ($rows as $row) {
$tree[$row['area']][$row['shelf']][$row['layer']][] = $row;
}
return $tree;
}
第四阶段:前端交互与辅助功能
货位批量录入
- 方式:后台提供一个可视化表格,管理员输入
起始货架、层数、每层格子数,系统自动生成编码。function batchGenerate(int $warehouseId, string $area, string $startShelf, int $shelfCount, int $layers, int $slots): array { $data = []; for ($s = 0; $s < $shelfCount; $s++) { $shelf = str_pad($startShelf + $s, 2, '0', STR_PAD_LEFT); for ($l = 1; $l <= $layers; $l++) { for ($sl = 1; $sl <= $slots; $sl++) { $data[] = [ 'warehouse_id' => $warehouseId, 'area' => $area, 'shelf' => $shelf, 'layer' => str_pad($l, 2, '0', STR_PAD_LEFT), 'slot' => str_pad($sl, 2, '0', STR_PAD_LEFT), ]; } } } return $data; }
货位扫码
- 原理:每个货位生成二维码(内容为
code字段),PDA扫码后,PHP接口接收扫码结果,直接定位货位。// 扫码查询接口 public function scan(string $locationCode): array { $db = getDB(); $stmt = $db->prepare( "SELECT l.*, GROUP_CONCAT(CONCAT(p.name, ' x', i.quantity) SEPARATOR ', ') as products FROM location l LEFT JOIN inventory i ON l.id = i.location_id LEFT JOIN products p ON i.product_id = p.id WHERE l.code = ? GROUP BY l.id" ); $stmt->execute([$locationCode]); return $stmt->fetch() ?: ['error' => '货位不存在']; }
库存移动/调拨
public function moveStock(int $fromLocationId, int $toLocationId, int $productId, int $quantity) {
$db = getDB();
$db->beginTransaction();
try {
// 1. 扣减源货位
$stmt = $db->prepare("UPDATE inventory SET quantity = quantity - ? WHERE location_id = ? AND product_id = ?");
$stmt->execute([$quantity, $fromLocationId, $productId]);
// 若源货位数量变为0,释放货位
$check = $db->prepare("SELECT SUM(quantity) as total FROM inventory WHERE location_id = ?");
$check->execute([$fromLocationId]);
if ($check->fetch()['total'] == 0) {
$db->prepare("UPDATE location SET status = 0 WHERE id = ?")->execute([$fromLocationId]);
}
// 2. 增加目标货位(逻辑同入库)
// ... (参考上文的 putaway 子逻辑)
$db->commit();
} catch (\Exception $e) {
$db->rollBack();
throw $e;
}
}
PHP货位管理的核心要点
- 编码统一:
仓库-区-架-层-位,是系统与物理世界的映射。 - 事务必须:库存变动(入库、出库、移动)务必使用 数据库事务,防止数据不一致。
- 状态分离:
location.status只表示货位物理状态(空闲/占用/损坏),库存数量由inventory.quantity管理。 - 锁定机制:高并发场景下(如PDA抢单),使用
SELECT ... FOR UPDATE行锁。 - 扩展性:未来如果增加RFID、AGV调度,只需在
location表增加rfid_tag、coordinate_x/y等字段即可。
方案基于纯PHP + MySQL,没有依赖特定框架,你可以直接适配到 Laravel、ThinkPHP、Yii 等框架中。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播