如何用PHP项目实现到货验收?

wen java案例 2

本文目录导读:

如何用PHP项目实现到货验收?

  1. 数据库设计
  2. PHP后端核心代码
  3. 前端界面示例
  4. 路由配置
  5. 注意事项

我来详细介绍如何用PHP实现到货验收功能,这是一个完整的解决方案,包含数据库设计、前端界面和业务逻辑。

数据库设计

首先创建必要的数据表:

-- 采购订单表
CREATE TABLE purchase_orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    order_no VARCHAR(50) UNIQUE NOT NULL,
    supplier_name VARCHAR(200) NOT NULL,
    order_date DATETIME NOT NULL,
    expected_delivery_date DATE,
    status ENUM('pending','partial','completed') DEFAULT 'pending',
    total_amount DECIMAL(10,2) DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 采购订单详情表
CREATE TABLE purchase_order_items (
    id INT PRIMARY KEY AUTO_INCREMENT,
    order_id INT NOT NULL,
    product_name VARCHAR(200) NOT NULL,
    specification VARCHAR(500),
    quantity_ordered INT NOT NULL,
    quantity_received INT DEFAULT 0,
    unit VARCHAR(20),
    unit_price DECIMAL(10,2),
    FOREIGN KEY (order_id) REFERENCES purchase_orders(id)
);
-- 验收单表
CREATE TABLE receiving_notes (
    id INT PRIMARY KEY AUTO_INCREMENT,
    receiving_no VARCHAR(50) UNIQUE NOT NULL,
    order_id INT NOT NULL,
    received_date DATETIME NOT NULL,
    receiver VARCHAR(100),
    remarks TEXT,
    status ENUM('pending','approved','rejected') DEFAULT 'pending',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (order_id) REFERENCES purchase_orders(id)
);
-- 验收详情表
CREATE TABLE receiving_items (
    id INT PRIMARY KEY AUTO_INCREMENT,
    receiving_id INT NOT NULL,
    order_item_id INT NOT NULL,
    quantity_received INT NOT NULL,
    qualified_quantity INT DEFAULT 0,
    defective_quantity INT DEFAULT 0,
    remark TEXT,
    FOREIGN KEY (receiving_id) REFERENCES receiving_notes(id),
    FOREIGN KEY (order_item_id) REFERENCES purchase_order_items(id)
);
-- 质量问题表
CREATE TABLE quality_issues (
    id INT PRIMARY KEY AUTO_INCREMENT,
    receiving_item_id INT NOT NULL,
    issue_type VARCHAR(50),
    description TEXT,
    severity ENUM('minor','major','critical'),
    handling_method VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (receiving_item_id) REFERENCES receiving_items(id)
);

PHP后端核心代码

验收主类 ReceivingManager.php

