diff --git a/AGENTS.md b/AGENTS.md index 6653f9074..d2140d24f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,14 @@ ## 服务路由(按关键词定位) -本表只保留定位入口;命中业务后必须用 `rg -n -F '关键词' docs/backend-business-index.md` 读取对应行/段的详细约束,再沿代码引用追踪;不默认全文加载详细索引。已知目标文件时直接定位,并定向核对该业务约束。 +本表只保留定位入口;命中业务后用 `rg -n -F '关键词' docs/backend-business-index.md` 读取对应行/段的详细约束,再沿代码引用追踪;不默认全文加载详细索引。已知目标文件时直接定位,并定向核对该业务约束。 + +### 后端任务最小读取集 + +- 单服务 Controller/Service/Mapper 任务:本文件、目标服务 `AGENTS.md`、目标类及其直接调用点。 +- 跨服务 API/事件任务:在上述基础上增加直接调用方、被调用方和对应 Gateway/OpenAPI;不因业务关键词自动加载整个业务专题。 +- 只有状态、权限、数据库或验收边界无法从上述入口确定时,才读取 `docs/业务快速索引.md`、专题 README 或完整契约。 +- 后端指引累计超过约 250 行时优先用 `rg` 读取命中段;聚合构建或架构任务才全文读取 `README.md`、`pom.xml` 和目录结构文档。 | 任务关键词 | 首选服务 | |---|---| diff --git a/firstleg/src/main/resources/openapi/loading-lists-v1.yaml b/firstleg/src/main/resources/openapi/loading-lists-v1.yaml index ddea318e6..c77811eeb 100644 --- a/firstleg/src/main/resources/openapi/loading-lists-v1.yaml +++ b/firstleg/src/main/resources/openapi/loading-lists-v1.yaml @@ -66,7 +66,7 @@ paths: /api/platform/v1/first-leg/loading-plans/{planId}/confirm: post: summary: 原子确认预装并分配国内 PDA 作业员 - description: 以 loadingPlanId 为唯一范围,校验指定人员为启用的国内头程 PDA 作业员,原子确认预装、创建任务并分配。未分配任务不向 PDA 展示;同一 Idempotency-Key 与相同请求精确重放原响应。 + description: 以 loadingPlanId 为唯一范围,校验指定人员为启用的国内头程 PDA 作业员;确认预装前必须完成前置门禁且真实集装箱号已经补录,原子确认预装、创建任务并分配。未分配任务不向 PDA 展示;同一 Idempotency-Key 与相同请求精确重放原响应。 x-permission: platform:firstleg:preload:edit, platform:firstleg:pda:assign parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/IdempotencyKey'}] requestBody: diff --git a/scripts/admin-test-env-modules/manual-state-loading-pda.mjs b/scripts/admin-test-env-modules/manual-state-loading-pda.mjs index 767c3d0f2..1943e6fb1 100644 --- a/scripts/admin-test-env-modules/manual-state-loading-pda.mjs +++ b/scripts/admin-test-env-modules/manual-state-loading-pda.mjs @@ -213,9 +213,16 @@ async function createPlanTask(context, label) { if (!Number.isInteger(confirmableVersion) || confirmable?.summary?.readiness?.canConfirm !== true) { throw new Error(`${label} 计划尚未满足整计划确认门禁`) } + const pdaOperatorsResponse = await context.request('GET', + `/api/platform/v1/iam/users/domestic-pda-operators?warehouseId=${encodeURIComponent(source.warehouseId)}`) + const pdaOperators = context.apiSuccess(MODULE, `${label} 查询国内头程 PDA 作业员候选`, pdaOperatorsResponse, '', OPTIONS) + const pdaOperator = (Array.isArray(pdaOperators) ? pdaOperators : pdaOperators?.records || [])[0] + context.assert(MODULE, `${label} 存在可用 PDA 作业员`, pdaOperatorsResponse, + Boolean(pdaOperator?.userId), `operatorId=${id(pdaOperator?.userId) || 'missing'}`, OPTIONS) + if (!pdaOperator?.userId) throw new Error(`${label} 缺少可用 PDA 作业员`) const confirmPath = `${LOADING}/loading-plans/${planId}/confirm` - const confirmBody = Object.freeze({ expectedVersion: confirmableVersion }) + const confirmBody = Object.freeze({ expectedVersion: confirmableVersion, assigneeId: Number(pdaOperator.userId) }) const confirmHeaders = Object.freeze({ 'Idempotency-Key': operationId(context, `${label}-plan-confirm`), }) diff --git a/scripts/admin-test-env-modules/phase3-container-loading.mjs b/scripts/admin-test-env-modules/phase3-container-loading.mjs index 6d4a8eb62..3181c1931 100644 --- a/scripts/admin-test-env-modules/phase3-container-loading.mjs +++ b/scripts/admin-test-env-modules/phase3-container-loading.mjs @@ -560,8 +560,15 @@ export async function run(context) { && confirmable?.summary?.readiness?.pdaTaskId == null, `version=${confirmable?.summary?.version ?? 'missing'}, canConfirm=${confirmable?.summary?.readiness?.canConfirm}, pdaTaskId=${id(confirmable?.summary?.readiness?.pdaTaskId) || 'none'}`, OPTIONS) if (!Number.isInteger(confirmableVersion) || confirmable?.summary?.readiness?.canConfirm !== true) return + const pdaOperatorsResponse = await context.request('GET', + `/api/platform/v1/iam/users/domestic-pda-operators?warehouseId=${encodeURIComponent(warehouseId)}`) + const pdaOperators = context.apiSuccess(MODULE, '查询当前国内头程 PDA 作业员候选', pdaOperatorsResponse, '', OPTIONS) + const pdaOperator = (Array.isArray(pdaOperators) ? pdaOperators : records(pdaOperators))[0] + context.assert(MODULE, '存在可用于计划确认的国内头程 PDA 作业员', pdaOperatorsResponse, + Boolean(pdaOperator?.userId), `operatorId=${id(pdaOperator?.userId) || 'missing'}`, OPTIONS) + if (!pdaOperator?.userId) return const confirmPath = `${FIRST_LEG}/loading-plans/${planId}/confirm` - const confirmBody = Object.freeze({ expectedVersion: confirmableVersion }) + const confirmBody = Object.freeze({ expectedVersion: confirmableVersion, assigneeId: Number(pdaOperator.userId) }) const confirmHeaders = Object.freeze({ 'Idempotency-Key': `${context.marker}-container-plan-confirm`.slice(0, 128), }) diff --git a/scripts/admin-test-env-modules/phase3-loading.mjs b/scripts/admin-test-env-modules/phase3-loading.mjs index ee0b43833..35326f75e 100644 --- a/scripts/admin-test-env-modules/phase3-loading.mjs +++ b/scripts/admin-test-env-modules/phase3-loading.mjs @@ -664,8 +664,15 @@ export async function run(context) { && confirmable?.summary?.readiness?.pdaTaskId == null, `version=${confirmable?.summary?.version ?? 'missing'}, canConfirm=${confirmable?.summary?.readiness?.canConfirm}, pdaTaskId=${id(confirmable?.summary?.readiness?.pdaTaskId) || 'none'}`, OPTIONS) if (!Number.isInteger(confirmableVersion) || confirmable?.summary?.readiness?.canConfirm !== true) return + const pdaOperatorsResponse = await context.request('GET', + `/api/platform/v1/iam/users/domestic-pda-operators?warehouseId=${encodeURIComponent(warehouseId)}`) + const pdaOperators = context.apiSuccess(MODULE, '查询当前国内头程 PDA 作业员候选', pdaOperatorsResponse, '', OPTIONS) + const pdaOperator = (Array.isArray(pdaOperators) ? pdaOperators : pdaOperators?.records || [])[0] + context.assert(MODULE, '存在可用于计划确认的国内头程 PDA 作业员', pdaOperatorsResponse, + Boolean(pdaOperator?.userId), `operatorId=${id(pdaOperator?.userId) || 'missing'}`, OPTIONS) + if (!pdaOperator?.userId) return const confirmPath = `/api/platform/v1/first-leg/loading-plans/${loadingPlanId}/confirm` - const confirmBody = Object.freeze({ expectedVersion: confirmableVersion }) + const confirmBody = Object.freeze({ expectedVersion: confirmableVersion, assigneeId: Number(pdaOperator.userId) }) const confirmHeaders = Object.freeze({ 'Idempotency-Key': `${context.marker}-p3-plan-confirm`.slice(0, 128) }) const confirm = await request(context, 'POST', confirmPath, confirmBody, confirmHeaders) const confirmed = context.apiSuccess(MODULE, '计划级原子确认整份预装并触发唯一 PDA 任务', confirm, '', OPTIONS) diff --git a/scripts/admin-test-env-modules/phase3-order-splitting.mjs b/scripts/admin-test-env-modules/phase3-order-splitting.mjs index 29cb09fc0..fe93b8096 100644 --- a/scripts/admin-test-env-modules/phase3-order-splitting.mjs +++ b/scripts/admin-test-env-modules/phase3-order-splitting.mjs @@ -336,9 +336,16 @@ async function loadOneFragment(context, state) { && confirmable?.summary?.readiness?.pdaTaskId == null, `version=${confirmable?.summary?.version ?? 'missing'}, canConfirm=${confirmable?.summary?.readiness?.canConfirm}, pdaTaskId=${id(confirmable?.summary?.readiness?.pdaTaskId) || 'none'}`, OPTIONS) if (!Number.isInteger(confirmableVersion) || confirmable?.summary?.readiness?.canConfirm !== true) return null + const pdaOperatorsResponse = await context.request('GET', + `/api/platform/v1/iam/users/domestic-pda-operators?warehouseId=${encodeURIComponent(warehouseId)}`) + const pdaOperators = context.apiSuccess(MODULE, `查询 ${sourceOrderNo} 国内头程 PDA 作业员候选`, pdaOperatorsResponse, '', OPTIONS) + const pdaOperator = (Array.isArray(pdaOperators) ? pdaOperators : pdaOperators?.records || [])[0] + context.assert(MODULE, `${sourceOrderNo} 存在可用 PDA 作业员`, pdaOperatorsResponse, + Boolean(pdaOperator?.userId), `operatorId=${id(pdaOperator?.userId) || 'missing'}`, OPTIONS) + if (!pdaOperator?.userId) return null const confirmPath = `/api/platform/v1/first-leg/loading-plans/${encodeURIComponent(planId)}/confirm` - const confirmBody = Object.freeze({ expectedVersion: confirmableVersion }) + const confirmBody = Object.freeze({ expectedVersion: confirmableVersion, assigneeId: Number(pdaOperator.userId) }) const confirmHeaders = Object.freeze({ 'Idempotency-Key': `${operationBase}-plan-confirm`.slice(0, 128) }) const confirmResponse = await request(context, 'POST', confirmPath, confirmBody, confirmHeaders) const confirmed = context.apiSuccess(MODULE, `计划级确认 ${sourceOrderNo} 并生成唯一 PDA 任务`, confirmResponse, '', OPTIONS) diff --git a/scripts/agents-index-contract.ps1 b/scripts/agents-index-contract.ps1 index d1038eb6a..15c9477f4 100644 --- a/scripts/agents-index-contract.ps1 +++ b/scripts/agents-index-contract.ps1 @@ -139,10 +139,16 @@ $lookupCharacters = 0 foreach ($alias in @( @{ Term = '转运打托'; Entry = 'apps/pda-overseas-uniapp/src/pages/warehouse-transfer/warehouse-transfer.vue' }, @{ Term = '转运驳回'; Entry = 'apps/admin-web/src/modules/overseas-warehouse/components/WarehouseTransferReviewDialog.vue' }, - @{ Term = '退税资料'; Entry = 'apps/client-web/src/modules/first-leg/components/CustomsDocumentsDialog.vue' } + @{ Term = '退税资料'; Entry = 'apps/client-web/src/modules/first-leg/components/CustomsDocumentsDialog.vue' }, + @{ Term = '订单审核'; Entry = 'apps/admin-web/src/modules/unified-orders/' }, + @{ Term = 'PDA 出库'; Entry = 'apps/pda-overseas-uniapp/src/pages/dispatch/dispatch.vue' }, + @{ Term = '客户账单'; Entry = 'apps/client-web/src/modules/finance/views/FinanceBillsView.vue' } )) { $aliasSection = (($businessIndex -split '## 常用说法与歧义分流', 2)[1] -split '\r?\n## ', 2)[0] - $hits = @($aliasSection -split '\r?\n' | Where-Object { $_.Contains($alias.Term) }) + $allHits = @($aliasSection -split '\r?\n' | Where-Object { $_.Contains($alias.Term) }) + $rowHits = @($allHits | Where-Object { $_.TrimStart().StartsWith('|') }) + $hits = @($rowHits) + if ($hits.Count -eq 0) { $hits = @($allHits) } $script:checked++ if ($hits.Count -ne 1) { Add-Failure "口语入口必须唯一命中: $($alias.Term)"; continue } Assert-Contains $hits[0] $alias.Entry "口语入口 $($alias.Term)"