文章目录
-
- 在当今电子商务快速发展的时代,库存管理成为企业运营的关键环节。WordPress作为全球最流行的内容管理系统,结合其强大的插件生态和自定义开发能力,可以构建出灵活高效的采购与库存预警系统。 本系统将实现以下核心功能: 实时库存监控与预警 智能采购建议生成 供应商管理集成 采购订单自动化处理 多仓库库存同步 系统采用模块化设计,便于后期功能扩展和维护。我们将使用WordPress自定义文章类型(CPT)存储产品信息,自定义数据库表记录库存变动,并结合REST API实现前后端数据交互。
- 首先,我们需要在WordPress环境中创建必要的数据库表结构。在您的插件主文件中添加以下代码: <?php /** * WordPress柔性采购与库存预警系统 * 数据库初始化模块 */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } class InventoryDB { /** * 创建库存相关数据表 */ public static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 库存变动记录表 $table_inventory_log = $wpdb->prefix . 'inventory_log'; $sql_inventory_log = "CREATE TABLE IF NOT EXISTS $table_inventory_log ( id bigint(20) NOT NULL AUTO_INCREMENT, product_id bigint(20) NOT NULL, warehouse_id int(11) DEFAULT 1, change_type varchar(50) NOT NULL COMMENT '变动类型: purchase, sale, adjust, return', change_qty int(11) NOT NULL COMMENT '变动数量,正数为入库,负数为出库', previous_qty int(11) NOT NULL COMMENT '变动前数量', current_qty int(11) NOT NULL COMMENT '变动后数量', reference_id bigint(20) DEFAULT NULL COMMENT '关联订单ID或采购单ID', operator_id bigint(20) DEFAULT NULL COMMENT '操作员ID', notes text COMMENT '备注信息', created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_product (product_id), KEY idx_created (created_at) ) $charset_collate;"; // 采购建议表 $table_purchase_suggest = $wpdb->prefix . 'purchase_suggestions'; $sql_purchase_suggest = "CREATE TABLE IF NOT EXISTS $table_purchase_suggest ( id bigint(20) NOT NULL AUTO_INCREMENT, product_id bigint(20) NOT NULL, suggested_qty int(11) NOT NULL COMMENT '建议采购数量', reason_code varchar(100) NOT NULL COMMENT '建议原因: low_stock, seasonal, trend', urgency_level tinyint(1) DEFAULT 1 COMMENT '紧急程度 1-5', calculated_at datetime DEFAULT CURRENT_TIMESTAMP, processed tinyint(1) DEFAULT 0 COMMENT '是否已处理', processed_at datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_product (product_id), KEY idx_urgency (urgency_level) ) $charset_collate;"; // 供应商信息表 $table_suppliers = $wpdb->prefix . 'suppliers'; $sql_suppliers = "CREATE TABLE IF NOT EXISTS $table_suppliers ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(200) NOT NULL, contact_person varchar(100) DEFAULT NULL, phone varchar(50) DEFAULT NULL, email varchar(100) DEFAULT NULL, lead_time int(11) DEFAULT 7 COMMENT '交货周期(天)', reliability_score decimal(3,2) DEFAULT 5.00 COMMENT '可靠性评分', payment_terms text COMMENT '付款条件', is_active tinyint(1) DEFAULT 1, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql_inventory_log); dbDelta($sql_purchase_suggest); dbDelta($sql_suppliers); } /** * 删除数据表(用于插件卸载) */ public static function drop_tables() { global $wpdb; $tables = [ $wpdb->prefix . 'inventory_log', $wpdb->prefix . 'purchase_suggestions', $wpdb->prefix . 'suppliers' ]; foreach ($tables as $table) { $wpdb->query("DROP TABLE IF EXISTS $table"); } } } // 插件激活时创建表 register_activation_hook(__FILE__, ['InventoryDB', 'create_tables']); // 插件卸载时删除表(根据需求选择是否启用) // register_uninstall_hook(__FILE__, ['InventoryDB', 'drop_tables']); ?>
- 接下来,我们创建产品自定义文章类型并添加库存管理字段: <?php /** * 产品与库存管理模块 */ class ProductInventoryManager { public function __construct() { // 注册产品自定义文章类型 add_action('init', [$this, 'register_product_post_type']); // 添加库存管理元框 add_action('add_meta_boxes', [$this, 'add_inventory_meta_box']); // 保存库存数据 add_action('save_post_product', [$this, 'save_inventory_data'], 10, 2); // 添加库存管理列 add_filter('manage_product_posts_columns', [$this, 'add_inventory_columns']); add_action('manage_product_posts_custom_column', [$this, 'display_inventory_columns'], 10, 2); } /** * 注册产品自定义文章类型 */ public function register_product_post_type() { $labels = [ 'name' => '产品', 'singular_name' => '产品', 'menu_name' => '产品管理', 'add_new' => '添加产品', 'add_new_item' => '添加新产品', 'edit_item' => '编辑产品', 'new_item' => '新产品', 'view_item' => '查看产品', 'search_items' => '搜索产品', 'not_found' => '未找到产品', 'not_found_in_trash' => '回收站中无产品' ]; $args = [ 'labels' => $labels, 'public' => true, 'has_archive' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => ['slug' => 'product'], 'capability_type' => 'post', 'hierarchical' => false, 'menu_position' => 30, 'menu_icon' => 'dashicons-products', 'supports' => ['title', 'editor', 'thumbnail', 'excerpt'], 'show_in_rest' => true ]; register_post_type('product', $args); } /** * 添加库存管理元框 */ public function add_inventory_meta_box() { add_meta_box( 'inventory_management', '库存管理', [$this, 'render_inventory_meta_box'], 'product', 'side', 'high' ); } /** * 渲染库存管理元框 */ public function render_inventory_meta_box($post) { // 获取现有库存数据 $current_stock = get_post_meta($post->ID, '_current_stock', true) ?: 0; $min_stock = get_post_meta($post->ID, '_min_stock', true) ?: 10; $max_stock = get_post_meta($post->ID, '_max_stock', true) ?: 100; $supplier_id = get_post_meta($post->ID, '_primary_supplier', true); // 添加安全验证 wp_nonce_field('save_inventory_data', 'inventory_nonce'); // 显示表单 ?> <div class="inventory-fields"> <p> <label for="current_stock">当前库存:</label> <input type="number" id="current_stock" name="current_stock" value="<?php echo esc_attr($current_stock); ?>" class="widefat"> </p> <p> <label for="min_stock">最低库存预警:</label> <input type="number" id="min_stock" name="min_stock" value="<?php echo esc_attr($min_stock); ?>" class="widefat"> </p> <p> <label for="max_stock">最大库存量:</label> <input type="number" id="max_stock" name="max_stock" value="<?php echo esc_attr($max_stock); ?>" class="widefat"> </p> <p> <label for="primary_supplier">主要供应商:</label> <?php $this->render_supplier_dropdown($supplier_id); ?> </p> <?php // 显示库存状态 $this->display_stock_status($current_stock, $min_stock, $max_stock); ?> </div> <?php } /** * 渲染供应商下拉列表 */ private function render_supplier_dropdown($selected_id) { global $wpdb; $table_name = $wpdb->prefix . 'suppliers'; $suppliers = $wpdb->get_results("SELECT id, name FROM $table_name WHERE is_active = 1"); echo '<select id="primary_supplier" name="primary_supplier" class="widefat">'; echo '<option value="">选择供应商</option>'; foreach ($suppliers as $supplier) { $selected = ($supplier->id == $selected_id) ? 'selected' : ''; echo '<option value="' . esc_attr($supplier->id) . '" ' . $selected . '>'; echo esc_html($supplier->name); echo '</option>'; } echo '</select>'; } /** * 显示库存状态 */ private function display_stock_status($current, $min, $max) { $percentage = ($current / $max) * 100; $status_class = 'good'; $status_text = '库存充足'; if ($current <= $min) { $status_class = 'low'; $status_text = '库存不足'; } elseif ($current >= $max * 0.9) { $status_class = 'high'; $status_text = '库存过高'; } echo '<div class="stock-status ' . $status_class . '">'; echo '<strong>状态:</strong> ' . $status_text; echo '<div class="stock-bar"><div class="stock-level" style="width:' . $percentage . '%;"></div></div>'; echo '</div>'; // 添加简单样式 echo '<style> .stock-status { padding: 8px; margin: 10px 0; border-radius: 4px; } .stock-status.low { background: #ffeaea; border-left: 4px solid #dc3232; } .stock-status.good { background: #e7f7e7; border-left: 4px solid #46b450; } .stock-status.high { background: #fff3e6; border-left: 4px solid #ffb900; } .stock-bar { height: 10px; background: #f0f0f0; border-radius: 5px; margin-top: 5px; } .stock-level { height: 100%; background: #0073aa; border-radius: 5px; } </style>'; } /** * 保存库存数据 */ public function save_inventory_data($post_id, $post) { // 安全检查 if (!isset($_POST['inventory_nonce']) || !wp_verify_nonce($_POST['inventory_nonce'], 'save_inventory_data')) { return; } // 权限检查 if (!current_user_can('edit_post', $post_id)) { return; } // 自动保存检查 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } // 保存库存数据 $fields = ['current_stock', 'min_stock', 'max_stock', 'primary_supplier']; foreach ($fields as $field) { if (isset($_POST[$field])) { $value = sanitize_text_field($_POST[$field]); update_post_meta($post_id, '_' . $field, $value); } } // 记录库存变动 $this->log_inventory_change($post_id, 'adjust', $_POST['current_stock']); } /** * 记录库存变动 */ private function log_inventory_change($product_id, $type, $new_qty) { global $wpdb; $old_qty = get_post_meta($product_id, '_current_stock', true) ?: 0; $change_qty = $new_qty - $old_qty; if ($change_qty == 0) { return; // 库存未变化,不记录 } $table_name = $wpdb->prefix . 'inventory_log'; $wpdb->insert( $table_name, [ 'product_id' => $product_id, 'change_type' => $type, 'change_qty' => $change_qty, 'previous_qty' => $old_qty, 'current_qty' => $new_qty, 'operator_id' => get_current_user_id(), 'created_at' => current_time('mysql') ], ['%d', '%s', '%d', '%d', '%d', '%d', '%s'] ); } /** * 添加库存管理列 */ public function add_inventory_columns($columns) { $new_columns = []; foreach ($columns as $key => $value) { $new_columns[$key] = $value; if ($key === 'title') { $new_columns['current_stock'] = '当前库存'; $new_columns['stock_status'] = '库存状态'; $new_columns['last_updated'] = '最后更新'; } } return $new_columns; } /** * 显示库存管理列内容 */ public function display_inventory_columns($column, $post_id) { switch ($column) { case 'current_stock': $stock = get_post_meta($post_id, '_current_stock', true); echo $stock ?: 0; break; case 'stock_status': $current = get_post_meta($post_id, '_current_stock', true) ?: 0; $min = get_post_meta($post_id, '_min_stock', true) ?: 10; if ($current <= $min) { echo '<span style="color: #dc3232; font-weight: bold;">需补货</span>'; } elseif ($current <= $min * 2) { echo '<span style="color: #ffb900;">库存偏低</span>'; } else { echo '<span style="color: #46b450;">正常</span>'; } break; case 'last_updated': global $wpdb; $table_name = $wpdb->prefix . 'inventory_log'; $last_update = $wpdb->get_var($wpdb->prepare( "SELECT created_at FROM $table_name WHERE product_id = %d ORDER BY created_at DESC LIMIT 1", $post_id )); echo $last_update ? date('Y-m-d H:i', strtotime($last_update)) : '无记录'; break; } } } // 初始化产品库存管理器 new ProductInventoryManager(); ?>
- 库存预警是系统的核心功能,下面我们实现智能预警算法: <?php /** * 智能预警与采购建议模块 */ class InventoryAlertSystem { private $alert_thresholds = [ 'critical' => 0.2, // 低于最低库存20%为紧急 'warning' => 0.5, // 低于最低库存50%为警告 'normal' => 1.0 // 低于最低库存100%为正常 ]; public function __construct() { // 每天检查库存 add_action('wp', [$this, 'schedule_daily_check']); add_action('inventory_daily_check', [$this, 'check_all_products']); // 添加管理页面 add_action('admin_menu', [$this, 'add_admin_pages']); } /** * 安排每日检查任务 */ public function schedule_daily_check() { if (!wp_next_scheduled('inventory_daily_check')) { wp_schedule_event(time(), 'daily', 'inventory_daily_check'); } } /** * 检查所有产品库存 */ public function check_all_products() { $args = [ 'post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'publish' ]; $products = get_posts($args); foreach ($products as $product) { $this->check_single_product($product->ID); }
- <?php /** * 智能预警与采购建议模块(续) */ class InventoryAlertSystem { // ... 前面的构造函数和基础方法保持不变 ... /** * 检查单个产品库存状态 */ private function check_single_product($product_id) { $current_stock = (int) get_post_meta($product_id, '_current_stock', true); $min_stock = (int) get_post_meta($product_id, '_min_stock', true); $max_stock = (int) get_post_meta($product_id, '_max_stock', true); // 计算库存比率 $stock_ratio = $min_stock > 0 ? $current_stock / $min_stock : 1; // 判断库存状态 if ($current_stock <= 0) { $this->generate_purchase_suggestion($product_id, 'out_of_stock', 5); $this->send_alert_notification($product_id, 'critical', '产品已售罄'); } elseif ($stock_ratio <= $this->alert_thresholds['critical']) { $suggest_qty = $max_stock - $current_stock; $this->generate_purchase_suggestion($product_id, 'critical_low', $suggest_qty, 5); $this->send_alert_notification($product_id, 'critical', '库存严重不足'); } elseif ($stock_ratio <= $this->alert_thresholds['warning']) { $suggest_qty = ceil(($max_stock - $current_stock) * 0.7); $this->generate_purchase_suggestion($product_id, 'low_stock', $suggest_qty, 3); $this->send_alert_notification($product_id, 'warning', '库存偏低'); } // 检查是否需要生成基于销售趋势的建议 $this->check_sales_trend($product_id); } /** * 生成采购建议 */ private function generate_purchase_suggestion($product_id, $reason, $quantity, $urgency = 1) { global $wpdb; $table_name = $wpdb->prefix . 'purchase_suggestions'; // 检查是否已有未处理的相同建议 $existing = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE product_id = %d AND reason_code = %s AND processed = 0", $product_id, $reason )); if ($existing > 0) { return; // 已有相同建议,不再重复生成 } $wpdb->insert( $table_name, [ 'product_id' => $product_id, 'suggested_qty' => $quantity, 'reason_code' => $reason, 'urgency_level' => $urgency, 'calculated_at' => current_time('mysql') ], ['%d', '%d', '%s', '%d', '%s'] ); // 记录到系统日志 $this->log_system_event('purchase_suggestion_generated', [ 'product_id' => $product_id, 'quantity' => $quantity, 'reason' => $reason ]); } /** * 检查销售趋势 */ private function check_sales_trend($product_id) { global $wpdb; $log_table = $wpdb->prefix . 'inventory_log'; // 获取最近30天的销售数据 $thirty_days_ago = date('Y-m-d H:i:s', strtotime('-30 days')); $sales_data = $wpdb->get_results($wpdb->prepare( "SELECT DATE(created_at) as sale_date, SUM(ABS(change_qty)) as daily_sales FROM $log_table WHERE product_id = %d AND change_type = 'sale' AND change_qty < 0 AND created_at >= %s GROUP BY DATE(created_at) ORDER BY sale_date DESC LIMIT 30", $product_id, $thirty_days_ago )); if (count($sales_data) < 7) { return; // 数据不足,不进行趋势分析 } // 计算平均日销量 $total_sales = 0; foreach ($sales_data as $data) { $total_sales += $data->daily_sales; } $avg_daily_sales = $total_sales / count($sales_data); // 获取当前库存和供应商交货周期 $current_stock = (int) get_post_meta($product_id, '_current_stock', true); $supplier_id = get_post_meta($product_id, '_primary_supplier', true); $lead_time = $this->get_supplier_lead_time($supplier_id); // 计算安全库存(考虑交货周期和销售波动) $safety_stock = ceil($avg_daily_sales * $lead_time * 1.5); // 如果当前库存低于安全库存,生成采购建议 if ($current_stock < $safety_stock) { $min_stock = (int) get_post_meta($product_id, '_min_stock', true); $suggest_qty = max($safety_stock - $current_stock, $min_stock - $current_stock); if ($suggest_qty > 0) { $this->generate_purchase_suggestion( $product_id, 'trend_based', $suggest_qty, 2 ); } } } /** * 获取供应商交货周期 */ private function get_supplier_lead_time($supplier_id) { if (empty($supplier_id)) { return 7; // 默认7天 } global $wpdb; $table_name = $wpdb->prefix . 'suppliers'; $lead_time = $wpdb->get_var($wpdb->prepare( "SELECT lead_time FROM $table_name WHERE id = %d", $supplier_id )); return $lead_time ?: 7; } /** * 发送预警通知 */ private function send_alert_notification($product_id, $level, $message) { $product = get_post($product_id); $current_stock = get_post_meta($product_id, '_current_stock', true); $min_stock = get_post_meta($product_id, '_min_stock', true); // 获取管理员邮箱 $admin_email = get_option('admin_email'); $subject = sprintf('[库存预警] %s - %s', ucfirst($level), $product->post_title ); $body = sprintf( "产品名称: %sn产品ID: %dn当前库存: %dn最低库存: %dn预警级别: %sn预警信息: %snn请登录系统查看详情: %s", $product->post_title, $product_id, $current_stock, $min_stock, $level, $message, admin_url('admin.php?page=inventory-alerts') ); // 发送邮件 wp_mail($admin_email, $subject, $body); // 记录通知发送 $this->log_system_event('alert_notification_sent', [ 'product_id' => $product_id, 'level' => $level, 'message' => $message, 'recipient' => $admin_email ]); } /** * 记录系统事件 */ private function log_system_event($event_type, $data = []) { global $wpdb; $log_table = $wpdb->prefix . 'inventory_log'; $wpdb->insert( $log_table, [ 'product_id' => 0, // 0表示系统事件 'change_type' => 'system_event', 'change_qty' => 0, 'previous_qty' => 0, 'current_qty' => 0, 'notes' => json_encode([ 'event' => $event_type, 'data' => $data, 'timestamp' => current_time('mysql') ]), 'created_at' => current_time('mysql') ], ['%d', '%s', '%d', '%d', '%d', '%s', '%s'] ); } /** * 添加管理页面 */ public function add_admin_pages() { add_menu_page( '库存预警系统', '库存预警', 'manage_options', 'inventory-alerts', [$this, 'render_alerts_page'], 'dashicons-warning', 56 ); add_submenu_page( 'inventory-alerts', '采购建议', '采购建议', 'manage_options', 'purchase-suggestions', [$this, 'render_suggestions_page'] ); } /** * 渲染预警页面 */ public function render_alerts_page() { global $wpdb; $log_table = $wpdb->prefix . 'inventory_log'; // 获取最近的预警记录 $alerts = $wpdb->get_results( "SELECT l.*, p.post_title FROM $log_table l LEFT JOIN {$wpdb->posts} p ON l.product_id = p.ID WHERE l.change_type = 'system_event' AND l.notes LIKE '%alert_notification_sent%' ORDER BY l.created_at DESC LIMIT 50" ); ?> <div class="wrap"> <h1>库存预警系统</h1> <div class="alert-summary"> <h2>今日预警统计</h2> <?php $this->render_today_stats(); ?> </div> <div class="alert-list"> <h2>最近预警记录</h2> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>时间</th> <th>产品</th> <th>预警级别</th> <th>详细信息</th> </tr> </thead> <tbody> <?php foreach ($alerts as $alert): $notes = json_decode($alert->notes, true); ?> <tr> <td><?php echo date('Y-m-d H:i', strtotime($alert->created_at)); ?></td> <td> <?php if ($alert->product_id > 0): ?> <a href="<?php echo get_edit_post_link($alert->product_id); ?>"> <?php echo esc_html($alert->post_title ?: '产品#' . $alert->product_id); ?> </a> <?php else: ?> 系统事件 <?php endif; ?> </td> <td> <?php $level = $notes['data']['level'] ?? 'unknown'; $level_labels = [ 'critical' => '<span class="dashicons dashicons-dismiss" style="color:#dc3232;"></span> 紧急', 'warning' => '<span class="dashicons dashicons-warning" style="color:#ffb900;"></span> 警告', 'normal' => '<span class="dashicons dashicons-yes" style="color:#46b450;"></span> 正常' ]; echo $level_labels[$level] ?? $level; ?> </td> <td><?php echo esc_html($notes['data']['message'] ?? ''); ?></td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> <style> .alert-summary { background: #fff; padding: 20px; margin-bottom: 20px; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .stat-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-top: 20px; } .stat-card { padding: 15px; border-radius: 4px; text-align: center; } .stat-card.critical { background: #ffeaea; border-left: 4px solid #dc3232; } .stat-card.warning { background: #fff3e6; border-left: 4px solid #ffb900; } .stat-card.normal { background: #e7f7e7; border-left: 4px solid #46b450; } .stat-number { font-size: 32px; font-weight: bold; display: block; margin-bottom: 5px; } </style> <?php } /** * 渲染今日统计 */ private function render_today_stats() { global $wpdb; $log_table = $wpdb->prefix . 'inventory_log'; $today = date('Y-m-d'); $stats = $wpdb->get_results( "SELECT SUM(CASE WHEN notes LIKE '%critical%' THEN 1 ELSE 0 END) as critical_count, SUM(CASE WHEN notes LIKE '%warning%' THEN 1 ELSE 0 END) as warning_count, SUM(CASE WHEN notes LIKE '%normal%' THEN 1 ELSE 0 END) as normal_count FROM $log_table WHERE DATE(created_at) = '$today' AND change_type = 'system_event' AND notes LIKE '%alert_notification_sent%'" ); $stat = $stats[0] ?? (object) [ 'critical_count' => 0, 'warning_count' => 0, 'normal_count' => 0 ]; ?> <div class="stat-cards"> <div class="stat-card critical"> <span class="stat-number"><?php echo $stat->critical_count; ?></span> <span>紧急预警</span> </div> <div class="stat-card warning"> <span class="stat-number"><?php echo $stat->warning_count; ?></span> <span>警告预警</span> </div> <div class="stat-card normal"> <span class="stat-number"><?php echo $stat->normal_count; ?></span> <span>正常通知</span> </div> </div> <?php } /** * 渲染采购建议页面 */ public function render_suggestions_page() { global $wpdb; $table_name = $wpdb->prefix . 'purchase_suggestions'; // 处理建议操作 if (isset($_POST['action']) && isset($_POST['suggestion_id'])) { $this->process_suggestion_action($_POST['suggestion_id'], $_POST['action']); } // 获取未处理的采购建议 $suggestions = $wpdb->get_results( "SELECT s.*, p.post_title, pm1.meta_value as current_stock, pm2.meta_value as min_stock, pm3.meta_value as primary_supplier FROM $table_name s LEFT JOIN {$wpdb->posts} p ON s.product_id = p.ID LEFT JOIN {$wpdb->postmeta} pm1 ON s.product_id = pm1.post_id AND pm1.meta_key = '_current_stock' LEFT JOIN {$wpdb->postmeta} pm2 ON s.product_id = pm2.post_id AND pm2.meta_key = '_min_stock' LEFT JOIN {$wpdb->postmeta} pm3 ON s.product_id = pm3.post_id AND pm3.meta_key = '_primary_supplier' WHERE s.processed = 0 ORDER BY s.urgency_level DESC, s.calculated_at DESC" ); ?> <div class="wrap"> <h1>采购建议</h1> <div class="notice notice-info"> <p>系统根据库存情况和销售趋势自动生成的采购建议。请及时处理紧急建议。</p> </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> </tr> </thead> <tbody> <?php foreach ($suggestions as $suggestion): $reason_labels = [ 'out_of_stock' => '已售罄', 'critical_low' => '库存严重不足', 'low_stock' => '库存偏低', 'trend_based' => '基于销售趋势' ]; ?> <tr> <td> <a href="<?php echo get_edit_post_link($suggestion->product_id); ?>"> <?php echo esc_html($suggestion->post_title ?: '产品#' . $suggestion->product_id); ?> </a> </td> <td><?php echo $suggestion->current_stock ?: 0; ?></td> <td><strong><?php echo $suggestion->suggested_qty; ?></strong></td> <td><?php echo $reason_labels[$suggestion->reason_code] ?? $suggestion->reason_code; ?></td> <td> <?php $urgency_stars = str_repeat('★', $suggestion->urgency_level); $urgency_class = $suggestion->urgency_level >= 4 ? 'critical' : ($suggestion->urgency_level >= 3 ? 'warning' : 'normal');
在当今电子商务快速发展的时代,库存管理成为企业运营的关键环节。WordPress作为全球最流行的内容管理系统,结合其强大的插件生态和自定义开发能力,可以构建出灵活高效的采购与库存预警系统。
本系统将实现以下核心功能:
- 实时库存监控与预警
- 智能采购建议生成
- 供应商管理集成
- 采购订单自动化处理
- 多仓库库存同步
系统采用模块化设计,便于后期功能扩展和维护。我们将使用WordPress自定义文章类型(CPT)存储产品信息,自定义数据库表记录库存变动,并结合REST API实现前后端数据交互。
首先,我们需要在WordPress环境中创建必要的数据库表结构。在您的插件主文件中添加以下代码:
<?php
/**
* WordPress柔性采购与库存预警系统
* 数据库初始化模块
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
class InventoryDB {
/**
* 创建库存相关数据表
*/
public static function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// 库存变动记录表
$table_inventory_log = $wpdb->prefix . 'inventory_log';
$sql_inventory_log = "CREATE TABLE IF NOT EXISTS $table_inventory_log (
id bigint(20) NOT NULL AUTO_INCREMENT,
product_id bigint(20) NOT NULL,
warehouse_id int(11) DEFAULT 1,
change_type varchar(50) NOT NULL COMMENT '变动类型: purchase, sale, adjust, return',
change_qty int(11) NOT NULL COMMENT '变动数量,正数为入库,负数为出库',
previous_qty int(11) NOT NULL COMMENT '变动前数量',
current_qty int(11) NOT NULL COMMENT '变动后数量',
reference_id bigint(20) DEFAULT NULL COMMENT '关联订单ID或采购单ID',
operator_id bigint(20) DEFAULT NULL COMMENT '操作员ID',
notes text COMMENT '备注信息',
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_product (product_id),
KEY idx_created (created_at)
) $charset_collate;";
// 采购建议表
$table_purchase_suggest = $wpdb->prefix . 'purchase_suggestions';
$sql_purchase_suggest = "CREATE TABLE IF NOT EXISTS $table_purchase_suggest (
id bigint(20) NOT NULL AUTO_INCREMENT,
product_id bigint(20) NOT NULL,
suggested_qty int(11) NOT NULL COMMENT '建议采购数量',
reason_code varchar(100) NOT NULL COMMENT '建议原因: low_stock, seasonal, trend',
urgency_level tinyint(1) DEFAULT 1 COMMENT '紧急程度 1-5',
calculated_at datetime DEFAULT CURRENT_TIMESTAMP,
processed tinyint(1) DEFAULT 0 COMMENT '是否已处理',
processed_at datetime DEFAULT NULL,
PRIMARY KEY (id),
KEY idx_product (product_id),
KEY idx_urgency (urgency_level)
) $charset_collate;";
// 供应商信息表
$table_suppliers = $wpdb->prefix . 'suppliers';
$sql_suppliers = "CREATE TABLE IF NOT EXISTS $table_suppliers (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(200) NOT NULL,
contact_person varchar(100) DEFAULT NULL,
phone varchar(50) DEFAULT NULL,
email varchar(100) DEFAULT NULL,
lead_time int(11) DEFAULT 7 COMMENT '交货周期(天)',
reliability_score decimal(3,2) DEFAULT 5.00 COMMENT '可靠性评分',
payment_terms text COMMENT '付款条件',
is_active tinyint(1) DEFAULT 1,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql_inventory_log);
dbDelta($sql_purchase_suggest);
dbDelta($sql_suppliers);
}
/**
* 删除数据表(用于插件卸载)
*/
public static function drop_tables() {
global $wpdb;
$tables = [
$wpdb->prefix . 'inventory_log',
$wpdb->prefix . 'purchase_suggestions',
$wpdb->prefix . 'suppliers'
];
foreach ($tables as $table) {
$wpdb->query("DROP TABLE IF EXISTS $table");
}
}
}
// 插件激活时创建表
register_activation_hook(__FILE__, ['InventoryDB', 'create_tables']);
// 插件卸载时删除表(根据需求选择是否启用)
// register_uninstall_hook(__FILE__, ['InventoryDB', 'drop_tables']);
?>
接下来,我们创建产品自定义文章类型并添加库存管理字段:
<?php
/**
* 产品与库存管理模块
*/
class ProductInventoryManager {
public function __construct() {
// 注册产品自定义文章类型
add_action('init', [$this, 'register_product_post_type']);
// 添加库存管理元框
add_action('add_meta_boxes', [$this, 'add_inventory_meta_box']);
// 保存库存数据
add_action('save_post_product', [$this, 'save_inventory_data'], 10, 2);
// 添加库存管理列
add_filter('manage_product_posts_columns', [$this, 'add_inventory_columns']);
add_action('manage_product_posts_custom_column', [$this, 'display_inventory_columns'], 10, 2);
}
/**
* 注册产品自定义文章类型
*/
public function register_product_post_type() {
$labels = [
'name' => '产品',
'singular_name' => '产品',
'menu_name' => '产品管理',
'add_new' => '添加产品',
'add_new_item' => '添加新产品',
'edit_item' => '编辑产品',
'new_item' => '新产品',
'view_item' => '查看产品',
'search_items' => '搜索产品',
'not_found' => '未找到产品',
'not_found_in_trash' => '回收站中无产品'
];
$args = [
'labels' => $labels,
'public' => true,
'has_archive' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => ['slug' => 'product'],
'capability_type' => 'post',
'hierarchical' => false,
'menu_position' => 30,
'menu_icon' => 'dashicons-products',
'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
'show_in_rest' => true
];
register_post_type('product', $args);
}
/**
* 添加库存管理元框
*/
public function add_inventory_meta_box() {
add_meta_box(
'inventory_management',
'库存管理',
[$this, 'render_inventory_meta_box'],
'product',
'side',
'high'
);
}
/**
* 渲染库存管理元框
*/
public function render_inventory_meta_box($post) {
// 获取现有库存数据
$current_stock = get_post_meta($post->ID, '_current_stock', true) ?: 0;
$min_stock = get_post_meta($post->ID, '_min_stock', true) ?: 10;
$max_stock = get_post_meta($post->ID, '_max_stock', true) ?: 100;
$supplier_id = get_post_meta($post->ID, '_primary_supplier', true);
// 添加安全验证
wp_nonce_field('save_inventory_data', 'inventory_nonce');
// 显示表单
?>
<div class="inventory-fields">
<p>
<label for="current_stock">当前库存:</label>
<input type="number" id="current_stock" name="current_stock"
value="<?php echo esc_attr($current_stock); ?>" class="widefat">
</p>
<p>
<label for="min_stock">最低库存预警:</label>
<input type="number" id="min_stock" name="min_stock"
value="<?php echo esc_attr($min_stock); ?>" class="widefat">
</p>
<p>
<label for="max_stock">最大库存量:</label>
<input type="number" id="max_stock" name="max_stock"
value="<?php echo esc_attr($max_stock); ?>" class="widefat">
</p>
<p>
<label for="primary_supplier">主要供应商:</label>
<?php $this->render_supplier_dropdown($supplier_id); ?>
</p>
<?php
// 显示库存状态
$this->display_stock_status($current_stock, $min_stock, $max_stock);
?>
</div>
<?php
}
/**
* 渲染供应商下拉列表
*/
private function render_supplier_dropdown($selected_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'suppliers';
$suppliers = $wpdb->get_results("SELECT id, name FROM $table_name WHERE is_active = 1");
echo '<select id="primary_supplier" name="primary_supplier" class="widefat">';
echo '<option value="">选择供应商</option>';
foreach ($suppliers as $supplier) {
$selected = ($supplier->id == $selected_id) ? 'selected' : '';
echo '<option value="' . esc_attr($supplier->id) . '" ' . $selected . '>';
echo esc_html($supplier->name);
echo '</option>';
}
echo '</select>';
}
/**
* 显示库存状态
*/
private function display_stock_status($current, $min, $max) {
$percentage = ($current / $max) * 100;
$status_class = 'good';
$status_text = '库存充足';
if ($current <= $min) {
$status_class = 'low';
$status_text = '库存不足';
} elseif ($current >= $max * 0.9) {
$status_class = 'high';
$status_text = '库存过高';
}
echo '<div class="stock-status ' . $status_class . '">';
echo '<strong>状态:</strong> ' . $status_text;
echo '<div class="stock-bar"><div class="stock-level" style="width:' . $percentage . '%;"></div></div>';
echo '</div>';
// 添加简单样式
echo '<style>
.stock-status { padding: 8px; margin: 10px 0; border-radius: 4px; }
.stock-status.low { background: #ffeaea; border-left: 4px solid #dc3232; }
.stock-status.good { background: #e7f7e7; border-left: 4px solid #46b450; }
.stock-status.high { background: #fff3e6; border-left: 4px solid #ffb900; }
.stock-bar { height: 10px; background: #f0f0f0; border-radius: 5px; margin-top: 5px; }
.stock-level { height: 100%; background: #0073aa; border-radius: 5px; }
</style>';
}
/**
* 保存库存数据
*/
public function save_inventory_data($post_id, $post) {
// 安全检查
if (!isset($_POST['inventory_nonce']) ||
!wp_verify_nonce($_POST['inventory_nonce'], 'save_inventory_data')) {
return;
}
// 权限检查
if (!current_user_can('edit_post', $post_id)) {
return;
}
// 自动保存检查
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
// 保存库存数据
$fields = ['current_stock', 'min_stock', 'max_stock', 'primary_supplier'];
foreach ($fields as $field) {
if (isset($_POST[$field])) {
$value = sanitize_text_field($_POST[$field]);
update_post_meta($post_id, '_' . $field, $value);
}
}
// 记录库存变动
$this->log_inventory_change($post_id, 'adjust', $_POST['current_stock']);
}
/**
* 记录库存变动
*/
private function log_inventory_change($product_id, $type, $new_qty) {
global $wpdb;
$old_qty = get_post_meta($product_id, '_current_stock', true) ?: 0;
$change_qty = $new_qty - $old_qty;
if ($change_qty == 0) {
return; // 库存未变化,不记录
}
$table_name = $wpdb->prefix . 'inventory_log';
$wpdb->insert(
$table_name,
[
'product_id' => $product_id,
'change_type' => $type,
'change_qty' => $change_qty,
'previous_qty' => $old_qty,
'current_qty' => $new_qty,
'operator_id' => get_current_user_id(),
'created_at' => current_time('mysql')
],
['%d', '%s', '%d', '%d', '%d', '%d', '%s']
);
}
/**
* 添加库存管理列
*/
public function add_inventory_columns($columns) {
$new_columns = [];
foreach ($columns as $key => $value) {
$new_columns[$key] = $value;
if ($key === 'title') {
$new_columns['current_stock'] = '当前库存';
$new_columns['stock_status'] = '库存状态';
$new_columns['last_updated'] = '最后更新';
}
}
return $new_columns;
}
/**
* 显示库存管理列内容
*/
public function display_inventory_columns($column, $post_id) {
switch ($column) {
case 'current_stock':
$stock = get_post_meta($post_id, '_current_stock', true);
echo $stock ?: 0;
break;
case 'stock_status':
$current = get_post_meta($post_id, '_current_stock', true) ?: 0;
$min = get_post_meta($post_id, '_min_stock', true) ?: 10;
if ($current <= $min) {
echo '<span style="color: #dc3232; font-weight: bold;">需补货</span>';
} elseif ($current <= $min * 2) {
echo '<span style="color: #ffb900;">库存偏低</span>';
} else {
echo '<span style="color: #46b450;">正常</span>';
}
break;
case 'last_updated':
global $wpdb;
$table_name = $wpdb->prefix . 'inventory_log';
$last_update = $wpdb->get_var($wpdb->prepare(
"SELECT created_at FROM $table_name
WHERE product_id = %d
ORDER BY created_at DESC LIMIT 1",
$post_id
));
echo $last_update ? date('Y-m-d H:i', strtotime($last_update)) : '无记录';
break;
}
}
}
// 初始化产品库存管理器
new ProductInventoryManager();
?>
库存预警是系统的核心功能,下面我们实现智能预警算法:
<?php
/**
* 智能预警与采购建议模块
*/
class InventoryAlertSystem {
private $alert_thresholds = [
'critical' => 0.2, // 低于最低库存20%为紧急
'warning' => 0.5, // 低于最低库存50%为警告
'normal' => 1.0 // 低于最低库存100%为正常
];
public function __construct() {
// 每天检查库存
add_action('wp', [$this, 'schedule_daily_check']);
add_action('inventory_daily_check', [$this, 'check_all_products']);
// 添加管理页面
add_action('admin_menu', [$this, 'add_admin_pages']);
}
/**
* 安排每日检查任务
*/
public function schedule_daily_check() {
if (!wp_next_scheduled('inventory_daily_check')) {
wp_schedule_event(time(), 'daily', 'inventory_daily_check');
}
}
/**
* 检查所有产品库存
*/
public function check_all_products() {
$args = [
'post_type' => 'product',
'posts_per_page' => -1,
'post_status' => 'publish'
];
$products = get_posts($args);
foreach ($products as $product) {
$this->check_single_product($product->ID);
}
<?php
/**
* 智能预警与采购建议模块(续)
*/
class InventoryAlertSystem {
// ... 前面的构造函数和基础方法保持不变 ...
/**
* 检查单个产品库存状态
*/
private function check_single_product($product_id) {
$current_stock = (int) get_post_meta($product_id, '_current_stock', true);
$min_stock = (int) get_post_meta($product_id, '_min_stock', true);
$max_stock = (int) get_post_meta($product_id, '_max_stock', true);
// 计算库存比率
$stock_ratio = $min_stock > 0 ? $current_stock / $min_stock : 1;
// 判断库存状态
if ($current_stock <= 0) {
$this->generate_purchase_suggestion($product_id, 'out_of_stock', 5);
$this->send_alert_notification($product_id, 'critical', '产品已售罄');
} elseif ($stock_ratio <= $this->alert_thresholds['critical']) {
$suggest_qty = $max_stock - $current_stock;
$this->generate_purchase_suggestion($product_id, 'critical_low', $suggest_qty, 5);
$this->send_alert_notification($product_id, 'critical', '库存严重不足');
} elseif ($stock_ratio <= $this->alert_thresholds['warning']) {
$suggest_qty = ceil(($max_stock - $current_stock) * 0.7);
$this->generate_purchase_suggestion($product_id, 'low_stock', $suggest_qty, 3);
$this->send_alert_notification($product_id, 'warning', '库存偏低');
}
// 检查是否需要生成基于销售趋势的建议
$this->check_sales_trend($product_id);
}
/**
* 生成采购建议
*/
private function generate_purchase_suggestion($product_id, $reason, $quantity, $urgency = 1) {
global $wpdb;
$table_name = $wpdb->prefix . 'purchase_suggestions';
// 检查是否已有未处理的相同建议
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table_name
WHERE product_id = %d
AND reason_code = %s
AND processed = 0",
$product_id, $reason
));
if ($existing > 0) {
return; // 已有相同建议,不再重复生成
}
$wpdb->insert(
$table_name,
[
'product_id' => $product_id,
'suggested_qty' => $quantity,
'reason_code' => $reason,
'urgency_level' => $urgency,
'calculated_at' => current_time('mysql')
],
['%d', '%d', '%s', '%d', '%s']
);
// 记录到系统日志
$this->log_system_event('purchase_suggestion_generated', [
'product_id' => $product_id,
'quantity' => $quantity,
'reason' => $reason
]);
}
/**
* 检查销售趋势
*/
private function check_sales_trend($product_id) {
global $wpdb;
$log_table = $wpdb->prefix . 'inventory_log';
// 获取最近30天的销售数据
$thirty_days_ago = date('Y-m-d H:i:s', strtotime('-30 days'));
$sales_data = $wpdb->get_results($wpdb->prepare(
"SELECT DATE(created_at) as sale_date,
SUM(ABS(change_qty)) as daily_sales
FROM $log_table
WHERE product_id = %d
AND change_type = 'sale'
AND change_qty < 0
AND created_at >= %s
GROUP BY DATE(created_at)
ORDER BY sale_date DESC
LIMIT 30",
$product_id, $thirty_days_ago
));
if (count($sales_data) < 7) {
return; // 数据不足,不进行趋势分析
}
// 计算平均日销量
$total_sales = 0;
foreach ($sales_data as $data) {
$total_sales += $data->daily_sales;
}
$avg_daily_sales = $total_sales / count($sales_data);
// 获取当前库存和供应商交货周期
$current_stock = (int) get_post_meta($product_id, '_current_stock', true);
$supplier_id = get_post_meta($product_id, '_primary_supplier', true);
$lead_time = $this->get_supplier_lead_time($supplier_id);
// 计算安全库存(考虑交货周期和销售波动)
$safety_stock = ceil($avg_daily_sales * $lead_time * 1.5);
// 如果当前库存低于安全库存,生成采购建议
if ($current_stock < $safety_stock) {
$min_stock = (int) get_post_meta($product_id, '_min_stock', true);
$suggest_qty = max($safety_stock - $current_stock, $min_stock - $current_stock);
if ($suggest_qty > 0) {
$this->generate_purchase_suggestion(
$product_id,
'trend_based',
$suggest_qty,
2
);
}
}
}
/**
* 获取供应商交货周期
*/
private function get_supplier_lead_time($supplier_id) {
if (empty($supplier_id)) {
return 7; // 默认7天
}
global $wpdb;
$table_name = $wpdb->prefix . 'suppliers';
$lead_time = $wpdb->get_var($wpdb->prepare(
"SELECT lead_time FROM $table_name WHERE id = %d",
$supplier_id
));
return $lead_time ?: 7;
}
/**
* 发送预警通知
*/
private function send_alert_notification($product_id, $level, $message) {
$product = get_post($product_id);
$current_stock = get_post_meta($product_id, '_current_stock', true);
$min_stock = get_post_meta($product_id, '_min_stock', true);
// 获取管理员邮箱
$admin_email = get_option('admin_email');
$subject = sprintf('[库存预警] %s - %s',
ucfirst($level),
$product->post_title
);
$body = sprintf(
"产品名称: %sn产品ID: %dn当前库存: %dn最低库存: %dn预警级别: %sn预警信息: %snn请登录系统查看详情: %s",
$product->post_title,
$product_id,
$current_stock,
$min_stock,
$level,
$message,
admin_url('admin.php?page=inventory-alerts')
);
// 发送邮件
wp_mail($admin_email, $subject, $body);
// 记录通知发送
$this->log_system_event('alert_notification_sent', [
'product_id' => $product_id,
'level' => $level,
'message' => $message,
'recipient' => $admin_email
]);
}
/**
* 记录系统事件
*/
private function log_system_event($event_type, $data = []) {
global $wpdb;
$log_table = $wpdb->prefix . 'inventory_log';
$wpdb->insert(
$log_table,
[
'product_id' => 0, // 0表示系统事件
'change_type' => 'system_event',
'change_qty' => 0,
'previous_qty' => 0,
'current_qty' => 0,
'notes' => json_encode([
'event' => $event_type,
'data' => $data,
'timestamp' => current_time('mysql')
]),
'created_at' => current_time('mysql')
],
['%d', '%s', '%d', '%d', '%d', '%s', '%s']
);
}
/**
* 添加管理页面
*/
public function add_admin_pages() {
add_menu_page(
'库存预警系统',
'库存预警',
'manage_options',
'inventory-alerts',
[$this, 'render_alerts_page'],
'dashicons-warning',
56
);
add_submenu_page(
'inventory-alerts',
'采购建议',
'采购建议',
'manage_options',
'purchase-suggestions',
[$this, 'render_suggestions_page']
);
}
/**
* 渲染预警页面
*/
public function render_alerts_page() {
global $wpdb;
$log_table = $wpdb->prefix . 'inventory_log';
// 获取最近的预警记录
$alerts = $wpdb->get_results(
"SELECT l.*, p.post_title
FROM $log_table l
LEFT JOIN {$wpdb->posts} p ON l.product_id = p.ID
WHERE l.change_type = 'system_event'
AND l.notes LIKE '%alert_notification_sent%'
ORDER BY l.created_at DESC
LIMIT 50"
);
?>
<div class="wrap">
<h1>库存预警系统</h1>
<div class="alert-summary">
<h2>今日预警统计</h2>
<?php $this->render_today_stats(); ?>
</div>
<div class="alert-list">
<h2>最近预警记录</h2>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>时间</th>
<th>产品</th>
<th>预警级别</th>
<th>详细信息</th>
</tr>
</thead>
<tbody>
<?php foreach ($alerts as $alert):
$notes = json_decode($alert->notes, true);
?>
<tr>
<td><?php echo date('Y-m-d H:i', strtotime($alert->created_at)); ?></td>
<td>
<?php if ($alert->product_id > 0): ?>
<a href="<?php echo get_edit_post_link($alert->product_id); ?>">
<?php echo esc_html($alert->post_title ?: '产品#' . $alert->product_id); ?>
</a>
<?php else: ?>
系统事件
<?php endif; ?>
</td>
<td>
<?php
$level = $notes['data']['level'] ?? 'unknown';
$level_labels = [
'critical' => '<span class="dashicons dashicons-dismiss" style="color:#dc3232;"></span> 紧急',
'warning' => '<span class="dashicons dashicons-warning" style="color:#ffb900;"></span> 警告',
'normal' => '<span class="dashicons dashicons-yes" style="color:#46b450;"></span> 正常'
];
echo $level_labels[$level] ?? $level;
?>
</td>
<td><?php echo esc_html($notes['data']['message'] ?? ''); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<style>
.alert-summary {
background: #fff;
padding: 20px;
margin-bottom: 20px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.stat-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-top: 20px;
}
.stat-card {
padding: 15px;
border-radius: 4px;
text-align: center;
}
.stat-card.critical {
background: #ffeaea;
border-left: 4px solid #dc3232;
}
.stat-card.warning {
background: #fff3e6;
border-left: 4px solid #ffb900;
}
.stat-card.normal {
background: #e7f7e7;
border-left: 4px solid #46b450;
}
.stat-number {
font-size: 32px;
font-weight: bold;
display: block;
margin-bottom: 5px;
}
</style>
<?php
}
/**
* 渲染今日统计
*/
private function render_today_stats() {
global $wpdb;
$log_table = $wpdb->prefix . 'inventory_log';
$today = date('Y-m-d');
$stats = $wpdb->get_results(
"SELECT
SUM(CASE WHEN notes LIKE '%critical%' THEN 1 ELSE 0 END) as critical_count,
SUM(CASE WHEN notes LIKE '%warning%' THEN 1 ELSE 0 END) as warning_count,
SUM(CASE WHEN notes LIKE '%normal%' THEN 1 ELSE 0 END) as normal_count
FROM $log_table
WHERE DATE(created_at) = '$today'
AND change_type = 'system_event'
AND notes LIKE '%alert_notification_sent%'"
);
$stat = $stats[0] ?? (object) [
'critical_count' => 0,
'warning_count' => 0,
'normal_count' => 0
];
?>
<div class="stat-cards">
<div class="stat-card critical">
<span class="stat-number"><?php echo $stat->critical_count; ?></span>
<span>紧急预警</span>
</div>
<div class="stat-card warning">
<span class="stat-number"><?php echo $stat->warning_count; ?></span>
<span>警告预警</span>
</div>
<div class="stat-card normal">
<span class="stat-number"><?php echo $stat->normal_count; ?></span>
<span>正常通知</span>
</div>
</div>
<?php
}
/**
* 渲染采购建议页面
*/
public function render_suggestions_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'purchase_suggestions';
// 处理建议操作
if (isset($_POST['action']) && isset($_POST['suggestion_id'])) {
$this->process_suggestion_action($_POST['suggestion_id'], $_POST['action']);
}
// 获取未处理的采购建议
$suggestions = $wpdb->get_results(
"SELECT s.*, p.post_title,
pm1.meta_value as current_stock,
pm2.meta_value as min_stock,
pm3.meta_value as primary_supplier
FROM $table_name s
LEFT JOIN {$wpdb->posts} p ON s.product_id = p.ID
LEFT JOIN {$wpdb->postmeta} pm1 ON s.product_id = pm1.post_id AND pm1.meta_key = '_current_stock'
LEFT JOIN {$wpdb->postmeta} pm2 ON s.product_id = pm2.post_id AND pm2.meta_key = '_min_stock'
LEFT JOIN {$wpdb->postmeta} pm3 ON s.product_id = pm3.post_id AND pm3.meta_key = '_primary_supplier'
WHERE s.processed = 0
ORDER BY s.urgency_level DESC, s.calculated_at DESC"
);
?>
<div class="wrap">
<h1>采购建议</h1>
<div class="notice notice-info">
<p>系统根据库存情况和销售趋势自动生成的采购建议。请及时处理紧急建议。</p>
</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>
</tr>
</thead>
<tbody>
<?php foreach ($suggestions as $suggestion):
$reason_labels = [
'out_of_stock' => '已售罄',
'critical_low' => '库存严重不足',
'low_stock' => '库存偏低',
'trend_based' => '基于销售趋势'
];
?>
<tr>
<td>
<a href="<?php echo get_edit_post_link($suggestion->product_id); ?>">
<?php echo esc_html($suggestion->post_title ?: '产品#' . $suggestion->product_id); ?>
</a>
</td>
<td><?php echo $suggestion->current_stock ?: 0; ?></td>
<td><strong><?php echo $suggestion->suggested_qty; ?></strong></td>
<td><?php echo $reason_labels[$suggestion->reason_code] ?? $suggestion->reason_code; ?></td>
<td>
<?php
$urgency_stars = str_repeat('★', $suggestion->urgency_level);
$urgency_class = $suggestion->urgency_level >= 4 ? 'critical' :
($suggestion->urgency_level >= 3 ? 'warning' : 'normal');
<?php
/**
* 智能预警与采购建议模块(续)
*/
class InventoryAlertSystem {
// ... 前面的构造函数和基础方法保持不变 ...
/**
* 检查单个产品库存状态
*/
private function check_single_product($product_id) {
$current_stock = (int) get_post_meta($product_id, '_current_stock', true);
$min_stock = (int) get_post_meta($product_id, '_min_stock', true);
$max_stock = (int) get_post_meta($product_id, '_max_stock', true);
// 计算库存比率
$stock_ratio = $min_stock > 0 ? $current_stock / $min_stock : 1;
// 判断库存状态
if ($current_stock <= 0) {
$this->generate_purchase_suggestion($product_id, 'out_of_stock', 5);
$this->send_alert_notification($product_id, 'critical', '产品已售罄');
} elseif ($stock_ratio <= $this->alert_thresholds['critical']) {
$suggest_qty = $max_stock - $current_stock;
$this->generate_purchase_suggestion($product_id, 'critical_low', $suggest_qty, 5);
$this->send_alert_notification($product_id, 'critical', '库存严重不足');
} elseif ($stock_ratio <= $this->alert_thresholds['warning']) {
$suggest_qty = ceil(($max_stock - $current_stock) * 0.7);
$this->generate_purchase_suggestion($product_id, 'low_stock', $suggest_qty, 3);
$this->send_alert_notification($product_id, 'warning', '库存偏低');
}
// 检查是否需要生成基于销售趋势的建议
$this->check_sales_trend($product_id);
}
/**
* 生成采购建议
*/
private function generate_purchase_suggestion($product_id, $reason, $quantity, $urgency = 1) {
global $wpdb;
$table_name = $wpdb->prefix . 'purchase_suggestions';
// 检查是否已有未处理的相同建议
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table_name
WHERE product_id = %d
AND reason_code = %s
AND processed = 0",
$product_id, $reason
));
if ($existing > 0) {
return; // 已有相同建议,不再重复生成
}
$wpdb->insert(
$table_name,
[
'product_id' => $product_id,
'suggested_qty' => $quantity,
'reason_code' => $reason,
'urgency_level' => $urgency,
'calculated_at' => current_time('mysql')
],
['%d', '%d', '%s', '%d', '%s']
);
// 记录到系统日志
$this->log_system_event('purchase_suggestion_generated', [
'product_id' => $product_id,
'quantity' => $quantity,
'reason' => $reason
]);
}
/**
* 检查销售趋势
*/
private function check_sales_trend($product_id) {
global $wpdb;
$log_table = $wpdb->prefix . 'inventory_log';
// 获取最近30天的销售数据
$thirty_days_ago = date('Y-m-d H:i:s', strtotime('-30 days'));
$sales_data = $wpdb->get_results($wpdb->prepare(
"SELECT DATE(created_at) as sale_date,
SUM(ABS(change_qty)) as daily_sales
FROM $log_table
WHERE product_id = %d
AND change_type = 'sale'
AND change_qty < 0
AND created_at >= %s
GROUP BY DATE(created_at)
ORDER BY sale_date DESC
LIMIT 30",
$product_id, $thirty_days_ago
));
if (count($sales_data) < 7) {
return; // 数据不足,不进行趋势分析
}
// 计算平均日销量
$total_sales = 0;
foreach ($sales_data as $data) {
$total_sales += $data->daily_sales;
}
$avg_daily_sales = $total_sales / count($sales_data);
// 获取当前库存和供应商交货周期
$current_stock = (int) get_post_meta($product_id, '_current_stock', true);
$supplier_id = get_post_meta($product_id, '_primary_supplier', true);
$lead_time = $this->get_supplier_lead_time($supplier_id);
// 计算安全库存(考虑交货周期和销售波动)
$safety_stock = ceil($avg_daily_sales * $lead_time * 1.5);
// 如果当前库存低于安全库存,生成采购建议
if ($current_stock < $safety_stock) {
$min_stock = (int) get_post_meta($product_id, '_min_stock', true);
$suggest_qty = max($safety_stock - $current_stock, $min_stock - $current_stock);
if ($suggest_qty > 0) {
$this->generate_purchase_suggestion(
$product_id,
'trend_based',
$suggest_qty,
2
);
}
}
}
/**
* 获取供应商交货周期
*/
private function get_supplier_lead_time($supplier_id) {
if (empty($supplier_id)) {
return 7; // 默认7天
}
global $wpdb;
$table_name = $wpdb->prefix . 'suppliers';
$lead_time = $wpdb->get_var($wpdb->prepare(
"SELECT lead_time FROM $table_name WHERE id = %d",
$supplier_id
));
return $lead_time ?: 7;
}
/**
* 发送预警通知
*/
private function send_alert_notification($product_id, $level, $message) {
$product = get_post($product_id);
$current_stock = get_post_meta($product_id, '_current_stock', true);
$min_stock = get_post_meta($product_id, '_min_stock', true);
// 获取管理员邮箱
$admin_email = get_option('admin_email');
$subject = sprintf('[库存预警] %s - %s',
ucfirst($level),
$product->post_title
);
$body = sprintf(
"产品名称: %sn产品ID: %dn当前库存: %dn最低库存: %dn预警级别: %sn预警信息: %snn请登录系统查看详情: %s",
$product->post_title,
$product_id,
$current_stock,
$min_stock,
$level,
$message,
admin_url('admin.php?page=inventory-alerts')
);
// 发送邮件
wp_mail($admin_email, $subject, $body);
// 记录通知发送
$this->log_system_event('alert_notification_sent', [
'product_id' => $product_id,
'level' => $level,
'message' => $message,
'recipient' => $admin_email
]);
}
/**
* 记录系统事件
*/
private function log_system_event($event_type, $data = []) {
global $wpdb;
$log_table = $wpdb->prefix . 'inventory_log';
$wpdb->insert(
$log_table,
[
'product_id' => 0, // 0表示系统事件
'change_type' => 'system_event',
'change_qty' => 0,
'previous_qty' => 0,
'current_qty' => 0,
'notes' => json_encode([
'event' => $event_type,
'data' => $data,
'timestamp' => current_time('mysql')
]),
'created_at' => current_time('mysql')
],
['%d', '%s', '%d', '%d', '%d', '%s', '%s']
);
}
/**
* 添加管理页面
*/
public function add_admin_pages() {
add_menu_page(
'库存预警系统',
'库存预警',
'manage_options',
'inventory-alerts',
[$this, 'render_alerts_page'],
'dashicons-warning',
56
);
add_submenu_page(
'inventory-alerts',
'采购建议',
'采购建议',
'manage_options',
'purchase-suggestions',
[$this, 'render_suggestions_page']
);
}
/**
* 渲染预警页面
*/
public function render_alerts_page() {
global $wpdb;
$log_table = $wpdb->prefix . 'inventory_log';
// 获取最近的预警记录
$alerts = $wpdb->get_results(
"SELECT l.*, p.post_title
FROM $log_table l
LEFT JOIN {$wpdb->posts} p ON l.product_id = p.ID
WHERE l.change_type = 'system_event'
AND l.notes LIKE '%alert_notification_sent%'
ORDER BY l.created_at DESC
LIMIT 50"
);
?>
<div class="wrap">
<h1>库存预警系统</h1>
<div class="alert-summary">
<h2>今日预警统计</h2>
<?php $this->render_today_stats(); ?>
</div>
<div class="alert-list">
<h2>最近预警记录</h2>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>时间</th>
<th>产品</th>
<th>预警级别</th>
<th>详细信息</th>
</tr>
</thead>
<tbody>
<?php foreach ($alerts as $alert):
$notes = json_decode($alert->notes, true);
?>
<tr>
<td><?php echo date('Y-m-d H:i', strtotime($alert->created_at)); ?></td>
<td>
<?php if ($alert->product_id > 0): ?>
<a href="<?php echo get_edit_post_link($alert->product_id); ?>">
<?php echo esc_html($alert->post_title ?: '产品#' . $alert->product_id); ?>
</a>
<?php else: ?>
系统事件
<?php endif; ?>
</td>
<td>
<?php
$level = $notes['data']['level'] ?? 'unknown';
$level_labels = [
'critical' => '<span class="dashicons dashicons-dismiss" style="color:#dc3232;"></span> 紧急',
'warning' => '<span class="dashicons dashicons-warning" style="color:#ffb900;"></span> 警告',
'normal' => '<span class="dashicons dashicons-yes" style="color:#46b450;"></span> 正常'
];
echo $level_labels[$level] ?? $level;
?>
</td>
<td><?php echo esc_html($notes['data']['message'] ?? ''); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<style>
.alert-summary {
background: #fff;
padding: 20px;
margin-bottom: 20px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.stat-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-top: 20px;
}
.stat-card {
padding: 15px;
border-radius: 4px;
text-align: center;
}
.stat-card.critical {
background: #ffeaea;
border-left: 4px solid #dc3232;
}
.stat-card.warning {
background: #fff3e6;
border-left: 4px solid #ffb900;
}
.stat-card.normal {
background: #e7f7e7;
border-left: 4px solid #46b450;
}
.stat-number {
font-size: 32px;
font-weight: bold;
display: block;
margin-bottom: 5px;
}
</style>
<?php
}
/**
* 渲染今日统计
*/
private function render_today_stats() {
global $wpdb;
$log_table = $wpdb->prefix . 'inventory_log';
$today = date('Y-m-d');
$stats = $wpdb->get_results(
"SELECT
SUM(CASE WHEN notes LIKE '%critical%' THEN 1 ELSE 0 END) as critical_count,
SUM(CASE WHEN notes LIKE '%warning%' THEN 1 ELSE 0 END) as warning_count,
SUM(CASE WHEN notes LIKE '%normal%' THEN 1 ELSE 0 END) as normal_count
FROM $log_table
WHERE DATE(created_at) = '$today'
AND change_type = 'system_event'
AND notes LIKE '%alert_notification_sent%'"
);
$stat = $stats[0] ?? (object) [
'critical_count' => 0,
'warning_count' => 0,
'normal_count' => 0
];
?>
<div class="stat-cards">
<div class="stat-card critical">
<span class="stat-number"><?php echo $stat->critical_count; ?></span>
<span>紧急预警</span>
</div>
<div class="stat-card warning">
<span class="stat-number"><?php echo $stat->warning_count; ?></span>
<span>警告预警</span>
</div>
<div class="stat-card normal">
<span class="stat-number"><?php echo $stat->normal_count; ?></span>
<span>正常通知</span>
</div>
</div>
<?php
}
/**
* 渲染采购建议页面
*/
public function render_suggestions_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'purchase_suggestions';
// 处理建议操作
if (isset($_POST['action']) && isset($_POST['suggestion_id'])) {
$this->process_suggestion_action($_POST['suggestion_id'], $_POST['action']);
}
// 获取未处理的采购建议
$suggestions = $wpdb->get_results(
"SELECT s.*, p.post_title,
pm1.meta_value as current_stock,
pm2.meta_value as min_stock,
pm3.meta_value as primary_supplier
FROM $table_name s
LEFT JOIN {$wpdb->posts} p ON s.product_id = p.ID
LEFT JOIN {$wpdb->postmeta} pm1 ON s.product_id = pm1.post_id AND pm1.meta_key = '_current_stock'
LEFT JOIN {$wpdb->postmeta} pm2 ON s.product_id = pm2.post_id AND pm2.meta_key = '_min_stock'
LEFT JOIN {$wpdb->postmeta} pm3 ON s.product_id = pm3.post_id AND pm3.meta_key = '_primary_supplier'
WHERE s.processed = 0
ORDER BY s.urgency_level DESC, s.calculated_at DESC"
);
?>
<div class="wrap">
<h1>采购建议</h1>
<div class="notice notice-info">
<p>系统根据库存情况和销售趋势自动生成的采购建议。请及时处理紧急建议。</p>
</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>
</tr>
</thead>
<tbody>
<?php foreach ($suggestions as $suggestion):
$reason_labels = [
'out_of_stock' => '已售罄',
'critical_low' => '库存严重不足',
'low_stock' => '库存偏低',
'trend_based' => '基于销售趋势'
];
?>
<tr>
<td>
<a href="<?php echo get_edit_post_link($suggestion->product_id); ?>">
<?php echo esc_html($suggestion->post_title ?: '产品#' . $suggestion->product_id); ?>
</a>
</td>
<td><?php echo $suggestion->current_stock ?: 0; ?></td>
<td><strong><?php echo $suggestion->suggested_qty; ?></strong></td>
<td><?php echo $reason_labels[$suggestion->reason_code] ?? $suggestion->reason_code; ?></td>
<td>
<?php
$urgency_stars = str_repeat('★', $suggestion->urgency_level);
$urgency_class = $suggestion->urgency_level >= 4 ? 'critical' :
($suggestion->urgency_level >= 3 ? 'warning' : 'normal');


