diff --git a/app/Console/Commands/VerifyQccEnterprisePackInfo.php b/app/Console/Commands/VerifyQccEnterprisePackInfo.php index c4f2318..e0c68c7 100644 --- a/app/Console/Commands/VerifyQccEnterprisePackInfo.php +++ b/app/Console/Commands/VerifyQccEnterprisePackInfo.php @@ -13,7 +13,7 @@ use Throwable; class VerifyQccEnterprisePackInfo extends Command { protected $signature = 'qcc:verify-pack-info - {company_id : 候选或已占额企业的本地企业 ID} + {company_id : 候选、已占额或待人工确认企业的本地企业 ID} {--timeout=60 : 请求超时时间(秒,正整数)} {--raw : 兼容旧用法,默认即输出完整响应}'; @@ -34,9 +34,9 @@ class VerifyQccEnterprisePackInfo extends Command } $account = $company->qccAccount; - if (!$account || !in_array($account->status, [CompanyQccAccount::STATUS_SELECTED, CompanyQccAccount::STATUS_OCCUPIED], true)) { + if (!$account || !in_array($account->status, [CompanyQccAccount::STATUS_SELECTED, CompanyQccAccount::STATUS_OCCUPIED, CompanyQccAccount::STATUS_UNKNOWN], true)) { $status = $account ? $account->status : 'none'; - $this->error("企业必须处于候选(selected)或已占额(occupied)状态,当前状态:{$status}。"); + $this->error("企业必须处于候选(selected)、已占额(occupied)或待人工确认(unknown)状态,当前状态:{$status}。"); return self::FAILURE; } diff --git a/docs/company-news.md b/docs/company-news.md index 1ac7759..4e3e068 100644 --- a/docs/company-news.md +++ b/docs/company-news.md @@ -7,7 +7,7 @@ - 点击“获取更新”创建一个全量候选和已占额企业快照,不受新闻列表筛选和分页影响;仅 `company_qcc_accounts.status` 为 `selected` 或 `occupied` 的企业进入批次,按公司 ID 顺序执行。 - 执行前再次检查企业是否仍为候选或已占额;不符合的任务记录失败/跳过,不调用元禾。 - 单次请求使用元禾 `enterprise/packInfo`,连接超时 10 秒、总超时 60 秒;只有外层 `code=200` 且 `data.news` 是数组或 null 才进行处理。null/空数组记成功、0 新增;缺字段、非数组记失败。 -- 本功能直接调用 Repository:code=200 且 data 中存在非空业务内容(新闻、股东等)时,仅将 selected 转为 occupied,记录首次成功时间、来源和引用;已占额及首次成功记录不重复改写。空数据、失败、超时不改状态。占额记录独立于新闻入库事务,避免本地保存失败丢失上游成功事实。`qcc:verify-pack-info` 诊断命令仅查询候选或已占额企业,按 code=200 判断成功,不修改企业状态,也不导入新闻。默认超时 60 秒,可用 `--timeout=120` 指定秒数;始终输出完整响应,保留 `--raw` 兼容旧用法。例如:`php artisan qcc:verify-pack-info 2280 --timeout=120 --raw`。成功退出码为 0,业务失败、超时或参数校验失败为 1。 +- 本功能直接调用 Repository:code=200 且 data 中存在非空业务内容(新闻、股东等)时,仅将 selected 转为 occupied,记录首次成功时间、来源和引用;已占额及首次成功记录不重复改写。空数据、失败、超时不改状态。占额记录独立于新闻入库事务,避免本地保存失败丢失上游成功事实。`qcc:verify-pack-info` 诊断命令仅查询候选、已占额或待人工确认(unknown)企业,按 code=200 判断成功,不修改企业状态,也不导入新闻。默认超时 60 秒,可用 `--timeout=120` 指定秒数;始终输出完整响应,保留 `--raw` 兼容旧用法。例如:`php artisan qcc:verify-pack-info 2280 --timeout=120 --raw`。成功退出码为 0,业务失败、超时或参数校验失败为 1。 - 按企业、数据提供方及去重键建立唯一约束。去重键按 NewsId、Id、原文 URL 的优先级生成 SHA-256;来源标识必须跨次稳定。同一新闻关联不同公司分别保存。 - 重复采集只更新来源字段、原始 JSON 与最近采集信息,不覆盖标题、人工正文和人工留存结论;自动判断的记录会按本批次分类设置重新判断,始终不展示。不留存是逻辑状态,不删除记录。 - 原文 HTML 使用现有 TinyMCE 组件编辑,并通过服务端 HTMLPurifier 清理。来源 Content 独立保存,接口正文为空时不会伪造原文。 diff --git a/tests/Feature/VerifyQccEnterprisePackInfoTest.php b/tests/Feature/VerifyQccEnterprisePackInfoTest.php index 478a54d..c43d5b0 100644 --- a/tests/Feature/VerifyQccEnterprisePackInfoTest.php +++ b/tests/Feature/VerifyQccEnterprisePackInfoTest.php @@ -39,7 +39,7 @@ class VerifyQccEnterprisePackInfoTest extends TestCase DB::table('companies')->insert(['id' => 333, 'company_name' => '测试企业', 'credit_code' => 'test-credit']); DB::table('company_qcc_accounts')->insert([ 'company_id' => 333, 'status' => $status, 'first_success_ref' => 'original-ref', - 'updated_at' => '2026-01-01 00:00:00', + 'updated_at' => '2026-01-01 00:00:00', 'unknown_note' => '原有诊断备注', ]); } @@ -64,6 +64,8 @@ class VerifyQccEnterprisePackInfoTest extends TestCase return [ ['selected', ['code' => 200, 'data' => ['news' => [['Title' => '标题']]]], [], 60, 0], ['occupied', ['code' => '200', 'data' => []], ['--timeout' => '120', '--raw' => true], 120, 0], + ['unknown', ['code' => 200, 'data' => ['news' => []]], [], 60, 0], + ['unknown', ['code' => 500], ['--timeout' => '120'], 120, 1], ['selected', ['code' => 0], [], 60, 1], ['occupied', ['code' => 500, 'msg' => '失败'], [], 60, 1], ['selected', ['data' => []], [], 60, 1], @@ -71,9 +73,9 @@ class VerifyQccEnterprisePackInfoTest extends TestCase } /** @dataProvider exceptionCases */ - public function test_exception_leaves_account_unchanged(?string $raw): void + public function test_exception_leaves_account_unchanged(string $status, ?string $raw): void { - $this->company('selected'); + $this->company($status); $before = DB::table('company_qcc_accounts')->get()->toJson(); $mock = Mockery::mock(YuanheRepository::class); $mock->shouldReceive('enterprisePackInfo')->once()->andThrow(new YuanhePackInfoException('请求超时', null, null, $raw)); @@ -85,7 +87,7 @@ class VerifyQccEnterprisePackInfoTest extends TestCase public static function exceptionCases(): array { - return [[null], ['Bad Gateway']]; + return [['selected', null], ['selected', 'Bad Gateway'], ['unknown', null], ['unknown', 'Bad Gateway']]; } /** @dataProvider rejectedCases */ @@ -102,7 +104,7 @@ class VerifyQccEnterprisePackInfoTest extends TestCase public static function rejectedCases(): array { return [ - ['unknown', []], ['non_occupying', []], ['selected', ['--timeout' => '0']], + ['none', []], ['non_occupying', []], ['selected', ['--timeout' => '0']], ['selected', ['--timeout' => '-1']], ['selected', ['--timeout' => '1.5']], ['selected', ['--timeout' => 'abc']], ];