Stores Schema
Source of truth:
- Postgres:
callytics-common/src/db/schema/rc-stores.ts·store-config.ts·staff.ts·user-stores.ts(verified 2026-06-15)- DynamoDB:
studio-website-monorepo/apps/api/infra/lib/stack.ts(CDK Table) +apps/api/src/types/domain.ts(item shape) +apps/api/src/repositories/stores.ts(writers)隔离架构: 唯一隔离键
store_id(UUID) — 详见 Store-Level 隔离。Postgresrc_stores.id与 DynamoDBStoresV2.storeId是同一个 UUID,跨数据库一致。 不在本文档: 多门店聚合/激活/通知邮箱的运行时计算口径在 stores-calculations.md;页面控件与卡片展示在 stores-display.md。
1. 表说明与分工
Stores 域 6 张表分两组:Postgres(callytics-common Drizzle schema)+ DynamoDB(legacy + 跨服务共享)。
⚠️ Postgres
rc_stores与 DDBStoresV2共享同一个storeIdUUID。Postgres 表是 v3 setup/dashboard 数据源;DDB 表是 callytics Lambda 跨服务读 businessConfig 的入口。改隔离逻辑时两边都要看。
写入者列说明:
- code = 自动写入(RC sync / Lambda pipeline / studio-api 系统级操作)
- user = 用户通过 UI 写入
2. Postgres rc_stores — 门店主表(RC 同步)
由 RingCentral Site 结构自动创建。syncStoresToNeon() 在 OAuth 连接和手动 Sync 时按 (user_id, connection_id, rc_site_id) upsert。
索引
为什么 3 列联合唯一: 一个用户可有多个 RC 连接(不同 providerAccountId),不同连接可能共用同一个合成 siteId(e.g. 单站点账户都叫
main)。
3. Postgres rc_store_phones — 电话 ↔ 门店映射(RC 同步)
phone→store 的物理基础。studio-api 用它做 store-level filtering(把通话号码映射到具体门店)。从 RC Extension→Site 关系派生,每次 sync 全量替换(按 store DELETE + INSERT)。无用户编辑入口 — 改门店或电话要去 RingCentral 改完再 re-sync。
索引
4. Postgres store_config — 门店配置(用户设置)
用户控制的门店配置,与 rc_stores 分离,RC sync 永不覆盖。1:1 关系,首次写配置时惰性创建。
activatedAt是 timestamp 不是 boolean: 它决定门店是否出现在用户的活跃工作区。Dashboard 的 multi-store 聚合用WHERE sc.activated_at IS NOT NULL过滤 — 只统计已激活门店,同时免费拿到激活时间线。(Gemini 设计建议,2026-04-30)
5. Postgres staff — 员工身份
V1.5:Close Task dialog 的 "Who are you?" 下拉。V2:完整员工管理(PIN 登录、角色权限)。Isolation key = store_id (NOT NULL);account_id 作为 legacy 审计字段保留。
索引
Writers: studio-api
syncStaffToNeon()(replace pattern,store 级全量替换)。 Readers: callytics-infrastructure transcribe-processor(keywords)/ ai-analysis-processor(staff name 识别)/ studio-api GET store(merge)。
6. Postgres store_members — 多门店共享访问
替代 DDB orangetheory-UserStore-{env} 给 v3 用。只记 non-owner(EDITOR / VIEWER);owner 隐含在 rc_stores.user_id,不在此表冗余存储。
约束 / 索引
v3 授权流程(
shared.ts:getStoreContext):
rc_stores.user_id === userId→ OWNER(零查询)store_members WHERE user_id = ? AND store_id = ?→ EDITOR / VIEWER- 都不是 →
ForbiddenError
7. DynamoDB orangetheory-StoresV2-{env} — 跨服务 store + businessConfig
为什么独立于 Postgres: callytics-infrastructure 多个 Lambda(transcribe-processor / ai-analysis-processor / contacts-analyzer)需要 storeId → businessConfig 低延迟查询。这张 DDB 表是跨服务共享入口,IAM cross-account 授权访问(见 iam-stack.ts)。Postgres rc_stores + store_config 是 v3 setup / dashboard 视图,DDB StoresV2 是 Lambda runtime view。两者通过同一 storeId UUID 对齐。
Table 配置(studio-website-monorepo/apps/api/infra/lib/stack.ts:240-254)
Item shape — 全字段平铺(Store / BusinessConfig / StaffMember / DefaultDaySchedule / DateSchedule interfaces,apps/api/src/types/domain.ts:89-150)
下表把嵌套对象按 Path 列展平,reviewer 一张表看完所有字段。Path 列约定:
- 单段(无
.)= 顶层 Store 字段(e.g.storeId/name) businessConfig.X= 顶层businessConfig下的二级字段(单值或数组容器)businessConfig.staff[].X=staff数组每个StaffMember元素的字段businessConfig.defaultSchedule[].X=defaultSchedule数组每个DefaultDaySchedule元素的字段businessConfig.schedules[].X=schedules数组每个DateSchedule元素的字段businessConfig.pricing.classPacks[].X同理(嵌套 2 层)
⚠️ Update 行为踩坑提醒:
updateStore()(apps/api/src/repositories/stores.ts:141-249)对businessConfig做字段级合并 — 只更新发送来的二级子字段(e.g. 只更新staff不会清空schedules/pricing),实现是 DDBSET businessConfig.staff = :val这种 nested path update。如果businessConfig整个字段本身还不存在,DDB 会拒绝 nested SET 抛ValidationException: document path,此时降级为整对象 SET(stores.ts:213-247的 catch 块处理)。⚠️ DDB
businessConfig.staff[]与 Postgresstaff表是两份独立数据:前者给 calendar / scheduling 用,后者给 AI 识别员工 + Close Task 归因用。改员工时两边都要写。
8. 跨表关系图
关键不变式:
- Postgres
rc_stores.id≡ DDBStoresV2.storeId— 同一个 UUID,跨数据库表达同一个门店 store_id在所有跨表查询中都必须出现(contacts / calls / tasks 全部带store_idguard,见 calls-schema.md / tasks-schema.md / contacts-schema.md)- owner 不在
store_members表里 — owner =rc_stores.user_id,授权流程先查 owner 命中就返回 - DDB
businessConfig.staff[]与 Postgresstaff表是两份独立数据 — 用途不同,改一边不会自动同步另一边
9. Appendix A — Re-verification commands
2026-06-01 verified。文档 stale(>30 天)时重跑下方命令并比对。
10. Cross-References
- 隔离架构(全 repo 看哪):
docs/system-design/store-level-isolation.md - 多门店运行时计算口径: stores-calculations.md
- 页面展示规范: stores-display.md
- 上游表(都带
storeId做 store guard):- calls-schema.md —
calls.storeId(text, NULL until Phase 3 rollout) - tasks-schema.md —
tasks.storeId(text, NULL) - contacts-schema.md —
contactsPK 已含storeId
- calls-schema.md —
- Unified pipeline: ../unified-pipeline/unified-pipeline-final.md
- Live schema:
- Postgres:
callytics-common/src/db/schema/rc-stores.ts·store-config.ts·staff.ts·user-stores.ts - DynamoDB CDK:
studio-website-monorepo/apps/api/infra/lib/stack.ts - DDB item shape:
studio-website-monorepo/apps/api/src/types/domain.ts - DDB repository:
studio-website-monorepo/apps/api/src/repositories/stores.ts
- Postgres: