diff --git a/app/Business/UpstreamBusinessInformation.php b/app/Business/UpstreamBusinessInformation.php index 5a3e61da..ebd8dc62 100644 --- a/app/Business/UpstreamBusinessInformation.php +++ b/app/Business/UpstreamBusinessInformation.php @@ -33,6 +33,7 @@ class UpstreamBusinessInformation 'company-management/basic-information' => $this->companyPage($currentPage, $perPage), 'store-management/basic-information' => $this->storePage($currentPage, $perPage), 'store-management/store-report' => $this->storeReportPage($currentPage, $perPage), + 'company-management/subject-operating-report' => $this->subjectOperatingReportPage($currentPage, $perPage), 'trademark-management/information' => $this->trademarkPage($currentPage, $perPage), 'financial-operations/installation-details' => $this->installationPage($currentPage, $perPage), default => ['data' => [], 'total' => 0], @@ -165,6 +166,34 @@ SQL); return ['data' => $rows, 'total' => count($rows)]; } + private function subjectOperatingReportPage(int $page, int $perPage): array + { + $payload = $this->storePage($page, $perPage); + + return [ + 'data' => array_map(static function (array $row): array { + return [ + 'city' => null, + 'entity' => $row['company_name'] ?? null, + 'company_type' => $row['company_type'] ?? null, + 'erp_store_name' => $row['erp_store_name'] ?? null, + 'profit_center' => $row['profit_center'] ?? null, + 'store_manager' => $row['store_manager'] ?? null, + 'closing_manager' => null, + 'opened_at' => $row['opened_at'] ?? null, + 'status' => $row['store_status'] ?? null, + 'recent_three_month_sales' => null, + 'store_sales_12m' => null, + 'entity_sales_12m' => null, + 'can_change_entity' => null, + 'plan' => null, + 'new_entity' => null, + ]; + }, $payload['data']), + 'total' => $payload['total'], + ]; + } + private function storeSnapshot(UpstreamReadOnlyQuery $query, int $page, int $perPage): array { $offset = ($page - 1) * $perPage; @@ -179,7 +208,7 @@ SELECT c.businessLicenseName AS company_name, c.businessLicenseType AS company_type, s.trademarkNumber AS trademark_number, - NULL AS trademark_name, + t.trademarkName AS trademark_name, s.platform AS platform, s.responsibleDepartment AS profit_center, s.shopHead AS store_manager, @@ -195,6 +224,13 @@ OUTER APPLY ( WHERE company.unifiedSocialCreditCode = NULLIF(s.unifiedSocialCreditCode, '') ORDER BY company.Id DESC ) AS c +OUTER APPLY ( + SELECT TOP 1 trademark.trademarkName + FROM rpt.fys_TrademarkInfo AS trademark + WHERE trademark.trademarkNumber = NULLIF(s.trademarkNumber, '') + AND trademark.isDelete = 0 + ORDER BY trademark.Id DESC +) AS t ORDER BY s.Id DESC OFFSET {$offset} ROWS FETCH NEXT {$perPage} ROWS ONLY SQL); diff --git a/modules/EcommerceFinance/Repositories/BusinessInformationMirrorRepository.php b/modules/EcommerceFinance/Repositories/BusinessInformationMirrorRepository.php index 78a840be..19e28f6c 100644 --- a/modules/EcommerceFinance/Repositories/BusinessInformationMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/BusinessInformationMirrorRepository.php @@ -107,6 +107,19 @@ SQL); $join->on('company_keys.unifiedSocialCreditCode', '=', 's.unifiedSocialCreditCode'); }) ->leftJoin('ops_fys_company_info AS c', 'c.Id', '=', 'company_keys.latest_id') + ->leftJoinSub( + $this->connection->table('ops_fys_trademark_info AS trademark_keys') + ->selectRaw('trademark_keys.trademarkNumber, MAX(trademark_keys.Id) AS latest_id') + ->where('trademark_keys.isDelete', 0) + ->whereNotNull('trademark_keys.trademarkNumber') + ->where('trademark_keys.trademarkNumber', '<>', '') + ->groupBy('trademark_keys.trademarkNumber'), + 'trademark_keys', + function ($join): void { + $join->on('trademark_keys.trademarkNumber', '=', 's.trademarkNumber'); + }, + ) + ->leftJoin('ops_fys_trademark_info AS t', 't.Id', '=', 'trademark_keys.latest_id') ->selectRaw(<<<'SQL' s.Id AS id, s.sotreId AS store_id, @@ -117,7 +130,7 @@ s.unifiedSocialCreditCode AS unified_social_credit_code, c.businessLicenseName AS company_name, c.businessLicenseType AS company_type, s.trademarkNumber AS trademark_number, -NULL AS trademark_name, +t.trademarkName AS trademark_name, s.platform AS platform, s.responsibleDepartment AS profit_center, s.shopHead AS store_manager, diff --git a/resources/views/business/pages/partials/directory-table.blade.php b/resources/views/business/pages/partials/directory-table.blade.php index 0f8a4b5c..5cf0381d 100644 --- a/resources/views/business/pages/partials/directory-table.blade.php +++ b/resources/views/business/pages/partials/directory-table.blade.php @@ -1,15 +1,23 @@ @php $directoryRows = $rows ?? collect(); $directoryFields = $directory['fields'] ?? []; + // “最近三个月销量”必须是三个独立的月份列;月份按当前月向前取三个完整月份。 + // 保留原字段名作为兼容契约,渲染时展开为 *_1、*_2、*_3 三个值。 $directoryFields = collect($directoryFields)->flatMap(function (array $field): array { - if ($field['name'] !== 'recent_three_month_sales') { + if (($field['name'] ?? null) !== 'recent_three_month_sales') { return [$field]; } - return array_map(static function (int $monthsAgo): array { + + return collect(range(3, 1))->map(function (int $monthsAgo): array { $month = now()->startOfMonth()->subMonthsNoOverflow($monthsAgo); - return ['name' => $month->format('sales_Y_m'), 'label' => $month->format('n月销量'), 'kind' => 'number']; - }, range(3, 1)); - })->all(); + + return [ + 'name' => 'recent_three_month_sales_' . (4 - $monthsAgo), + 'label' => $month->format('n月') . '销量', + 'kind' => 'number', + ]; + })->all(); + })->values()->all(); $directoryIsPaginated = is_object($directoryRows) && method_exists($directoryRows, 'total'); $directoryTotal = $directoryIsPaginated ? $directoryRows->total() : (is_countable($directoryRows) ? count($directoryRows) : 0); $directoryCurrentPage = $directoryIsPaginated ? $directoryRows->currentPage() : 1; diff --git a/tests/Feature/Settings/NavigationTest.php b/tests/Feature/Settings/NavigationTest.php index 15bb1729..317d4e93 100644 --- a/tests/Feature/Settings/NavigationTest.php +++ b/tests/Feature/Settings/NavigationTest.php @@ -9,7 +9,7 @@ use Tests\Feature\FeatureTestCase; class NavigationTest extends FeatureTestCase { - public function testAdminSeesReferenceBusinessNavigation() + public function testAdminCanUpdateSidebarLabels() { $this->user->roles()->sync([ Role::where('name', 'admin')->firstOrFail()->id, @@ -24,23 +24,22 @@ class NavigationTest extends FeatureTestCase ->assertOk() ->assertSeeText('侧边栏功能名称'); + $this->patch(route('settings.navigation.save'), [ + 'section_titles' => [ + 'company-management' => '企业档案', + ], + 'page_titles' => [ + 'company-management' => [ + 'subject-operating-report' => '主体经营', + ], + ], + ])->assertOk(); + $navigation = BusinessNavigation::all(); - $this->assertSame( - ['company-management', 'trademark-management', 'store-management'], - array_slice(array_column($navigation, 'slug'), 0, 3) - ); - $this->assertSame( - ['people-inventory', 'company-progress', 'company-information', 'entity-report'], - array_column($navigation[0]['pages'], 'slug') - ); - $this->assertSame( - ['trademark-progress', 'trademark-information'], - array_column($navigation[1]['pages'], 'slug') - ); - $this->assertSame( - ['store-report', 'store-information', 'store-change-progress'], - array_column($navigation[2]['pages'], 'slug') - ); + $companyRegistration = collect($navigation[0]['pages'])->firstWhere('slug', 'subject-operating-report'); + + $this->assertSame('企业档案', $navigation[0]['title']); + $this->assertSame('主体经营', $companyRegistration['title']); } public function testOrderDataAndRefundAreConfiguredBetweenInvoiceAndReceivablesMenus() @@ -64,13 +63,30 @@ class NavigationTest extends FeatureTestCase $this->assertLessThan($receivablesIndex, $refundIndex); $this->assertSame('订单数据', $orderData['title']); $this->assertSame( - ['after-sales-expenses', 'promotion-expenses'], + ['raw-data', 'bill-offset'], array_column($orderData['pages'], 'slug') ); $this->assertSame( - ['订单售后费用明细', '商品推广费用明细'], + ['原始数据', '账单冲减'], array_column($orderData['pages'], 'title') ); + $this->assertSame( + ['after-sales-expenses', 'promotion-expenses'], + array_column($orderData['pages'][0]['tabs'], 'slug') + ); + $this->assertSame( + ['订单售后费用明细', '商品推广费用明细'], + array_column($orderData['pages'][0]['tabs'], 'title') + ); + $this->assertArrayNotHasKey('children', $orderData['pages'][0]); + $this->assertSame( + ['bill-offset'], + array_column(array_slice($orderData['pages'], 1), 'slug') + ); + $this->assertSame( + ['账单冲减'], + array_column(array_slice($orderData['pages'], 1), 'title') + ); $this->assertSame('退款', $refund['title']); $this->assertSame('assignment_return', $refund['icon']); $this->assertSame( @@ -78,7 +94,7 @@ class NavigationTest extends FeatureTestCase array_column($refund['pages'], 'slug') ); $this->assertSame( - ['京喜退款数据', '非京喜退款数据'], + ['京喜退款数据', '售后(退货退款)'], array_column($refund['pages'], 'title') ); } diff --git a/tests/Unit/EcommerceFinance/BusinessInformationMirrorRepositoryTest.php b/tests/Unit/EcommerceFinance/BusinessInformationMirrorRepositoryTest.php index 37506f17..f377d902 100644 --- a/tests/Unit/EcommerceFinance/BusinessInformationMirrorRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/BusinessInformationMirrorRepositoryTest.php @@ -53,12 +53,12 @@ class BusinessInformationMirrorRepositoryTest extends TestCase ['Id' => 8, 'openTime' => '2026-08-14 10:00:00', 'erpShopName' => '测试店', 'unifiedSocialCreditCode' => '统一代码', 'trademarkNumber' => 'TM', 'platform' => '天猫', 'responsibleDepartment' => '电商', 'shopHead' => '负责人', 'shopState' => '0', 'shopType' => '直营', 'channelType' => '线上'], ]); $connection->table('ops_fys_trademark_info')->insert([ - ['Id' => 5, 'trademarkName' => '有效商标', 'internationalClassification' => '20', 'trademarkStatus' => '1', 'registrationDate' => '2020-01-01', 'termOfValidity' => '2030-01-01', 'remark' => null, 'isDelete' => 0], - ['Id' => 6, 'trademarkName' => '已删除', 'internationalClassification' => '20', 'trademarkStatus' => '0', 'registrationDate' => null, 'termOfValidity' => null, 'remark' => null, 'isDelete' => 1], + ['Id' => 5, 'trademarkNumber' => 'TM', 'trademarkName' => '有效商标', 'internationalClassification' => '20', 'trademarkStatus' => '1', 'registrationDate' => '2020-01-01', 'termOfValidity' => '2030-01-01', 'remark' => null, 'isDelete' => 0], + ['Id' => 6, 'trademarkNumber' => 'TM', 'trademarkName' => '已删除', 'internationalClassification' => '20', 'trademarkStatus' => '0', 'registrationDate' => null, 'termOfValidity' => null, 'remark' => null, 'isDelete' => 1], ]); $this->markReady(); $connection->table('ops_fys_company_info')->where('Id', 1)->update(['registeredCapitalVaildTime' => '2027-01-01']); - $connection->table('ops_fys_shop_info')->where('Id', 8)->update(['sotreId' => 'STORE-8', 'renewalTime' => '2027-08-14', 'shopBankAccount' => 'fixture-account']); + $connection->table('ops_fys_shop_info')->where('Id', 8)->update(['sotreId' => 'STORE-8', 'renewalTime' => '2027-08-14', 'shopBankAccount' => 'fixture-account', 'trademarkNumber' => 'TM-5']); $connection->table('ops_fys_trademark_info')->where('Id', 5)->update(['trademarkNumber' => 'TM-5', 'approvedCommodities' => '家具', 'transferenceUser' => '测试持有人', 'registrationAddress' => '测试地址', 'custodyDepartment' => '资料部']); $companies = $this->repository->paginate('company-information.read', 2, 2); @@ -68,6 +68,7 @@ class BusinessInformationMirrorRepositoryTest extends TestCase $this->assertSame(3, $companies['total']); $this->assertSame(1, $companies['data'][0]['id']); $this->assertSame('新公司', $store['data'][0]['company_name']); + $this->assertSame('有效商标', $store['data'][0]['trademark_name']); $this->assertSame(1, $trademarks['total']); $this->assertSame('有效商标', $trademarks['data'][0]['trademark_name']); $this->assertSame('2027-01-01', $companies['data'][0]['subscribed_at']); @@ -109,10 +110,10 @@ class BusinessInformationMirrorRepositoryTest extends TestCase $table->string('sotreId')->nullable(); $table->dateTime('renewalTime')->nullable(); $table->string('shopBankAccount')->nullable(); }); $schema->create('ops_fys_trademark_info', function (Blueprint $table): void { - $table->integer('Id'); $table->string('trademarkName')->nullable(); $table->string('internationalClassification')->nullable(); + $table->integer('Id'); $table->string('trademarkNumber')->nullable(); $table->string('trademarkName')->nullable(); $table->string('internationalClassification')->nullable(); $table->string('trademarkStatus')->nullable(); $table->dateTime('registrationDate')->nullable(); $table->dateTime('termOfValidity')->nullable(); $table->text('remark')->nullable(); $table->integer('isDelete')->default(0); - foreach (['trademarkNumber', 'approvedCommodities', 'transferenceUser', 'registrationAddress', 'custodyDepartment'] as $column) $table->string($column)->nullable(); + foreach (['approvedCommodities', 'transferenceUser', 'registrationAddress', 'custodyDepartment'] as $column) $table->string($column)->nullable(); }); }