修复部署复检链路(05-31-deploy-unified-list-detail-reimbursement-public-check)
这个提交包含在:
@@ -168,5 +168,98 @@ await expect(
|
|||||||
page.getByRole("link", { name: "工作台", exact: true })
|
page.getByRole("link", { name: "工作台", exact: true })
|
||||||
.or(page.getByRole("link", { name: "Portal", exact: true }))
|
.or(page.getByRole("link", { name: "Portal", exact: true }))
|
||||||
.first(),
|
.first(),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scenario: Trial Run Runtime Navigation Baseline
|
||||||
|
|
||||||
|
### 1. Scope / Trigger
|
||||||
|
|
||||||
|
- Trigger: changing sidebar grouping, workspace module visibility defaults,
|
||||||
|
`server/src/services/workspaceSettings.js`, or
|
||||||
|
`server/src/scripts/smoke-trialrun-runtime.js`.
|
||||||
|
- Purpose: keep deployment smoke aligned with the current small-team navigation
|
||||||
|
model instead of blocking deployment on retired menu assumptions.
|
||||||
|
|
||||||
|
### 2. Signatures
|
||||||
|
|
||||||
|
- Runtime smoke command inside deployment:
|
||||||
|
`docker compose exec -T server node src/scripts/smoke-trialrun-runtime.js`
|
||||||
|
- Source of truth for sidebar grouping:
|
||||||
|
`server/src/services/workspaceSettings.js`
|
||||||
|
- Runtime smoke assertion file:
|
||||||
|
`server/src/scripts/smoke-trialrun-runtime.js`
|
||||||
|
|
||||||
|
### 3. Contracts
|
||||||
|
|
||||||
|
- Runtime smoke must distinguish primary sidebar entries from collapsed
|
||||||
|
`更多` entries.
|
||||||
|
- Core day-to-day ledgers such as `报销`, `采购申请`, `合同台账`, `项目` and
|
||||||
|
`资产` are expected in `main` when the active role has access.
|
||||||
|
- Low-frequency entries such as `周报` and `公告` may be enabled but should be
|
||||||
|
checked in `backup` / `更多`, not forced into `main`.
|
||||||
|
- `待办` follows the current workspace and role-aware navigation baseline; do
|
||||||
|
not move it between `main`, `backup` or hidden as part of a deployment-smoke
|
||||||
|
fix unless a product task explicitly changes that rule.
|
||||||
|
- Management pages should remain represented by the single `管理` entry unless
|
||||||
|
a current task explicitly changes the management navigation model.
|
||||||
|
|
||||||
|
### 4. Validation & Error Matrix
|
||||||
|
|
||||||
|
- Core ledger missing from `main` -> runtime smoke fails with
|
||||||
|
`系统导航 main 缺少 <key> 入口`.
|
||||||
|
- `周报` or `公告` missing from `backup` while enabled -> runtime smoke fails
|
||||||
|
with `系统导航 backup 缺少 <key> 入口`.
|
||||||
|
- Management children are hidden behind `管理`, but smoke still expects
|
||||||
|
`workspace`, `people`, `featureFlags` or `auditLogs` in `admin` -> update the
|
||||||
|
smoke baseline; do not re-expand the sidebar for the smoke.
|
||||||
|
- `systemSettings` missing from `admin` -> runtime smoke fails because the
|
||||||
|
management entry is no longer reachable.
|
||||||
|
|
||||||
|
### 5. Good/Base/Bad Cases
|
||||||
|
|
||||||
|
- Good: `main` includes `portal`, `reimbursements`, `purchaseRequests`,
|
||||||
|
`contractsRegistry`, `projects` and `assets`; `backup` includes
|
||||||
|
`weeklyReports` and `announcements`; `admin` includes `systemSettings`.
|
||||||
|
- Base: `待办` is in `main` for a configured admin/owner trial account; smoke
|
||||||
|
accepts it there because it follows the current workspace baseline.
|
||||||
|
- Bad: smoke expects `weeklyReports` in `main` after the sidebar rule moved
|
||||||
|
low-frequency modules to `更多`.
|
||||||
|
- Bad: smoke expects every management child as a sidebar row after the product
|
||||||
|
rule consolidated management into one `管理` entry.
|
||||||
|
|
||||||
|
### 6. Tests Required
|
||||||
|
|
||||||
|
- Run `node --check server/src/scripts/smoke-trialrun-runtime.js` after editing
|
||||||
|
runtime smoke.
|
||||||
|
- Run `SYNC_ENV=0 bash deploy/trialrun/deploy.sh` for public deployment so the
|
||||||
|
remote runtime smoke validates the actual workspace and feature-flag state.
|
||||||
|
|
||||||
|
### 7. Wrong vs Correct
|
||||||
|
|
||||||
|
#### Wrong
|
||||||
|
|
||||||
|
```js
|
||||||
|
const REQUIRED_MAIN_NAV_KEYS = [
|
||||||
|
"portal",
|
||||||
|
"reimbursements",
|
||||||
|
"weeklyReports",
|
||||||
|
"announcements",
|
||||||
|
];
|
||||||
|
const REQUIRED_ADMIN_NAV_KEYS = ["workspace", "people", "featureFlags"];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Correct
|
||||||
|
|
||||||
|
```js
|
||||||
|
const REQUIRED_MAIN_NAV_KEYS = [
|
||||||
|
"portal",
|
||||||
|
"reimbursements",
|
||||||
|
"purchaseRequests",
|
||||||
|
"contractsRegistry",
|
||||||
|
"projects",
|
||||||
|
"assets",
|
||||||
|
];
|
||||||
|
const REQUIRED_BACKUP_NAV_KEYS = ["weeklyReports", "announcements"];
|
||||||
|
const REQUIRED_ADMIN_NAV_KEYS = ["systemSettings"];
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{"file": ".trellis/spec/hyb-oa/index.md", "reason": "hyb-oa spec entry and task governance read order"}
|
||||||
|
{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Trellis task, approval, and commit rules for public deployment"}
|
||||||
|
{"file": ".trellis/spec/hyb-oa/deployment-trialrun.md", "reason": "public deployment command, env contract, smoke, and reporting requirements"}
|
||||||
|
{"file": ".trellis/spec/hyb-oa/docs-and-evidence.md", "reason": "evidence recording and documentation hygiene for public checks"}
|
||||||
|
{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "secret and sensitive-data handling during deployment evidence collection"}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{"file": ".trellis/spec/hyb-oa/index.md", "reason": "hyb-oa spec entry and task governance read order"}
|
||||||
|
{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Trellis task, approval, and commit rules for public deployment"}
|
||||||
|
{"file": ".trellis/spec/hyb-oa/deployment-trialrun.md", "reason": "public deployment command, env contract, smoke, and reporting requirements"}
|
||||||
|
{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "secret and sensitive-data handling during deployment evidence collection"}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Implementation Notes
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
1. Run local deployment preflight checks and confirm the reimbursement page source no longer contains the old top task-card entry markers.
|
||||||
|
2. Deploy with `SYNC_ENV=0 bash deploy/trialrun/deploy.sh`, preserving the remote `.env`.
|
||||||
|
3. If deploy verification is blocked by deployment tooling or smoke assertions that are inconsistent with current specs, fix only those deployment/checking assets.
|
||||||
|
4. Rerun deployment until the remote env contract, `/ready`, external deps smoke, runtime smoke and public bundle scan pass.
|
||||||
|
5. Record evidence and residual limitations in this task.
|
||||||
|
|
||||||
|
## Implemented Adjustments
|
||||||
|
|
||||||
|
- `deploy/trialrun/deploy.sh`: added explicit `/dev/null` stdin redirection to remote `docker compose exec -T` checks so the SSH here-doc continues through `/ready`, external deps smoke, runtime smoke and service-status steps.
|
||||||
|
- `server/src/scripts/smoke-trialrun-runtime.js`: updated navigation smoke expectations to match the current sidebar model:
|
||||||
|
- core business ledgers in `main`;
|
||||||
|
- `周报` and `公告` in `backup` / `更多`;
|
||||||
|
- one `systemSettings` management entry instead of many sidebar management children.
|
||||||
|
- `.trellis/spec/hyb-oa/deployment-trialrun.md`: recorded the runtime navigation smoke baseline so future deploys do not regress to old menu assumptions.
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# 部署统一列表详情改造并复检报销页
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
将当前 `main` 的统一列表、详情页操作区和报销页改造部署到公网试运行环境,复检用户反馈的“报销页面顶部仍看到三个圆形按钮”是否已由最新版本修正。
|
||||||
|
|
||||||
|
## What I Already Know
|
||||||
|
|
||||||
|
- 用户已明确批准公网部署。
|
||||||
|
- 本地 `main` 最新提交为 `efd6d28`,工作区干净。
|
||||||
|
- 本地 `client/src/pages/ReimbursementsPage.tsx` 已不再保留旧的三张顶部快捷任务卡/圆形入口,当前顶部应为常规 `WorkspaceHeader`、`新建报销`、状态筛选和按权限显隐的 `台账工具`。
|
||||||
|
- 公网试运行环境属于准生产环境,部署必须保留远端 `.env`,使用 `SYNC_ENV=0 bash deploy/trialrun/deploy.sh`。
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- 部署当前 `main` 到 `https://oa.hyinda.com` 对应公网试运行环境。
|
||||||
|
- 不覆盖远端环境变量,不执行真实付款、真实业务写入或数据库重置。
|
||||||
|
- 部署后验证 `/ready` 可用。
|
||||||
|
- 部署后复检公网前端资源已更新到当前提交对应版本。
|
||||||
|
- 尽量复检报销页视觉状态:报销页顶部不应再出现旧的三个圆形按钮或三张任务入口。
|
||||||
|
- 如果本地缺少 `LIVE_SMOKE_*` 登录凭据,必须明确标注完整登录后页面检查的限制,并使用可用的公开 readiness/bundle 检查补足证据。
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- 公网试运行部署脚本、部署后 smoke、runtime smoke 基线和本任务证据记录。
|
||||||
|
- 允许修复阻塞部署验证完成的部署脚本或 smoke 基线问题。
|
||||||
|
- 不改变报销、列表、详情等业务页面实现。
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [x] 本地部署前检查通过。
|
||||||
|
- [x] `SYNC_ENV=0 bash deploy/trialrun/deploy.sh` 成功完成。
|
||||||
|
- [x] 公网 `/ready` 返回健康状态。
|
||||||
|
- [x] 公网 bundle 检查通过或给出明确失败原因。
|
||||||
|
- [x] 报告部署提交 hash、ready 结果、smoke/复检结果,以及用户是否可以测试。
|
||||||
|
|
||||||
|
## Out Of Scope
|
||||||
|
|
||||||
|
- 不新增或修改业务代码。
|
||||||
|
- 不修改数据库结构和远端 `.env`。
|
||||||
|
- 不执行真实付款、银行、ERP 或其他外部写入。
|
||||||
|
- 不处理与本次部署无关的 UI 新问题。
|
||||||
|
|
||||||
|
## Test Plan
|
||||||
|
|
||||||
|
- `node deploy/trialrun/validate-env-contract.js`
|
||||||
|
- `cd client && npm run build`
|
||||||
|
- `node --check server/src/scripts/smoke-trialrun-runtime.js`
|
||||||
|
- `bash -n deploy/trialrun/deploy.sh`
|
||||||
|
- `SYNC_ENV=0 bash deploy/trialrun/deploy.sh`
|
||||||
|
- `curl -fsS https://oa.hyinda.com/ready`
|
||||||
|
- `LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-live-smoke-readiness.sh`
|
||||||
|
- `node scripts/qa/check-public-bundle-copy.mjs https://oa.hyinda.com`
|
||||||
|
- Public reimbursement bundle scan for absence of old task-card markers.
|
||||||
|
|
||||||
|
## Technical Notes
|
||||||
|
|
||||||
|
- 部署规范:`.trellis/spec/hyb-oa/deployment-trialrun.md`
|
||||||
|
- 部署命令:`SYNC_ENV=0 bash deploy/trialrun/deploy.sh`
|
||||||
|
- 环境契约检查:`node deploy/trialrun/validate-env-contract.js`
|
||||||
|
- 公网目标:`https://oa.hyinda.com`
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
# Deployment Evidence
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
- Date: 2026-05-31
|
||||||
|
- Target: `https://oa.hyinda.com`
|
||||||
|
- Command: `SYNC_ENV=0 bash deploy/trialrun/deploy.sh`
|
||||||
|
- App baseline before deployment: `efd6d28`
|
||||||
|
- Scope note: deployment included the current task's deployment-script/runtime-smoke fixes so the deploy command could complete the intended post-deploy checks.
|
||||||
|
|
||||||
|
## Findings During Deploy
|
||||||
|
|
||||||
|
- First full rerun showed that `docker compose exec -T` could consume the SSH here-doc and skip later deployment steps. Fixed by redirecting the affected remote `exec` commands from `/dev/null`.
|
||||||
|
- Runtime smoke still assumed the old sidebar model. Updated it to match the current mature Ant Design-style navigation baseline:
|
||||||
|
- core business ledgers in `main`;
|
||||||
|
- `周报` and `公告` in `backup` / `更多`;
|
||||||
|
- management represented by one `管理` entry.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `node deploy/trialrun/validate-env-contract.js`: PASS.
|
||||||
|
- `cd client && npm run build`: PASS before deployment.
|
||||||
|
- `node --check server/src/scripts/smoke-trialrun-runtime.js`: PASS.
|
||||||
|
- `bash -n deploy/trialrun/deploy.sh`: PASS.
|
||||||
|
- `git diff --check`: PASS.
|
||||||
|
- Final deploy script run:
|
||||||
|
- env contract: PASS;
|
||||||
|
- Prisma migration status: up to date;
|
||||||
|
- `/ready`: PASS;
|
||||||
|
- external deps smoke: PASS;
|
||||||
|
- runtime smoke: PASS;
|
||||||
|
- public bundle copy scan: `scanned=93 focused=2 findings=0`.
|
||||||
|
- Independent `/ready`: `{"ok":true}`.
|
||||||
|
- `LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-live-smoke-readiness.sh`: PASS with live browser/OCR checks skipped because local `LIVE_SMOKE_*` credentials and OCR sample were not configured.
|
||||||
|
|
||||||
|
## Reimbursement Page Check
|
||||||
|
|
||||||
|
- Public asset `https://oa.hyinda.com/assets/ReimbursementsPage-DgJ0EYZV.js` is available.
|
||||||
|
- The public reimbursement bundle contains the new header/action shape: `我的报销`, `新建报销`, status tabs and optional `台账工具`.
|
||||||
|
- The public reimbursement bundle does not contain old task-card markers:
|
||||||
|
- `reimbursements-task-card`
|
||||||
|
- `被驳回待补正`
|
||||||
|
- `草稿待完善`
|
||||||
|
- `审批中进度`
|
||||||
|
|
||||||
|
## Residual Limits
|
||||||
|
|
||||||
|
- Full logged-in live smoke and logged-in visual inspection were skipped because this local environment does not have complete `LIVE_SMOKE_*` credentials.
|
||||||
|
- Public readiness, deployment-in-container smoke, and public bundle checks passed, so the deployed app is available for user testing.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"id": "deploy-unified-list-detail-reimbursement-public-check",
|
||||||
|
"name": "deploy-unified-list-detail-reimbursement-public-check",
|
||||||
|
"title": "部署统一列表详情改造并复检报销页",
|
||||||
|
"description": "Deploy the unified business list/detail UI changes to the public trial run environment and verify the reimbursement page no longer serves the old top task-card buttons.",
|
||||||
|
"status": "in_progress",
|
||||||
|
"dev_type": null,
|
||||||
|
"scope": "deployment-trialrun",
|
||||||
|
"package": null,
|
||||||
|
"priority": "P2",
|
||||||
|
"creator": "shunli",
|
||||||
|
"assignee": "shunli",
|
||||||
|
"createdAt": "2026-05-31",
|
||||||
|
"completedAt": null,
|
||||||
|
"branch": null,
|
||||||
|
"base_branch": "main",
|
||||||
|
"worktree_path": null,
|
||||||
|
"commit": null,
|
||||||
|
"pr_url": null,
|
||||||
|
"subtasks": [],
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"relatedFiles": [
|
||||||
|
"deploy/trialrun/deploy.sh",
|
||||||
|
"server/src/scripts/smoke-trialrun-runtime.js",
|
||||||
|
".trellis/spec/hyb-oa/deployment-trialrun.md"
|
||||||
|
],
|
||||||
|
"notes": "",
|
||||||
|
"meta": {}
|
||||||
|
}
|
||||||
@@ -141,7 +141,7 @@ echo ">>> 4.4 执行 init"
|
|||||||
"${compose_cmd[@]}" run --rm -T --entrypoint sh init -lc 'cd /app/server && npm run prisma:migrate:deploy && npm run seed:config && npm run seed:trialrun' </dev/null
|
"${compose_cmd[@]}" run --rm -T --entrypoint sh init -lc 'cd /app/server && npm run prisma:migrate:deploy && npm run seed:config && npm run seed:trialrun' </dev/null
|
||||||
|
|
||||||
echo ">>> 4.5 校验 Prisma migration status"
|
echo ">>> 4.5 校验 Prisma migration status"
|
||||||
if ! "${compose_cmd[@]}" exec -T server sh -lc 'cd /app/server && npx prisma migrate status'; then
|
if ! "${compose_cmd[@]}" exec -T server sh -lc 'cd /app/server && npx prisma migrate status' </dev/null; then
|
||||||
echo "[ERROR] Prisma migration status 未通过,停止部署" >&2
|
echo "[ERROR] Prisma migration status 未通过,停止部署" >&2
|
||||||
"${compose_cmd[@]}" logs --tail=200 server || true
|
"${compose_cmd[@]}" logs --tail=200 server || true
|
||||||
exit 1
|
exit 1
|
||||||
@@ -164,14 +164,14 @@ if [[ "$ready_ok" != "1" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo ">>> 4.7 执行 external deps smoke"
|
echo ">>> 4.7 执行 external deps smoke"
|
||||||
if ! "${compose_cmd[@]}" exec -T server node src/scripts/smoke-external-deps.js; then
|
if ! "${compose_cmd[@]}" exec -T server node src/scripts/smoke-external-deps.js </dev/null; then
|
||||||
echo "[ERROR] external deps smoke 失败,输出最近 server 日志" >&2
|
echo "[ERROR] external deps smoke 失败,输出最近 server 日志" >&2
|
||||||
"${compose_cmd[@]}" logs --tail=200 server || true
|
"${compose_cmd[@]}" logs --tail=200 server || true
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ">>> 4.8 执行 runtime smoke"
|
echo ">>> 4.8 执行 runtime smoke"
|
||||||
if ! "${compose_cmd[@]}" exec -T server node src/scripts/smoke-trialrun-runtime.js; then
|
if ! "${compose_cmd[@]}" exec -T server node src/scripts/smoke-trialrun-runtime.js </dev/null; then
|
||||||
echo "[ERROR] runtime smoke 失败,输出最近 server 日志" >&2
|
echo "[ERROR] runtime smoke 失败,输出最近 server 日志" >&2
|
||||||
"${compose_cmd[@]}" logs --tail=200 server || true
|
"${compose_cmd[@]}" logs --tail=200 server || true
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -43,17 +43,19 @@ const REQUIRED_MAIN_NAV_KEYS = [
|
|||||||
'leaveRequests',
|
'leaveRequests',
|
||||||
'reimbursements',
|
'reimbursements',
|
||||||
'assets',
|
'assets',
|
||||||
'weeklyReports',
|
'contractsRegistry',
|
||||||
|
'purchaseRequests',
|
||||||
|
'projects',
|
||||||
'todos',
|
'todos',
|
||||||
|
];
|
||||||
|
|
||||||
|
const REQUIRED_BACKUP_NAV_KEYS = [
|
||||||
|
'weeklyReports',
|
||||||
'announcements',
|
'announcements',
|
||||||
];
|
];
|
||||||
|
|
||||||
const REQUIRED_ADMIN_NAV_KEYS = [
|
const REQUIRED_ADMIN_NAV_KEYS = [
|
||||||
'workspace',
|
|
||||||
'people',
|
|
||||||
'systemSettings',
|
'systemSettings',
|
||||||
'featureFlags',
|
|
||||||
'auditLogs',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const REQUIRED_WORKSPACE_VISIBILITY = {
|
const REQUIRED_WORKSPACE_VISIBILITY = {
|
||||||
@@ -137,14 +139,18 @@ function assertNavigation(navigation) {
|
|||||||
|
|
||||||
const mainKeys = new Set((navigation.sections.main ?? []).map((item) => item.key));
|
const mainKeys = new Set((navigation.sections.main ?? []).map((item) => item.key));
|
||||||
const adminKeys = new Set((navigation.sections.admin ?? []).map((item) => item.key));
|
const adminKeys = new Set((navigation.sections.admin ?? []).map((item) => item.key));
|
||||||
|
const backupKeys = new Set((navigation.sections.backup ?? []).map((item) => item.key));
|
||||||
|
|
||||||
for (const key of REQUIRED_MAIN_NAV_KEYS) {
|
for (const key of REQUIRED_MAIN_NAV_KEYS) {
|
||||||
assertCondition(mainKeys.has(key), `系统导航 main 缺少 ${key} 入口`);
|
assertCondition(mainKeys.has(key), `系统导航 main 缺少 ${key} 入口`);
|
||||||
}
|
}
|
||||||
|
for (const key of REQUIRED_BACKUP_NAV_KEYS) {
|
||||||
|
assertCondition(backupKeys.has(key), `系统导航 backup 缺少 ${key} 入口`);
|
||||||
|
}
|
||||||
for (const key of REQUIRED_ADMIN_NAV_KEYS) {
|
for (const key of REQUIRED_ADMIN_NAV_KEYS) {
|
||||||
assertCondition(adminKeys.has(key), `系统导航 admin 缺少 ${key} 入口`);
|
assertCondition(adminKeys.has(key), `系统导航 admin 缺少 ${key} 入口`);
|
||||||
}
|
}
|
||||||
console.log(`[PASS] 导航构建成功,main=${navigation.sections.main.length} admin=${navigation.sections.admin.length}`);
|
console.log(`[PASS] 导航构建成功,main=${navigation.sections.main.length} backup=${navigation.sections.backup.length} admin=${navigation.sections.admin.length}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户