售后退货退款预警子页 #744

Merged
sumu merged 1 commits from feature/sumu-after-sales-refund-warning into main 2026-10-28 17:13:56 +08:00
6 changed files with 183 additions and 23 deletions

View File

@ -4705,6 +4705,7 @@ class Pages extends Controller
'withdrawal-management' => $this->contentForWithdrawalManagement(),
'systemization-todos' => app(\App\Http\Controllers\Business\SystemizationTodos::class)->page(),
'todos' => $this->contentForTodos(),
'after-sales-return-refund' => ['view' => 'business.pages.other.after-sales-return-refund'],
'bill-offset' => app(\Modules\EcommerceFinance\View\BillOffsetPageData::class)->page(),
'account-mapping' => $this->contentForAccountMapping(),
'ledger' => app(\Modules\EcommerceFinance\View\FinanceLedgerPageData::class)->page($tab),

View File

@ -127,8 +127,12 @@ class SidebarPermissions
$pageKey = $sectionKey . '/' . $page['slug'];
if (in_array($pageKey, $allowed, true)) {
// 若有 children,保留 children 的精确权限过滤
// 页面拆分为子页时,可显式沿用父页授权,避免旧权限配置丢失入口。
if (! empty($page['children'])) {
if (($page['children_inherit_parent_access'] ?? false) === true) {
return $page;
}
$page['children'] = array_values(array_filter($page['children'], function (array $child) use ($allowed, $sectionKey) {
return in_array($sectionKey . '/' . $child['slug'], $allowed, true);
}));

View File

@ -1071,27 +1071,38 @@ $navigation = [
[
'slug' => 'warnings',
'title' => '预警页面',
'directory' => [
'icon' => 'warning',
'status' => '尚未同步',
'description' => '集中展示销量、税务、主体、人员、商标和续签风险,并可关联待办编号追踪处理结果。',
'empty' => '预警数据尚未同步。完成规则引擎和预警来源接入后,这里会显示风险记录。',
'fields' => [
['name' => 'warning_number', 'label' => '预警编号'],
['name' => 'warning_type', 'label' => '预警类型'],
['name' => 'risk_level', 'label' => '风险等级'],
['name' => 'related_object', 'label' => '关联对象'],
['name' => 'risk_description', 'label' => '风险描述'],
['name' => 'impact_amount', 'label' => '影响金额', 'kind' => 'number'],
['name' => 'trigger_rule', 'label' => '触发规则'],
['name' => 'triggered_at', 'label' => '触发时间', 'kind' => 'date'],
['name' => 'owner', 'label' => '责任人'],
['name' => 'collaborator', 'label' => '协同人'],
['name' => 'due_at', 'label' => '截止时间', 'kind' => 'date'],
['name' => 'todo_number', 'label' => '待办编号'],
['name' => 'status', 'label' => '处理状态'],
['name' => 'evidence', 'label' => '处理证据'],
['name' => 'closed_by_date', 'label' => '关闭人/日期'],
'children_inherit_parent_access' => true,
'children' => [
[
'slug' => 'warning-overview',
'title' => '预警总览',
'directory' => [
'icon' => 'warning',
'status' => '尚未同步',
'description' => '集中展示销量、税务、主体、人员、商标和续签风险,并可关联待办编号追踪处理结果。',
'empty' => '预警数据尚未同步。完成规则引擎和预警来源接入后,这里会显示风险记录。',
'fields' => [
['name' => 'warning_number', 'label' => '预警编号'],
['name' => 'warning_type', 'label' => '预警类型'],
['name' => 'risk_level', 'label' => '风险等级'],
['name' => 'related_object', 'label' => '关联对象'],
['name' => 'risk_description', 'label' => '风险描述'],
['name' => 'impact_amount', 'label' => '影响金额', 'kind' => 'number'],
['name' => 'trigger_rule', 'label' => '触发规则'],
['name' => 'triggered_at', 'label' => '触发时间', 'kind' => 'date'],
['name' => 'owner', 'label' => '责任人'],
['name' => 'collaborator', 'label' => '协同人'],
['name' => 'due_at', 'label' => '截止时间', 'kind' => 'date'],
['name' => 'todo_number', 'label' => '待办编号'],
['name' => 'status', 'label' => '处理状态'],
['name' => 'evidence', 'label' => '处理证据'],
['name' => 'closed_by_date', 'label' => '关闭人/日期'],
],
],
],
[
'slug' => 'after-sales-return-refund',
'title' => '售后退货退款',
],
],
],

View File

@ -0,0 +1,13 @@
@include('business.pages.negative-alerts._placeholder', [
'icon' => 'assignment_return',
'scopeLabel' => '售后退货退款',
'statusTitle' => '售后退货退款预警规则待接入',
'description' => '页面入口已建立。待确认售后退货、退款及售后差异的判定口径和取数来源后,这里将集中展示需要财务跟进的预警记录。',
'scopeItems' => [
['label' => '预警范围', 'value' => '退货、退款及售后差异'],
['label' => '判定规则', 'value' => '待确认'],
['label' => '数据来源', 'value' => '待接入'],
],
'emptyTitle' => '接入后在这里查看售后退货退款预警',
'emptyDescription' => '预警将保留订单、店铺、金额、原因、责任人和处理结果,便于财务核对与追踪。',
])

View File

@ -777,9 +777,22 @@ class ManagementCrudTest extends FeatureTestCase
['systemization-todos', 'todos', 'warnings'],
array_column($sections['other']['pages'], 'slug')
);
$warnings = collect($sections['other']['pages'])->firstWhere('slug', 'warnings');
$this->assertSame(
['warning-overview', 'after-sales-return-refund'],
array_column($warnings['children'], 'slug')
);
$this->assertSame(
['预警总览', '售后退货退款'],
array_column($warnings['children'], 'title')
);
foreach (['company-management', 'trademark-management', 'store-management', 'other'] as $sectionSlug) {
foreach ($sections[$sectionSlug]['pages'] as $page) {
if (! empty($page['children']) || $page['slug'] === 'after-sales-return-refund') {
continue;
}
$this->assertArrayHasKey('directory', $page, "{$sectionSlug}/{$page['slug']} 缺少目录字段契约");
foreach ($page['directory']['fields'] as $field) {
@ -851,7 +864,7 @@ class ManagementCrudTest extends FeatureTestCase
['section' => 'store-management', 'page' => 'change-progress', 'headers' => ['接替公司', '完成时间', '办理人']],
['section' => 'other', 'page' => 'systemization-todos', 'headers' => ['类型', '内容', '待办事项', '总负责人', '责任人', '状态', '完成截图']],
['section' => 'other', 'page' => 'todos', 'headers' => ['待办编号', '是否逾期', '关闭日期']],
['section' => 'other', 'page' => 'warnings', 'headers' => ['风险等级', '影响金额', '处理证据']],
['section' => 'other', 'page' => 'warning-overview', 'headers' => ['风险等级', '影响金额', '处理证据']],
];
foreach ($expectedHeaders as $page) {

View File

@ -0,0 +1,118 @@
<?php
namespace Tests\Feature\Business;
use App\Models\Auth\Permission;
use App\Models\Auth\Role;
use App\Models\Auth\User;
use App\Utilities\BusinessNavigation;
use App\Utilities\SidebarPermissions;
use Tests\Feature\FeatureTestCase;
class WarningPageTest extends FeatureTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
}
public function test_warning_page_contains_after_sales_return_refund_child(): void
{
$warnings = collect(BusinessNavigation::all())
->firstWhere('slug', 'other')['pages'];
$warnings = collect($warnings)->firstWhere('slug', 'warnings');
$this->assertSame(
['warning-overview', 'after-sales-return-refund'],
array_column($warnings['children'], 'slug'),
);
$this->assertSame(
['预警总览', '售后退货退款'],
array_column($warnings['children'], 'title'),
);
}
public function test_warning_children_render_and_are_linked_from_the_sidebar(): void
{
$this->loginAs();
$overviewUrl = route('business.pages.show', [
'section' => 'other',
'page' => 'warning-overview',
]);
$afterSalesRefundUrl = route('business.pages.show', [
'section' => 'other',
'page' => 'after-sales-return-refund',
]);
$this->get(route('business.pages.show', [
'section' => 'other',
'page' => 'warnings',
]))
->assertRedirect($overviewUrl);
$this->get($overviewUrl)
->assertOk()
->assertSeeText('预警总览')
->assertSeeText('售后退货退款')
->assertSeeText('风险等级')
->assertSee($afterSalesRefundUrl, false);
$this->get($afterSalesRefundUrl)
->assertOk()
->assertSeeText('售后退货退款')
->assertSeeText('售后退货退款预警规则待接入')
->assertSeeText('退货、退款及售后差异')
->assertSeeText('接入后在这里查看售后退货退款预警');
}
public function test_existing_warning_page_permission_keeps_both_child_entries(): void
{
$regular = $this->regularAccount();
$subject = 'user_' . $regular->id;
setting()->set('system.sidebar_permissions', [
'configured' => [$subject],
'subjects' => [$subject => ['other/warnings']],
]);
$other = collect(SidebarPermissions::navigationFor($regular))
->firstWhere('slug', 'other');
$warnings = collect($other['pages'])->firstWhere('slug', 'warnings');
$this->assertSame(
['warning-overview', 'after-sales-return-refund'],
array_column($warnings['children'], 'slug'),
);
$this->loginAs($regular)
->get(route('business.pages.show', [
'section' => 'other',
'page' => 'after-sales-return-refund',
]))
->assertOk()
->assertSeeText('售后退货退款预警规则待接入');
}
private function regularAccount(): User
{
$name = 'fixture-warning-page-' . User::count();
$user = User::create([
'name' => $name,
'email' => $name . '@example.test',
'password' => 'fixture-unused-password',
'enabled' => true,
'locale' => 'zh-CN',
'landing_page' => 'dashboard',
]);
$user->roles()->attach(Role::where('name', 'user')->firstOrFail()->id);
$user->permissions()->attach(Permission::where('name', 'read-admin-panel')->firstOrFail()->id);
$user->companies()->attach($this->company->id);
return $user;
}
}