文章目录
-
- 在制造业和产品开发领域,BOM(Bill of Materials,物料清单)是产品所需的所有原材料、组件和子组件的结构化列表。动态BOM管理指的是能够根据产品配置、版本变化或生产需求实时调整和更新物料清单的能力。 对于使用WordPress搭建产品展示网站或小型电商平台的企业,集成动态BOM管理功能可以帮助他们更好地管理产品组件、成本计算和生产流程。本教程将指导您开发一个WordPress插件,实现小批量定制产品的动态BOM管理功能。
- 首先,我们需要设计插件的基本结构和数据库表。我们的插件将包含以下核心表: 产品表:存储基本产品信息 BOM主表:存储BOM的基本信息 BOM明细表:存储BOM的具体物料项 物料库表:存储可用的物料信息 <?php /** * WordPress动态BOM管理插件 - 数据库安装脚本 */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } class DynamicBOM_Installer { /** * 创建插件所需的数据库表 */ public static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // BOM产品表 $table_products = $wpdb->prefix . 'bom_products'; $sql_products = "CREATE TABLE IF NOT EXISTS $table_products ( id INT(11) NOT NULL AUTO_INCREMENT, product_name VARCHAR(255) NOT NULL, product_description TEXT, base_price DECIMAL(10,2) DEFAULT 0.00, status ENUM('active', 'inactive') DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; // BOM主表 $table_boms = $wpdb->prefix . 'bom_master'; $sql_boms = "CREATE TABLE IF NOT EXISTS $table_boms ( id INT(11) NOT NULL AUTO_INCREMENT, product_id INT(11) NOT NULL, bom_name VARCHAR(255) NOT NULL, bom_version VARCHAR(50) DEFAULT '1.0', is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), FOREIGN KEY (product_id) REFERENCES $table_products(id) ON DELETE CASCADE ) $charset_collate;"; // 物料库表 $table_materials = $wpdb->prefix . 'bom_materials'; $sql_materials = "CREATE TABLE IF NOT EXISTS $table_materials ( id INT(11) NOT NULL AUTO_INCREMENT, material_code VARCHAR(100) NOT NULL UNIQUE, material_name VARCHAR(255) NOT NULL, material_type ENUM('raw', 'component', 'subassembly') DEFAULT 'raw', unit_price DECIMAL(10,2) DEFAULT 0.00, unit_of_measure VARCHAR(50) DEFAULT 'pcs', stock_quantity INT(11) DEFAULT 0, min_stock_level INT(11) DEFAULT 10, supplier_info TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; // BOM明细表 $table_bom_items = $wpdb->prefix . 'bom_items'; $sql_bom_items = "CREATE TABLE IF NOT EXISTS $table_bom_items ( id INT(11) NOT NULL AUTO_INCREMENT, bom_id INT(11) NOT NULL, material_id INT(11) NOT NULL, quantity DECIMAL(10,3) NOT NULL DEFAULT 1.000, operation_sequence INT(11) DEFAULT 1, scrap_factor DECIMAL(5,3) DEFAULT 0.000, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), FOREIGN KEY (bom_id) REFERENCES $table_boms(id) ON DELETE CASCADE, FOREIGN KEY (material_id) REFERENCES $table_materials(id) ON DELETE CASCADE ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); // 执行SQL创建表 dbDelta($sql_products); dbDelta($sql_boms); dbDelta($sql_materials); dbDelta($sql_bom_items); // 添加默认数据(可选) self::add_default_data(); } /** * 添加默认数据 */ private static function add_default_data() { global $wpdb; // 检查是否已有数据 $table_materials = $wpdb->prefix . 'bom_materials'; $count = $wpdb->get_var("SELECT COUNT(*) FROM $table_materials"); if ($count == 0) { // 添加示例物料 $wpdb->insert($table_materials, array( 'material_code' => 'MAT-001', 'material_name' => '铝合金框架', 'material_type' => 'raw', 'unit_price' => 25.50, 'unit_of_measure' => 'pcs', 'stock_quantity' => 100, 'min_stock_level' => 20 )); $wpdb->insert($table_materials, array( 'material_code' => 'MAT-002', 'material_name' => '电路板', 'material_type' => 'component', 'unit_price' => 15.75, 'unit_of_measure' => 'pcs', 'stock_quantity' => 200, 'min_stock_level' => 30 )); } } } ?>
- 接下来,我们创建插件的主类,负责初始化插件功能、注册短码和管理后台菜单。 <?php /** * WordPress动态BOM管理插件主类 */ class DynamicBOM_Plugin { private static $instance = null; /** * 获取插件实例(单例模式) */ public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } /** * 构造函数 */ private function __construct() { // 注册激活和停用钩子 register_activation_hook(__FILE__, array($this, 'activate_plugin')); register_deactivation_hook(__FILE__, array($this, 'deactivate_plugin')); // 初始化插件 add_action('plugins_loaded', array($this, 'init_plugin')); } /** * 激活插件时执行 */ public function activate_plugin() { // 创建数据库表 DynamicBOM_Installer::create_tables(); // 添加插件版本号 update_option('dynamic_bom_version', '1.0.0'); // 设置默认选项 add_option('bom_currency', 'USD'); add_option('bom_decimal_places', 2); } /** * 停用插件时执行 */ public function deactivate_plugin() { // 清理临时数据 // 注意:这里不删除数据库表,以便保留用户数据 } /** * 初始化插件 */ public function init_plugin() { // 加载文本域(国际化) load_plugin_textdomain('dynamic-bom', false, dirname(plugin_basename(__FILE__)) . '/languages/'); // 添加管理菜单 add_action('admin_menu', array($this, 'add_admin_menus')); // 注册短码 add_shortcode('bom_calculator', array($this, 'bom_calculator_shortcode')); add_shortcode('product_bom', array($this, 'product_bom_shortcode')); // 注册AJAX处理 add_action('wp_ajax_calculate_bom_cost', array($this, 'ajax_calculate_bom_cost')); add_action('wp_ajax_nopriv_calculate_bom_cost', array($this, 'ajax_calculate_bom_cost')); // 加载CSS和JS add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); } /** * 添加管理菜单 */ public function add_admin_menus() { // 主菜单 add_menu_page( __('动态BOM管理', 'dynamic-bom'), __('BOM管理', 'dynamic-bom'), 'manage_options', 'dynamic-bom', array($this, 'render_main_page'), 'dashicons-clipboard', 30 ); // 子菜单 add_submenu_page( 'dynamic-bom', __('产品管理', 'dynamic-bom'), __('产品管理', 'dynamic-bom'), 'manage_options', 'bom-products', array($this, 'render_products_page') ); add_submenu_page( 'dynamic-bom', __('物料库', 'dynamic-bom'), __('物料库', 'dynamic-bom'), 'manage_options', 'bom-materials', array($this, 'render_materials_page') ); add_submenu_page( 'dynamic-bom', __('BOM配置', 'dynamic-bom'), __('BOM配置', 'dynamic-bom'), 'manage_options', 'bom-config', array($this, 'render_config_page') ); } /** * 渲染主页面 */ public function render_main_page() { include plugin_dir_path(__FILE__) . 'templates/admin/main.php'; } /** * 渲染产品管理页面 */ public function render_products_page() { include plugin_dir_path(__FILE__) . 'templates/admin/products.php'; } /** * 渲染物料库页面 */ public function render_materials_page() { include plugin_dir_path(__FILE__) . 'templates/admin/materials.php'; } /** * 渲染配置页面 */ public function render_config_page() { include plugin_dir_path(__FILE__) . 'templates/admin/config.php'; } /** * 加载前端资源 */ public function enqueue_frontend_assets() { wp_enqueue_style( 'dynamic-bom-frontend', plugin_dir_url(__FILE__) . 'assets/css/frontend.css', array(), '1.0.0' ); wp_enqueue_script( 'dynamic-bom-frontend', plugin_dir_url(__FILE__) . 'assets/js/frontend.js', array('jquery'), '1.0.0', true ); // 本地化脚本,传递AJAX URL wp_localize_script('dynamic-bom-frontend', 'bom_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('bom_calculator_nonce') )); } /** * 加载管理端资源 */ public function enqueue_admin_assets($hook) { // 仅在我们的插件页面加载 if (strpos($hook, 'dynamic-bom') !== false || strpos($hook, 'bom-') !== false) { wp_enqueue_style( 'dynamic-bom-admin', plugin_dir_url(__FILE__) . 'assets/css/admin.css', array(), '1.0.0' ); wp_enqueue_script( 'dynamic-bom-admin', plugin_dir_url(__FILE__) . 'assets/js/admin.js', array('jquery', 'jquery-ui-sortable'), '1.0.0', true ); } } /** * BOM计算器短码 */ public function bom_calculator_shortcode($atts) { // 解析短码属性 $atts = shortcode_atts(array( 'product_id' => 0, 'show_total' => 'yes' ), $atts, 'bom_calculator'); ob_start(); include plugin_dir_path(__FILE__) . 'templates/frontend/calculator.php'; return ob_get_clean(); } /** * 产品BOM展示短码 */ public function product_bom_shortcode($atts) { $atts = shortcode_atts(array( 'product_id' => 0, 'version' => 'latest' ), $atts, 'product_bom'); ob_start(); include plugin_dir_path(__FILE__) . 'templates/frontend/product-bom.php'; return ob_get_clean(); } /** * AJAX计算BOM成本 */ public function ajax_calculate_bom_cost() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'bom_calculator_nonce')) { wp_die('安全验证失败'); } $product_id = intval($_POST['product_id']); $quantities = $_POST['quantities']; // 预期为物料ID=>数量的数组 // 计算总成本 $total_cost = $this->calculate_total_cost($product_id, $quantities); // 返回JSON响应 wp_send_json_success(array( 'total_cost' => $total_cost, 'formatted_cost' => $this->format_currency($total_cost) )); } /** * 计算总成本 */ private function calculate_total_cost($product_id, $quantities) { global $wpdb; $total = 0; // 获取产品的基础BOM $bom_table = $wpdb->prefix . 'bom_master'; $bom_items_table = $wpdb->prefix . 'bom_items'; $materials_table = $wpdb->prefix . 'bom_materials'; $bom_id = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $bom_table WHERE product_id = %d AND is_active = 1 ORDER BY created_at DESC LIMIT 1", $product_id )); if ($bom_id) { // 获取BOM项 $items = $wpdb->get_results($wpdb->prepare( "SELECT i.material_id, i.quantity, m.unit_price, m.material_code FROM $bom_items_table i JOIN $materials_table m ON i.material_id = m.id WHERE i.bom_id = %d", $bom_id )); foreach ($items as $item) { $material_id = $item->material_id; $base_quantity = floatval($item->quantity); $unit_price = floatval($item->unit_price); // 如果提供了自定义数量,使用自定义数量,否则使用基础数量 $quantity = isset($quantities[$material_id]) ? floatval($quantities[$material_id]) : $base_quantity; $total += $quantity * $unit_price; } } return $total; } /** * 格式化货币显示 */ private function format_currency($amount) { $currency = get_option('bom_currency', 'USD'); $decimal_places = intval(get_option('bom_decimal_places', 2)); $symbols = array( 'USD' => '$', 'EUR' => '€', 'GBP' => '£', 'CNY' => '¥' ); $symbol = isset($symbols[$currency]) ? $symbols[$currency] : $currency . ' '; return $symbol . number_format($amount, $decimal_places); } } // 初始化插件 DynamicBOM_Plugin::get_instance(); ?>
- 现在,让我们创建一个前端BOM计算器模板,允许用户动态调整物料数量并实时计算成本。 <?php /** * 前端BOM计算器模板 * 文件路径: templates/frontend/calculator.php */ global $wpdb; $product_id = isset($atts['product_id']) ? intval($atts['product_id']) : 0; // 获取产品信息 $products_table = $wpdb->prefix . 'bom_products'; $product = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $products_table WHERE id = %d AND status = 'active'", $product_id )); if (!$product) { echo '<p>' . __('未找到产品信息', 'dynamic-bom') . '</p>'; return; } // 获取产品的BOM $bom_table = $wpdb->prefix . 'bom_master'; $bom_items_table = $wpdb->prefix . 'bom_items'; $materials_table = $wpdb->prefix . 'bom_materials'; $bom = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $bom_table WHERE product_id = %d AND is_active = 1 ORDER BY created_at DESC LIMIT 1", $product_id )); if (!$bom) { echo '<p>' . __('该产品暂无BOM配置', 'dynamic-bom') . '</p>'; return; } // 获取BOM项 $items = $wpdb->get_results($wpdb->prepare( "SELECT i.*, m.material_code, m.material_name, m.unit_price, m.unit_of_measure FROM $bom_items_table i JOIN $materials_table m ON i.material_id = m.id WHERE i.bom_id = %d ORDER BY i.operation_sequence ASC", $bom->id )); // 计算基础总成本 $base_total = 0; foreach ($items as $item) { $base_total += floatval($item->quantity) * floatval($item->unit_price); } ?> <div class="bom-calculator-container" data-product-id="<?php echo esc_attr($product_id); ?>"> <div class="bom-header"> <h3><?php echo esc_html($product->product_name); ?> - <?php _e('BOM成本计算器', 'dynamic-bom'); ?></h3> <p class="bom-description"><?php echo esc_html($product->product_description); ?></p> <p class="bom-version"><?php printf(__('BOM版本: %s', 'dynamic-bom'), esc_html($bom->bom_version)); ?></p> </div> <div class="bom-items-table"> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th width="5%"><?php _e('序号', 'dynamic-bom'); ?></th> <th width="15%"><?php _e('物料编码', 'dynamic-bom'); ?></th> <th width="25%"><?php _e('物料名称', 'dynamic-bom'); ?></th> <th width="10%"><?php _e('单位', 'dynamic-bom'); ?></th> <th width="15%"><?php _e('单价', 'dynamic-bom'); ?></th> <th width="15%"><?php _e('基础数量', 'dynamic-bom'); ?></th> <th width="15%"><?php _e('调整数量', 'dynamic-bom'); ?></th> <th width="15%"><?php _e('小计', 'dynamic-bom'); ?></th> </tr> </thead> <tbody> <?php foreach ($items as $index => $item): ?> <?php $base_qty = floatval($item->quantity); $unit_price = floatval($item->unit_price); $base_subtotal = $base_qty * $unit_price; ?> <tr class="bom-item" data-material-id="<?php echo esc_attr($item->material_id); ?>"> <td><?php echo $index + 1; ?></td> <td><?php echo esc_html($item->material_code); ?></td> <td><?php echo esc_html($item->material_name); ?></td> <td><?php echo esc_html($item->unit_of_measure); ?></td> <td class="unit-price"><?php echo $this->format_currency($unit_price); ?></td> <td class="base-quantity"><?php echo number_format($base_qty, 3); ?></td> <td> <input type="number" class="quantity-input" data-base-quantity="<?php echo esc_attr($base_qty); ?>" data-unit-price="<?php echo esc_attr($unit_price); ?>" value="<?php echo number_format($base_qty, 3); ?>" step="0.001" min="0" style="width: 100%;"> </td> <td class="item-subtotal"><?php echo $this->format_currency($base_subtotal); ?></td> </tr> <?php endforeach; ?> </tbody> <tfoot> <tr> <td colspan="7" class="text-right"><strong><?php _e('总成本:', 'dynamic-bom'); ?></strong></td> <td id="bom-total-cost" class="total-cost"><?php echo $this->format_currency($base_total); ?></td> </tr> </tfoot> </table> </div> <div class="bom-controls"> <button type="button" class="button button-primary" id="reset-bom"><?php _e('重置为默认', 'dynamic-bom'); ?></button> <button type="button" class="button button-secondary" id="save-configuration"><?php _e('保存配置', 'dynamic-bom'); ?></button> <button type="button" class="button button-secondary" id="export-bom"><?php _e('导出BOM', 'dynamic-bom'); ?></button> </div> <div class="bom-notes"> <h4><?php _e('配置说明', 'dynamic-bom'); ?></h4> <ul> <li><?php _e('调整"调整数量"列中的数值来自定义物料用量', 'dynamic-bom'); ?></li> <li><?php _e('总成本将根据您的调整实时更新', 'dynamic-bom'); ?></li> <li><?php _e('点击"保存配置"可将当前配置保存为新的BOM版本', 'dynamic-bom'); ?></li> </ul> </div> </div> <script type="text/javascript"> jQuery(document).ready(function($) { // 实时计算总成本 function calculateTotal() { var total = 0; $('.bom-item').each(function() { var $row = $(this); var quantity = parseFloat($row.find('.quantity-input').val()) || 0; var unitPrice = parseFloat($row.find('.quantity-input').data('unit-price')) || 0; var subtotal = quantity * unitPrice; $row.find('.item-subtotal').text(formatCurrency(subtotal)); total += subtotal; }); $('#bom-total-cost').text(formatCurrency(total)); } // 格式化货币显示 function formatCurrency(amount) { // 这里应该使用从服务器传递的货币设置 // 简化版本,实际应从localize_script获取设置 return '$' + amount.toFixed(2); } // 绑定输入事件 $('.quantity-input').on('input change', function() { calculateTotal(); }); // 重置按钮 $('#reset-bom').on('click', function() { $('.quantity-input').each(function() { var baseQty = parseFloat($(this).data('base-quantity')) || 0; $(this).val(baseQty.toFixed(3)); }); calculateTotal(); }); // 保存配置 $('#save-configuration').on('click', function() { var productId = $('.bom-calculator-container').data('product-id'); var quantities = {}; $('.bom-item').each(function() { var materialId = $(this).data('material-id'); var quantity = parseFloat($(this).find('.quantity-input').val()) || 0; quantities[materialId] = quantity; }); $.ajax({ url: bom_ajax.ajax_url, type: 'POST', data: { action: 'save_bom_configuration', product_id: productId, quantities: quantities, nonce: bom_ajax.nonce }, success: function(response) { if (response.success) { alert('配置已保存为新的BOM版本: ' + response.data.version); } else { alert('保存失败: ' + response.data.message); } } }); }); // 导出BOM $('#export-bom').on('click', function() { var productId = $('.bom-calculator-container').data('product-id'); var quantities = {}; $('.bom-item').each(function() { var materialId = $(this).data('material-id'); var quantity = parseFloat($(this).find('.quantity-input').val()) || 0; quantities[materialId] = quantity; }); // 创建导出数据 var exportData = { product_id: productId, quantities: quantities, timestamp: new Date().toISOString() }; // 下载JSON文件 var dataStr = JSON.stringify(exportData, null, 2); var dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); var exportFileDefaultName = 'bom-config-' + productId + '-' + new Date().getTime() + '.json'; var linkElement = document.createElement('a'); linkElement.setAttribute('href', dataUri); linkElement.setAttribute('download', exportFileDefaultName); linkElement.click(); }); }); </script>
- 接下来,我们实现BOM版本管理功能,允许用户保存不同的配置作为新的BOM版本。 <?php /** * BOM版本管理类 * 文件路径: includes/class-bom-version-manager.php */ class BOM_Version_Manager { /** * 创建新的BOM版本 */ public static function create_version($product_id, $bom_name, $items_data) { global $wpdb; // 开始事务 $wpdb->query('START TRANSACTION'); try { // 1. 获取当前版本号并递增 $bom_table = $wpdb->prefix . 'bom_master'; $current_version = $wpdb->get_var($wpdb->prepare( "SELECT MAX(bom_version) FROM $bom_table WHERE product_id = %d", $product_id )); $new_version = self::increment_version($current_version); // 2. 创建新的BOM主记录 $wpdb->insert($bom_table, array( 'product_id' => $product_id, 'bom_name' => $bom_name, 'bom_version' => $new_version, 'is_active' => 1 )); $new_bom_id = $wpdb->insert_id; // 3. 复制BOM项 $bom_items_table = $wpdb->prefix . 'bom_items'; foreach ($items_data as $item) { $wpdb->insert($bom_items_table, array( 'bom_id' => $new_bom_id, 'material_id' => $item['material_id'], 'quantity' => $item['quantity'], 'operation_sequence' => $item['sequence'] ?? 1, 'scrap_factor' => $item['scrap_factor'] ?? 0, 'notes' => $item['notes'] ?? '' )); } // 4. 将旧版本标记为非活跃(可选) $wpdb->update($bom_table, array('is_active' => 0), array('product_id' => $product_id, 'id !=' => $new_bom_id) ); // 提交事务 $wpdb->query('COMMIT'); return array( 'success' => true, 'bom_id' => $new_bom_id, 'version' => $new_version ); } catch (Exception $e) { // 回滚事务 $wpdb->query('ROLLBACK'); return array( 'success' => false, 'message' => $e->getMessage() ); } } /** * 递增版本号 */ private static function increment_version($current_version) { if (empty($current_version)) { return '1.0'; } // 解析版本号 (支持 x.y 格式) $parts = explode('.', $current_version); if (count($parts) == 2) { // 递增小版本号 $parts[1] = intval($parts[1]) + 1; return implode('.', $parts); } else { // 如果版本号格式不符合预期,返回默认递增 return '1.' . (intval($current_version) + 1); } } /** * 获取产品的所有BOM版本 */ public static function get_product_versions($product_id) { global $wpdb; $bom_table = $wpdb->prefix . 'bom_master'; return $wpdb->get_results($wpdb->prepare( "SELECT * FROM $bom_table WHERE product_id = %d ORDER BY created_at DESC", $product_id )); } /** * 比较两个BOM版本的差异 */ public static function compare_versions($bom_id_1, $bom_id_2) { global $wpdb; $bom_items_table = $wpdb->prefix . 'bom_items'; $materials_table = $wpdb->prefix . 'bom_materials'; // 获取第一个版本的物料 $version1_items = $wpdb->get_results($wpdb->prepare( "SELECT i.*, m.material_code, m.material_name FROM $bom_items_table i JOIN $materials_table m ON i.material_id = m.id WHERE i.bom_id = %d", $bom_id_1 )); // 获取第二个版本的物料 $version2_items = $wpdb->get_results($wpdb->prepare( "SELECT i.*, m.material_code, m.material_name FROM $bom_items_table i JOIN $materials_table m ON i.material_id = m.id WHERE i.bom_id = %d", $bom_id_2 )); // 转换为以物料ID为键的数组 $items1 = array(); foreach ($version1_items as $item) { $items1[$item->material_id] = $item; } $items2 = array(); foreach ($version2_items as $item) { $items2[$item->material_id] = $item; } // 比较差异 $differences = array( 'added' => array(), // 新增的物料 'removed' => array(), // 删除的物料 'changed' => array() // 数量变化的物料 ); // 检查新增和变化的物料 foreach ($items2 as $material_id => $item2) { if (!isset($items1[$material_id])) { // 新增的物料 $differences['added'][] = array( 'material_id' => $material_id, 'material_code' => $item2->material_code, 'material_name' => $item2->material_name, 'quantity_v2' => $item2->quantity ); } else { $item1 = $items1[$material_id]; if (floatval($item1->quantity) != floatval($item2->quantity)) { // 数量变化的物料 $differences['changed'][] = array( 'material_id' => $material_id, 'material_code' => $item2->material_code, 'material_name' => $item2->material_name, 'quantity_v1' => $item1->quantity, 'quantity_v2' => $item2->quantity, 'change_percent' => (floatval($item2->quantity) - floatval($item1->quantity)) / floatval($item1->quantity) * 100 ); } } } // 检查删除的物料 foreach ($items1 as $material_id => $item1) { if (!isset($items2[$material_id])) { $differences['removed'][] = array( 'material_id' => $material_id, 'material_code' => $item1->material_code, 'material_name' => $item1->material_name, 'quantity_v1' => $item1->quantity ); } } return $differences; } }
- 为了完善BOM管理,我们添加一个物料库存预警系统。 <?php /** * 物料库存预警系统 * 文件路径: includes/class-inventory-alert.php */ class Inventory_Alert_System { /** * 检查库存水平并发送预警 */ public static function check_inventory_levels() { global $wpdb; $materials_table = $wpdb->prefix . 'bom_materials'; $alert_log_table = $wpdb->prefix . 'bom_inventory_alerts'; // 获取库存低于最低水平的物料 $low_stock_materials = $wpdb->get_results( "SELECT * FROM $materials_table WHERE stock_quantity <= min_stock_level AND stock_quantity > 0" ); // 获取库存为0的物料 $out_of_stock_materials = $wpdb->get_results( "SELECT * FROM $materials_table WHERE stock_quantity = 0" ); $alerts = array(); // 处理低库存预警 foreach ($low_stock_materials as $material) { $alert_type = 'low_stock'; $message = sprintf( __('物料 %s (%s) 库存偏低。当前库存: %d,最低库存水平: %d', 'dynamic-bom'), $material->material_name, $material->material_code, $material->stock_quantity, $material->min_stock_level ); $alerts[] = self::log_alert($material->id, $alert_type, $message); // 发送邮件通知 self::send_email_alert($material, $alert_type, $message); } // 处理缺货预警 foreach ($out_of_stock_materials as $material) { $alert_type = 'out_of_stock'; $message = sprintf( __('物料 %s (%s) 已缺货!', 'dynamic-bom'), $material->material_name, $material->material_code ); $alerts[] = self::log_alert($material->id, $alert_type, $message); // 发送邮件通知 self::send_email_alert($material, $alert_type, $message); } return $alerts;
在制造业和产品开发领域,BOM(Bill of Materials,物料清单)是产品所需的所有原材料、组件和子组件的结构化列表。动态BOM管理指的是能够根据产品配置、版本变化或生产需求实时调整和更新物料清单的能力。
对于使用WordPress搭建产品展示网站或小型电商平台的企业,集成动态BOM管理功能可以帮助他们更好地管理产品组件、成本计算和生产流程。本教程将指导您开发一个WordPress插件,实现小批量定制产品的动态BOM管理功能。
首先,我们需要设计插件的基本结构和数据库表。我们的插件将包含以下核心表:
- 产品表:存储基本产品信息
- BOM主表:存储BOM的基本信息
- BOM明细表:存储BOM的具体物料项
- 物料库表:存储可用的物料信息
<?php
/**
* WordPress动态BOM管理插件 - 数据库安装脚本
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
class DynamicBOM_Installer {
/**
* 创建插件所需的数据库表
*/
public static function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// BOM产品表
$table_products = $wpdb->prefix . 'bom_products';
$sql_products = "CREATE TABLE IF NOT EXISTS $table_products (
id INT(11) NOT NULL AUTO_INCREMENT,
product_name VARCHAR(255) NOT NULL,
product_description TEXT,
base_price DECIMAL(10,2) DEFAULT 0.00,
status ENUM('active', 'inactive') DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
// BOM主表
$table_boms = $wpdb->prefix . 'bom_master';
$sql_boms = "CREATE TABLE IF NOT EXISTS $table_boms (
id INT(11) NOT NULL AUTO_INCREMENT,
product_id INT(11) NOT NULL,
bom_name VARCHAR(255) NOT NULL,
bom_version VARCHAR(50) DEFAULT '1.0',
is_active TINYINT(1) DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
FOREIGN KEY (product_id) REFERENCES $table_products(id) ON DELETE CASCADE
) $charset_collate;";
// 物料库表
$table_materials = $wpdb->prefix . 'bom_materials';
$sql_materials = "CREATE TABLE IF NOT EXISTS $table_materials (
id INT(11) NOT NULL AUTO_INCREMENT,
material_code VARCHAR(100) NOT NULL UNIQUE,
material_name VARCHAR(255) NOT NULL,
material_type ENUM('raw', 'component', 'subassembly') DEFAULT 'raw',
unit_price DECIMAL(10,2) DEFAULT 0.00,
unit_of_measure VARCHAR(50) DEFAULT 'pcs',
stock_quantity INT(11) DEFAULT 0,
min_stock_level INT(11) DEFAULT 10,
supplier_info TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
// BOM明细表
$table_bom_items = $wpdb->prefix . 'bom_items';
$sql_bom_items = "CREATE TABLE IF NOT EXISTS $table_bom_items (
id INT(11) NOT NULL AUTO_INCREMENT,
bom_id INT(11) NOT NULL,
material_id INT(11) NOT NULL,
quantity DECIMAL(10,3) NOT NULL DEFAULT 1.000,
operation_sequence INT(11) DEFAULT 1,
scrap_factor DECIMAL(5,3) DEFAULT 0.000,
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
FOREIGN KEY (bom_id) REFERENCES $table_boms(id) ON DELETE CASCADE,
FOREIGN KEY (material_id) REFERENCES $table_materials(id) ON DELETE CASCADE
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
// 执行SQL创建表
dbDelta($sql_products);
dbDelta($sql_boms);
dbDelta($sql_materials);
dbDelta($sql_bom_items);
// 添加默认数据(可选)
self::add_default_data();
}
/**
* 添加默认数据
*/
private static function add_default_data() {
global $wpdb;
// 检查是否已有数据
$table_materials = $wpdb->prefix . 'bom_materials';
$count = $wpdb->get_var("SELECT COUNT(*) FROM $table_materials");
if ($count == 0) {
// 添加示例物料
$wpdb->insert($table_materials, array(
'material_code' => 'MAT-001',
'material_name' => '铝合金框架',
'material_type' => 'raw',
'unit_price' => 25.50,
'unit_of_measure' => 'pcs',
'stock_quantity' => 100,
'min_stock_level' => 20
));
$wpdb->insert($table_materials, array(
'material_code' => 'MAT-002',
'material_name' => '电路板',
'material_type' => 'component',
'unit_price' => 15.75,
'unit_of_measure' => 'pcs',
'stock_quantity' => 200,
'min_stock_level' => 30
));
}
}
}
?>
接下来,我们创建插件的主类,负责初始化插件功能、注册短码和管理后台菜单。
<?php
/**
* WordPress动态BOM管理插件主类
*/
class DynamicBOM_Plugin {
private static $instance = null;
/**
* 获取插件实例(单例模式)
*/
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 构造函数
*/
private function __construct() {
// 注册激活和停用钩子
register_activation_hook(__FILE__, array($this, 'activate_plugin'));
register_deactivation_hook(__FILE__, array($this, 'deactivate_plugin'));
// 初始化插件
add_action('plugins_loaded', array($this, 'init_plugin'));
}
/**
* 激活插件时执行
*/
public function activate_plugin() {
// 创建数据库表
DynamicBOM_Installer::create_tables();
// 添加插件版本号
update_option('dynamic_bom_version', '1.0.0');
// 设置默认选项
add_option('bom_currency', 'USD');
add_option('bom_decimal_places', 2);
}
/**
* 停用插件时执行
*/
public function deactivate_plugin() {
// 清理临时数据
// 注意:这里不删除数据库表,以便保留用户数据
}
/**
* 初始化插件
*/
public function init_plugin() {
// 加载文本域(国际化)
load_plugin_textdomain('dynamic-bom', false, dirname(plugin_basename(__FILE__)) . '/languages/');
// 添加管理菜单
add_action('admin_menu', array($this, 'add_admin_menus'));
// 注册短码
add_shortcode('bom_calculator', array($this, 'bom_calculator_shortcode'));
add_shortcode('product_bom', array($this, 'product_bom_shortcode'));
// 注册AJAX处理
add_action('wp_ajax_calculate_bom_cost', array($this, 'ajax_calculate_bom_cost'));
add_action('wp_ajax_nopriv_calculate_bom_cost', array($this, 'ajax_calculate_bom_cost'));
// 加载CSS和JS
add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
}
/**
* 添加管理菜单
*/
public function add_admin_menus() {
// 主菜单
add_menu_page(
__('动态BOM管理', 'dynamic-bom'),
__('BOM管理', 'dynamic-bom'),
'manage_options',
'dynamic-bom',
array($this, 'render_main_page'),
'dashicons-clipboard',
30
);
// 子菜单
add_submenu_page(
'dynamic-bom',
__('产品管理', 'dynamic-bom'),
__('产品管理', 'dynamic-bom'),
'manage_options',
'bom-products',
array($this, 'render_products_page')
);
add_submenu_page(
'dynamic-bom',
__('物料库', 'dynamic-bom'),
__('物料库', 'dynamic-bom'),
'manage_options',
'bom-materials',
array($this, 'render_materials_page')
);
add_submenu_page(
'dynamic-bom',
__('BOM配置', 'dynamic-bom'),
__('BOM配置', 'dynamic-bom'),
'manage_options',
'bom-config',
array($this, 'render_config_page')
);
}
/**
* 渲染主页面
*/
public function render_main_page() {
include plugin_dir_path(__FILE__) . 'templates/admin/main.php';
}
/**
* 渲染产品管理页面
*/
public function render_products_page() {
include plugin_dir_path(__FILE__) . 'templates/admin/products.php';
}
/**
* 渲染物料库页面
*/
public function render_materials_page() {
include plugin_dir_path(__FILE__) . 'templates/admin/materials.php';
}
/**
* 渲染配置页面
*/
public function render_config_page() {
include plugin_dir_path(__FILE__) . 'templates/admin/config.php';
}
/**
* 加载前端资源
*/
public function enqueue_frontend_assets() {
wp_enqueue_style(
'dynamic-bom-frontend',
plugin_dir_url(__FILE__) . 'assets/css/frontend.css',
array(),
'1.0.0'
);
wp_enqueue_script(
'dynamic-bom-frontend',
plugin_dir_url(__FILE__) . 'assets/js/frontend.js',
array('jquery'),
'1.0.0',
true
);
// 本地化脚本,传递AJAX URL
wp_localize_script('dynamic-bom-frontend', 'bom_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bom_calculator_nonce')
));
}
/**
* 加载管理端资源
*/
public function enqueue_admin_assets($hook) {
// 仅在我们的插件页面加载
if (strpos($hook, 'dynamic-bom') !== false || strpos($hook, 'bom-') !== false) {
wp_enqueue_style(
'dynamic-bom-admin',
plugin_dir_url(__FILE__) . 'assets/css/admin.css',
array(),
'1.0.0'
);
wp_enqueue_script(
'dynamic-bom-admin',
plugin_dir_url(__FILE__) . 'assets/js/admin.js',
array('jquery', 'jquery-ui-sortable'),
'1.0.0',
true
);
}
}
/**
* BOM计算器短码
*/
public function bom_calculator_shortcode($atts) {
// 解析短码属性
$atts = shortcode_atts(array(
'product_id' => 0,
'show_total' => 'yes'
), $atts, 'bom_calculator');
ob_start();
include plugin_dir_path(__FILE__) . 'templates/frontend/calculator.php';
return ob_get_clean();
}
/**
* 产品BOM展示短码
*/
public function product_bom_shortcode($atts) {
$atts = shortcode_atts(array(
'product_id' => 0,
'version' => 'latest'
), $atts, 'product_bom');
ob_start();
include plugin_dir_path(__FILE__) . 'templates/frontend/product-bom.php';
return ob_get_clean();
}
/**
* AJAX计算BOM成本
*/
public function ajax_calculate_bom_cost() {
// 验证nonce
if (!wp_verify_nonce($_POST['nonce'], 'bom_calculator_nonce')) {
wp_die('安全验证失败');
}
$product_id = intval($_POST['product_id']);
$quantities = $_POST['quantities']; // 预期为物料ID=>数量的数组
// 计算总成本
$total_cost = $this->calculate_total_cost($product_id, $quantities);
// 返回JSON响应
wp_send_json_success(array(
'total_cost' => $total_cost,
'formatted_cost' => $this->format_currency($total_cost)
));
}
/**
* 计算总成本
*/
private function calculate_total_cost($product_id, $quantities) {
global $wpdb;
$total = 0;
// 获取产品的基础BOM
$bom_table = $wpdb->prefix . 'bom_master';
$bom_items_table = $wpdb->prefix . 'bom_items';
$materials_table = $wpdb->prefix . 'bom_materials';
$bom_id = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM $bom_table WHERE product_id = %d AND is_active = 1 ORDER BY created_at DESC LIMIT 1",
$product_id
));
if ($bom_id) {
// 获取BOM项
$items = $wpdb->get_results($wpdb->prepare(
"SELECT i.material_id, i.quantity, m.unit_price, m.material_code
FROM $bom_items_table i
JOIN $materials_table m ON i.material_id = m.id
WHERE i.bom_id = %d",
$bom_id
));
foreach ($items as $item) {
$material_id = $item->material_id;
$base_quantity = floatval($item->quantity);
$unit_price = floatval($item->unit_price);
// 如果提供了自定义数量,使用自定义数量,否则使用基础数量
$quantity = isset($quantities[$material_id]) ?
floatval($quantities[$material_id]) : $base_quantity;
$total += $quantity * $unit_price;
}
}
return $total;
}
/**
* 格式化货币显示
*/
private function format_currency($amount) {
$currency = get_option('bom_currency', 'USD');
$decimal_places = intval(get_option('bom_decimal_places', 2));
$symbols = array(
'USD' => '$',
'EUR' => '€',
'GBP' => '£',
'CNY' => '¥'
);
$symbol = isset($symbols[$currency]) ? $symbols[$currency] : $currency . ' ';
return $symbol . number_format($amount, $decimal_places);
}
}
// 初始化插件
DynamicBOM_Plugin::get_instance();
?>
现在,让我们创建一个前端BOM计算器模板,允许用户动态调整物料数量并实时计算成本。
<?php
/**
* 前端BOM计算器模板
* 文件路径: templates/frontend/calculator.php
*/
global $wpdb;
$product_id = isset($atts['product_id']) ? intval($atts['product_id']) : 0;
// 获取产品信息
$products_table = $wpdb->prefix . 'bom_products';
$product = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $products_table WHERE id = %d AND status = 'active'",
$product_id
));
if (!$product) {
echo '<p>' . __('未找到产品信息', 'dynamic-bom') . '</p>';
return;
}
// 获取产品的BOM
$bom_table = $wpdb->prefix . 'bom_master';
$bom_items_table = $wpdb->prefix . 'bom_items';
$materials_table = $wpdb->prefix . 'bom_materials';
$bom = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $bom_table WHERE product_id = %d AND is_active = 1 ORDER BY created_at DESC LIMIT 1",
$product_id
));
if (!$bom) {
echo '<p>' . __('该产品暂无BOM配置', 'dynamic-bom') . '</p>';
return;
}
// 获取BOM项
$items = $wpdb->get_results($wpdb->prepare(
"SELECT i.*, m.material_code, m.material_name, m.unit_price, m.unit_of_measure
FROM $bom_items_table i
JOIN $materials_table m ON i.material_id = m.id
WHERE i.bom_id = %d
ORDER BY i.operation_sequence ASC",
$bom->id
));
// 计算基础总成本
$base_total = 0;
foreach ($items as $item) {
$base_total += floatval($item->quantity) * floatval($item->unit_price);
}
?>
<div class="bom-calculator-container" data-product-id="<?php echo esc_attr($product_id); ?>">
<div class="bom-header">
<h3><?php echo esc_html($product->product_name); ?> - <?php _e('BOM成本计算器', 'dynamic-bom'); ?></h3>
<p class="bom-description"><?php echo esc_html($product->product_description); ?></p>
<p class="bom-version"><?php printf(__('BOM版本: %s', 'dynamic-bom'), esc_html($bom->bom_version)); ?></p>
</div>
<div class="bom-items-table">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th width="5%"><?php _e('序号', 'dynamic-bom'); ?></th>
<th width="15%"><?php _e('物料编码', 'dynamic-bom'); ?></th>
<th width="25%"><?php _e('物料名称', 'dynamic-bom'); ?></th>
<th width="10%"><?php _e('单位', 'dynamic-bom'); ?></th>
<th width="15%"><?php _e('单价', 'dynamic-bom'); ?></th>
<th width="15%"><?php _e('基础数量', 'dynamic-bom'); ?></th>
<th width="15%"><?php _e('调整数量', 'dynamic-bom'); ?></th>
<th width="15%"><?php _e('小计', 'dynamic-bom'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $index => $item): ?>
<?php
$base_qty = floatval($item->quantity);
$unit_price = floatval($item->unit_price);
$base_subtotal = $base_qty * $unit_price;
?>
<tr class="bom-item" data-material-id="<?php echo esc_attr($item->material_id); ?>">
<td><?php echo $index + 1; ?></td>
<td><?php echo esc_html($item->material_code); ?></td>
<td><?php echo esc_html($item->material_name); ?></td>
<td><?php echo esc_html($item->unit_of_measure); ?></td>
<td class="unit-price"><?php echo $this->format_currency($unit_price); ?></td>
<td class="base-quantity"><?php echo number_format($base_qty, 3); ?></td>
<td>
<input type="number"
class="quantity-input"
data-base-quantity="<?php echo esc_attr($base_qty); ?>"
data-unit-price="<?php echo esc_attr($unit_price); ?>"
value="<?php echo number_format($base_qty, 3); ?>"
step="0.001"
min="0"
style="width: 100%;">
</td>
<td class="item-subtotal"><?php echo $this->format_currency($base_subtotal); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td colspan="7" class="text-right"><strong><?php _e('总成本:', 'dynamic-bom'); ?></strong></td>
<td id="bom-total-cost" class="total-cost"><?php echo $this->format_currency($base_total); ?></td>
</tr>
</tfoot>
</table>
</div>
<div class="bom-controls">
<button type="button" class="button button-primary" id="reset-bom"><?php _e('重置为默认', 'dynamic-bom'); ?></button>
<button type="button" class="button button-secondary" id="save-configuration"><?php _e('保存配置', 'dynamic-bom'); ?></button>
<button type="button" class="button button-secondary" id="export-bom"><?php _e('导出BOM', 'dynamic-bom'); ?></button>
</div>
<div class="bom-notes">
<h4><?php _e('配置说明', 'dynamic-bom'); ?></h4>
<ul>
<li><?php _e('调整"调整数量"列中的数值来自定义物料用量', 'dynamic-bom'); ?></li>
<li><?php _e('总成本将根据您的调整实时更新', 'dynamic-bom'); ?></li>
<li><?php _e('点击"保存配置"可将当前配置保存为新的BOM版本', 'dynamic-bom'); ?></li>
</ul>
</div>
</div>
<script type="text/javascript">
jQuery(document).ready(function($) {
// 实时计算总成本
function calculateTotal() {
var total = 0;
$('.bom-item').each(function() {
var $row = $(this);
var quantity = parseFloat($row.find('.quantity-input').val()) || 0;
var unitPrice = parseFloat($row.find('.quantity-input').data('unit-price')) || 0;
var subtotal = quantity * unitPrice;
$row.find('.item-subtotal').text(formatCurrency(subtotal));
total += subtotal;
});
$('#bom-total-cost').text(formatCurrency(total));
}
// 格式化货币显示
function formatCurrency(amount) {
// 这里应该使用从服务器传递的货币设置
// 简化版本,实际应从localize_script获取设置
return '$' + amount.toFixed(2);
}
// 绑定输入事件
$('.quantity-input').on('input change', function() {
calculateTotal();
});
// 重置按钮
$('#reset-bom').on('click', function() {
$('.quantity-input').each(function() {
var baseQty = parseFloat($(this).data('base-quantity')) || 0;
$(this).val(baseQty.toFixed(3));
});
calculateTotal();
});
// 保存配置
$('#save-configuration').on('click', function() {
var productId = $('.bom-calculator-container').data('product-id');
var quantities = {};
$('.bom-item').each(function() {
var materialId = $(this).data('material-id');
var quantity = parseFloat($(this).find('.quantity-input').val()) || 0;
quantities[materialId] = quantity;
});
$.ajax({
url: bom_ajax.ajax_url,
type: 'POST',
data: {
action: 'save_bom_configuration',
product_id: productId,
quantities: quantities,
nonce: bom_ajax.nonce
},
success: function(response) {
if (response.success) {
alert('配置已保存为新的BOM版本: ' + response.data.version);
} else {
alert('保存失败: ' + response.data.message);
}
}
});
});
// 导出BOM
$('#export-bom').on('click', function() {
var productId = $('.bom-calculator-container').data('product-id');
var quantities = {};
$('.bom-item').each(function() {
var materialId = $(this).data('material-id');
var quantity = parseFloat($(this).find('.quantity-input').val()) || 0;
quantities[materialId] = quantity;
});
// 创建导出数据
var exportData = {
product_id: productId,
quantities: quantities,
timestamp: new Date().toISOString()
};
// 下载JSON文件
var dataStr = JSON.stringify(exportData, null, 2);
var dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
var exportFileDefaultName = 'bom-config-' + productId + '-' + new Date().getTime() + '.json';
var linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click();
});
});
</script>
接下来,我们实现BOM版本管理功能,允许用户保存不同的配置作为新的BOM版本。
<?php
/**
* BOM版本管理类
* 文件路径: includes/class-bom-version-manager.php
*/
class BOM_Version_Manager {
/**
* 创建新的BOM版本
*/
public static function create_version($product_id, $bom_name, $items_data) {
global $wpdb;
// 开始事务
$wpdb->query('START TRANSACTION');
try {
// 1. 获取当前版本号并递增
$bom_table = $wpdb->prefix . 'bom_master';
$current_version = $wpdb->get_var($wpdb->prepare(
"SELECT MAX(bom_version) FROM $bom_table WHERE product_id = %d",
$product_id
));
$new_version = self::increment_version($current_version);
// 2. 创建新的BOM主记录
$wpdb->insert($bom_table, array(
'product_id' => $product_id,
'bom_name' => $bom_name,
'bom_version' => $new_version,
'is_active' => 1
));
$new_bom_id = $wpdb->insert_id;
// 3. 复制BOM项
$bom_items_table = $wpdb->prefix . 'bom_items';
foreach ($items_data as $item) {
$wpdb->insert($bom_items_table, array(
'bom_id' => $new_bom_id,
'material_id' => $item['material_id'],
'quantity' => $item['quantity'],
'operation_sequence' => $item['sequence'] ?? 1,
'scrap_factor' => $item['scrap_factor'] ?? 0,
'notes' => $item['notes'] ?? ''
));
}
// 4. 将旧版本标记为非活跃(可选)
$wpdb->update($bom_table,
array('is_active' => 0),
array('product_id' => $product_id, 'id !=' => $new_bom_id)
);
// 提交事务
$wpdb->query('COMMIT');
return array(
'success' => true,
'bom_id' => $new_bom_id,
'version' => $new_version
);
} catch (Exception $e) {
// 回滚事务
$wpdb->query('ROLLBACK');
return array(
'success' => false,
'message' => $e->getMessage()
);
}
}
/**
* 递增版本号
*/
private static function increment_version($current_version) {
if (empty($current_version)) {
return '1.0';
}
// 解析版本号 (支持 x.y 格式)
$parts = explode('.', $current_version);
if (count($parts) == 2) {
// 递增小版本号
$parts[1] = intval($parts[1]) + 1;
return implode('.', $parts);
} else {
// 如果版本号格式不符合预期,返回默认递增
return '1.' . (intval($current_version) + 1);
}
}
/**
* 获取产品的所有BOM版本
*/
public static function get_product_versions($product_id) {
global $wpdb;
$bom_table = $wpdb->prefix . 'bom_master';
return $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $bom_table
WHERE product_id = %d
ORDER BY created_at DESC",
$product_id
));
}
/**
* 比较两个BOM版本的差异
*/
public static function compare_versions($bom_id_1, $bom_id_2) {
global $wpdb;
$bom_items_table = $wpdb->prefix . 'bom_items';
$materials_table = $wpdb->prefix . 'bom_materials';
// 获取第一个版本的物料
$version1_items = $wpdb->get_results($wpdb->prepare(
"SELECT i.*, m.material_code, m.material_name
FROM $bom_items_table i
JOIN $materials_table m ON i.material_id = m.id
WHERE i.bom_id = %d",
$bom_id_1
));
// 获取第二个版本的物料
$version2_items = $wpdb->get_results($wpdb->prepare(
"SELECT i.*, m.material_code, m.material_name
FROM $bom_items_table i
JOIN $materials_table m ON i.material_id = m.id
WHERE i.bom_id = %d",
$bom_id_2
));
// 转换为以物料ID为键的数组
$items1 = array();
foreach ($version1_items as $item) {
$items1[$item->material_id] = $item;
}
$items2 = array();
foreach ($version2_items as $item) {
$items2[$item->material_id] = $item;
}
// 比较差异
$differences = array(
'added' => array(), // 新增的物料
'removed' => array(), // 删除的物料
'changed' => array() // 数量变化的物料
);
// 检查新增和变化的物料
foreach ($items2 as $material_id => $item2) {
if (!isset($items1[$material_id])) {
// 新增的物料
$differences['added'][] = array(
'material_id' => $material_id,
'material_code' => $item2->material_code,
'material_name' => $item2->material_name,
'quantity_v2' => $item2->quantity
);
} else {
$item1 = $items1[$material_id];
if (floatval($item1->quantity) != floatval($item2->quantity)) {
// 数量变化的物料
$differences['changed'][] = array(
'material_id' => $material_id,
'material_code' => $item2->material_code,
'material_name' => $item2->material_name,
'quantity_v1' => $item1->quantity,
'quantity_v2' => $item2->quantity,
'change_percent' => (floatval($item2->quantity) - floatval($item1->quantity)) / floatval($item1->quantity) * 100
);
}
}
}
// 检查删除的物料
foreach ($items1 as $material_id => $item1) {
if (!isset($items2[$material_id])) {
$differences['removed'][] = array(
'material_id' => $material_id,
'material_code' => $item1->material_code,
'material_name' => $item1->material_name,
'quantity_v1' => $item1->quantity
);
}
}
return $differences;
}
}
为了完善BOM管理,我们添加一个物料库存预警系统。
<?php
/**
* 物料库存预警系统
* 文件路径: includes/class-inventory-alert.php
*/
class Inventory_Alert_System {
/**
* 检查库存水平并发送预警
*/
public static function check_inventory_levels() {
global $wpdb;
$materials_table = $wpdb->prefix . 'bom_materials';
$alert_log_table = $wpdb->prefix . 'bom_inventory_alerts';
// 获取库存低于最低水平的物料
$low_stock_materials = $wpdb->get_results(
"SELECT * FROM $materials_table
WHERE stock_quantity <= min_stock_level
AND stock_quantity > 0"
);
// 获取库存为0的物料
$out_of_stock_materials = $wpdb->get_results(
"SELECT * FROM $materials_table
WHERE stock_quantity = 0"
);
$alerts = array();
// 处理低库存预警
foreach ($low_stock_materials as $material) {
$alert_type = 'low_stock';
$message = sprintf(
__('物料 %s (%s) 库存偏低。当前库存: %d,最低库存水平: %d', 'dynamic-bom'),
$material->material_name,
$material->material_code,
$material->stock_quantity,
$material->min_stock_level
);
$alerts[] = self::log_alert($material->id, $alert_type, $message);
// 发送邮件通知
self::send_email_alert($material, $alert_type, $message);
}
// 处理缺货预警
foreach ($out_of_stock_materials as $material) {
$alert_type = 'out_of_stock';
$message = sprintf(
__('物料 %s (%s) 已缺货!', 'dynamic-bom'),
$material->material_name,
$material->material_code
);
$alerts[] = self::log_alert($material->id, $alert_type, $message);
// 发送邮件通知
self::send_email_alert($material, $alert_type, $message);
}
return $alerts;


