"""数据结构。""" from __future__ import annotations import re import time from dataclasses import dataclass, field @dataclass class User: user_id: str real_name: str user_name: str @dataclass class Team: team_id: str team_name: str @dataclass class Project: project_id: str project_name: str @dataclass class WorkSheet: """工单(每项目每 Sprint 一张)。""" worksheet_id: int code: str = "" status: str = "" version_id: int | None = None version_code: str = "" system_name: str = "" start_time: str = "" end_time: str = "" @property def done(self) -> bool: """03/04 = 甲方已确认,流程闭环。""" return self.status in {"03", "04"} @dataclass class Feature: feature_id: int code: str = "" name: str = "" @dataclass class WorkSheetVersion: """工单版本(审批链上的一环)。 `usp_UpdateWorkSheetById` 在第一次保存时插入「技术责任人确认版」(VersionStatus='N'), 但**不回填** `WorkSheet.CurrentVersionId` —— 那要等 `usp_VerifyWorkSheetById` 才写。 所以导入明细/确认工单都必须用「待确认版本」的 VersionId,不能用 CurrentVersionId。 """ version_id: int worksheet_id: int version_code: str = "" version_status: str = "" # N=待处理(当前),Y=已确认 next_operator: str = "" @property def pending(self) -> bool: return self.version_status.strip().upper() == "N" @dataclass class TfsTeam: team_name: str area_path: str iterations: list[dict] = field(default_factory=list) def ms_to_date(value: object) -> str: """`/Date(1785081600000)/` → `2026-07-26`。""" if not value: return "" if isinstance(value, str) and value.startswith("/Date("): match = re.search(r"\((-?\d+)", value) if match: return time.strftime("%Y-%m-%d", time.localtime(int(match.group(1)) / 1000)) return str(value) def normalize_name(name: str) -> str: """项目/迭代名归一化:去空白、统一破折号、忽略大小写。用于精确匹配,避免 contains 误命中。""" text = re.sub(r"\s+", "", name or "") for dash in ("-", "—", "–", "−"): text = text.replace(dash, "-") return text.casefold() def normalize_iteration(name: str) -> str: """迭代名比对:在 normalize_name 基础上忽略数字前导零(`2026 Sprint 06` ≈ `2026 Sprint 6`)。""" text = normalize_name(name) return re.sub(r"(?<=\D)0+(?=\d)", "", text)