PHP项目如何实现采购订单?

wen java案例 4

本文目录导读:

PHP项目如何实现采购订单?

  1. 第一阶段:数据库设计与建模
  2. 第二阶段:后端PHP业务逻辑(以MVC为例)
  3. 第三阶段:前端交互
  4. 第四阶段:关键注意事项
  5. 最小可行实现步骤

在PHP项目中实现采购订单功能,通常需要构建一个完整的增删改查(CRUD) 流程,并涉及数据建模、业务逻辑和前端交互

下面是一个从零开始的、结构化的实现方案,适用于中小型项目(如ERP、进销存系统)。


第一阶段:数据库设计与建模

采购订单的核心是一对多关系:一笔订单(主表)对应多个商品明细(子表)。

主表 purchase_orders

CREATE TABLE `purchase_orders` (
  `id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `order_no` VARCHAR(32) NOT NULL COMMENT '订单编号(自动生成)',
  `supplier_id` INT(11) UNSIGNED NOT NULL COMMENT '供应商ID',
  `warehouse_id` INT(11) UNSIGNED DEFAULT NULL COMMENT '目标仓库ID',
  `order_date` DATE NOT NULL COMMENT '下单日期',
  `expected_delivery_date` DATE DEFAULT NULL COMMENT '预计到货日期',
  `status` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '状态: 0=待审核, 1=已审核, 2=已收货, 3=已完成, 9=已取消',
  `total_amount` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '总金额',
  `created_by` INT(11) UNSIGNED DEFAULT NULL COMMENT '创建人ID',
  `remark` TEXT COMMENT '备注',
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

明细表 purchase_order_items

CREATE TABLE `purchase_order_items` (
  `id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `order_id` INT(11) UNSIGNED NOT NULL COMMENT '订单主表ID',
  `product_id` INT(11) UNSIGNED NOT NULL COMMENT '商品ID',
  `product_name` VARCHAR(100) NOT NULL COMMENT '冗余商品名(防止商品修改)',
  `product_sku` VARCHAR(50) DEFAULT NULL COMMENT '冗余SKU',
  `quantity` INT(11) NOT NULL DEFAULT 1 COMMENT '采购数量',
  `unit_price` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '采购单价',
  `subtotal` DECIMAL(12,2) GENERATED ALWAYS AS (quantity * unit_price) STORED COMMENT '小计(可计算字段)',
  `received_quantity` INT(11) NOT NULL DEFAULT 0 COMMENT '已收货数量',
  `remark` TEXT COMMENT '明细备注',
  FOREIGN KEY (`order_id`) REFERENCES `purchase_orders`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

第二阶段:后端PHP业务逻辑(以MVC为例)

创建订单(核心业务)

流程: 验证输入 → 生成订单号 → 计算总金额 → 事务写入主表和明细表。

// Controller: PurchaseOrderController.php
public function store(Request $request)
{
    // 1. 验证数据
    $validated = $request->validate([
        'supplier_id' => 'required|integer|exists:suppliers,id',
        'items' => 'required|array|min:1',
        'items.*.product_id' => 'required|integer|exists:products,id',
        'items.*.quantity' => 'required|integer|min:1',
        'items.*.unit_price' => 'required|numeric|min:0',
    ]);
    // 2. 开启数据库事务
    DB::beginTransaction();
    try {
        // 3. 生成订单号:PO + 年月日 + 5位流水号
        $orderNo = 'PO' . date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
        // 4. 计算总金额
        $totalAmount = 0;
        foreach ($validated['items'] as $item) {
            $totalAmount += $item['quantity'] * $item['unit_price'];
        }
        // 5. 插入主表
        $order = PurchaseOrder::create([
            'order_no'    => $orderNo,
            'supplier_id' => $validated['supplier_id'],
            'order_date'  => date('Y-m-d'),
            'status'      => 0, // 待审核
            'total_amount'=> $totalAmount,
            'created_by'  => auth()->id(),
        ]);
        // 6. 插入明细表
        foreach ($validated['items'] as $item) {
            $product = Product::find($item['product_id']);
            $order->items()->create([
                'product_id'   => $item['product_id'],
                'product_name' => $product->name,
                'product_sku'  => $product->sku,
                'quantity'     => $item['quantity'],
                'unit_price'   => $item['unit_price'],
            ]);
        }
        DB::commit();
        return response()->json(['message' => '订单创建成功', 'data' => $order]);
    } catch (\Exception $e) {
        DB::rollBack();
        return response()->json(['error' => '创建失败: ' . $e->getMessage()], 500);
    }
}

审核与收货(状态流转)

采购订单有生命周期,典型的状态机如下:

// 审核操作
public function approve($id)
{
    $order = PurchaseOrder::findOrFail($id);
    if ($order->status != 0) {
        return response()->json(['error' => '仅待审核订单可审核'], 400);
    }
    $order->status = 1; // 已审核
    $order->save();
    // 可选择:通知采购员/供应商
}
// 收货入库(最复杂,会涉及库存变化)
public function receive($id, Request $request)
{
    $order = PurchaseOrder::findOrFail($id);
    if ($order->status != 1) {
        return response()->json(['error' => '仅已审核订单可收货'], 400);
    }
    DB::transaction(function () use ($order, $request) {
        foreach ($request->input('items') as $item) {
            // 1. 更新订单明细的已收货数量
            $orderItem = $order->items()->where('product_id', $item['product_id'])->first();
            $newReceived = $orderItem->received_quantity + $item['receive_quantity'];
            if ($newReceived > $orderItem->quantity) {
                throw new \Exception("收货数量超过采购数量");
            }
            $orderItem->update(['received_quantity' => $newReceived]);
            // 2. 增加库存(假设有 inventory_movements 表或直接更新库存表)
            Inventory::increase($item['product_id'], $order->warehouse_id, $item['receive_quantity']);
        }
        // 3. 判断是否全部收货,若是则更新订单状态
        $allReceived = $order->items->every(function ($item) {
            return $item->received_quantity >= $item->quantity;
        });
        if ($allReceived) {
            $order->status = 2; // 已收货
            $order->save();
        }
    });
}

第三阶段:前端交互

推荐使用前后端分离(Vue/React + API)或 服务端渲染(Blade/Twig)。

典型前端流程:

  1. 选择供应商 → 下拉框异步加载。
  2. 添加商品行
    • 输入商品名称或扫码,通过AJAX搜索返回商品价格、单位。
    • 每行可修改数量、单价,实时计算小计和合计
  3. 提交前校验:数量>0,单价>0,至少一行。
  4. 提交订单:发送JSON到后端/api/purchase-orders

示例(Vue3 + Axios):

// 提交采购订单
async function submitOrder() {
    const formData = {
        supplier_id: selectedSupplier.value.id,
        items: orderLines.value.map(line => ({
            product_id: line.product.id,
            quantity: line.quantity,
            unit_price: line.price
        }))
    };
    const res = await axios.post('/api/purchase-orders', formData);
    if (res.data.message) {
        alert('创建成功');
        // 清空表单或跳转
    }
}

第四阶段:关键注意事项

数据一致性(必须使用事务)

采购订单涉及主表+明细表+库存操作,必须用 DB::transaction()beginTransaction/commit/rollback 保证原子性。

防止并发问题

高并发场景下,库存扣减和状态更新可能冲突,建议:

  • 使用数据库行锁 (SELECT ... FOR UPDATE)。
  • 或者使用Redis分布式锁。

金额精度

使用 DECIMAL(10,2)DECIMAL(12,2)绝对不要用float/double

历史追溯

采购单一旦创建,商品名称/单价应冗余存储(如明细表中的product_name),防止未来商品信息变更导致历史单据混乱。

权限控制

  • 普通员工:可创建、查看自己的订单。
  • 主管:可审核。
  • 仓库人员:可收货。

与其他模块联动

  • 供应商模块:选择供应商时报出历史采购价。
  • 库存模块:收货后自动增加库存。
  • 财务模块:生成应付账款记录。

最小可行实现步骤

  1. 设计数据库表(主表 + 明细表)。
  2. 用框架(Laravel/ThinkPHP/Symfony)搭建模型和迁移。
  3. 实现核心API:
    • POST /api/purchase-orders(创建)
    • GET /api/purchase-orders/{id}(详情)
    • PUT /api/purchase-orders/{id}/approve(审核)
    • PUT /api/purchase-orders/{id}/receive(收货)
  4. 前端实现表单、列表页、状态管理。
  5. 加入日志、权限、异常处理。

这个结构适用于90%的中小型采购管理需求,如果需要更复杂的场景(如分批收货、退货、采购合同关联),可以在明细表增加更多字段(如已退货数量、合同ID)来扩展。

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