交付出件箱
← 返回出件箱
批次四 83.26/83.27 开工前 verify 原始输出
# 83.26/83.27 开工前 verify · 基线 origin/test 81962b75 · 2026-09-28 22:13:53
## V1 基线漂移(81962b75→origin/test 提交数)
0
## V2 本地库迁移状态
phase83_002
-- 对照:本地库 org_parent_seat_counter / seat_open_batches / users.seat_batch_id 是否存在
org_parent_seat_counter||1
## V3 仓库里 phase83_002 的子迁移(应只有 phase85_001)
migrations/versions/phase85_001_wecom_agent.py
## V4 user.py 插入点上下文
150: # 传了二维码的门店席位"兜底(app/api/parent_account.py 取码逻辑)。
151: # FK 加 ondelete="SET NULL"(镜像本文件 linked_org_id 写法):这里不是跨库/跨服务
152: # 引用(不同于结算侧 MR #419 "结算账号对 users 无 REFERENCES 权限"那种跨账号场景)——
153: # 本列与 users.id 同表同库同迁移账号,该账号本就是建 users 表的账号,天然有权限;
154: # 且 internal_settlement.py:393 有真实的批量硬删除孤儿席位路径,若不设 ON DELETE
155: # SET NULL,删除一个"曾开通过子席位"的席位会被 FK 拦下导致清理事务整体失败。
156: created_by_seat_id = Column(
157: Integer,
158: ForeignKey("users.id", ondelete="SET NULL"),
159: nullable=True,
160: comment="开通该席位的门店席位 id(自引用 users.id)· NULL=平台开通或老数据 · Phase 74",
161: )
162: # 二期批3(方案总纲五.3 拍板)· 席位备注名:门店开子席位时顺手起的好认名字
163: # (如"梓豪妈妈"),子席位列表 + 绑定家长选择器都展示它 · 选填 · 无 backfill,
164: # 存量席位天然 NULL(前端约定"备注为空的行不显示该段文字")。
165: seat_remark_name = Column(
166: String(50),
167: nullable=True,
168: comment="席位备注名(门店起的好认名·选填)· 二期批3",
169: )
170: # 【已废弃 2026-09-04】Phase 68 初始密码 7 天有效期机制整体拆除(生产大量客户超 7 天
171: # 才首登被集体拒登 · 长官拍板不设限)。列保留不删(免 DB 变更)· 业务代码不再读写 ·
172: # 存量值一律忽略。
## V5 models/__init__ __all__ 末尾
106:__all__ = [
160:]
154: "BrandCode",
155: "BrandCodeActivation",
156: "WecomServiceGroup",
157: "WecomOrgSettings",
158: "WecomEvent",
159: "WecomAgentHeartbeat",
160: ]
161:
## V6 模型里部分索引写法先例
app/models/brand_code.py:113: postgresql_where=text("wechat_unionid IS NULL"),
app/models/user.py:208: postgresql_where=text("role = 'channel' AND channel_id IS NOT NULL"),
## V7 依赖版本(venv 实装)
sqlalchemy 2.0.35 | cryptography 50.0.0 | openpyxl 3.1.5
7:sqlalchemy==2.0.35
8:alembic==1.18.4
9:asyncpg==0.31.0
## V8 pre-commit 守卫
-rwxrwxr-x 1 dev2 dev2 5492 Aug 11 13:05 /home/dev2/doctor-ai-brain/.git/hooks/pre-commit
## V9 alembic 配置
8:script_location = %(here)s/migrations
45:# to <script_location>/versions. When using multiple version
89:sqlalchemy.url = postgresql://casdoor:casdoor_password@localhost:5432/doctor_ai_brain?options=-csearch_path%%3Dpublic
15:# 从环境变量读取 DATABASE_URL(优先级高于 alembic.ini)
16:database_url = os.getenv("DATABASE_URL")
## V10 迁移建表先例(phase83_002 全文 upgrade/downgrade 段)
60: "ck_organizations_subseat_prefix_format",
61: "organizations",
62: "subseat_prefix ~ '^[A-Z]{1,6}$'",
63: )
64:
65: # --- d) 新表 org_parent_seat_counter ------------------------------------
66: op.create_table(
67: "org_parent_seat_counter",
68: sa.Column(
69: "org_id",
70: sa.Integer,
71: sa.ForeignKey("organizations.id", ondelete="CASCADE"),
72: primary_key=True,
73: ),
74: sa.Column(
75: "last_seq",
76: sa.Integer,
77: nullable=False,
78: server_default=sa.text("100000"),
79: comment="该机构已分配到的最大家长号序号(六位数字,100001 起用)",
80: ),
81: sa.Column(
82: "updated_at",
83: sa.TIMESTAMP(timezone=True),
84: server_default=sa.text("now()"),
85: ),
86: sa.CheckConstraint(
87: "last_seq BETWEEN 100000 AND 999999",
88: name="ck_org_parent_seat_counter_range",
89: ),
90: )
91: op.execute(
92: "COMMENT ON TABLE org_parent_seat_counter IS "
93: "'按机构原子计数家长号序号(Phase 83.13)· 取代 seat_seq_counter 的按月计数'"
94: )
95:
96: # --- e) users.phone_login_enabled ---------------------------------------
97: op.add_column(
98: "users",
99: sa.Column(
100: "phone_login_enabled",
101: sa.Boolean,
102: nullable=False,
103: server_default=sa.text("false"),
104: comment="新席位开 · 设好密码后只认手机号登录 · 老席位 false 零感知 · "
105: "Phase 83.13",
106: ),
107: )
108:
109:
110: def downgrade() -> None:
111: # 严格逆序:e → d → c → b → a
112: op.drop_column("users", "phone_login_enabled")
113:
114: op.execute("DROP TABLE IF EXISTS org_parent_seat_counter")
115:
116: op.drop_constraint(
117: "ck_organizations_subseat_prefix_format", "organizations", type_="check"
118: )
119: op.drop_index("uq_organizations_subseat_prefix", table_name="organizations")
120: op.drop_column("organizations", "subseat_prefix")