feat(strategy): P0 战略基座 — strategy_charter + GET/PUT /api/cma/charter + P组入口
ci/woodpecker/push/main Pipeline was successful

- 表 strategy_charter(一账套一行,entity_id 唯一):mission/vision/vision_year/vision_measurable/values_text/value_items(JSON) 全 NOT NULL
- 迁移脚本 backend/scripts/migrate_strategy_charter_20261021.py:--check/--apply/--down(回滚先 mysqldump,有数据需 --force),零 INSERT
- API:GET 无行 → filled=false 空态(禁示例数据);PUT 字段白名单 + 账套隔离三道 + vision_year 4位年份 + value_items [{name,meaning}] 结构校验
- 前端:P 组「战略章程」入口 + MyDialog 编辑(ceo/finance 可写,其余只读);战略地图页顶部只读章程条
- 用例 43 条 + auth-surface 58 条 = 101 passed;vite build rc=0
This commit is contained in:
wecom-fullstack
2026-10-21 18:54:09 +08:00
parent 7022a05928
commit 0a6525edab
11 changed files with 1082 additions and 6 deletions
+64
View File
@@ -0,0 +1,64 @@
"""战略基座 — 战略章程(使命 / 愿景 / 价值观)
任务:`project-20261021-cma-strategy-charter-p0-fullstack`(P0 战略基座)
依据:《2026-10-21 CMA 知识库 M0 与系统实现差距报告》§四 P0 —— 知识卡
`CMA-P1-B-009 愿景、使命与价值观`:使命=为何存在;愿景=要成为什么
(**带年份与可验证状态**);价值观=不可交易的底线,是决策冲突时的裁决依据。
单一事实源:每个账套(entity_id)**一条**章程行,唯一键 uq_strategy_charter_entity。
本模块只做「表结构 + 契约常量」;读写端点见 app/api/charter.py。
字段语义(派单表:语义不可减,类型可调整):
mission 使命 TEXT NOT NULL
vision 愿景 TEXT NOT NULL
vision_year 愿景达成年份 VARCHAR(20) NOT NULL(**不可空**;4 位年份)
vision_measurable 可验证状态 TEXT NOT NULL(**不可空**;用可核对的规模/业务口径描述)
values_text 价值观总述 TEXT NOT NULL
value_items 价值观条目 JSON NOT NULL `[{name, meaning}]`
—— 每条须能作"决策冲突时的裁决依据"
隔离:`entity_id NOT NULL` + 唯一键以 entity_id 打头(多租户隔离 + 索引可用性)。
禁止示例数据:本模块与迁移脚本**零 INSERT**;GET 无行返回 `filled=false` 空态。
"""
from sqlalchemy import Column, Integer, String, Text, JSON, DateTime, UniqueConstraint, func
from app.database import Base
# 愿景年度格式:4 位年份(不臆造业务上限;允许字符串以兼容前端输入)
VISION_YEAR_RE = r"^\d{4}$"
# 长文本上限(与列宽一致,避免 MySQL 截断静默丢字)
MISSION_MAX_LEN = 1000
VISION_MAX_LEN = 1000
VISION_MEASURABLE_MAX_LEN = 1000
VALUES_TEXT_MAX_LEN = 2000
VALUE_ITEM_NAME_MAX_LEN = 50
VALUE_ITEM_MEANING_MAX_LEN = 300
VALUE_ITEMS_MAX = 20
class StrategyCharter(Base):
"""战略章程 — 使命/愿景(年度+可验证状态)/价值观(每账套一行)"""
__tablename__ = "strategy_charter"
id = Column(Integer, primary_key=True)
entity_id = Column(Integer, nullable=False, comment="企业ID(账套,多租户隔离;唯一键已建索引)")
mission = Column(Text, nullable=False, comment="使命:我们为何存在(做什么、为谁做)")
vision = Column(Text, nullable=False, comment="愿景:要成为什么")
vision_year = Column(String(20), nullable=False, comment="愿景达成年份 YYYY(不可空)")
vision_measurable = Column(
Text, nullable=False,
comment="可验证状态:用可核对的规模/业务口径描述(不可空)",
)
values_text = Column(Text, nullable=False, comment="价值观总述(不可交易的底线)")
value_items = Column(
JSON, nullable=False,
comment="价值观条目数组 [{name, meaning}],每条可作决策冲突时的裁决依据",
)
updated_by = Column(Integer, nullable=True, comment="最后修改人 user.id")
updated_by_name = Column(String(50), nullable=True, comment="最后修改人姓名(快照)")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
__table_args__ = (
UniqueConstraint("entity_id", name="uq_strategy_charter_entity"),
)