<?php
class ReceivingManager {
    private $db;
    public function __construct($db) {
        $this->db = $db;
    }
    /**
     * 生成验收单号
     */
    public function generateReceivingNo() {
        $prefix = 'RECV';
        $date = date('Ymd');
        // 查询当天最大序号
        $stmt = $this->db->prepare(
            "SELECT COUNT(*) FROM receiving_notes WHERE DATE(created_at) = CURDATE()"
        );
        $stmt->execute();
        $count = $stmt->fetchColumn() + 1;
        return sprintf('%s-%s-%04d', $prefix, $date, $count);
    }
    /**
     * 创建验收单
     */
    public function createReceivingNote($orderId, $receiver, $items, $remarks = '') {
        try {
            $this->db->beginTransaction();
            // 创建验收单
            $receivingNo = $this->generateReceivingNo();
            $stmt = $this->db->prepare(
                "INSERT INTO receiving_notes (receiving_no, order_id, received_date, receiver, remarks) 
                 VALUES (?, ?, NOW(), ?, ?)"
            );
            $stmt->execute([$receivingNo, $orderId, $receiver, $remarks]);
            $receivingId = $this->db->lastInsertId();
            // 处理验收明细
            foreach ($items as $item) {
                $stmt = $this->db->prepare(
                    "INSERT INTO receiving_items (receiving_id, order_item_id, quantity_received, 
                     qualified_quantity, defective_quantity, remark) 
                     VALUES (?, ?, ?, ?, ?, ?)"
                );
                $stmt->execute([
                    $receivingId,
                    $item['order_item_id'],
                    $item['quantity_received'],
                    $item['qualified_quantity'],
                    $item['defective_quantity'],
                    $item['remark'] ?? ''
                ]);
                // 更新采购订单明细的已收数量
                $stmt = $this->db->prepare(
                    "UPDATE purchase_order_items SET quantity_received = quantity_received + ? WHERE id = ?"
                );
                $stmt->execute([$item['quantity_received'], $item['order_item_id']]);
                // 如果存在质量问题,记录质量异常
                if (!empty($item['quality_issues'])) {
                    $this->recordQualityIssues(
                        $this->db->lastInsertId(), 
                        $item['quality_issues']
                    );
                }
            }
            // 更新采购订单状态
            $this->updateOrderStatus($orderId);
            $this->db->commit();
            return ['success' => true, 'receiving_id' => $receivingId, 'receiving_no' => $receivingNo];
        } catch (Exception $e) {
            $this->db->rollBack();
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }
    /**
     * 记录质量问题
     */
    private function recordQualityIssues($receivingItemId, $issues) {
        foreach ($issues as $issue) {
            $stmt = $this->db->prepare(
                "INSERT INTO quality_issues (receiving_item_id, issue_type, description, severity, handling_method) 
                 VALUES (?, ?, ?, ?, ?)"
            );
            $stmt->execute([
                $receivingItemId,
                $issue['type'],
                $issue['description'],
                $issue['severity'],
                $issue['handling_method']
            ]);
        }
    }
    /**
     * 更新订单状态
     */
    private function updateOrderStatus($orderId) {
        $stmt = $this->db->prepare(
            "SELECT 
                SUM(quantity_ordered) as total_ordered,
                SUM(quantity_received) as total_received
             FROM purchase_order_items 
             WHERE order_id = ?"
        );
        $stmt->execute([$orderId]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($result['total_ordered'] == $result['total_received']) {
            $status = 'completed';
        } elseif ($result['total_received'] > 0) {
            $status = 'partial';
        } else {
            $status = 'pending';
        }
        $stmt = $this->db->prepare("UPDATE purchase_orders SET status = ? WHERE id = ?");
        $stmt->execute([$status, $orderId]);
    }
    /**
     * 获取待验收订单
     */
    public function getPendingOrders() {
        $stmt = $this->db->query(
            "SELECT po.*, 
                    (SELECT COUNT(*) FROM purchase_order_items WHERE order_id = po.id AND quantity_received < quantity_ordered) as pending_items
             FROM purchase_orders po 
             WHERE po.status IN ('pending', 'partial')
             ORDER BY po.expected_delivery_date ASC"
        );
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    /**
     * 获取订单详细商品
     */
    public function getOrderItems($orderId) {
        $stmt = $this->db->prepare(
            "SELECT * FROM purchase_order_items 
             WHERE order_id = ? AND quantity_received < quantity_ordered"
        );
        $stmt->execute([$orderId]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    /**
     * 获取验收历史
     */
    public function getReceivingHistory($orderId = null) {
        $sql = "SELECT rn.*, po.order_no, po.supplier_name
                FROM receiving_notes rn
                JOIN purchase_orders po ON rn.order_id = po.id";
        if ($orderId) {
            $sql .= " WHERE rn.order_id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$orderId]);
        } else {
            $stmt = $this->db->query($sql);
        }
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

验收控制器 ReceivingController.php

<?php
class ReceivingController {
    private $receivingManager;
    private $twig;
    public function __construct($receivingManager, $twig) {
        $this->receivingManager = $receivingManager;
        $this->twig = $twig;
    }
    /**
     * 显示待验收订单列表
     */
    public function index() {
        $pendingOrders = $this->receivingManager->getPendingOrders();
        echo $this->twig->render('receiving/index.html.twig', [
            'orders' => $pendingOrders
        ]);
    }
    /**
     * 显示验收表单
     */
    public function create($orderId) {
        $orderItems = $this->receivingManager->getOrderItems($orderId);
        echo $this->twig->render('receiving/create.html.twig', [
            'order_id' => $orderId,
            'items' => $orderItems
        ]);
    }
    /**
     * 保存验收结果
     */
    public function store($request) {
        $result = $this->receivingManager->createReceivingNote(
            $request['order_id'],
            $request['receiver'],
            $request['items'],
            $request['remarks'] ?? ''
        );
        if ($result['success']) {
            header('Location: /receiving/' . $result['receiving_id'] . '/success');
        } else {
            // 显示错误信息
            echo $this->twig->render('receiving/create.html.twig', [
                'error' => $result['error']
            ]);
        }
    }
    /**
     * 显示验收单详情
     */
    public function show($receivingId) {
        $stmt = $this->db->prepare(
            "SELECT ri.*, poi.product_name, poi.specification
             FROM receiving_items ri
             JOIN purchase_order_items poi ON ri.order_item_id = poi.id
             WHERE ri.receiving_id = ?"
        );
        $stmt->execute([$receivingId]);
        $items = $stmt->fetchAll(PDO::FETCH_ASSOC);
        echo $this->twig->render('receiving/show.html.twig', [
            'items' => $items
        ]);
    }
}

前端界面示例

验收表单 create.html.twig

{% extends 'layout.html.twig' %}
{% block content %}
<div class="container mt-4">
    <h2>到货验收</h2>
    <form id="receivingForm" method="POST" action="/receiving/store">
        <input type="hidden" name="order_id" value="{{ order_id }}">
        <div class="form-group">
            <label>验收人</label>
            <input type="text" name="receiver" class="form-control" required>
        </div>
        <div class="card mt-3">
            <div class="card-header">
                <h5>验收商品明细</h5>
            </div>
            <div class="card-body">
                <table class="table">
                    <thead>
                        <tr>
                            <th>商品名称</th>
                            <th>规格</th>
                            <th>应到数量</th>
                            <th>实收数量</th>
                            <th>合格数量</th>
                            <th>不合格数量</th>
                            <th>备注</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for item in items %}
                        <tr>
                            <td>{{ item.product_name }}</td>
                            <td>{{ item.specification }}</td>
                            <td>{{ item.quantity_ordered - item.quantity_received }}</td>
                            <td>
                                <input type="number" 
                                       name="items[{{ item.id }}][quantity_received]" 
                                       class="form-control received-qty"
                                       min="0" 
                                       max="{{ item.quantity_ordered - item.quantity_received }}"
                                       required>
                                <input type="hidden" 
                                       name="items[{{ item.id }}][order_item_id]" 
                                       value="{{ item.id }}">
                            </td>
                            <td>
                                <input type="number" 
                                       name="items[{{ item.id }}][qualified_quantity]" 
                                       class="form-control qualified-qty"
                                       min="0">
                            </td>
                            <td>
                                <input type="number" 
                                       name="items[{{ item.id }}][defective_quantity]" 
                                       class="form-control defective-qty"
                                       min="0">
                            </td>
                            <td>
                                <input type="text" 
                                       name="items[{{ item.id }}][remark]" 
                                       class="form-control">
                            </td>
                            <td>
                                <button type="button" class="btn btn-sm btn-warning quality-issue-btn"
                                        data-item-id="{{ item.id }}">
                                    质量问题
                                </button>
                            </td>
                        </tr>
                        <tr id="quality-issues-{{ item.id }}" style="display:none;">
                            <td colspan="8">
                                <div class="quality-issue-section">
                                    <h6>质量问题记录</h6>
                                    <div class="issue-list">
                                        <div class="issue-item">
                                            <select name="items[{{ item.id }}][quality_issues][0][type]" 
                                                    class="form-control mb-2">
                                                <option value="">选择问题类型</option>
                                                <option value="damage">损坏</option>
                                                <option value="expired">过期</option>
                                                <option value="wrong_spec">规格不符</option>
                                                <option value="quality_defect">质量缺陷</option>
                                            </select>
                                            <textarea name="items[{{ item.id }}][quality_issues][0][description]" 
                                                      class="form-control mb-2" 
                                                      placeholder="问题描述"></textarea>
                                            <select name="items[{{ item.id }}][quality_issues][0][severity]" 
                                                    class="form-control mb-2">
                                                <option value="minor">轻微</option>
                                                <option value="major">严重</option>
                                                <option value="critical">致命</option>
                                            </select>
                                            <input type="text" 
                                                   name="items[{{ item.id }}][quality_issues][0][handling_method]" 
                                                   class="form-control mb-2" 
                                                   placeholder="处理方式(退货/换货/降级使用)">
                                        </div>
                                    </div>
                                    <button type="button" class="btn btn-sm btn-info add-issue-btn" 
                                            data-item-id="{{ item.id }}">添加问题</button>
                                </div>
                            </td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
        <div class="form-group mt-3">
            <label>验收备注</label>
            <textarea name="remarks" class="form-control" rows="3"></textarea>
        </div>
        <div class="mt-4">
            <button type="submit" class="btn btn-primary">确认验收</button>
            <a href="/receiving" class="btn btn-secondary">取消</a>
        </div>
    </form>
</div>
<script>
// 实收数量自动同步到合格数量
document.querySelectorAll('.received-qty').forEach(input => {
    input.addEventListener('input', function() {
        const row = this.closest('tr');
        const qualifiedInput = row.querySelector('.qualified-qty');
        if (!qualifiedInput.value || qualifiedInput.value === '') {
            qualifiedInput.value = this.value;
        }
    });
});
// 显示/隐藏质量问题输入
document.querySelectorAll('.quality-issue-btn').forEach(btn => {
    btn.addEventListener('click', function() {
        const itemId = this.dataset.itemId;
        const issuesRow = document.getElementById('quality-issues-' + itemId);
        issuesRow.style.display = issuesRow.style.display === 'none' ? '' : 'none';
    });
});
// 添加质量问题项
document.querySelectorAll('.add-issue-btn').forEach(btn => {
    btn.addEventListener('click', function() {
        const itemId = this.dataset.itemId;
        const issueList = this.previousElementSibling;
        const issueCount = issueList.children.length;
        const newIssue = document.createElement('div');
        newIssue.className = 'issue-item';
        newIssue.innerHTML = `
            <select name="items[${itemId}][quality_issues][${issueCount}][type]" 
                    class="form-control mb-2">
                <option value="">选择问题类型</option>
                <option value="damage">损坏</option>
                <option value="expired">过期</option>
                <option value="wrong_spec">规格不符</option>
                <option value="quality_defect">质量缺陷</option>
            </select>
            <textarea name="items[${itemId}][quality_issues][${issueCount}][description]" 
                      class="form-control mb-2" 
                      placeholder="问题描述"></textarea>
            <select name="items[${itemId}][quality_issues][${issueCount}][severity]" 
                    class="form-control mb-2">
                <option value="minor">轻微</option>
                <option value="major">严重</option>
                <option value="critical">致命</option>
            </select>
            <input type="text" 
                   name="items[${itemId}][quality_issues][${issueCount}][handling_method]" 
                   class="form-control mb-2" 
                   placeholder="处理方式(退货/换货/降级使用)">
        `;
        issueList.appendChild(newIssue);
    });
});
</script>
{% endblock %}

路由配置

// routes.php 或 index.php
$router = new Router();
// 验收相关路由
$router->get('/receiving', 'ReceivingController@index');
$router->get('/receiving/{order_id}/create', 'ReceivingController@create');
$router->post('/receiving/store', 'ReceivingController@store');
$router->get('/receiving/{id}', 'ReceivingController@show');

注意事项

  1. 安全考虑

    • 使用参数化查询防止SQL注入
    • 实现CSRF令牌验证
    • 添加用户权限控制(验收人必须有相应权限)
  2. 业务逻辑

    • 验收数量不能大于订单剩余数量
    • 合格数量 + 不合格数量 = 实收数量
    • 支持分批验收
    • 记录验收人和验收时间
  3. 扩展功能

    • 添加附件上传功能(验收照片)
    • 打印验收单PDF
    • 与库存系统集成(自动入库)
    • 验收流程审批(需要审批的验收)

这个实现提供了一个完整的到货验收系统,涵盖了从订单查看、验收录入到质量管理的全过程,你可以根据实际业务需求进行调整和扩展。

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