diff --git a/2meet-data-optimizer-quotation-addon.php b/2meet-data-optimizer-quotation-addon.php index da55880..1060742 100644 --- a/2meet-data-optimizer-quotation-addon.php +++ b/2meet-data-optimizer-quotation-addon.php @@ -3,7 +3,7 @@ * Plugin Name: 2meet Data Optimizer — Quotation AddOn * Plugin URI: https://2meet.io/2meet-data-optimizer/quotation * Description: 2meet-quotation 8 張自訂表 (2mq_*) 註冊(含 hp_vendor post_type_link) - * Version: 0.1.0 + * Version: 0.1.1 * Requires at least: 6.0 * Tested up to: 6.9.4 * Requires PHP: 8.1 @@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } -define( 'TMDO_QUOTATION_VERSION', '0.1.0' ); +define( 'TMDO_QUOTATION_VERSION', '0.1.1' ); define( 'TMDO_QUOTATION_PATH', plugin_dir_path( __FILE__ ) ); define( 'TMDO_QUOTATION_URL', plugin_dir_url( __FILE__ ) ); define( 'TMDO_QUOTATION_FILE', __FILE__ ); diff --git a/CHANGELOG.md b/CHANGELOG.md index 27e8aac..7ab82a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,33 @@ Versioning follows [Semantic Versioning](https://semver.org/). --- +## [0.1.1] — 2026-08-08 — 8 張 `2mq_*` 表補齊 schema 期望值 + +### Added + +- 為全部 8 張 `2mq_*` 表補上 `expected_columns` 與 `indexes`:`2mq_quotations`、 + `2mq_quotation_items`、`2mq_quotation_history`、`2mq_vendor_presets`、 + `2mq_catalog_categories`、`2mq_catalog_items`、`2mq_quotation_templates`、 + `2mq_vendor_catalog_prices`。欄位與索引逐項轉錄自 2meet-quotation activator 的 + `CREATE TABLE`。先前只註冊表名,`wp wpdo doctor` 只知道表存在,無從判斷欄位或 + 索引是否漂移。 + +### Fixed + +- `TMDO_Custom_Table_Registry::register()` 的索引期望值 config key 由 + `expected_indexes` 更正為 `indexes`。該 API 以 `wp_parse_args()` 併入預設值, + 未知 key 會被**靜默丟棄**——先前索引期望值等同從未送達 registry, + `wp wpdo doctor` 的 schema drift 只驗欄位、不驗索引。此問題橫跨全部 8 個 AddOn, + 已一併更正。 +- `TMDO_Quotation::register()` 的 partner 偵測符號對齊實際存在的常數。舊版偵測 + `TMQUO_Main` / `TMQUO_Plugin` 兩個類別,但這兩者在 2meet-quotation 中**根本不存在**, + 偵測實際完全靠 `file_exists()` 後備撐著。改用 `TMQUO_VERSION` / `TMQUO_PLUGIN_DIR`, + 兩者在主檔檔案解析當下即 define,早於 `plugins_loaded`。 + 實測(mu-plugin 於 `plugins_loaded:6` 量測):`TMQUO_VERSION` = AVAILABLE。 + `file_exists()` 後備保留不動,行為不變;但偵測不再是死碼。 + +--- + ## [0.1.0] — TBD ### Added (initial release, scaffold) diff --git a/includes/class-tmdo-quotation.php b/includes/class-tmdo-quotation.php index 46ce7d8..e663cf5 100644 --- a/includes/class-tmdo-quotation.php +++ b/includes/class-tmdo-quotation.php @@ -21,9 +21,18 @@ if ( ! defined( 'ABSPATH' ) ) { */ final class TMDO_Quotation { + /** + * 偵測以常數優先:`TMQUO_VERSION` / `TMQUO_PLUGIN_DIR` 在 2meet-quotation 主檔的 + * **檔案解析當下**(第 22/25 行)即 define,早於 `plugins_loaded`;本 AddOn + * bootstrap 掛在 `plugins_loaded:6`,故此時常數已可用。 + * + * 舊版偵測的 `TMQUO_Main` / `TMQUO_Plugin` 兩個類別在 2meet-quotation 中**並不存在**, + * 偵測實際完全靠 `file_exists()` 撐著。保留 file_exists 作最終後備,但常數檢查 + * 現在真的有效——即使將來移除後備也不會像 infocards 那樣整個 AddOn 靜默失效。 + */ public static function register(): void { - $detected = class_exists( 'TMQUO_Main' ) - || class_exists( 'TMQUO_Plugin' ) + $detected = defined( 'TMQUO_VERSION' ) + || defined( 'TMQUO_PLUGIN_DIR' ) || file_exists( WP_PLUGIN_DIR . '/2meet-quotation/2meet-quotation.php' ); if ( ! $detected ) { return; @@ -33,23 +42,201 @@ final class TMDO_Quotation { public static function register_custom_tables( TMDO_Custom_Table_Registry $registry ): void { $tables = array( - '2mq_quotations' => array( 'id', 'hp_vendor' ), // vendor_id 關聯 hp_vendor - '2mq_quotation_items' => array( 'id', null ), - '2mq_quotation_history' => array( 'id', null ), - '2mq_vendor_presets' => array( 'id', 'hp_vendor' ), - '2mq_catalog_categories' => array( 'id', null ), - '2mq_catalog_items' => array( 'id', null ), - '2mq_quotation_templates' => array( 'id', null ), - '2mq_vendor_catalog_prices' => array( 'id', 'hp_vendor' ), + // vendor_id 關聯 hp_vendor. + '2mq_quotations' => array( + 'primary_key' => 'id', + 'post_type_link' => 'hp_vendor', + 'expected_columns' => array( + 'id' => 'BIGINT', + 'vendor_id' => 'BIGINT', + 'template_id' => 'BIGINT', + 'quote_number' => 'VARCHAR', + 'status' => 'VARCHAR', + 'client_name' => 'VARCHAR', + 'client_email' => 'VARCHAR', + 'client_phone' => 'VARCHAR', + 'event_type' => 'VARCHAR', + 'event_date' => 'DATE', + 'event_time' => 'VARCHAR', + 'event_duration_hours' => 'DECIMAL', + 'event_location' => 'VARCHAR', + 'venue_name' => 'VARCHAR', + 'guest_count' => 'INT', + 'performer_count' => 'INT', + 'subtotal' => 'DECIMAL', + 'discount_amount' => 'DECIMAL', + 'discount_reason' => 'VARCHAR', + 'tax_amount' => 'DECIMAL', + 'total' => 'DECIMAL', + 'currency' => 'VARCHAR', + 'deposit_percent' => 'INT', + 'deposit_amount' => 'DECIMAL', + 'balance_amount' => 'DECIMAL', + 'deposit_status' => 'VARCHAR', + 'deposit_order_id' => 'BIGINT', + 'client_signature' => 'TEXT', + 'contract_html' => 'TEXT', + 'deposit_due_days' => 'INT', + 'balance_due_days' => 'INT', + 'cancel_policy' => 'VARCHAR', + 'included_services' => 'TEXT', + 'notes' => 'TEXT', + 'internal_notes' => 'TEXT', + 'valid_days' => 'INT', + 'valid_until' => 'DATE', + 'sent_at' => 'DATETIME', + 'viewed_at' => 'DATETIME', + 'accepted_at' => 'DATETIME', + 'declined_at' => 'DATETIME', + 'access_token' => 'VARCHAR', + 'created_at' => 'DATETIME', + 'updated_at' => 'DATETIME', + ), + 'indexes' => array( + 'quote_number' => array( 'quote_number' ), + 'vendor_id' => array( 'vendor_id' ), + 'status' => array( 'status' ), + 'access_token' => array( 'access_token' ), + ), + ), + '2mq_quotation_items' => array( + 'primary_key' => 'id', + 'post_type_link' => null, + 'expected_columns' => array( + 'id' => 'BIGINT', + 'quotation_id' => 'BIGINT', + 'catalog_item_id' => 'BIGINT', + 'module_code' => 'VARCHAR', + 'label' => 'VARCHAR', + 'description' => 'TEXT', + 'quantity' => 'DECIMAL', + 'unit' => 'VARCHAR', + 'unit_price' => 'DECIMAL', + 'subtotal' => 'DECIMAL', + 'is_optional' => 'TINYINT', + 'config' => 'TEXT', + 'sort_order' => 'INT', + ), + 'indexes' => array( + 'quotation_id' => array( 'quotation_id' ), + ), + ), + '2mq_quotation_history' => array( + 'primary_key' => 'id', + 'post_type_link' => null, + 'expected_columns' => array( + 'id' => 'BIGINT', + 'quotation_id' => 'BIGINT', + 'action' => 'VARCHAR', + 'actor' => 'VARCHAR', + 'snapshot' => 'TEXT', + 'note' => 'TEXT', + 'created_at' => 'DATETIME', + ), + 'indexes' => array( + 'quotation_id' => array( 'quotation_id' ), + ), + ), + '2mq_vendor_presets' => array( + 'primary_key' => 'id', + 'post_type_link' => 'hp_vendor', + 'expected_columns' => array( + 'id' => 'BIGINT', + 'vendor_id' => 'BIGINT', + 'module_code' => 'VARCHAR', + 'label' => 'VARCHAR', + 'config' => 'TEXT', + 'is_active' => 'TINYINT', + 'sort_order' => 'INT', + 'created_at' => 'DATETIME', + 'updated_at' => 'DATETIME', + ), + 'indexes' => array( + 'vendor_module' => array( 'vendor_id', 'module_code' ), + ), + ), + '2mq_catalog_categories' => array( + 'primary_key' => 'id', + 'post_type_link' => null, + 'expected_columns' => array( + 'id' => 'BIGINT', + 'code' => 'VARCHAR', + 'name' => 'VARCHAR', + 'icon' => 'VARCHAR', + 'sort_order' => 'INT', + 'is_active' => 'TINYINT', + ), + 'indexes' => array( + 'code' => array( 'code' ), + ), + ), + '2mq_catalog_items' => array( + 'primary_key' => 'id', + 'post_type_link' => null, + 'expected_columns' => array( + 'id' => 'BIGINT', + 'category_code' => 'VARCHAR', + 'module_code' => 'VARCHAR', + 'name' => 'VARCHAR', + 'description' => 'TEXT', + 'unit' => 'VARCHAR', + 'default_price' => 'DECIMAL', + 'default_quantity' => 'DECIMAL', + 'pricing_type' => 'VARCHAR', + 'config' => 'TEXT', + 'is_optional' => 'TINYINT', + 'sort_order' => 'INT', + 'is_active' => 'TINYINT', + 'created_at' => 'DATETIME', + 'updated_at' => 'DATETIME', + ), + 'indexes' => array( + 'category_code' => array( 'category_code' ), + 'module_code' => array( 'module_code' ), + ), + ), + '2mq_quotation_templates' => array( + 'primary_key' => 'id', + 'post_type_link' => null, + 'expected_columns' => array( + 'id' => 'BIGINT', + 'name' => 'VARCHAR', + 'description' => 'TEXT', + 'event_type' => 'VARCHAR', + 'items' => 'TEXT', + 'config' => 'TEXT', + 'sort_order' => 'INT', + 'is_active' => 'TINYINT', + 'created_at' => 'DATETIME', + 'updated_at' => 'DATETIME', + ), + 'indexes' => array( + 'event_type' => array( 'event_type' ), + ), + ), + '2mq_vendor_catalog_prices' => array( + 'primary_key' => 'id', + 'post_type_link' => 'hp_vendor', + 'expected_columns' => array( + 'id' => 'BIGINT', + 'vendor_id' => 'BIGINT', + 'catalog_item_id' => 'BIGINT', + 'custom_price' => 'DECIMAL', + 'custom_label' => 'VARCHAR', + 'is_active' => 'TINYINT', + 'created_at' => 'DATETIME', + 'updated_at' => 'DATETIME', + ), + 'indexes' => array( + 'vendor_item' => array( 'vendor_id', 'catalog_item_id' ), + 'vendor_id' => array( 'vendor_id' ), + ), + ), ); foreach ( $tables as $table_name => $meta ) { $registry->register( '2meet-quotation', - array( - 'table_name' => $table_name, - 'primary_key' => $meta[0], - 'post_type_link' => $meta[1], - ) + array_merge( array( 'table_name' => $table_name ), $meta ) ); } }