Merge remote-tracking branch 'origin/main' into feature/limeng-third-party-warehouse-data

This commit is contained in:
limeng 2026-09-12 11:14:39 +08:00
commit 7ca35fc796
10 changed files with 165 additions and 15 deletions

View File

@ -1732,6 +1732,29 @@ class Pages extends Controller
return;
}
if ($platform === 'jd') {
fputcsv($csv, [
'平台', '店铺', '账户', '日期', '收支类型', '交易类型',
'金额(元)', '余额(元)', '备注',
]);
foreach ($data['jd'] ?? [] as $row) {
fputcsv($csv, [
'京东京准通',
$row['shop_name'] ?? '',
$row['account'] ?? '',
$row['show_date'] ?? $row['settle_datetime'] ?? '',
$row['in_out_type_name'] ?? '',
$row['trade_type_name'] ?? '',
$row['amount'] ?? '',
$row['balance_amount'] ?? '',
$row['remark'] ?? '',
]);
}
fclose($csv);
return;
}
fputcsv($csv, ['平台', '店铺/账户', '日期', '类型', '金额(元)', '余额(元)', '备注']);
$emit = static function (string $platform, array $rows, array $map) use ($csv): void {
foreach ($rows as $row) {
@ -1746,9 +1769,7 @@ class Pages extends Controller
]);
}
};
if ($platform === 'jd') {
$emit('京东京准通', $data['jd'] ?? [], ['name' => fn ($row) => $row['shop_name'] . ' / ' . $row['account'], 'date' => fn ($row) => $row['show_date'] ?? $row['settle_datetime'], 'type' => fn ($row) => $row['trade_type_name'] ?? $row['in_out_type_name'], 'amount' => fn ($row) => $row['amount'] ?? '', 'balance' => fn ($row) => $row['balance_amount'] ?? '', 'remark' => fn ($row) => $row['remark'] ?? '']);
} elseif ($platform === 'douyin') {
if ($platform === 'douyin') {
$emit('抖音千川', $data['dy'] ?? [], ['name' => fn ($row) => $row['shopName'] . ' / ' . $row['account'] . ' / ' . $row['qianchuanAavid'], 'date' => fn ($row) => $row['statDate'] ?? '', 'type' => fn () => '花费', 'amount' => fn ($row) => $row['cost'] ?? '', 'balance' => fn ($row) => $row['balance'] ?? '', 'remark' => fn ($row) => $row['remark'] ?? '']);
} elseif ($platform === 'dewu') {
$emit('得物推广', $data['dw'] ?? [], ['name' => fn ($row) => $row['shop_name'] . ' / ' . $row['account'], 'date' => fn ($row) => $row['trade_date'] ?? $row['date_time'], 'type' => fn ($row) => $row['flow_type'] ?? '', 'amount' => fn ($row) => $row['trade_amount_yuan'] ?? '', 'balance' => fn ($row) => $row['balance_yuan'] ?? '', 'remark' => fn ($row) => $row['remark'] ?? '']);

View File

@ -20,7 +20,34 @@ class ShowInAdmin
*/
public function handle(Event $event): void
{
$this->renderSections($event->menu, self::groupPayables(SidebarPermissions::navigationFor(user())));
$this->renderSections($event->menu, self::groupBasicData(self::groupPayables(SidebarPermissions::navigationFor(user()))));
}
public static function groupBasicData(array $navigation): array
{
$keys = ['order-data', 'refunds'];
$children = array_values(array_filter($navigation, static fn (array $section): bool => in_array($section['slug'], $keys, true)));
if ($children === []) {
return $navigation;
}
$group = ['slug' => 'basic-data', 'title' => '基础数据', 'icon' => 'folder', 'sections' => $children];
$hasDirectory = in_array('basic-data', array_column($navigation, 'slug'), true);
$result = [];
foreach ($navigation as $section) {
if ($section['slug'] === 'basic-data') {
$result[] = array_replace($section, ['sections' => $children]);
} elseif (in_array($section['slug'], $keys, true)) {
if (! $hasDirectory) {
$result[] = $group;
$hasDirectory = true;
}
} else {
$result[] = $section;
}
}
return $result;
}
public static function groupPayables(array $navigation): array

View File

@ -289,9 +289,15 @@ $navigation = [
],
],
],
[
'slug' => 'basic-data',
'title' => '基础数据',
'icon' => 'folder',
'pages' => [],
],
[
'slug' => 'product-inventory',
'title' => '商品及库存管理',
'title' => '商品价格管理',
'icon' => 'inventory_2',
'pages' => [
['slug' => 'normal-products', 'title' => '普通商品资料'],
@ -654,6 +660,7 @@ $navigation = [
// Keep the requested finance-data sections together while preserving every other section's order.
$requestedSectionOrder = [
'basic-data',
'invoice-management',
'fund-management',
'order-data',

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@
$titles = ['jd' => '京东京准通', 'douyin' => '抖音千川', 'dewu' => '得物推广账户', 'vip' => '唯品会推广流水'];
$filterLabel = $platform === 'vip' ? $titles[$platform] . '筛选' : $titles[$platform] . '推广账户筛选';
$columns = match ($platform) {
'jd' => [['shop_name', '店铺'], ['account', '账户'], ['show_date', '日期'], ['trade_type_name', '交易类型'], ['amount', '金额'], ['balance_amount', '余额'], ['remark', '备注']],
'jd' => [['shop_name', '店铺'], ['account', '账户'], ['show_date', '日期'], ['in_out_type_name', '收支类型'], ['trade_type_name', '交易类型'], ['amount', '金额'], ['balance_amount', '余额'], ['remark', '备注']],
'douyin' => [['shopName', '店铺'], ['account', '账户'], ['qianchuanAavid', '广告账户'], ['statDate', '日期'], ['cost', '花费'], ['income', '收入'], ['transferIn', '转入'], ['transferOut', '转出'], ['balance', '余额'], ['status', '状态'], ['remark', '备注']],
'dewu' => [['shop_name', '店铺'], ['account', '账户'], ['trade_date', '日期'], ['amount_type', '金额类型'], ['flow_type', '收支类型'], ['trade_amount_yuan', '金额'], ['balance_yuan', '余额'], ['remark', '备注']],
default => [

View File

@ -32,6 +32,8 @@ class MarketingPlatformPromotionBillsTest extends FeatureTestCase
->assertSeeText('京东京准通')
->assertSee('aria-label="京东京准通推广账户筛选"', false)
->assertSeeText('测试店铺')
->assertSeeText('收支类型')
->assertSeeText('出账')
->assertSeeText('导出 CSV')
->assertSee('class="my-6 business-page--marketing-account-bills"', false)
->assertSee(route('business.pages.show', [
@ -72,7 +74,12 @@ class MarketingPlatformPromotionBillsTest extends FeatureTestCase
"filename*=utf-8''" . rawurlencode($filename),
(string) $response->headers->get('Content-Disposition'),
);
$this->assertStringContainsString('测试店铺', $response->streamedContent());
$csv = $response->streamedContent();
$this->assertStringContainsString('测试店铺', $csv);
$this->assertStringContainsString('收支类型', $csv);
$this->assertStringContainsString('交易类型', $csv);
$this->assertStringContainsString('出账', $csv);
$this->assertStringContainsString('推广费', $csv);
}
public function test_vip_tab_uses_marketing_daily_data(): void
@ -195,7 +202,8 @@ class MarketingPlatformPromotionBillsTest extends FeatureTestCase
'shop_name' => '测试店铺',
'account' => 'account-01',
'show_date' => '2026-08-01',
'trade_type_name' => '充值',
'in_out_type_name' => '出账',
'trade_type_name' => '推广费',
'amount' => 100,
'balance_amount' => 100,
'remark' => '测试记录',

View File

@ -217,14 +217,17 @@ class PagesTest extends FeatureTestCase
]);
}
public function testDouyinAccountBillsExportUsesSelectedColumnOrder(): void
public function testDouyinAccountBillsExportUsesDateFiltersAndSelectedColumnOrder(): void
{
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
$repository = Mockery::mock(StoreReceivingDouyinBillMirrorRepository::class);
$repository->shouldReceive('exportRows')->once()->with([], 'occurred_at', 'desc')->andReturn([[
$repository->shouldReceive('exportRows')->once()->with([
'start' => '2026-09-09',
'end' => '2026-09-09',
], 'occurred_at', 'desc')->andReturn([[
'record_code' => '9001',
'payload' => [
'move_scene' => '头条放心购',
@ -235,6 +238,8 @@ class PagesTest extends FeatureTestCase
$response = $this->loginAs()->get(route('ecommerce-finance.account-bills.export', [
'bill_type' => 'douyin',
'start' => '2026-09-09',
'end' => '2026-09-09',
'columns' => 'payload.move_scene,record_code,payload.settle_account',
]));

View File

@ -9,6 +9,40 @@ use Tests\Feature\FeatureTestCase;
class NavigationTest extends FeatureTestCase
{
public function testGroupedNavigationRendersExistingPageLinks(): void
{
$this->loginAs()
->get(route('business.pages.show', ['section' => 'basic-data']))
->assertOk()
->assertSeeText('基础数据')
->assertSeeText('商品价格管理')
->assertSee('/workspace/order-data/raw-data', false)
->assertSee('/workspace/refunds/non-jingxi-refund-data', false)
->assertSee('/workspace/product-inventory/normal-products', false);
}
public function testGroupedNavigationKeepsRestrictedPagePermissions(): void
{
$this->user->roles()->sync([Role::where('name', 'manager')->firstOrFail()->id]);
$subject = 'user_' . $this->user->id;
setting()->set('system.sidebar_permissions', [
'configured' => [$subject],
'subjects' => [$subject => ['basic-data', 'refunds/non-jingxi-refund-data']],
]);
setting()->save();
$this->loginAs()
->get(route('business.pages.show', ['section' => 'basic-data']))
->assertOk()
->assertSee('/workspace/refunds/non-jingxi-refund-data', false)
->assertDontSee('/workspace/order-data/raw-data', false)
->assertDontSee('/workspace/product-inventory/normal-products', false);
$this->withExceptionHandling()
->get(route('business.pages.show', ['section' => 'order-data']))
->assertForbidden();
}
public function testAdminCanUpdateSidebarLabels()
{
$this->user->roles()->sync([
@ -87,7 +121,7 @@ class NavigationTest extends FeatureTestCase
'退款数据',
'账单流水数据',
'账单余额数据',
'商品及库存管理',
'商品价格管理',
],
array_column(array_slice($navigation, $invoiceIndex, 7), 'title')
);

View File

@ -21,7 +21,8 @@ class PlatformPromotionAccountRepositoryTest extends TestCase
$factory->shouldReceive('forSource')->once()->with('js_data_middle_platform')->andReturn($query);
$query->shouldReceive('select')->once()->ordered()->with(
Mockery::on(static fn (string $sql): bool => str_contains($sql, 'SELECT TOP 5000')
&& str_contains($sql, 'jd_jzt_finance_records')),
&& str_contains($sql, 'jd_jzt_finance_records')
&& str_contains($sql, 'in_out_type_name')),
['2026-08'],
)->andReturn([['shop_name' => '测试店铺', 'amount' => 100]]);
$query->shouldReceive('select')->once()->ordered()->with(

View File

@ -0,0 +1,46 @@
<?php
namespace Tests\Unit\View;
use PHPUnit\Framework\TestCase;
use App\Listeners\Menu\ShowInAdmin;
require_once dirname(__DIR__, 3) . '/app/Listeners/Menu/ShowInAdmin.php';
class BasicDataNavigationTest extends TestCase
{
public function test_group_moves_only_order_data_and_refunds_without_changing_pages(): void
{
$navigation = require dirname(__DIR__, 3) . '/config/business_navigation.php';
$grouped = ShowInAdmin::groupBasicData($navigation);
$slugs = array_column($grouped, 'slug');
$group = $grouped[array_search('basic-data', $slugs, true)];
$this->assertSame(['order-data', 'refunds'], array_column($group['sections'], 'slug'));
$this->assertNotContains('order-data', $slugs);
$this->assertNotContains('refunds', $slugs);
foreach ($group['sections'] as $section) {
$this->assertSame($navigation[array_search($section['slug'], array_column($navigation, 'slug'), true)], $section);
}
}
public function test_group_contains_only_authorized_sections_even_without_directory_permission(): void
{
$allowed = [['slug' => 'refunds', 'title' => '退款', 'pages' => [['slug' => 'non-jingxi-refund-data']]]];
$grouped = ShowInAdmin::groupBasicData($allowed);
$this->assertSame($allowed, $grouped[0]['sections']);
$this->assertSame([], ShowInAdmin::groupBasicData([]));
}
public function test_basic_data_is_an_empty_top_level_directory_before_invoice_management(): void
{
$navigation = require dirname(__DIR__, 3) . '/config/business_navigation.php';
$slugs = array_column($navigation, 'slug');
$index = array_search('basic-data', $slugs, true);
$this->assertNotFalse($index);
$this->assertSame('基础数据', $navigation[$index]['title']);
$this->assertSame([], $navigation[$index]['pages']);
$this->assertSame('invoice-management', $slugs[$index + 1]);
$this->assertCount(1, array_keys($slugs, 'basic-data', true));
}
}