refactor(after-sale): replace review decisions with direct status panel
售后面板改为状态下拉(待审核/处理中/已完成/已拒绝/已取消)加确认/取消按钮:取消仅收起手风琴,确认为纯状态更新;删除已通过状态、review() 决策机制及其专用退款流水线,无双路无迁移;举证图片并入申请说明列与详情整行。
This commit is contained in:
@@ -37,7 +37,6 @@ namespace AKSRT\Payment {
|
||||
class AfterSaleService {
|
||||
public const STATUS_PENDING = 'pending';
|
||||
public const STATUS_PROCESSING = 'processing';
|
||||
public const STATUS_APPROVED = 'approved';
|
||||
public const STATUS_REJECTED = 'rejected';
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
public const STATUS_COMPLETED = 'completed';
|
||||
@@ -47,7 +46,7 @@ namespace AKSRT\Payment {
|
||||
return [
|
||||
['id' => 5, 'order_number' => 'ORD100', 'user_id' => 3, 'reason' => 'legacy-reason',
|
||||
'apply_form' => ['reason_label' => '商品与描述不符', 'materials' => [['label' => '联系方式', 'value' => 'wx-abc']], 'description' => '说明文字'],
|
||||
'images' => [], 'refund_amount' => '12.50', 'status' => 'pending', 'admin_note' => '', 'gateway_result' => '', 'created_at' => '2026-10-01 10:00:00'],
|
||||
'images' => ['https://example.test/img1.png', 'https://example.test/img2.png'], 'refund_amount' => '12.50', 'status' => 'pending', 'admin_note' => '', 'gateway_result' => '', 'created_at' => '2026-10-01 10:00:00'],
|
||||
['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'],
|
||||
|
||||
@@ -140,23 +140,30 @@ test('order detail page drops tabs; after-sales becomes a standalone submenu', (
|
||||
assert.ok(page.includes('render_after_sales_admin_page'), 'after-sales admin page entry')
|
||||
assert.ok(page.includes("PAGE_AFTER_SALES_SLUG = 'aksrt_after_sales'"), 'after-sales slug constant')
|
||||
assert.ok(!page.includes('after-sales'), 'old tab slug must not remain')
|
||||
// 售后表格列对齐售后策略的申请表单配置项:原因 / 申请资料 / 申请说明
|
||||
// 售后表格列对齐售后策略的申请表单配置项:原因 / 申请资料 / 申请说明
|
||||
assert.ok(page.includes('<th>申请资料</th>'), 'materials column')
|
||||
assert.ok(page.includes('<th>申请说明</th>'), 'description column')
|
||||
assert.ok(page.includes('<th>举证图片</th>'), 'evidence images column')
|
||||
assert.ok(page.includes('$images_cell'), 'evidence images cell rendered from item images')
|
||||
// 操作列:点击「操作」以手风琴展开整条记录详情(含完整字段与内联审核表单),经 JS 以 POST 提交
|
||||
assert.ok(!page.includes('<th>举证图片</th>'), 'evidence images must not be a standalone column')
|
||||
assert.ok(page.includes('aksrt-as-desc-thumbs'), 'evidence thumbnails render inside the description cell')
|
||||
// 操作列:点击「操作」以手风琴展开整条记录详情(含完整字段与状态面板),经 JS 以 POST 提交
|
||||
assert.ok(page.includes('aksrt-toggle-row-actions'), 'toggle to expand record accordion')
|
||||
assert.ok(page.includes('aksrt-as-detail-row'), 'accordion detail row per after-sale record')
|
||||
assert.ok(page.includes('aksrt-as-detail-grid'), 'accordion panel renders full record fields')
|
||||
assert.ok(page.includes('aksrt-review-note'), 'review note textarea inside expanded panel')
|
||||
assert.ok(page.includes('aksrt-as-review-actions'), 'inline approve/reject buttons inside expanded panel')
|
||||
assert.ok(page.includes('aksrt-as-review-actions'), 'review actions row inside expanded panel')
|
||||
assert.ok(page.includes('aksrt-status-select'), 'status select inside expanded panel')
|
||||
assert.ok(page.includes('aksrt-after-sale-confirm'), 'confirm button posts the selected status')
|
||||
assert.ok(page.includes('aksrt-after-sale-cancel'), 'cancel button only collapses the panel')
|
||||
assert.ok(page.includes('aksrt_after_sale_update'), 'status update handler registered')
|
||||
assert.ok(page.includes('aksrt_after_sale_status_'), 'per-row nonce covers the status update')
|
||||
assert.ok(page.includes('aksrt-as-field--wide'), 'description renders as a full-width row')
|
||||
assert.ok(page.includes('justify-content:flex-end'), 'confirm/cancel buttons right-aligned')
|
||||
assert.ok(page.includes('.aksrt-as-detail-row[hidden]'), 'hidden attribute guard keeps accordion closed')
|
||||
assert.ok(page.includes('document.addEventListener("click"'), 'delegated click handler survives row re-render')
|
||||
assert.ok(!page.includes('class="row-actions"'), 'no hover-only row-actions')
|
||||
assert.ok(page.includes('aksrt_after_sale_bulk'), 'bulk review handler registered')
|
||||
assert.ok(page.includes('批量操作'), 'bulk actions dropdown')
|
||||
assert.ok(page.includes('after_sale_ids[]'), 'row checkboxes for bulk')
|
||||
assert.ok(!page.includes('aksrt_after_sale_bulk'), 'bulk review handler removed')
|
||||
assert.ok(!page.includes('批量操作'), 'bulk actions dropdown removed')
|
||||
assert.ok(!page.includes('after_sale_ids[]'), 'row checkboxes removed')
|
||||
assert.ok(page.includes('$materials_cell'), 'materials cell rendered from apply_form')
|
||||
assert.ok(page.includes('$desc_cell'), 'description cell rendered from apply_form')
|
||||
// 页头展示售后策略摘要(功能开关 / 时效 / 预置原因 / 申请材料)
|
||||
@@ -165,7 +172,8 @@ test('order detail page drops tabs; after-sales becomes a standalone submenu', (
|
||||
assert.ok(page.includes('申请所需材料'), 'policy summary shows materials')
|
||||
const domain = read('src/Core/DomainModel.php')
|
||||
for (const status of ['pending', 'paid', 'refunded', 'closed']) assert.ok(domain.includes(`ORDER_STATUS_${status.toUpperCase()}`))
|
||||
assert.match(page, /STATUS_PENDING[\s\S]*?STATUS_PROCESSING[\s\S]*?STATUS_APPROVED[\s\S]*?STATUS_REJECTED[\s\S]*?STATUS_CANCELLED[\s\S]*?STATUS_COMPLETED/)
|
||||
assert.match(page, /STATUS_PENDING[\s\S]*?STATUS_PROCESSING[\s\S]*?STATUS_COMPLETED[\s\S]*?STATUS_REJECTED[\s\S]*?STATUS_CANCELLED/)
|
||||
assert.ok(!page.includes('STATUS_APPROVED'), 'approved status removed')
|
||||
assert.match(page, /ORDER_PRODUCT_READING[\s\S]*?付费阅读[\s\S]*?ORDER_PRODUCT_DOWNLOAD[\s\S]*?付费下载[\s\S]*?ORDER_PRODUCT_IMAGE[\s\S]*?付费图片[\s\S]*?ORDER_PRODUCT_VIDEO[\s\S]*?付费视频[\s\S]*?ORDER_PRODUCT_VIP[\s\S]*?购买会员[\s\S]*?ORDER_PRODUCT_RECHARGE[\s\S]*?余额充值[\s\S]*?ORDER_PRODUCT_POINTS[\s\S]*?购买积分/)
|
||||
})
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ test('free points keep a bounded daily summary alongside the recent points histo
|
||||
assert.match(assets, /'points_record'/)
|
||||
})
|
||||
|
||||
test('after-sale submissions are order-locked and only completed by their own refund application', () => {
|
||||
test('after-sale submissions are order-locked and admins set status directly', () => {
|
||||
const afterSale = read('src/Payment/AfterSaleService.php')
|
||||
const schema = read('src/Core/DBMigratorPart2.php')
|
||||
const repository = read('src/Payment/OrderRepositoryCore.php')
|
||||
@@ -57,14 +57,12 @@ test('after-sale submissions are order-locked and only completed by their own re
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}aksrt_order_aftersale[\s\S]*?gateway_request_id[\s\S]*?gateway_result[\s\S]*?completed_at/)
|
||||
assert.match(afterSale, /GET_LOCK\(%s, 5\)[\s\S]*?get_user_order\(\$user_id, \$order_number\)[\s\S]*?find_active\(\$order_number\)/)
|
||||
assert.match(afterSale, /Order::STATUS_PAID/)
|
||||
assert.match(afterSale, /\$gateway->refund\(\$order_number, \(string\) \$row\['refund_amount'\], \$request_id\)/)
|
||||
assert.match(afterSale, /query_refund\(\$request_id, \(string\) \$row\['order_number'\]\)/)
|
||||
assert.match(afterSale, /sanitize_key\(\(string\) \(\$gateway_result\['status'\] \?\? ''\)\) !== 'manual'/)
|
||||
assert.match(afterSale, /\$decision === 'reject'[\s\S]*?compensate_revoke\(\$row, \$order, \$revoke, \$note\)/)
|
||||
assert.match(afterSale, /Order::STATUS_REFUNDED/)
|
||||
assert.match(afterSale, /public function set_status\(int \$id, string \$status, string \$note = ''\)/)
|
||||
assert.match(afterSale, /in_array\(\$status, \[self::STATUS_PENDING, self::STATUS_PROCESSING, self::STATUS_COMPLETED, self::STATUS_REJECTED, self::STATUS_CANCELLED\], true\)/)
|
||||
assert.ok(!afterSale.includes('STATUS_APPROVED'), 'approved status removed')
|
||||
assert.ok(!afterSale.includes('function review('), 'decision-based review removed')
|
||||
assert.ok(!afterSale.includes('approve('), 'refund pipeline removed')
|
||||
assert.match(repository, /can_finalize_order_refund\(\(int\) \(\$extra\['after_sale_id'\] \?\? 0\), \$order_number\)/)
|
||||
assert.match(read('src/Payment/Gateways/PayPalGateway.php'), /'status' => 'manual'/)
|
||||
assert.match(read('src/Payment/Gateways/EpayGateway.php'), /'status'\s*=>\s*'manual'/)
|
||||
})
|
||||
|
||||
test('withdrawal holds and returns use the selected user-meta or commission pool', () => {
|
||||
|
||||
Reference in New Issue
Block a user