Files
wordpress-theme-aksrt/src/Core/DBMigratorTicket.php
T
kate522andClaude Fable 5 5febdb35c8 feat(ticket): record urges in thread, show urge count in admin, mail admins
- aksrt_ticket gains urge_count / last_urge_time (schema 3.1.0)
- cooldown moves from transient to an atomic conditional UPDATE
- urge inserts a system reply rendered as a centered notice (not quotable)
- admin list badge + info card row for urge count / last time
- new ticket_urge_admin mail event via aksrt_ticket_urged

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-27 22:17:40 +08:00

180 lines
6.9 KiB
PHP

<?php
/**
* Ticket (工单) tables.
*
* 6 张表取代旧的 aksrt_order_aftersale:工单不再绑定订单退款流程,
* 标题 + 部门 + 描述为必填,关联订单为可选项。
*
* @package AKSRT
*/
namespace AKSRT\Core;
defined('ABSPATH') || exit;
trait DBMigratorTicket {
private static function migrate_ticket_tables(string $prefix, string $charset_collate): void {
$ticket = "CREATE TABLE {$prefix}aksrt_ticket (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
ticket_num VARCHAR(32) NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
title VARCHAR(200) NOT NULL,
ticket_type_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
content TEXT NOT NULL,
status VARCHAR(24) NOT NULL DEFAULT 'pending',
order_number VARCHAR(32) NOT NULL DEFAULT '',
attachment LONGTEXT DEFAULT NULL,
last_reply_time DATETIME DEFAULT NULL,
post_time DATETIME NOT NULL,
notes TEXT DEFAULT NULL,
admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
last_reply_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
post_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
urge_count INT UNSIGNED NOT NULL DEFAULT 0,
last_urge_time DATETIME DEFAULT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY uniq_ticket_num (ticket_num),
KEY idx_user_created (user_id, created_at),
KEY idx_status_created (status, created_at),
KEY idx_type_status (ticket_type_id, status),
KEY idx_order (order_number)
) ENGINE=InnoDB {$charset_collate};";
dbDelta($ticket);
$reply = "CREATE TABLE {$prefix}aksrt_ticket_reply (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
ticket_id BIGINT UNSIGNED NOT NULL,
author_type VARCHAR(16) NOT NULL DEFAULT 'user',
author_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
content TEXT NOT NULL,
attachment LONGTEXT DEFAULT NULL,
quote_reply_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY idx_ticket_created (ticket_id, created_at),
KEY idx_quote (quote_reply_id)
) ENGINE=InnoDB {$charset_collate};";
dbDelta($reply);
// status_key 是工单上 aksrt_ticket.status 的外延键:管理员改状态名只动 name,
// 存量工单的 status 不受影响。默认状态(is_default)不可改删。
$status = "CREATE TABLE {$prefix}aksrt_ticket_status (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
status_key VARCHAR(24) NOT NULL,
name VARCHAR(60) NOT NULL,
color VARCHAR(20) NOT NULL DEFAULT '#6b7280',
is_closed TINYINT(1) NOT NULL DEFAULT 0,
is_default TINYINT(1) NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
updated_at DATETIME DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY uniq_status_key (status_key)
) ENGINE=InnoDB {$charset_collate};";
dbDelta($status);
$type = "CREATE TABLE {$prefix}aksrt_ticket_type (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(120) NOT NULL,
description TEXT DEFAULT NULL,
admin_ids LONGTEXT DEFAULT NULL,
sort_order INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
updated_at DATETIME DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB {$charset_collate};";
dbDelta($type);
$prereply = "CREATE TABLE {$prefix}aksrt_ticket_prereply (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
content TEXT NOT NULL,
sort_order INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB {$charset_collate};";
dbDelta($prereply);
$note = "CREATE TABLE {$prefix}aksrt_ticket_note (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
ticket_id BIGINT UNSIGNED NOT NULL,
admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
content TEXT NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY idx_ticket_created (ticket_id, created_at)
) ENGINE=InnoDB {$charset_collate};";
dbDelta($note);
self::seed_ticket_statuses($prefix);
self::seed_ticket_types($prefix);
}
/**
* 工单状态 seeds:按 status_key 幂等补齐,不覆盖管理员已改过的名称与颜色。
* 「待接单」标记 is_default,后台拒绝改删。
*
* @return void
*/
private static function seed_ticket_statuses(string $prefix): void {
global $wpdb;
$table = $prefix . 'aksrt_ticket_status';
$now = current_time('mysql');
$seeds = [
['pending', '待接单', '#f59e0b', 0, 1],
['handling', '处理中', '#8b5cf6', 0, 0],
['user_replied', '待回复', '#3b82f6', 0, 0],
['admin_replied', '已回复', '#10b981', 0, 0],
['closed', '已关闭', '#6b7280', 1, 0],
];
$exists = $wpdb->get_col("SELECT status_key FROM {$table}"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL
if (!is_array($exists)) {
$exists = [];
}
$sort = 0;
foreach ($seeds as [$key, $label, $color, $is_closed, $is_default]) {
if (in_array($key, $exists, true)) {
continue;
}
$wpdb->insert($table, [
'status_key' => $key,
'name' => $label,
'color' => $color,
'is_closed' => $is_closed,
'is_default' => $is_default,
'sort_order' => $sort++,
'created_at' => $now,
], ['%s', '%s', '%s', '%d', '%d', '%d', '%s']);
}
}
/** 工单部门 seeds:按 name 幂等补齐。 */
private static function seed_ticket_types(string $prefix): void {
global $wpdb;
$table = $prefix . 'aksrt_ticket_type';
$now = current_time('mysql');
$seeds = ['技术支持', '账号问题', '财务问题', '产品建议', '其他'];
$exists = $wpdb->get_col("SELECT name FROM {$table}"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL
if (!is_array($exists)) {
$exists = [];
}
$sort = 0;
foreach ($seeds as $label) {
if (in_array($label, $exists, true)) {
continue;
}
$wpdb->insert($table, [
'name' => $label,
'sort_order' => $sort++,
'created_at' => $now,
], ['%s', '%d', '%s']);
}
}
}