本文目录导读:

我来详细介绍如何用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');
注意事项
-
安全考虑:
- 使用参数化查询防止SQL注入
- 实现CSRF令牌验证
- 添加用户权限控制(验收人必须有相应权限)
-
业务逻辑:
- 验收数量不能大于订单剩余数量
- 合格数量 + 不合格数量 = 实收数量
- 支持分批验收
- 记录验收人和验收时间
-
扩展功能:
- 添加附件上传功能(验收照片)
- 打印验收单PDF
- 与库存系统集成(自动入库)
- 验收流程审批(需要审批的验收)
这个实现提供了一个完整的到货验收系统,涵盖了从订单查看、验收录入到质量管理的全过程,你可以根据实际业务需求进行调整和扩展。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播