feat: 主体报表接入公司盘点税务工商类型
This commit is contained in:
parent
11db14c758
commit
178bf2840f
@ -275,25 +275,14 @@ class UpstreamBusinessInformation
|
||||
}
|
||||
|
||||
$references ??= $this->companyPlanClassificationReferences($rows);
|
||||
$byCreditCode = [];
|
||||
$byCompanyName = [];
|
||||
|
||||
foreach ($references as $reference) {
|
||||
$creditCode = $this->normalizeCreditCode($reference['unified_social_credit_code'] ?? null);
|
||||
if ($creditCode !== '' && ! isset($byCreditCode[$creditCode])) {
|
||||
$byCreditCode[$creditCode] = $reference;
|
||||
}
|
||||
|
||||
$companyName = $this->normalizeCompanyName($reference['company_name'] ?? null);
|
||||
if ($companyName !== '' && ! isset($byCompanyName[$companyName])) {
|
||||
$byCompanyName[$companyName] = $reference;
|
||||
}
|
||||
}
|
||||
$index = $this->companyPlanClassificationIndex($references);
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$creditCode = $this->normalizeCreditCode($row['unified_social_credit_code'] ?? null);
|
||||
$companyName = $this->normalizeCompanyName($row['company_name'] ?? null);
|
||||
$reference = $byCreditCode[$creditCode] ?? $byCompanyName[$companyName] ?? null;
|
||||
$reference = $this->matchedCompanyPlanClassification(
|
||||
$index,
|
||||
$row['company_name'] ?? null,
|
||||
[$row],
|
||||
);
|
||||
|
||||
$planTaxType = trim((string) ($reference['taxpayer_type'] ?? ''));
|
||||
$row['taxpayer_category'] = $this->normalizeTaxType(
|
||||
@ -319,6 +308,10 @@ class UpstreamBusinessInformation
|
||||
*/
|
||||
private function companyPlanClassificationReferences(array $rows = [], bool $all = false): array
|
||||
{
|
||||
if ($this->companyPlans === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$companyId = (int) (company_id() ?: 1);
|
||||
if ($all) {
|
||||
@ -337,6 +330,61 @@ class UpstreamBusinessInformation
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string,mixed>> $references
|
||||
* @return array{
|
||||
* by_credit_code:array<string,array<string,mixed>>,
|
||||
* by_company_name:array<string,array<string,mixed>>
|
||||
* }
|
||||
*/
|
||||
private function companyPlanClassificationIndex(array $references): array
|
||||
{
|
||||
$byCreditCode = [];
|
||||
$byCompanyName = [];
|
||||
|
||||
foreach ($references as $reference) {
|
||||
$creditCode = $this->normalizeCreditCode($reference['unified_social_credit_code'] ?? null);
|
||||
if ($creditCode !== '' && ! isset($byCreditCode[$creditCode])) {
|
||||
$byCreditCode[$creditCode] = $reference;
|
||||
}
|
||||
|
||||
$companyName = $this->normalizeCompanyName($reference['company_name'] ?? null);
|
||||
if ($companyName !== '' && ! isset($byCompanyName[$companyName])) {
|
||||
$byCompanyName[$companyName] = $reference;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'by_credit_code' => $byCreditCode,
|
||||
'by_company_name' => $byCompanyName,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* by_credit_code:array<string,array<string,mixed>>,
|
||||
* by_company_name:array<string,array<string,mixed>>
|
||||
* } $index
|
||||
* @param list<array<string,mixed>> $companyRows
|
||||
* @return array<string,mixed>|null
|
||||
*/
|
||||
private function matchedCompanyPlanClassification(
|
||||
array $index,
|
||||
mixed $companyName,
|
||||
array $companyRows = [],
|
||||
): ?array {
|
||||
foreach ($companyRows as $companyRow) {
|
||||
$creditCode = $this->normalizeCreditCode($companyRow['unified_social_credit_code'] ?? null);
|
||||
if ($creditCode !== '' && isset($index['by_credit_code'][$creditCode])) {
|
||||
return $index['by_credit_code'][$creditCode];
|
||||
}
|
||||
}
|
||||
|
||||
$normalizedCompanyName = $this->normalizeCompanyName($companyName);
|
||||
|
||||
return $index['by_company_name'][$normalizedCompanyName] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,string|list<string>> $filters
|
||||
*/
|
||||
@ -819,15 +867,29 @@ SQL);
|
||||
} catch (Throwable) {
|
||||
$classifications = [];
|
||||
}
|
||||
$planClassifications = $this->companyPlanClassificationIndex(
|
||||
$this->companyPlanClassificationReferences(all: true),
|
||||
);
|
||||
foreach ($rows as &$row) {
|
||||
$row['company_details'] = $details[$row['entity']] ?? [];
|
||||
$row['city'] = $this->cityFromDetails($row['company_details']);
|
||||
$row['company_status'] = $row['company_details'][0]['company_status'] ?? null;
|
||||
$classification = $classifications[$row['entity']] ?? [];
|
||||
$taxType = $classification['tax_type']
|
||||
$planClassification = $this->matchedCompanyPlanClassification(
|
||||
$planClassifications,
|
||||
$row['entity'],
|
||||
$row['company_details'],
|
||||
);
|
||||
$planTaxType = trim((string) ($planClassification['taxpayer_type'] ?? ''));
|
||||
$planBusinessType = trim((string) ($planClassification['business_registration_type'] ?? ''));
|
||||
$taxType = $planTaxType !== ''
|
||||
? $planTaxType
|
||||
: $classification['tax_type']
|
||||
?? $row['company_details'][0]['taxpayer_category']
|
||||
?? null;
|
||||
$businessType = $classification['business_registration_type']
|
||||
$businessType = $planBusinessType !== ''
|
||||
? $planBusinessType
|
||||
: $classification['business_registration_type']
|
||||
?? $row['company_details'][0]['business_registration_type']
|
||||
?? null;
|
||||
$classificationStatus = trim((string) ($classification['status'] ?? ''));
|
||||
@ -839,7 +901,7 @@ SQL);
|
||||
$row['tax_type'] = $this->normalizeTaxType($taxType) ?? '/';
|
||||
$businessType = trim((string) $businessType);
|
||||
$row['business_registration_type'] = $retained
|
||||
&& in_array($businessType, ['核定', '个人独资企业'], true)
|
||||
&& ($planBusinessType !== '' || in_array($businessType, ['核定', '个人独资企业'], true))
|
||||
? $businessType
|
||||
: '/';
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ $navigation = [
|
||||
'directory' => [
|
||||
'icon' => 'query_stats',
|
||||
'status' => '收入汇总',
|
||||
'description' => '按主体生效、失效日期归属账单收入;上月店铺结算收入取业绩明细收入金额,按店铺、主体期间汇总。',
|
||||
'description' => '按主体生效、失效日期归属账单收入;税务类型和工商类型优先引用公司盘点,上月店铺结算收入取业绩明细收入金额并按主体期间汇总。',
|
||||
'empty' => '经营报表尚未同步。完成店铺主体和销量镜像接入后,这里会显示可追溯的主体数据。',
|
||||
'fields' => [
|
||||
['name' => 'city', 'label' => '市'],
|
||||
|
||||
@ -27,8 +27,8 @@
|
||||
| 1 | 市 | 由主体基础资料 `rpt.fys_companyInfo.registeredAddress` 解析地级市(`App\Business\RegisteredAddressCity::parse`):直辖市直接返回「XX市」;其余取省/自治区前缀后第一个地级行政区;本地地址省略省市前缀时按常州区划前缀兜底;识别不出按统一缺失值展示,不伪造数据 |
|
||||
| 2 | 主体 | 主体名称;作为主体合并键 |
|
||||
| 3 | 公司类型 | 主体公司类型,来源于 `rpt` |
|
||||
| 4 | 税务类型 | 主体税务类型,优先读取 `fin_companies.tax_type`,缺失时使用 `rpt.fys_companyInfo.taxCategoryAndType` |
|
||||
| 5 | 工商类型 | 主体工商类型,读取 `fin_companies.business_type`;仅“核定”和“个人独资企业”为有效值,其他统一显示 `/` |
|
||||
| 4 | 税务类型 | 主体税务类型,优先读取当前公司盘点的 `taxpayer_type`;盘点未填写时回退 `fin_companies.tax_type`,再回退 `rpt.fys_companyInfo.taxCategoryAndType` |
|
||||
| 5 | 工商类型 | 主体工商类型,优先读取当前公司盘点的 `business_registration_type`;盘点未填写时回退 `fin_companies.business_type` 或 `rpt.fys_companyInfo` |
|
||||
| 6 | 最近12个月累计收入 | 最近12个完整自然月收入合计,排除当前月 |
|
||||
| 7 | 上月店铺结算收入-客服公司 | 按运行时上一个完整自然月动态命名,例如当前为 11 月时显示「10月店铺结算收入-客服公司」;金额取业绩明细 `store_receipt_order_match_rows.income_amount`,按店铺主体期间拆分后汇总到主体 |
|
||||
| 8 | 1月收入 | 当前年份 1 月收入,当前月或无账单时为空 |
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\Unit\EcommerceFinance;
|
||||
|
||||
use App\Business\CompanyPlan\CompanyPlanRepository;
|
||||
use App\Business\UpstreamBusinessInformation;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Carbon;
|
||||
@ -455,6 +456,81 @@ class StoreReportRepositoryTest extends TestCase
|
||||
$this->assertSame(0, $service->paginate('company-management', 'subject-operating-report', 50)->total());
|
||||
}
|
||||
|
||||
public function test_subject_operating_report_uses_company_plan_tax_and_business_types(): void
|
||||
{
|
||||
Carbon::setTestNow('2026-09-15 10:00:00');
|
||||
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
|
||||
$mirror->shouldReceive('ready')->with('store-information.read')->andReturnTrue();
|
||||
$mirror->shouldReceive('allStores')->once()->andReturn([
|
||||
['erp_store_name' => '店铺盘点类型', 'company_name' => '主体甲', 'store_status' => '0'],
|
||||
]);
|
||||
$mirror->shouldReceive('ready')->with('company-information.read')->andReturnTrue();
|
||||
$mirror->shouldReceive('companiesByNames')->once()->with(['主体甲'])->andReturn([
|
||||
'主体甲' => [[
|
||||
'Id' => 1,
|
||||
'businessLicenseName' => '主体甲',
|
||||
'unifiedSocialCreditCode' => 'CODE-SUBJECT',
|
||||
'companyStatus' => '0',
|
||||
'taxCategoryAndType' => '一般纳税人',
|
||||
'business_registration_type' => '核定',
|
||||
]],
|
||||
]);
|
||||
$mirror->shouldReceive('companyClassificationsByNames')->once()->with(['主体甲'])->andReturn([
|
||||
'主体甲' => [
|
||||
'status' => '保留',
|
||||
'tax_type' => '一般纳税人',
|
||||
'business_registration_type' => '核定',
|
||||
],
|
||||
]);
|
||||
|
||||
$companyPlans = Mockery::mock(CompanyPlanRepository::class);
|
||||
$companyPlans->shouldReceive('allClassificationReferences')
|
||||
->once()
|
||||
->with((int) (company_id() ?: 1))
|
||||
->andReturn([[
|
||||
'company_name' => '盘点中的主体名称',
|
||||
'unified_social_credit_code' => 'CODE-SUBJECT',
|
||||
'taxpayer_type' => '小规模',
|
||||
'business_registration_type' => '有限责任公司',
|
||||
]]);
|
||||
|
||||
$storeReports = Mockery::mock(StoreReportRepository::class);
|
||||
$storeReports->shouldReceive('rows')->once()->andReturn([[
|
||||
'entity' => '主体甲',
|
||||
'company_type' => '店铺-普通公司',
|
||||
'rolling_12m_income' => 88,
|
||||
]]);
|
||||
$storeAccounts = Mockery::mock(StoreAccountMirrorRepository::class);
|
||||
$storeAccounts->shouldReceive('ownershipByStoreName')->once()->andReturn([]);
|
||||
|
||||
$service = new UpstreamBusinessInformation(
|
||||
Mockery::mock(UpstreamQueryCacheFactory::class),
|
||||
$mirror,
|
||||
$storeReports,
|
||||
$storeAccounts,
|
||||
null,
|
||||
$companyPlans,
|
||||
);
|
||||
|
||||
$rows = $service->paginate('company-management', 'subject-operating-report', 50)->items();
|
||||
$statusFields = collect(
|
||||
$this->app['request']->attributes->get('directory.status-counts')['fields']
|
||||
)->keyBy('name');
|
||||
$summaryCards = collect(
|
||||
$this->app['request']->attributes->get('business-page-summary-cards')
|
||||
)->keyBy('label');
|
||||
|
||||
$this->assertCount(1, $rows);
|
||||
$this->assertSame('小规模纳税人', $rows[0]->tax_type);
|
||||
$this->assertSame('有限责任公司', $rows[0]->business_registration_type);
|
||||
$this->assertSame(1, $statusFields['tax_type']['values'][0]['count']);
|
||||
$this->assertSame('小规模纳税人', $statusFields['tax_type']['values'][0]['value']);
|
||||
$this->assertSame(1, $statusFields['business_registration_type']['values'][0]['count']);
|
||||
$this->assertSame('有限责任公司', $statusFields['business_registration_type']['values'][0]['value']);
|
||||
$this->assertSame(1, $summaryCards['小规模纳税人']['value']);
|
||||
$this->assertSame(0, $summaryCards['一般纳税人']['value']);
|
||||
}
|
||||
|
||||
public function test_subject_cards_exclude_deregistered_entities_and_show_slash_for_their_types(): void
|
||||
{
|
||||
Carbon::setTestNow('2026-09-15 10:00:00');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user