feat(ticket): replace the after-sale flow with a general ticket system

The old 售后 flow was an order-refund appendage: tickets were hard-bound to an
order number, users could only file them from 「我的订单」against a paid order,
and the admin action bottomed out at 「标记订单已退款」. It could not carry
account questions or feature requests that have nothing to do with an order.

Replace it with a general ticket system modelled on 魔方财务云's idcsmart_ticket:
title + department + description are required, linking an order is optional
(user's own paid orders only). Add departments, custom statuses, preset replies
and internal admin notes as four configuration surfaces. Conversation history
moves off the ticket row's JSON log into dedicated ticket_reply rows, which
unlocks quoted replies. Front end gets a 工单中心 panel under 我的服务; back end
moves from 支付中心 → 售后服务 to users.php → 工单管理.

Per CLAUDE.md's rewrite-over-compatibility rule: drop the old table, build new
ones, delete the old code. No dual paths, no data migration. The upstream
plugin's upstream/downstream delivery and internal-ticket conversion are
deliberately left out — both are multi-provider platform concerns with no
meaning on a single WordPress site.

Data: order_aftersale becomes six aksrt_ticket* tables (ticket, ticket_reply,
ticket_status, ticket_type, ticket_prereply, ticket_note). SCHEMA_VERSION
2.6.0 -> 3.0.0. Status and department seeds are idempotent; the default status
「待接单」is flagged as non-editable.

Domain: new src/Ticket/ (13 files, all under 460 lines). Attachments reuse
UserController::rest_upload_comment_image with a ticket-specific permission
callback, mirroring the existing /report/upload-image route rather than
growing a second upload path.

Notifications go through AKSRT\Messages\Message::send() and deep-link to the
right surface; email/SMS stay out of scope since the theme's mail and SMS
stacks are separate implementations.

Refund gate opens up: marking an order refunded no longer requires an in-flight
ticket, only that the order is paid.

Also generalise a hardcoded special case in tools/audit-admin-contracts.php —
schema domain inference now reads the parent directory instead of matching
"PostList" by name, so new leaf schema files need no per-name exception.

Verification:
  php tools/audit-admin-contracts.php   exit 0, errors=0
  node --test (widget-system + admin)   664 pass / 0 fail
  php -l over all new/changed files     clean
  node --check over all JS              clean
  npm run build + build:public          clean

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-10-25 23:29:51 +08:00
1 parent 761c84d70f
commit dbb7de41e9
81 files changed
+5812 -4179

No files matched your search

-28
View File
@@ -571,34 +571,6 @@ class SchemaPayment {
]],
],
],
/* ------------------------------------------------------------------
* 售后策略
* ------------------------------------------------------------------ */
'after_sale' => [
'key' => 'after_sale',
'title' => '售后策略',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'main', 'title' => '售后功能', 'icon' => 'fa fas fa-undo-alt', 'fields' => [
['key' => 'after_sale_s', 'label' => '售后功能', 'type' => 'switch', 'default' => true, 'help' => '开启后用户可以申请售后;关闭后不再处理售后,前台「我的订单」不再显示申请售后按钮'],
['key' => 'after_sale_time_limit', 'label' => '申请售后时效(天)', 'type' => 'number', 'default' => 7, 'min' => 0, 'max' => 3650, 'help' => '订单支付后多少天内可以申请售后,超过时效将无法申请;0 表示不限制'],
['key' => 'after_sale_expired_text', 'label' => '超过时效提示', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '<div>抱歉,您的订单已超过售后申请时效</div>', 'rows' => 3, 'help' => '订单超过售后时效后,用户点击申请售后时弹窗提示的内容(支持HTML代码,请注意代码规范)'],
]],
['key' => 'form', 'title' => '申请表单', 'icon' => 'fa fas fa-file-alt', 'fields' => [
['key' => 'after_sale_reasons', 'label' => '预置原因', 'type' => 'group', 'button_title' => '添加原因', 'help' => '用户申请售后时可选择的原因', 'default' => [['t' => '质量问题'], ['t' => '假冒品牌'], ['t' => '其他原因']], 'fields' => [
['key' => 't', 'label' => '原因', 'type' => 'input', 'default' => ''],
]],
['key' => 'after_sale_desc', 'label' => '申请售后说明', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '', 'rows' => 5, 'help' => '用户申请售后时显示的内容(支持HTML代码,请注意代码规范)'],
['key' => 'after_sale_materials', 'label' => '申请所需材料', 'type' => 'group', 'button_title' => '添加材料', 'help' => '用户提交售后申请时需要提供的资料,如联系邮箱、联系电话;此处的信息收集,仅用于售后联系与审核', 'default' => [['t' => '联系邮箱'], ['t' => '联系电话']], 'fields' => [
['key' => 't', 'label' => '资料项', 'type' => 'input', 'default' => ''],
]],
['key' => 'after_sale_reason_placeholder', 'label' => '申请说明占位文案', 'type' => 'input', 'default' => '必填,请您详细填写申请原因', 'help' => '前台申请售后弹窗中「申请说明」输入框的提示文字'],
['key' => 'after_sale_img_s', 'label' => '申请售后可上传图片', 'type' => 'switch', 'default' => true, 'help' => '开启后申请售后弹窗中出现图片上传入口(最多上传3张)'],
]],
],
],
],
];
}