文章目录[隐藏]
WordPress文创商品柔性预售与产能匹配插件教程
引言:文创电商的产能挑战与解决方案
在文创商品电商领域,生产者常常面临一个两难困境:一方面,预售可以准确掌握市场需求,避免库存积压;另一方面,产能有限可能导致订单延迟,影响客户体验。柔性预售与产能匹配系统正是解决这一问题的创新方案。
本教程将指导您如何为WordPress文创电商网站开发一个智能的柔性预售与产能匹配插件。该系统将根据实时产能数据动态调整预售库存,实现供需平衡,优化生产计划。
插件架构设计
1.1 系统核心功能模块
我们的插件将包含以下核心模块:
- 产能管理模块:设置和管理生产能力
- 预售商品管理:特殊商品类型定义
- 订单产能匹配:实时匹配订单与产能
- 生产计划生成:自动生成生产排期
- 客户通知系统:自动更新订单状态
1.2 数据库表结构设计
<?php
/**
* 创建插件所需数据库表
* 这段代码应该放在插件激活钩子中执行
*/
function fpp_create_database_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// 产能计划表
$capacity_table = $wpdb->prefix . 'fpp_capacity';
$capacity_sql = "CREATE TABLE IF NOT EXISTS $capacity_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
date date NOT NULL,
max_capacity int NOT NULL DEFAULT 0,
booked_capacity int NOT NULL DEFAULT 0,
product_id bigint(20) DEFAULT 0,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY date_index (date),
KEY product_index (product_id)
) $charset_collate;";
// 预售商品扩展表
$presale_table = $wpdb->prefix . 'fpp_presale_products';
$presale_sql = "CREATE TABLE IF NOT EXISTS $presale_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
product_id bigint(20) NOT NULL,
is_presale tinyint(1) DEFAULT 0,
estimated_days int DEFAULT 7,
capacity_per_unit int DEFAULT 1,
max_presale_quantity int DEFAULT 100,
start_date date DEFAULT NULL,
end_date date DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY product_id (product_id)
) $charset_collate;";
// 订单产能分配表
$order_allocation_table = $wpdb->prefix . 'fpp_order_allocation';
$order_allocation_sql = "CREATE TABLE IF NOT EXISTS $order_allocation_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
order_id bigint(20) NOT NULL,
order_item_id bigint(20) NOT NULL,
product_id bigint(20) NOT NULL,
quantity int NOT NULL,
allocated_date date NOT NULL,
production_date date DEFAULT NULL,
status varchar(20) DEFAULT 'pending',
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY order_index (order_id),
KEY date_index (allocated_date),
KEY status_index (status)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($capacity_sql);
dbDelta($presale_sql);
dbDelta($order_allocation_sql);
}
?>
产能管理模块开发
2.1 产能设置界面
<?php
/**
* 产能管理后台界面
* 添加产能管理菜单和页面
*/
class FPP_Capacity_Manager {
public function __construct() {
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
}
public function add_admin_menu() {
add_menu_page(
'柔性预售产能管理',
'产能管理',
'manage_options',
'fpp-capacity',
array($this, 'render_capacity_page'),
'dashicons-calendar-alt',
56
);
add_submenu_page(
'fpp-capacity',
'产能日历',
'产能日历',
'manage_options',
'fpp-capacity-calendar',
array($this, 'render_calendar_page')
);
}
public function render_capacity_page() {
?>
<div class="wrap">
<h1>柔性预售产能管理</h1>
<div class="fpp-admin-container">
<div class="fpp-admin-card">
<h2>每日产能设置</h2>
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>">
<input type="hidden" name="action" value="fpp_save_capacity">
<?php wp_nonce_field('fpp_save_capacity_nonce'); ?>
<table class="form-table">
<tr>
<th scope="row">
<label for="capacity_date">日期</label>
</th>
<td>
<input type="date"
id="capacity_date"
name="capacity_date"
value="<?php echo date('Y-m-d'); ?>"
required>
</td>
</tr>
<tr>
<th scope="row">
<label for="max_capacity">最大产能</label>
</th>
<td>
<input type="number"
id="max_capacity"
name="max_capacity"
min="0"
step="1"
value="100"
required>
<p class="description">该日期可处理的最大订单数量</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="product_id">关联商品 (可选)</label>
</th>
<td>
<select id="product_id" name="product_id">
<option value="0">所有商品</option>
<?php
$products = wc_get_products(array(
'limit' => -1,
'type' => 'presale'
));
foreach ($products as $product) {
echo '<option value="' . $product->get_id() . '">'
. $product->get_name() . '</option>';
}
?>
</select>
</td>
</tr>
</table>
<?php submit_button('保存产能设置'); ?>
</form>
</div>
<div class="fpp-admin-card">
<h2>近期产能概览</h2>
<?php $this->render_capacity_overview(); ?>
</div>
</div>
</div>
<style>
.fpp-admin-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-top: 20px;
}
.fpp-admin-card {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
@media (max-width: 1200px) {
.fpp-admin-container {
grid-template-columns: 1fr;
}
}
</style>
<?php
}
private function render_capacity_overview() {
global $wpdb;
$table_name = $wpdb->prefix . 'fpp_capacity';
$capacities = $wpdb->get_results(
"SELECT * FROM $table_name
WHERE date >= CURDATE()
ORDER BY date ASC
LIMIT 7"
);
if (empty($capacities)) {
echo '<p>暂无产能数据</p>';
return;
}
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead>
<tr>
<th>日期</th>
<th>最大产能</th>
<th>已预订</th>
<th>剩余</th>
<th>利用率</th>
</tr>
</thead>';
echo '<tbody>';
foreach ($capacities as $capacity) {
$remaining = $capacity->max_capacity - $capacity->booked_capacity;
$utilization = $capacity->max_capacity > 0
? round(($capacity->booked_capacity / $capacity->max_capacity) * 100, 1)
: 0;
echo '<tr>';
echo '<td>' . $capacity->date . '</td>';
echo '<td>' . $capacity->max_capacity . '</td>';
echo '<td>' . $capacity->booked_capacity . '</td>';
echo '<td>' . $remaining . '</td>';
echo '<td>' . $utilization . '%</td>';
echo '</tr>';
}
echo '</tbody></table>';
}
}
?>
预售商品功能集成
3.1 自定义商品类型与字段
<?php
/**
* 扩展WooCommerce商品类型
* 添加预售商品类型和自定义字段
*/
class FPP_Product_Type_Extension {
public function __construct() {
// 注册预售商品类型
add_filter('product_type_selector', array($this, 'add_presale_product_type'));
// 添加商品数据选项卡
add_filter('woocommerce_product_data_tabs', array($this, 'add_product_data_tab'));
// 显示预售设置面板
add_action('woocommerce_product_data_panels', array($this, 'show_presale_settings_panel'));
// 保存预售设置
add_action('woocommerce_process_product_meta', array($this, 'save_presale_settings'));
// 在前端显示预售信息
add_action('woocommerce_single_product_summary', array($this, 'show_frontend_presale_info'), 25);
}
public function add_presale_product_type($types) {
$types['presale'] = __('预售商品', 'fpp');
return $types;
}
public function add_product_data_tab($tabs) {
$tabs['presale'] = array(
'label' => __('预售设置', 'fpp'),
'target' => 'presale_product_data',
'class' => array('show_if_presale'),
'priority' => 80,
);
return $tabs;
}
public function show_presale_settings_panel() {
global $post;
$product = wc_get_product($post->ID);
$is_presale = $product->get_meta('_is_presale', true);
$estimated_days = $product->get_meta('_estimated_days', true) ?: 7;
$capacity_per_unit = $product->get_meta('_capacity_per_unit', true) ?: 1;
$max_presale_quantity = $product->get_meta('_max_presale_quantity', true) ?: 100;
?>
<div id="presale_product_data" class="panel woocommerce_options_panel hidden">
<div class="options_group">
<?php
// 是否启用预售
woocommerce_wp_checkbox(array(
'id' => '_is_presale',
'label' => __('启用预售', 'fpp'),
'description' => __('启用后此商品将作为预售商品销售', 'fpp'),
'value' => $is_presale,
));
// 预计发货天数
woocommerce_wp_text_input(array(
'id' => '_estimated_days',
'label' => __('预计发货天数', 'fpp'),
'description' => __('下单后预计发货所需天数', 'fpp'),
'type' => 'number',
'custom_attributes' => array(
'min' => '1',
'step' => '1',
),
'value' => $estimated_days,
));
// 单位产能消耗
woocommerce_wp_text_input(array(
'id' => '_capacity_per_unit',
'label' => __('单位产能消耗', 'fpp'),
'description' => __('每件商品消耗的产能单位数', 'fpp'),
'type' => 'number',
'custom_attributes' => array(
'min' => '1',
'step' => '1',
),
'value' => $capacity_per_unit,
));
// 最大预售数量
woocommerce_wp_text_input(array(
'id' => '_max_presale_quantity',
'label' => __('最大预售数量', 'fpp'),
'description' => __('该商品最大可预售数量', 'fpp'),
'type' => 'number',
'custom_attributes' => array(
'min' => '1',
'step' => '1',
),
'value' => $max_presale_quantity,
));
// 预售开始时间
woocommerce_wp_text_input(array(
'id' => '_presale_start_date',
'label' => __('预售开始时间', 'fpp'),
'description' => __('预售开始日期和时间', 'fpp'),
'type' => 'datetime-local',
'value' => $product->get_meta('_presale_start_date', true),
));
// 预售结束时间
woocommerce_wp_text_input(array(
'id' => '_presale_end_date',
'label' => __('预售结束时间', 'fpp'),
'description' => __('预售结束日期和时间', 'fpp'),
'type' => 'datetime-local',
'value' => $product->get_meta('_presale_end_date', true),
));
?>
</div>
</div>
<script>
jQuery(document).ready(function($) {
// 显示/隐藏预售设置面板
$('.product_type').change(function() {
var isPresale = $('#product-type').val() === 'presale';
$('.show_if_presale').toggle(isPresale);
}).change();
});
</script>
<?php
}
public function save_presale_settings($product_id) {
$product = wc_get_product($product_id);
// 保存预售设置
$is_presale = isset($_POST['_is_presale']) ? 'yes' : 'no';
$product->update_meta_data('_is_presale', $is_presale);
if (isset($_POST['_estimated_days'])) {
$product->update_meta_data('_estimated_days', absint($_POST['_estimated_days']));
}
if (isset($_POST['_capacity_per_unit'])) {
$product->update_meta_data('_capacity_per_unit', absint($_POST['_capacity_per_unit']));
}
if (isset($_POST['_max_presale_quantity'])) {
$product->update_meta_data('_max_presale_quantity', absint($_POST['_max_presale_quantity']));
}
if (isset($_POST['_presale_start_date'])) {
$product->update_meta_data('_presale_start_date', sanitize_text_field($_POST['_presale_start_date']));
}
if (isset($_POST['_presale_end_date'])) {
$product->update_meta_data('_presale_end_date', sanitize_text_field($_POST['_presale_end_date']));
}
$product->save();
}
public function show_frontend_presale_info() {
global $product;
if ($product->get_meta('_is_presale', true) !== 'yes') {
return;
}
$estimated_days = $product->get_meta('_estimated_days', true);
$start_date = $product->get_meta('_presale_start_date', true);
$end_date = $product->get_meta('_presale_end_date', true);
echo '<div class="presale-notice" style="background: #f0f8ff; padding: 15px; border-radius: 5px; margin: 20px 0;">';
echo '<h3>' . __('预售商品说明', 'fpp') . '</h3>';
if ($estimated_days) {
echo '<p>' . sprintf(__('预计下单后 %d 天内发货', 'fpp'), $estimated_days) . '</p>';
}
if ($start_date && strtotime($start_date) > time()) {
echo '<p>' . sprintf(__('预售开始时间: %s', 'fpp'), date('Y-m-d H:i', strtotime($start_date))) . '</p>';
}
if ($end_date && strtotime($end_date) > time()) {
echo '<p>' . sprintf(__('预售结束时间: %s', 'fpp'), date('Y-m-d H:i', strtotime($end_date))) . '</p>';
}
echo '<p><strong>' . __('注意:预售商品将根据产能安排生产,具体发货时间可能根据订单量有所调整', 'fpp') . '</strong></p>';
echo '</div>';
}
}
?>
订单处理与产能匹配引擎
4.1 智能产能分配算法
<?php
/**
* 订单产能匹配引擎
* 处理新订单并分配到合适的产能日期
*/
class FPP_Order_Allocation_Engine {
private $db;
public function __construct() {
global $wpdb;
$this->db = $wpdb;
// 订单创建时触发产能分配
add_action('woocommerce_checkout_order_processed', array($this, 'allocate_order_capacity'), 10, 3);
// 订单状态变化时更新产能
add_action('woocommerce_order_status_changed', array($this, 'update_capacity_on_status_change'), 10, 4);
}
/**
* 为新订单分配产能
* @param int $order_id 订单ID
* @param array $posted_data 提交的数据
* @param WC_Order $order 订单对象
*/
public function allocate_order_capacity($order_id, $posted_data, $order) {
// 获取订单中的所有商品
$items = $order->get_items();
foreach ($items as $item_id => $item) {
$product = $item->get_product();
// 检查是否为预售商品
if ($product->get_meta('_is_presale', true) !== 'yes') {
continue; // 非预售商品跳过
}
$product_id = $product->get_id();
$quantity = $item->get_quantity();
$capacity_per_unit = $product->get_meta('_capacity_per_unit', true) ?: 1;
$total_capacity_needed = $quantity * $capacity_per_unit;
// 查找可用的产能日期
$allocated_date = $this->find_available_capacity_date($product_id, $total_capacity_needed);
if ($allocated_date) {
// 分配产能并记录
$this->allocate_capacity_for_item(
$order_id,
$item_id,
$product_id,
$quantity,
$allocated_date
);
// 更新产能表的已预订数量
$this->update_booked_capacity($allocated_date, $product_id, $total_capacity_needed);
// 计算预计发货日期
$estimated_days = $product->get_meta('_estimated_days', true) ?: 7;
$production_date = date('Y-m-d', strtotime($allocated_date));
$shipping_date = date('Y-m-d', strtotime($allocated_date . " + $estimated_days days"));
// 添加订单备注
$order->add_order_note(
sprintf(
__('预售商品 "%s" 已分配产能,生产日期: %s,预计发货日期: %s', 'fpp'),
$product->get_name(),
$production_date,
$shipping_date
)
);
// 更新订单项meta
wc_add_order_item_meta($item_id, '_production_date', $production_date);
wc_add_order_item_meta($item_id, '_estimated_shipping_date', $shipping_date);
} else {
// 没有可用产能,标记为等待分配
$this->allocate_capacity_for_item(
$order_id,
$item_id,
$product_id,
$quantity,
null,
'waiting'
);
$order->add_order_note(
sprintf(
__('预售商品 "%s" 暂时无法分配产能,已加入等待队列', 'fpp'),
$product->get_name()
)
);
}
}
}
/**
* 查找可用的产能日期
* @param int $product_id 商品ID
* @param int $needed_capacity 所需产能
* @return string|null 可用日期或null
*/
private function find_available_capacity_date($product_id, $needed_capacity) {
$capacity_table = $this->db->prefix . 'fpp_capacity';
// 查询未来30天内的产能情况
$query = $this->db->prepare(
"SELECT date, max_capacity, booked_capacity
FROM $capacity_table
WHERE (product_id = %d OR product_id = 0)
AND date >= CURDATE()
AND date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)
AND max_capacity - booked_capacity >= %d
ORDER BY date ASC
LIMIT 1",
$product_id,
$needed_capacity
);
$result = $this->db->get_row($query);
return $result ? $result->date : null;
}
/**
* 为订单项分配产能
*/
private function allocate_capacity_for_item($order_id, $item_id, $product_id, $quantity, $allocated_date, $status = 'allocated') {
$table_name = $this->db->prefix . 'fpp_order_allocation';
$this->db->insert(
$table_name,
array(
'order_id' => $order_id,
'order_item_id' => $item_id,
'product_id' => $product_id,
'quantity' => $quantity,
'allocated_date' => $allocated_date ?: date('Y-m-d'),
'production_date' => $allocated_date,
'status' => $status,
'created_at' => current_time('mysql')
),
array('%d', '%d', '%d', '%d', '%s', '%s', '%s', '%s')
);
}
/**
* 更新已预订产能
*/
private function update_booked_capacity($date, $product_id, $capacity) {
$table_name = $this->db->prefix . 'fpp_capacity';
// 先检查是否存在该日期的记录
$existing = $this->db->get_var(
$this->db->prepare(
"SELECT id FROM $table_name WHERE date = %s AND product_id = %d",
$date,
$product_id
)
);
if ($existing) {
// 更新现有记录
$this->db->query(
$this->db->prepare(
"UPDATE $table_name
SET booked_capacity = booked_capacity + %d
WHERE date = %s AND product_id = %d",
$capacity,
$date,
$product_id
)
);
} else {
// 创建新记录(使用通用产能设置)
$this->db->insert(
$table_name,
array(
'date' => $date,
'product_id' => $product_id,
'max_capacity' => 100, // 默认值
'booked_capacity' => $capacity
),
array('%s', '%d', '%d', '%d')
);
}
}
/**
* 订单状态变化时更新产能
*/
public function update_capacity_on_status_change($order_id, $old_status, $new_status, $order) {
// 如果订单取消或退款,释放产能
if (in_array($new_status, array('cancelled', 'refunded'))) {
$this->release_capacity_for_order($order_id);
}
// 如果订单完成,标记生产完成
if ($new_status === 'completed') {
$this->mark_production_completed($order_id);
}
}
/**
* 释放订单占用的产能
*/
private function release_capacity_for_order($order_id) {
$allocation_table = $this->db->prefix . 'fpp_order_allocation';
$capacity_table = $this->db->prefix . 'fpp_capacity';
// 获取订单的所有产能分配记录
$allocations = $this->db->get_results(
$this->db->prepare(
"SELECT * FROM $allocation_table WHERE order_id = %d AND status = 'allocated'",
$order_id
)
);
foreach ($allocations as $allocation) {
// 获取商品信息计算产能
$product = wc_get_product($allocation->product_id);
$capacity_per_unit = $product->get_meta('_capacity_per_unit', true) ?: 1;
$total_capacity = $allocation->quantity * $capacity_per_unit;
// 释放产能
$this->db->query(
$this->db->prepare(
"UPDATE $capacity_table
SET booked_capacity = GREATEST(0, booked_capacity - %d)
WHERE date = %s AND product_id = %d",
$total_capacity,
$allocation->allocated_date,
$allocation->product_id
)
);
// 更新分配状态
$this->db->update(
$allocation_table,
array('status' => 'cancelled'),
array('id' => $allocation->id),
array('%s'),
array('%d')
);
}
}
}
?>
## 生产计划与报表系统
### 5.1 生产计划生成器
<?php
/**
- 生产计划生成与管理系统
*/
class FPP_Production_Planner {
public function __construct() {
add_action('admin_menu', array($this, 'add_production_menu'));
add_action('wp_ajax_generate_production_plan', array($this, 'ajax_generate_plan'));
add_action('wp_ajax_export_production_csv', array($this, 'ajax_export_csv'));
}
public function add_production_menu() {
add_submenu_page(
'fpp-capacity',
'生产计划',
'生产计划',
'manage_options',
'fpp-production',
array($this, 'render_production_page')
);
}
public function render_production_page() {
?>
<div class="wrap">
<h1>生产计划管理</h1>
<div class="fpp-production-controls">
<form method="get" action="<?php echo admin_url('admin.php'); ?>">
<input type="hidden" name="page" value="fpp-production">
<label for="start_date">开始日期:</label>
<input type="date" id="start_date" name="start_date"
value="<?php echo isset($_GET['start_date']) ? $_GET['start_date'] : date('Y-m-d'); ?>">
<label for="end_date">结束日期:</label>
<input type="date" id="end_date" name="end_date"
value="<?php echo isset($_GET['end_date']) ? $_GET['end_date'] : date('Y-m-d', strtotime('+7 days')); ?>">
<label for="product_id">商品筛选:</label>
<select id="product_id" name="product_id">
<option value="0">所有商品</option>
<?php
$products = wc_get_products(array(
'limit' => -1,
'meta_key' => '_is_presale',
'meta_value' => 'yes'
));
foreach ($products as $product) {
$selected = isset($_GET['product_id']) && $_GET['product_id'] == $product->get_id() ? 'selected' : '';
echo '<option value="' . $product->get_id() . '" ' . $selected . '>'
. $product->get_name() . '</option>';
}
?>
</select>
<?php submit_button('筛选', 'secondary', 'filter', false); ?>
<button type="button" class="button button-primary"
onclick="generateProductionPlan()">
生成生产计划
</button>
<button type="button" class="button"
onclick="exportProductionCSV()">
导出CSV
</button>
</form>
</div>
<div id="production-plan-result" style="margin-top: 20px;"></div>
<?php $this->render_production_table(); ?>
</div>
<script>
function generateProductionPlan() {
const startDate = document.getElementById('start_date').value;
const endDate = document.getElementById('end_date').value;
const productId = document.getElementById('product_id').value;
jQuery('#production-plan-result').html('<p>正在生成计划...</p>');
jQuery.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'generate_production_plan',
start_date: startDate,
end_date: endDate,
product_id: productId,
nonce: '<?php echo wp_create_nonce('fpp_generate_plan'); ?>'
},
success: function(response) {
jQuery('#production-plan-result').html(response.data);
}
});
}
function exportProductionCSV() {
const startDate = document.getElementById('start_date').value;
const endDate = document.getElementById('end_date').value;
const productId = document.getElementById('product_id').value;
const url = ajaxurl + '?action=export_production_csv&start_date=' + startDate +
'&end_date=' + endDate + '&product_id=' + productId +
'&nonce=<?php echo wp_create_nonce('fpp_export_csv'); ?>';
window.location.href = url;
}
</script>
<style>
.fpp-production-controls {
background: #fff;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.fpp-production-controls label {
margin-right: 5px;
margin-left: 15px;
}
.fpp-production-controls input,
.fpp-production-controls select {
margin-right: 15px;
}
.production-summary {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
margin: 20px 0;
}
.summary-card {
background: #fff;
padding: 15px;
border-radius: 8px;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.summary-card h3 {
margin-top: 0;
color: #333;
}
.summary-card .number {
font-size: 24px;
font-weight: bold;
color: #0073aa;
}
</style>
<?php
}
public function render_production_table() {
global $wpdb;
$start_date = isset($_GET['start_date']) ? $_GET['start_date'] : date('Y-m-d');
$end_date = isset($_GET['end_date']) ? $_GET['end_date'] : date('Y-m-d', strtotime('+7 days'));
$product_id = isset($_GET['product_id']) ? intval($_GET['product_id']) : 0;
$allocation_table = $wpdb->prefix . 'fpp_order_allocation';
$capacity_table = $wpdb->prefix . 'fpp_capacity';
// 构建查询
$query = "SELECT
a.allocated_date as production_date,
a.product_id,
p.post_title as product_name,
SUM(a.quantity) as total_quantity,
COUNT(DISTINCT a.order_id) as order_count,
c.max_capacity,
c.booked_capacity
FROM $allocation_table a
LEFT JOIN {$wpdb->posts} p ON a.product_id = p.ID
LEFT JOIN $capacity_table c ON a.allocated_date = c.date AND a.product_id = c.product_id
WHERE a.status = 'allocated'
AND a.allocated_date BETWEEN %s AND %s";
$params = array($start_date, $end_date);
if ($product_id > 0) {
$query .= " AND a.product_id = %d";
$params[] = $product_id;
}
$query .= " GROUP BY a.allocated_date, a.product_id
ORDER BY a.allocated_date ASC, a.product_id ASC";
$results = $wpdb->get_results($wpdb->prepare($query, $params));
if (empty($results)) {
echo '<p>没有找到生产计划数据</p>';
return;
}
// 显示统计摘要
$total_quantity = array_sum(array_column($results, 'total_quantity'));
$total_orders = array_sum(array_column($results, 'order_count'));
$unique_products = count(array_unique(array_column($results, 'product_id')));
$utilization_rate = 0;
if (!empty($results)) {
$total_capacity = array_sum(array_column($results, 'max_capacity'));
$total_booked = array_sum(array_column($results, 'booked_capacity'));
$utilization_rate = $total_capacity > 0 ? round(($total_booked / $total_capacity) * 100, 1) : 0;
}
?>
<div class="production-summary">
<div class="summary-card">
<h3>总生产数量</h3>
<div class="number"><?php echo $total_quantity; ?></div>
</div>
<div class="summary-card">
<h3>订单数量</h3>
<div class="number"><?php echo $total_orders; ?></div>
</div>
<div class="summary-card">
<h3>商品种类</h3>
<div class="number"><?php echo $unique_products; ?></div>
</div>
<div class="summary-card">
<h3>产能利用率</h3>
<div class="number"><?php echo $utilization_rate; ?>%</div>
</div>
</div>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>生产日期</th>
<th>商品名称</th>
<th>生产数量</th>
<th>订单数</th>
<th>最大产能</th>
<th>已分配</th>
<th>剩余产能</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($results as $row):
$remaining = $row->max_capacity - $row->booked_capacity;
$utilization = $row->max_capacity > 0 ? round(($row
