From f5c514c520e20eebdd42716a36885bdec7472c33 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Tue, 13 Oct 2026 19:41:55 +0800 Subject: [PATCH] docs: plan complete mobile market parity implementation --- ...-10-13-market-five-module-mobile-parity.md | 398 ++++++++++++++++++ ...market-five-module-mobile-parity-design.md | 2 +- 2 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-10-13-market-five-module-mobile-parity.md diff --git a/docs/superpowers/plans/2026-10-13-market-five-module-mobile-parity.md b/docs/superpowers/plans/2026-10-13-market-five-module-mobile-parity.md new file mode 100644 index 00000000..ed7fdc25 --- /dev/null +++ b/docs/superpowers/plans/2026-10-13-market-five-module-mobile-parity.md @@ -0,0 +1,398 @@ +# 市场五模块小程序与 Web 功能对齐 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 让市场专员、地区经理在小程序中的预签约订单、协议单、缴费信息、催缴信息、运费信息与对应 Web 页面具备相同信息元素和允许业务能力,同时保持既有角色数据范围和移动端交互。 + +**Architecture:** 继续使用 `mobile-oa-h5` 的共享市场页面壳,以模块契约描述筛选、卡片、详情和表单;复杂协议/预签交互使用专用组件。后端市场路由只适配 DTO 与权限,查询和写入复用现有业务 service/CRUD,并在每个入口重新验证租户、角色现场范围和业务状态。 + +**Tech Stack:** Vue 3、Vant 4、原生 Node test runner、FastAPI、SQLAlchemy、Pydantic v2、pytest、MySQL 5.7。 + +--- + +## 文件结构 + +### 新建 + +- `mobile-oa-h5/src/market/list-filters.js`:筛选默认值、请求参数和已选数量的纯函数。 +- `mobile-oa-h5/src/market/list-filters.test.js`:五模块筛选契约测试。 +- `mobile-oa-h5/src/market/components/MarketFilterSheet.vue`:移动端多条件筛选弹层。 +- `docs/superpowers/specs/2026-10-13-market-five-module-mobile-parity-completion.md`:最终逐项完成矩阵与环境验收清单。 + +### 修改 + +- `mobile-oa-h5/src/views/market/MarketModuleList.vue`:接入通用筛选弹层,移除审核 UI,增加协议批量选择/删除的移动交互。 +- `mobile-oa-h5/src/views/market/MarketModuleDetail.vue`:只呈现允许动作;协议、缴费、催缴跨模块跳转与刷新。 +- `mobile-oa-h5/src/views/market/MarketModuleForm.vue`:收敛当前协议单样本、运输、客户、缴费、转账在制改动。 +- `mobile-oa-h5/src/market/modules/{presign,protocol,payment-chase,payment,freight}.js`:定义完整筛选、字段、详情与动作契约。 +- `mobile-oa-h5/src/market/list-presenter.js`:补齐 Web 列表字段、语义标签和动作名称。 +- `mobile-oa-h5/src/market/action-policy.js`:删除协议审核白名单,保留明确开放动作。 +- `mobile-oa-h5/src/api/market.js`:删除审核映射,保留协议删除及五模块 API 封装。 +- `mobile-oa-h5/src/styles/market.css`:筛选弹层、批量选择、复杂卡片移动样式。 +- `backend/app/api/market_mobile.py`:补齐筛选 Query 参数,移除市场协议审核路由。 +- `backend/app/services/market_protocol.py`:协议状态、创建时间、采集时间过滤;完成当前协议 DTO/详情改动。 +- `backend/app/services/market_payment.py`:缴费进度、应缴日期过滤和详情字段对齐。 +- `backend/app/services/market_freight.py`:创建时间过滤和完整可见行字段。 +- `backend/app/services/market_support.py`:动作白名单、表单选项和只读详情补齐。 +- `backend/app/schemas/market_mobile.py`:完成协议样本/运费/缴费写 DTO。 +- 对应 `backend/tests/test_market_*.py` 与 `mobile-oa-h5/src/market/*.test.js`:回归和契约测试。 + +## Task 1:收敛当前协议单在制改动与动作边界 + +- [ ] **Step 1:把“不开放审核”写成失败测试** + +修改 `mobile-oa-h5/src/market/module-actions.test.js`: + +```js +test('protocol never exposes management-only actions', () => { + const actions = moduleActions('protocol', 'specialist') + assert.deepEqual(actions, ['view', 'create', 'edit', 'delete']) + for (const denied of ['audit', 'status-change', 'refund', 'invoice', 'finance-confirm', 'finance-reject']) { + assert.equal(actions.includes(denied), false) + } +}) +``` + +修改 `backend/tests/test_market_mobile_permissions.py`,断言 FastAPI 路由表不存在 `/mini/market/{role}/protocols/{protocol_id}/audit`,而 DELETE 路由存在。 + +- [ ] **Step 2:运行测试并确认按预期失败** + +```bash +cd mobile-oa-h5 && node --test src/market/module-actions.test.js +cd backend && venv/bin/python -m pytest tests/test_market_mobile_permissions.py tests/test_market_protocol_api.py -q +``` + +预期:前端审核白名单或后端审核路由断言失败;已有 `test_protocol_audit_and_delete_recheck_scoped_access_before_crud` 与新边界冲突。 + +- [ ] **Step 3:实现最小动作边界修正** + +- 从 `backend/app/api/market_mobile.py` 删除两个角色注册出的协议审核 endpoint。 +- `market_support.allowed_actions('protocol', ...)` 不再返回 `audit`。 +- 从 `MarketModuleList.vue` 删除审核 action sheet、状态和处理函数。 +- 从 `market.js` 删除协议 `audit` path 分支,只保留 `delete`。 +- 将旧审核 service 单测改为只验证删除的 scope 复核;Web 自身审核 API 和 service 不做删除。 + +- [ ] **Step 4:运行协议定向测试** + +```bash +cd mobile-oa-h5 && node --test src/market/module-actions.test.js src/market/api-contract.test.js src/market/mobile-parity-ui.test.js +cd backend && venv/bin/python -m pytest tests/test_market_mobile_permissions.py tests/test_market_protocol_api.py tests/test_market_support_api.py -q +``` + +预期:全部通过,且不再出现“当前状态不可审核”的旧市场端测试失败。 + +- [ ] **Step 5:提交** + +```bash +git add backend/app/api/market_mobile.py backend/app/services/market_support.py backend/tests/test_market_mobile_permissions.py backend/tests/test_market_protocol_api.py mobile-oa-h5/src/api/market.js mobile-oa-h5/src/market/action-policy.js mobile-oa-h5/src/market/module-actions.test.js mobile-oa-h5/src/market/api-contract.test.js mobile-oa-h5/src/market/mobile-parity-ui.test.js mobile-oa-h5/src/views/market/MarketModuleList.vue +git commit -m "fix: keep mobile protocol management actions restricted to web" +``` + +## Task 2:建立五模块通用移动筛选能力 + +- [ ] **Step 1:新增筛选纯函数失败测试** + +创建 `mobile-oa-h5/src/market/list-filters.test.js`,覆盖: + +```js +test('maps every web-equivalent filter to backend query names', () => { + assert.deepEqual(buildListParams('protocol', { + source: 'cell_storage', protocolStatus: 'sxz', + createTimeRange: ['2026-10-01', '2026-10-13'], + sampleCollectTimeRange: ['2026-09-01', '2026-09-30'] + }), { + source: 'cell_storage', protocolStatus: 'sxz', + createTimeStart: '2026-10-01', createTimeEnd: '2026-10-13', + sampleCollectTimeStart: '2026-09-01', sampleCollectTimeEnd: '2026-09-30' + }) + assert.deepEqual(buildListParams('payment', { + payMode: 'fq', progressStatus: 'bfyj', dueDateRange: ['2026-10-01', '2026-10-31'] + }), { + payMode: 'fq', progressStatus: 'bfyj', + installmentDueDateStart: '2026-10-01', installmentDueDateEnd: '2026-10-31' + }) + assert.deepEqual(buildListParams('payment-chase', { chaseType: 'overdue', payMode: 'dj' }), { + chaseType: 'overdue', payMode: 'dj' + }) + assert.deepEqual(buildListParams('freight', { + transportMode: 'cold_chain', sampleSource: 'cell_storage', + createTimeRange: ['2026-10-01', '2026-10-13'] + }), { + transportMode: 'cold_chain', sampleSource: 'cell_storage', + createTimeStart: '2026-10-01', createTimeEnd: '2026-10-13', includeItems: true + }) +}) +``` + +- [ ] **Step 2:运行测试并确认模块不存在** + +```bash +cd mobile-oa-h5 && node --test src/market/list-filters.test.js +``` + +预期:FAIL,`list-filters.js` 尚不存在。 + +- [ ] **Step 3:实现纯函数和模块筛选契约** + +`list-filters.js` 导出 `createListFilters(module, source)`、`buildListParams(module, filters)`、`activeFilterCount(filters)`。五个模块增加 `filterFields`: + +```js +// payment.js +filterFields: [ + { key: 'payMode', label: '缴费方式', type: 'select', options: PAY_MODE_OPTIONS }, + { key: 'progressStatus', label: '缴费进度', type: 'select', options: PROGRESS_OPTIONS }, + { key: 'dueDateRange', label: '应缴费时间', type: 'date-range' } +] +``` + +协议使用 `protocolStatus/createTimeRange/sampleCollectTimeRange`;催缴使用 `chaseType/payMode`;运费使用 `transportMode/sampleSource/createTimeRange`;预签约使用 `orderStatus`。 + +- [ ] **Step 4:实现 `MarketFilterSheet.vue` 并接入列表** + +组件 props 为 `modelValue/fields/options`,事件为 `update:modelValue/apply/reset`。`MarketModuleList.vue` 保存一份草稿和一份已应用筛选;只有 Apply 才发请求,Reset 清空筛选并重新加载第一页。运输方式选项通过 `fetchMarketFormOptions` 获取。 + +- [ ] **Step 5:运行筛选与 UI 契约测试** + +```bash +cd mobile-oa-h5 && node --test src/market/list-filters.test.js src/market/module-contracts.test.js src/market/mobile-parity-ui.test.js src/market/request-state.test.js +``` + +预期:全部通过。 + +- [ ] **Step 6:提交** + +```bash +git add mobile-oa-h5/src/market/list-filters.js mobile-oa-h5/src/market/list-filters.test.js mobile-oa-h5/src/market/components/MarketFilterSheet.vue mobile-oa-h5/src/market/modules mobile-oa-h5/src/views/market/MarketModuleList.vue mobile-oa-h5/src/styles/market.css mobile-oa-h5/src/market/module-contracts.test.js mobile-oa-h5/src/market/mobile-parity-ui.test.js +git commit -m "feat: add web-equivalent mobile filters for market modules" +``` + +## Task 3:补齐后端列表筛选契约 + +- [ ] **Step 1:先写两个角色的 API 失败测试** + +在现有测试中新增参数透传和真实查询断言: + +```python +@pytest.mark.parametrize('role', ['specialist', 'manager']) +def test_market_list_filters_match_web_contract(client, role, auth_headers): + cases = [ + ('protocols', {'source': 'cell_storage', 'protocolStatus': 'sxz', 'createTimeStart': '2026-10-01', 'createTimeEnd': '2026-10-13'}), + ('payments', {'payMode': 'fq', 'progressStatus': 'bfyj', 'installmentDueDateStart': '2026-10-01', 'installmentDueDateEnd': '2026-10-31'}), + ('payment-chase', {'chaseType': 'overdue', 'payMode': 'fq'}), + ('freights', {'sampleSource': 'cell_storage', 'createTimeStart': '2026-10-01', 'createTimeEnd': '2026-10-13'}), + ] + for resource, params in cases: + response = client.get(f'/api/mini/market/{role}/{resource}', params=params, headers=auth_headers(role)) + assert response.status_code == 200 +``` + +服务层测试分别创建命中和不命中的行,并断言 `total/list` 只包含命中项。 + +- [ ] **Step 2:运行并确认缺少 Query 参数或过滤行为** + +```bash +cd backend && venv/bin/python -m pytest tests/test_market_protocol_api.py tests/test_market_payment_api.py tests/test_market_freight_scope.py -q +``` + +预期:新增用例失败。 + +- [ ] **Step 3:实现路由与 service 过滤** + +- 协议:`protocolStatus/createTimeStart/createTimeEnd/sampleCollectTimeStart/sampleCollectTimeEnd`。 +- 缴费:`progressStatus/installmentDueDateStart/installmentDueDateEnd`;进度按与 Web 相同的已缴/欠缴聚合口径过滤。 +- 运费:`createTimeStart/createTimeEnd`。 +- 催缴、预签约已有参数保持并补测试。 +- 所有日期输入解析失败返回 400,不静默扩大结果集;结束日期按当天 23:59:59 或左闭右开次日处理。 + +- [ ] **Step 4:运行后端市场列表测试** + +```bash +cd backend && venv/bin/python -m pytest tests/test_market_presign_api.py tests/test_market_protocol_api.py tests/test_market_payment_api.py tests/test_market_freight_scope.py tests/test_market_field_scope.py -q +``` + +预期:全部通过。 + +- [ ] **Step 5:提交** + +```bash +git add backend/app/api/market_mobile.py backend/app/services/market_protocol.py backend/app/services/market_payment.py backend/app/services/market_freight.py backend/tests/test_market_presign_api.py backend/tests/test_market_protocol_api.py backend/tests/test_market_payment_api.py backend/tests/test_market_freight_scope.py +git commit -m "feat: align mobile market list filters with web queries" +``` + +## Task 4:完成协议单复杂表单、详情与删除能力 + +- [ ] **Step 1:运行当前协议相关测试,保留真实失败清单** + +```bash +cd mobile-oa-h5 && node --test src/market/protocol-*.test.js src/market/form-payload.test.js src/market/module-contracts.test.js src/market/mobile-parity-ui.test.js +cd backend && venv/bin/python -m pytest tests/test_market_protocol_api.py tests/test_market_support_api.py -q +``` + +当前基线:前端 176 项市场测试通过;后端协议/支撑定向测试 105 项中 1 项旧审核预期失败。Task 1 修正后,本步骤必须为全绿。 + +- [ ] **Step 2:补写协议完整闭环失败测试** + +覆盖:客户档案检索与绑定、身份证出生年月联动、样本字段、运费新建/关联/取消、按样本缴费、协议总金额跟随、转账新增/编辑/提交/撤回、五类附件、只读储存证/保险/审核/状态记录、单条删除和批量删除。契约测试必须断言 §2.2 管理按钮不存在。 + +- [ ] **Step 3:完成现有在制代码并修复失败测试** + +收敛以下文件中的既有改动,不重写已通过逻辑: + +```text +backend/app/{api/market_mobile.py,schemas/market_mobile.py,services/market_protocol.py,services/market_support.py,crud/sample_freight.py} +mobile-oa-h5/src/market/components/{ProtocolCustomerSearchField.vue,ProtocolSampleTransportPanel.vue,ProtocolSamplePaymentPanel.vue} +mobile-oa-h5/src/market/{protocol-customer-link.js,protocol-sample-transport.js,protocol-sample-payment.js,protocol-sample-transfer.js} +mobile-oa-h5/src/views/market/MarketModuleForm.vue +``` + +协议删除使用后端软删除;批量删除在移动端逐条调用已授权 DELETE,任一失败时停止并刷新列表,成功/失败数量明确展示,不新增绕过单条 scope 校验的批量后端口。 + +- [ ] **Step 4:验证协议前后端** + +```bash +cd mobile-oa-h5 && node --test src/market/protocol-*.test.js src/market/form-payload.test.js src/market/module-contracts.test.js src/market/mobile-parity-ui.test.js +cd mobile-oa-h5 && npm run build +cd backend && venv/bin/python -m pytest tests/test_market_protocol_api.py tests/test_market_support_api.py tests/test_market_freight_scope.py tests/test_market_payment_api.py -q +``` + +预期:全部测试和构建通过。 + +- [ ] **Step 5:提交协议在制改动** + +仅添加上述协议相关文件及对应测试,不提交无关工作区文件。提交信息: + +```bash +git commit -m "feat: complete mobile collection protocol parity for market roles" +``` + +## Task 5:复核并补齐预签约订单 + +- [ ] **Step 1:写缺口失败测试** + +在 `presign-form-view.test.js`、`presign-detail-actions.test.js`、`presign-list-model.test.js` 中断言 Web 字段全集、订单删除/转正式、五个转账动作、附件即时上传、状态锁定及返回刷新。 + +- [ ] **Step 2:运行定向测试** + +```bash +cd mobile-oa-h5 && node --test src/market/presign/*.test.js +cd backend && venv/bin/python -m pytest tests/test_market_presign_api.py tests/test_market_presign_mobile_actions.py -q +``` + +现有能力若已满足则新增测试直接通过;只有失败项才进入实现。 + +- [ ] **Step 3:按失败项最小补齐** + +修改范围限定在 `views/market/presign/`、`market/presign/`、`modules/presign.js`、`services/market_presign.py` 和对应测试。不得更改财务确认/拒绝边界。 + +- [ ] **Step 4:复验并提交** + +```bash +cd mobile-oa-h5 && node --test src/market/presign/*.test.js +cd backend && venv/bin/python -m pytest tests/test_market_presign_api.py tests/test_market_presign_mobile_actions.py -q +git commit -m "fix: close remaining mobile presign parity gaps" +``` + +## Task 6:完成缴费信息与催缴信息对齐 + +- [ ] **Step 1:写列表和详情失败测试** + +`list-presenter.test.js` 断言缴费卡包含 Web 基础列、6 期 × 5 列和汇总列;催缴卡包含 13 个 Web 字段及催缴状态。`module-contracts.test.js` 断言催缴可跳协议和缴费期次详情,缴费仅开放登记/编辑/提交/撤回。 + +- [ ] **Step 2:写后端过滤及 DTO 失败测试** + +`test_market_payment_api.py` 创建不同进度和应缴日期的方案,验证 Web 同口径过滤,并断言每一期返回 `dueAmount/dueDate/displayPaidAmount/payDate/arrearsAmount/transfers`。 + +- [ ] **Step 3:运行并确认失败点** + +```bash +cd mobile-oa-h5 && node --test src/market/list-presenter.test.js src/market/module-contracts.test.js src/market/module-actions.test.js +cd backend && venv/bin/python -m pytest tests/test_market_payment_api.py -q +``` + +- [ ] **Step 4:实现共享期次/转账展示** + +补齐 `_payment_dict` 和 `_period_dict`,让列表与详情使用同一 DTO;`payment.js` 显式展示所有转账字段和状态;催缴行的“已缴金额”可进入限定 `scheduleId` 的缴费详情,“协议单”进入协议只读详情。市场端仍不提供财务确认/拒绝、退费、开票和修改应缴计划。 + +- [ ] **Step 5:验证并提交** + +```bash +cd mobile-oa-h5 && node --test src/market/list-presenter.test.js src/market/module-contracts.test.js src/market/module-actions.test.js src/market/api-contract.test.js +cd backend && venv/bin/python -m pytest tests/test_market_payment_api.py tests/test_market_field_scope.py -q +git commit -m "feat: align mobile payment and payment chase with web" +``` + +## Task 7:完成运费信息对齐 + +- [ ] **Step 1:写失败测试** + +前端断言卡片与详情包含 Web 字段、筛选包含创建时间、编辑/关联/取消关联使用后端授权。后端创建包含范围内/范围外明细的共享批次,断言列表不泄露隐藏数量或人员,批次头编辑被拒绝但可对可见样本执行关联/取消。 + +- [ ] **Step 2:运行定向测试** + +```bash +cd mobile-oa-h5 && node --test src/market/list-presenter.test.js src/market/module-contracts.test.js src/market/form-view-lifecycle.test.js +cd backend && venv/bin/python -m pytest tests/test_market_freight_scope.py -q +``` + +- [ ] **Step 3:补齐字段和交互** + +`freight.js` 详情补齐创建/更新时间、备注和全部可见样本卡;`MarketModuleForm.vue` 保持新增、编辑、关联、取消四种模式;取消关联从详情完整 items 选择,不依赖全局第一页。后端 `sample_freight.py` 与 `market_freight.py` 继续软删除关联行并执行锁后 scope 复核。 + +- [ ] **Step 4:验证并提交** + +```bash +cd mobile-oa-h5 && node --test src/market/list-presenter.test.js src/market/module-contracts.test.js src/market/form-view-lifecycle.test.js +cd backend && venv/bin/python -m pytest tests/test_market_freight_scope.py tests/test_market_field_scope.py -q +git commit -m "feat: complete mobile freight parity without scope leakage" +``` + +## Task 8:全量验证、知识图谱与完成矩阵 + +- [ ] **Step 1:运行前端市场全量测试和构建** + +```bash +cd mobile-oa-h5 +node --test src/market/**/*.test.js src/market/*.test.js +npm run build +``` + +预期:0 failed,Vite exit 0;既有 chunk 大小 warning 可记录但不能把错误当 warning 忽略。 + +- [ ] **Step 2:运行后端市场全量测试和静态检查** + +```bash +cd backend +venv/bin/python -m pytest tests/test_market_*.py -q +venv/bin/python -m compileall -q app +venv/bin/python scripts/check_field_consistency.py +``` + +预期:pytest 0 failed、compileall exit 0;字段一致性无新增 error,既有 warning 单独记录。 + +- [ ] **Step 3:更新并验证后端知识图谱** + +```bash +./scripts/update-graph.sh backend +``` + +检查 `backend/graphify-out/GRAPH_REPORT.md`、`graph.json` 和 manifest 已随最终源码更新。 + +- [ ] **Step 4:运行仓库差异检查** + +```bash +git diff --check +git status --short +``` + +预期:`git diff --check` 无输出;状态中只包含本目标相关文件。 + +- [ ] **Step 5:写完成矩阵** + +创建 `docs/superpowers/specs/2026-10-13-market-five-module-mobile-parity-completion.md`,逐模块列出 Web 字段、移动实现位置、后端字段来源、自动化测试证据和仍需测试环境执行的 375/390/430px 双角色验收项。不得把未执行的真实账号/微信 WebView 验收写成已通过。 + +- [ ] **Step 6:最终提交** + +```bash +git add backend/graphify-out docs/superpowers/specs/2026-10-13-market-five-module-mobile-parity-completion.md +git commit -m "docs: record verified mobile market parity completion" +``` diff --git a/docs/superpowers/specs/2026-10-13-market-five-module-mobile-parity-design.md b/docs/superpowers/specs/2026-10-13-market-five-module-mobile-parity-design.md index 91e1005c..357cace8 100644 --- a/docs/superpowers/specs/2026-10-13-market-five-module-mobile-parity-design.md +++ b/docs/superpowers/specs/2026-10-13-market-five-module-mobile-parity-design.md @@ -103,7 +103,7 @@ ### 5.5 运费信息 -筛选补齐关键词、运输方式、来源。列表与详情完整展示批次号、运输方式、运费金额、当前范围内关联数、来源、协议号、母亲姓名、子女姓名、样本编码、样本类型、创建人、创建时间、更新时间和备注。 +筛选补齐关键词、运输方式、来源、创建时间范围。列表与详情完整展示批次号、运输方式、运费金额、当前范围内关联数、来源、协议号、母亲姓名、子女姓名、样本编码、样本类型、创建人、创建时间、更新时间和备注。 开放动作:新增运费批次、编辑批次、关联样本、取消关联。关联与取消关联使用移动卡片多选。共享批次继续执行既有脱敏和写权限规则,不泄露范围外明细或数量。