feat(after-sale): 售后处理记录改为每次提交留痕并新增时间线
工单行内新增 handling_log 列,用户申请、用户撤销与后台每次改状态都落一条
「状态 + 备注 + 来源 + 时间」,按时间正序存放、上限 50 条(对齐
MetaAssetService::MAX_RECORDS)。set_status 因此变成读-改-写,补上
GET_LOCK/RELEASE_LOCK 包裹,并在锁内重读工单快照——锁外取快照仍会被并发
追加覆盖。
三处界面由「只显示最后一条」改为回放整条处理链路:
- 后台订单明细「售后记录」按工单渲染头部(状态/金额/申请时间)+ 原因 +
完整处理记录
- 售后面板手风琴「处理备注」单独占整行并置于「申请说明」上方,删掉已无
意义的「审核时间」
- 前台售后详细弹窗与订单详情弹窗的售后记录改为时间线:左列绳结状态条
(节点 + 状态 + 时间),右列处理备注;样式只在 modal-runtime.css 定义
一份,两个弹窗共用
清理随之失效的死代码:工单的 admin_note / reviewed_at / completed_at,以及
全仓库无写入方的 gateway_request_id / gateway_refund_id / gateway_result;
连带删掉 gatewayResultText()、uninstall 里从未存在过的 aksrt_after_sale
表项,并把两处后台页面重复的状态标签数组收成 after_sale_status_labels()、
重复的 .aksrt-as-log 样式收成 after_sale_log_css()。
顺带修复 88669f69 重构退款流水线时误删 refund_amount()、却仍在 create()
里调用的问题——此前任何用户提交售后都会直接 fatal。
验证:tests/widget-system/ 462 项、tests/admin/ 76 项通过;新增
after-sale-log-harness.php 驱动真实 AfterSaleService,覆盖首条日志、空备注
也留痕、撤销记录、50 条上限与畸形行规范化;两个后台页面经 dev harness
渲染校验「处理备注」宽行位于「申请说明」之前;未做浏览器视觉验收。
This commit is contained in:
@@ -77,11 +77,16 @@ namespace AKSRT\Payment {
|
||||
public const STATUS_REJECTED = 'rejected';
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
public const STATUS_COMPLETED = 'completed';
|
||||
public const SOURCE_ADMIN = 'admin';
|
||||
public const SOURCE_USER = 'user';
|
||||
private static $i;
|
||||
/** @var array<int,array<string,mixed>> keyed by after-sale id */
|
||||
public static array $rows = [];
|
||||
public static function get_instance(): self { return self::$i ?: self::$i = new self(); }
|
||||
public function admin_list(array $args = []): array { return array_values(self::$rows); }
|
||||
public function list_for_order(string $order_number): array {
|
||||
return array_values(array_filter(self::$rows, static fn($row): bool => (string) ($row['order_number'] ?? '') === $order_number));
|
||||
}
|
||||
public function count(array $args = []): int { return count(self::$rows); }
|
||||
public function get(int $id): ?array { return self::$rows[$id] ?? null; }
|
||||
public function can_finalize_order_refund(int $after_sale_id, string $order_number): bool {
|
||||
@@ -109,8 +114,7 @@ namespace AKSRT\Payment {
|
||||
}
|
||||
if (!isset(self::$rows[$id])) return new \WP_Error('aksrt_after_sale_state', '售后申请不存在', ['status' => 409]);
|
||||
self::$rows[$id]['status'] = $status;
|
||||
if ($note !== '') self::$rows[$id]['admin_note'] = $note;
|
||||
if ($status !== self::STATUS_PENDING) self::$rows[$id]['reviewed_at'] = current_time('mysql');
|
||||
self::$rows[$id]['handling_log'][] = ['status' => $status, 'note' => $note, 'source' => 'admin', 'created_at' => current_time('mysql')];
|
||||
return self::$rows[$id];
|
||||
}
|
||||
}
|
||||
@@ -121,6 +125,7 @@ namespace AKSRT\Payment {
|
||||
public static array $orders = [];
|
||||
public static function get_instance(): self { return self::$i ?: self::$i = new self(); }
|
||||
public function get($n): ?array { return self::$orders[(string) $n] ?? null; }
|
||||
public function get_admin_order_context(string $order_number): array { return ['customer_note' => '买家备注示例']; }
|
||||
public function get_orders_admin(array $args = [], string $search = ''): array { return array_values(self::$orders); }
|
||||
public function get_orders_count(array $args = [], string $search = ''): int { return count(self::$orders); }
|
||||
public function update_status(string $order_number, string $new_status, array $extra = []): bool {
|
||||
@@ -207,11 +212,19 @@ require 'C:/Users/Xing/Desktop/AKSRT/AKSRT-theme/src/Admin/Native/PayOrders.php'
|
||||
5 => ['id' => 5, 'order_number' => 'ORD100', 'user_id' => 3, 'reason' => 'legacy-reason',
|
||||
'apply_form' => ['reason_label' => '商品与描述不符', 'materials' => [['label' => '联系方式', 'value' => 'wx-abc']], 'description' => '说明文字'],
|
||||
'images' => ['https://example.test/img1.png'], 'refund_amount' => '12.50', 'status' => 'pending',
|
||||
'admin_note' => '', 'gateway_result' => '', 'created_at' => '2026-10-01 10:00:00', 'reviewed_at' => ''],
|
||||
'handling_log' => [
|
||||
['status' => 'pending', 'note' => '', 'source' => 'user', 'created_at' => '2026-10-01 10:00:00'],
|
||||
['status' => 'processing', 'note' => '已联系仓库核实库存', 'source' => 'admin', 'created_at' => '2026-10-01 14:20:00'],
|
||||
],
|
||||
'created_at' => '2026-10-01 10:00:00'],
|
||||
6 => ['id' => 6, 'order_number' => 'ORD101', 'user_id' => 4, 'reason' => 'r2',
|
||||
'apply_form' => null, 'images' => [], 'refund_amount' => '8.00', 'status' => 'processing',
|
||||
'admin_note' => '已有备注', 'gateway_result' => json_encode(['status' => 'manual', 'message' => '线下退款']),
|
||||
'created_at' => '2026-10-02 11:00:00', 'reviewed_at' => '2026-10-03 09:00:00'],
|
||||
'handling_log' => [
|
||||
['status' => 'pending', 'note' => '', 'source' => 'user', 'created_at' => '2026-10-02 11:00:00'],
|
||||
['status' => 'processing', 'note' => '资料已补齐,进入处理', 'source' => 'admin', 'created_at' => '2026-10-02 15:40:00'],
|
||||
['status' => 'rejected', 'note' => '超出七天无理由范围,已驳回', 'source' => 'admin', 'created_at' => '2026-10-03 09:00:00'],
|
||||
],
|
||||
'created_at' => '2026-10-02 11:00:00'],
|
||||
];
|
||||
\AKSRT\Payment\OrderRepository::$orders = [
|
||||
'ORD100' => ['order_number' => 'ORD100', 'user_id' => 3, 'product_id' => 9, 'product_type' => 'reading', 'meta_payload' => ['product_title' => '测试商品'], 'currency' => 'CNY', 'amount' => '12.50', 'payment_method' => 'balance', 'status' => 'paid', 'created_at' => '2026-10-01 09:00:00', 'paid_at' => '2026-10-01 09:05:00'],
|
||||
@@ -230,6 +243,16 @@ require 'C:/Users/Xing/Desktop/AKSRT/AKSRT-theme/src/Admin/Native/PayOrders.php'
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// 订单明细页:售后记录单元格按工单渲染完整处理记录。
|
||||
if ($scenario === 'order-detail') {
|
||||
$_GET = ['page' => 'aksrt_orders', 'order' => 'ORD100'];
|
||||
$pay = AKSRT\Admin\Native\PayOrders::get_instance();
|
||||
ob_start();
|
||||
$pay->render_orders_page();
|
||||
file_put_contents(__DIR__ . '/order-detail-rendered.html', ob_get_clean());
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ($scenario !== '') {
|
||||
// The handler ends in exit(), so the call log is flushed on shutdown.
|
||||
register_shutdown_function(static function (): void {
|
||||
|
||||
Reference in New Issue
Block a user