PHP项目怎么实现货位管理?

wen java案例 5

本文目录导读:

PHP项目怎么实现货位管理?

  1. 第一阶段:数据库设计
  2. 第二阶段:货位编码规范
  3. 第三阶段:核心业务逻辑(PHP实现)
  4. 第四阶段:前端交互与辅助功能
  5. PHP货位管理的核心要点

实现一个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货位管理的核心要点

  1. 编码统一仓库-区-架-层-位,是系统与物理世界的映射。
  2. 事务必须:库存变动(入库、出库、移动)务必使用 数据库事务,防止数据不一致。
  3. 状态分离location.status 只表示货位物理状态(空闲/占用/损坏),库存数量由 inventory.quantity 管理。
  4. 锁定机制:高并发场景下(如PDA抢单),使用 SELECT ... FOR UPDATE 行锁。
  5. 扩展性:未来如果增加RFID、AGV调度,只需在 location 表增加 rfid_tagcoordinate_x/y 等字段即可。

方案基于纯PHP + MySQL,没有依赖特定框架,你可以直接适配到 Laravel、ThinkPHP、Yii 等框架中。

抱歉,评论功能暂时关闭!