Files
wangjia 05203a9b5b
Design Source Checks / design-source (push) Failing after 14m23s
refactor: 原型迁移 .superpowers/prototype → design/prototype(与 CONTRACT 同目录)
- git mv 全目录(历史保留);.gitignore 收敛为整个 .superpowers/ 忽略
- 全仓 16 处引用同步(CI checks.yml / l1-sync / screens.mjs / hooks /
  CLAUDE.md / CONTRACT / SSR 模板注释 / docs / web 注释)
- 修 pre-commit 路径正则残留;5180 评审服务已切新路径
- 验证:check-ds 12 道 / l1-sync 6 道 / fidelity 抽查全过

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-07 18:47:31 +08:00

376 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 酒库管理系统 — 项目规则与 Orchestrator
## 必读
**开始任何任务前,先读 `docs/context/project.md`** 了解项目全貌。
**文档地图**(入口 `docs/index.html`):
- 用户使用手册:`docs/manual/user-manual.html`(官网帮助页精简版源=`web/content/docs.md`,两者同步改)
- 开发手册(架构/后端/前端/数据库/API/运维/发版):`docs/manual/dev-manual.html`
- 数据库列级文档:`docs/db-schema.html`schema 变更时同步)
- 设计契约与像素闸:`design/CONTRACT.md` + `tools/screens.mjs`
---
## Orchestrator:如何自动选择 Agent
根据用户请求的关键词和意图,按以下规则判断调用哪些 Agent:
### 新功能开发(完整流水线)
触发词:「新增」「开发」「实现」「做一个」+ 功能描述
```
串行:requirements-analyst → architect
并行:api-designer + db-designer(同时进行,都依赖架构但互不依赖)
→ ui-designer 判断复杂度:
简单变更:直接输出规范 → 继续下一步
复杂设计:输出 Figma 需求文档 → 暂停等用户确认设计稿
并行:backend-coder + flutter-coder(都依赖 API 设计,互不依赖)
串行:test-engineer → linter → code-reviewer → security-auditor
```
### 仅后端修改
触发词:「修复后端」「后端接口」「API」+ 具体描述
```
backend-coder → test-engineer → linter → code-reviewer
```
### 仅前端修改
触发词:「UI」「界面」「Flutter」「页面」
```
flutter-coder → linter
```
### Bug 修复
触发词:「bug」「报错」「修复」「问题」
```
1. 先判断 bug 来源(后端/前端/数据库)
2. 调用对应的 coder agent 修复
3. test-engineer 验证修复
```
### 安全/代码质量检查
触发词:「安全」「漏洞」「review」「审查」「代码质量」
```
并行:security-auditor + code-reviewer + linter
```
### 线上问题
触发词:「线上」「故障」「宕机」「慢」「报错」
```
sre(优先级最高,立即响应)
→ 根据 sre 诊断结果,再决定是否调用 backend-coder 修复
```
### 文档需求
触发词:「文档」「说明」「手册」「README」
```
doc-writer
```
---
## Agent 边界规则(强制执行)
| Agent | 可以写 | 不可以写 |
|-------|--------|----------|
| requirements-analyst | docs/requirements/ | 任何代码 |
| architect | docs/architecture/ | 任何代码 |
| api-designer | docs/api/ | 任何代码 |
| db-designer | backend/schema/ | 业务代码、测试代码 |
| backend-coder | backend/internal/, backend/main.go | *_test.go, schema/ |
| ui-designer | docs/design/ | 任何代码 |
| flutter-coder | client/ | backend/ |
| test-engineer | *_test.go, docs/review/*-bugs.md | 业务代码 |
| linter | 格式自动修复(gofmt/dart format),docs/review/lint-report.md | 逻辑代码 |
| code-reviewer | docs/review/*-review.md | 任何代码 |
| security-auditor | docs/security/ | 任何代码 |
| devops | deploy/, .gitea/workflows/, scripts/ci/ | 业务代码 |
| sre | docs/runbooks/ | 任何代码 |
| doc-writer | docs/(除 context/ 外) | 任何代码 |
---
## Agent 间通信协议
**文件传递**(主要方式):
- 每个 Agent 完成后将结果写入约定文件
- 下游 Agent 开始前读取上游产出文件
- 文件格式:混合模式(中文叙述 + YAML/代码块)
**短链式调用**(反馈循环):
- test-engineer 发现 bug → 直接将错误信息和 `docs/review/{功能}-bugs.md` 路径传给 backend-coder
- security-auditor 发现 Critical 问题 → 直接告知 backend-coder 修复,不等下次迭代
---
## 并行 Agent 调度规则
当多个 Agent 可以并行时,在同一条消息中发起多个 Agent 调用:
```
# 可以并行的情况(互不依赖):
- api-designer + db-designer
- backend-coder + flutter-coder
- security-auditor + code-reviewer + linter
# 必须串行的情况(有依赖):
- requirements-analyst 完成后 → architect 才能开始
- architect 完成后 → api-designer 和 db-designer 才能并行开始
- backend-coder 完成后 → test-engineer 才能开始
```
---
## Git 提交规范
每个 Agent 完成工作后提交,commit message 格式:
```
{类型}({模块}): {简短说明}
类型:feat | fix | test | docs | chore | security | refactor
模块:backend | client | db | deploy | docs
示例:
feat(backend): 新增财务报表接口
test(backend): 财务报表模块测试用例
docs(api): 财务报表 API 接口文档
security: 修复入库单多租户隔离漏洞
```
---
## 开发完成标准(Definition of Done
**任何功能或修复,必须满足以下全部条件才算完成:**
### 后端变更
```bash
# 1. 编译通过
export PATH="/opt/homebrew/bin:$PATH"
cd backend && go build ./...
# 2. 所有测试通过(不得有 FAIL)
cd backend && go test ./...
# 3. 无编译警告(vet 检查)
cd backend && go vet ./...
```
### 前端变更
```bash
# 1. 静态分析无 errorwarning/info 允许存在)
cd client && flutter analyze --no-fatal-infos --no-fatal-warnings
# 2. 所有测试通过
cd client && flutter test
```
### 通用要求
- [ ] 代码已按 Git 提交规范提交(feat/fix/test/...
- [ ] 涉及新接口:已在对应测试文件中覆盖核心路径
- [ ] 涉及数据库变更:schema.sql 和 model 同步更新
- [ ] 多租户隔离未被破坏(所有查询含 shop_id 条件)
**测试未通过前,禁止提交代码、禁止打 tag 发版。**
---
## 代码质量门禁
以下情况**不得提交**
- `go build ./...` 失败
- `go test ./...` 有失败用例
- 存在 security-auditor 标记的 Critical 级别问题
- 存在 code-reviewer 标记的 blocking 级别问题
---
## 项目强制规则
### 多租户隔离
- `shop_id` **永远**从 JWT token 中提取,使用 `middleware.GetShopID(c)`
- **绝不**从请求参数、URL 或请求体中读取 `shop_id`
- 所有数据库查询必须带 `WHERE shop_id = ?` 条件
### 数据模型:product = 特有产品/序列号(核心铁律)
- `products` 每行 = 一个**特有产品/序列号**(`code`=商品编号,同店唯一 `uk_shop_code`),**不是 SKU**;语义:`name`=品牌、`series`=型号、`spec`=版本。product 是商品信息的**单一来源**(序列号/品牌型号版本/生产日期 `production_date`/批次 `batch_no`/进价/图片/`public_id`)。
- **入库每录一行 = 新建一个独立 product**`createIndependentProduct` + `nextProductCode` max+1),**绝不**按名称/系列/规格复用(`findOrCreate` 复用已废弃)。同理任何"导入/建库存"逻辑:有商品编号时按编号匹配/建 product,**禁止**按名称合并(历史 bug 根因)。
- 基础数据字典(品牌/型号/版本 + 产地/保质期/储存/介绍)在「基础数据」页管理,入库选文本后据此建 product。
- 库存(`inventories`)/出入库明细 = 指向 product 的引用 + **快照列**`product_code/name/series/spec/...`,导入/审核时拷贝,作历史保真+显示兜底)。显示优先 product、回退快照(`COALESCE(p.*, 快照)` / 前端 `lineOrProduct`)。**快照列保留,勿擅自删**。
### 定价字段口径(2026-07 消歧后,唯一口径)
- 明细两侧价格分列:`cost_price`(成本/进价单价)+ `cost_amount`(成本小计);出库另有 `sale_price`(售价)+ `sale_amount`(售价小计,待定价=0)。**旧列 unit_price/total_price/total_amount 已弃用**(值已回填新列,观察一版后 DROP),新代码禁止读写旧列。
- 单据合计:入库 `cost_total`(应付=Σ总进价);出库 `sale_total`(应收=Σ售价小计)+ `profit_total`(总利润=Σ(售价>0?(售价-成本)×数量:0),建单落库,确认售价/确认进价联动重算,重算函数 `recalcStockOutProfit`)。
- **成本与利润仅管理员可见**:出库 List/Get 对 operator/readonly 服务端抹零(`stripStockOutCost`),前端同时隐藏对应列——新增出库相关展示时两侧都要遵守。**库存同口径**(2026-07-06):库存 List 抹空 unit_price、Summary 抹零 stock_value/last_month_value`stripInventoryCost`),前端隐藏成本价/总价列、卡片单价、货值 KPI(`isAdminProvider`)、商品抽屉成本行。
- 财务口径不变:应收/应付、退单冲账均按上述合计列;出库退单展示与冲账同为售价口径。
### 库存变更
- 入库/出库审核通过时,必须在同一事务中同时完成:
1. 更新 `inventories` 表数量
2. 写入 `inventory_logs` 流水记录
- 出库前必须校验库存充足,不足时返回错误并回滚
- **库存三态**2026-07-07 落库 `inventories.status`):`stock` 库存(默认,不公开)⇄ `on_sale` 在售(公开酒单展示,抽屉 switch 手动挂售,接口 `PUT /inventory/:id/status` 仅收这两值)→ `sold` 已售(出库审核扣光自动置入,**留痕不再软删**;退单恢复置回 stock)。列表默认排除已售(显式 `status=sold` 才返回);Summary 的 SKU/数量/货值排除已售、另出 `sold_count`;公开店铺页只列 on_sale。盘亏归零仍软删(灭失≠售出)。预警/缺货退出状态维度,由数量 vs 安全库存染色承担
- 审核中(pending)单据支持**撤回**(`withdraw`)回 draft:管理员/超管任意单、操作员限本人单(`OperatorID==本人`handler 内判权,非 AdminOnly 中间件)
### Schema 管理
- 表结构变更:修改 `backend/schema/schema.sql` + `backend/internal/model/` 对应 model
- 启动时自动 AutoMigrate,无单独迁移文件
- **禁止**为每个新业务字段修改表结构,使用 `custom_fields JSON` 动态扩展
### 种子数据
- 测试数据以 SQL 文件形式维护:`backend/seeds/<shop_code>.sql`
- 通过 `sh scripts/dev.sh seed <shop_code>` 执行(内部用 `docker exec` 注入 MySQL 容器)
- 每个门店一个文件,SQL 文件顶部包含 TRUNCATE,每次执行完整重建
### Go 命令
- 所有 Go 命令前须确保 PATH`export PATH="/opt/homebrew/bin:$PATH"`
- 或直接使用 `sh scripts/dev.sh` 脚本(已内置 PATH
### 前端 URL 配置
- 所有后端 URL 必须通过 `AppConfig` 读取(`client/lib/core/config/app_config.dart`
- **禁止**在任何 provider / repository / widget 中硬编码 `localhost:8080` 或任何 IP/域名
- 使用:`AppConfig.apiBaseUrl``AppConfig.healthUrl``AppConfig.versionUrl`
### 前端平台判断
- 使用 `dart:io``Platform` 类前,必须先检查 `kIsWeb``import 'package:flutter/foundation.dart'`
- Web 平台不支持 `dart:io`,直接使用会在 Web 构建时崩溃
### 设计真相源分层(原型漂移治理,2026-07-04 拍板)
原型(`design/prototype/`)与真实页面**不追求全量永久同步**,按层分治:
- **L1 设计系统层**`tokens.css` / `atoms.css` / `mobile-atoms.css` / `icons.js` + `index.html` 登记):原型是**单一真源**。新增颜色/组件/图标必须**先登记原型再同步**代码与官网(如 `web/_includes/icons.njk`)。闸:pre-commit `check-ds.mjs` 12 道(本地钩子,新机器先跑 `sh scripts/hooks/install.sh`+ 代码侧 `client/tool/check_ds_code.mjs`
- **L2 屏级三态**(台账 = `design/CONTRACT.md` 块 4「真相源」列):
- `同步`:注册于 `tools/screens.mjs`,fidelity 像素闸生效。整屏改版须原型先行;小迭代改后跑 fidelity 确认阈内,超阈则同步原型或降级快照。
- `快照`:原型退役为历史参考,**golden + CONTRACT 文字规格为准**;小迭代只更 golden,**不回填原型**。
- `代码先行`:无原型屏,golden 为唯一像素基准。
- **L3 新屏/整屏改版**design-first 不变(原型 → CONTRACT → 实现 → fidelity 验收),建成后入 `同步` 态。
- **漂移体检**:不逐提交强制同步;大版本/整屏改版前跑 `node tools/fidelity.mjs`,按报告逐屏决定「重新对齐原型」或「降级快照」,结果回填 CONTRACT。
- **CI 闸**`.gitea/workflows/checks.yml`PR + main push 自动跑):① `check-ds.mjs` 原型 12 道;② `tools/check-l1-sync.mjs` L1 同源四道(tokens 快照逐字节 / icons 两端同集同内容 / 官网 token 值对齐原型主题 A / web 硬编码 hex,白名单 #fff#1677ff`ds-allow` 可豁免);③ `client/tool/check_ds_code.mjs` Flutter 颜色单源;④ codegen 新鲜度(regen→`dart format`→与入库产物零 diff)。本地可直接跑同名命令。
### 表格与筛选/排序(ds 真相源)
- 列表屏统一 `DsTable``widgets/ds/ds_table.dart`toolbar+表格+pager 连成一卡),列定义 `DsColumn`,可隐藏列走列设置菜单
- **筛选统一放工具栏 `DsChip`**2026-07-07 用户口径,形式参照出库管理:单选/多选 chip + 选中值/N 项摘要 + × 单清);列头**只放排序** `DsSortHeader`(点击循环 无→升→降→无,服务端全局排序)。列头漏斗 `DsFilterHeader` 已退役删除
-`FilterableColumnHeader`/`multi_select_dropdown.dart` 仅存量引用,勿在新屏使用
### 响应式 / 移动端适配
- 断点判定统一用 `context.isMobile``client/lib/core/responsive/responsive.dart`,宽度 < 600 为窄屏/手机),**禁止**在各处散落 `MediaQuery.width < 600` 这类魔法数
- **列表屏**:统一用 `DsTable``widgets/ds/ds_table.dart`,真相源表格:toolbar+列+pager);窄屏卡片流用 `MCard``widgets/ds/m_card.dart`,镜像原型 mobile-atoms)。旧 `MobileListCard` 为 legacy 仅存量引用(迁移在 todo),旧 `DataTableCard` 已删除(2026-07-03),勿再引用
- **弹窗固定宽度**:一律用 `context.dialogWidth(X)`(≤ 屏宽 92%),**禁止**裸写 `width: <固定值>` 导致窄屏溢出
- **导航**:窄屏(手机)= 底部 5 tab(库存/入库/出库/财务/我的)+「我的」hub 聚合二级屏(`app_shell.dart` + `m_tab_bar.dart`2026-07-04 起,Drawer 已退役);宽屏保持侧边栏;一切弹层窄屏统一走 `showMSheet` 底部 sheet
- 平台判断仍遵守上面「前端平台判断」规则(`kIsWeb` 先于 `dart:io`
### 权限中间件(已全局挂载)
- `middleware.ReadOnly()`:已挂载到主 `api` 路由组,只读用户(role=readonly)所有写操作自动返回 403
- `middleware.AdminOnly()`:挂载在 `/users` 路由组,仅管理员可管理用户
- **新增写操作路由**时,默认受 `ReadOnly()` 保护;若仅管理员可用,需加入 `AdminOnly()` 子组
### 并发安全(数据库操作)
- 在同一事务内读后写的操作,必须用 `FOR UPDATE` 锁行:
```go
tx.Set("gorm:query_option", "FOR UPDATE").Where(...).First(&model)
```
- 适用场景:单号生成(`number_rules`)、库存扣减(`inventories`)、任何 check-then-act 模式
### 发版流程
**发版拆成三条互不影响的流水线**,各有 tag 前缀、独立版本序列、独立 CHANGELOG:
| part | 范围 | tag 前缀 | CHANGELOG | workflow |
|------|------|---------|-----------|----------|
| **client** | `client/` Flutter 全平台(Web→`/app`、macOS、Windows、Android、iOS+ 应用自更新清单 `version.yaml` | `client-v*` | `CHANGELOG-client.md` | `deploy-client.yml` |
| **site** | `web/` Eleventy 营销宣传站(→`/opt/jiu/marketing`nginx `/`)。**不含** Web 版 app | `site-v*` | `CHANGELOG-site.md` | `deploy-site.yml` |
| **server** | `backend/` Go 服务(`jiu-server`+ 共享基建 nginx-jiu.conf / jiu.service | `server-v*` | `CHANGELOG-server.md` | `deploy-server.yml` |
使用 `/release <part> [version]` slash command
```
/release client 1.0.55 # 指定版本
/release server # 省略则自增 server-v* 最新 tag 的 patch
```
执行顺序:本地 build → test(按 part)→ 更新对应 CHANGELOG → git commit → tag `<part>-v<ver>` → push main+tag。
CI/CDForgejo)按 tag 前缀触发对应 workflow,自动:编译 → 测试 → 创建 Release → 部署 alijiu.51yanmei.com443)→ Telegram 通知。
**归属与解耦**(关键):
- `version.yaml` 归 **client**(写 version/build_number/release_notes/下载链接/changelog);nginx/systemd 归 **server**。
- 后端 `/version` 与 `/api/v1/public/release` **每请求实时读** `version.yaml`,无缓存——client 部署 version.yaml 后立即生效,**不重启后端、不触发 server 流水线**。
- 官网下载页运行时 `fetch('/api/v1/public/release')`:版本徽章、各平台下载链接、**更新日志时间线**全部动态刷新——client 发版**无需重建官网**。接口不可达时回退到构建时静态内容(`web/_data/changelog.js` 读 `CHANGELOG-client.md`)。
**CI 脚本**`scripts/ci/`):`lib-forgejo.sh`(公共函数)+ `compile-{client-web,site,backend,macos,android,ios,windows}.sh` + `release-{client,site,server}.sh` + `deploy-{client,site,server}.sh`。`compile-{macos,android,ios,windows}.sh` 去 `client-v` 前缀取版本。
**client 多平台构建矩阵**mac runner(容量 1)串行链 `build-client-web → build-macos → build-android → build-ios`windows runner 并行 `build-windows`Inno Setup);`release-deploy-client` 收齐后发布。Android 需 `ANDROID_*`、iOS 走 TestFlight 需 `IOS_*`/`APPSTORE_*` secrets,未配置时该 job 优雅跳过(exit 0,不阻塞)。
**手动回滚**`manual.yml` 输入带前缀 tag(如 `client-v1.0.54`),按前缀路由到对应 `deploy-<part>.sh`,从 Forgejo Release 下载产物。
**CHANGELOG 格式**Keep a Changelog,三个文件同构):
```markdown
## [1.0.2] - YYYY-MM-DD
### 新功能
- ...
### 改进
- ...
### 修复
- ...
```
只保留有内容的分类。`/release` 会检查对应 CHANGELOG 是否已有该版本节,没有则从该 part 上一个 tag 的 git log 自动生成。
### 异常上报
客户端异常已在两处统一捕获,**无需**在每个业务层重复处理:
- `main.dart``FlutterError.onError` + `runZonedGuarded` 捕获所有未处理异常
- `api_client.dart`:Dio 拦截器自动上报所有 HTTP 5xx 错误
需要**手动调用** `reportError(e, st)` 的场景:
- 技术性异常(JSON 解析失败、第三方 SDK 崩溃等)
- **不需要**上报:`AppException` 及其子类(已知业务错误)
```dart
} catch (e, st) {
reportError(e, st); // 一行,fire-and-forget
rethrow;
}
```
### 项目 TODO 管理
**统一使用全局 todo 工具**(`~/.claude/skills/todo/`,遵循全局 `~/.claude/CLAUDE.md` 的规则)。本项目**不再维护自己的 `/todo` slash command**(已删除),一律调用全局 todo skill。
- **数据落地**:全局 skill 把待办存到**当前项目**的 `todo/` 目录(`todo.json` + 看板 `todo/todo.html`,首次使用自动创建);命令须在项目根目录运行,例如 `node ~/.claude/skills/todo/todo.mjs list`。
- **禁止**使用 TaskCreate / TodoWrite 等任何内置 todo 工具,一律走全局 todo skill。
- **改动等级把控**:接口/schema/跨 2+ 模块的「大改」先进 plan 模式经用户批准,完成后同步更新设计文档;小 bugfix 直接做。
---
### 搜索实现(拼音)
商品名搜索同时支持汉字、全拼、首字母:
- `products` 表有 `name_pinyin`(全拼)和 `name_initials`(首字母)两列
- Create / Update / FindOrCreate 写入时自动调用 `util.ToPinyin()` 生成,**无需手动维护**
- 启动时对 `name_pinyin = ''` 的存量数据自动回填(`backfillPinyin`
- 库存搜索 SQL 同时 LIKE 匹配 name、code、name_pinyin、name_initials
- 新增其他需要拼音搜索的实体时,复用 `backend/internal/util/pinyin.go` 的 `ToPinyin()`