refactor(server): 单号规则收紧为 A/R/D+8 位紧凑格式
- DOCUMENT_NUMBER_PREFIXES 改为 A/R/D,新增短格式与旧格式正则并存识别,提取正则加边界锚定避免误匹配 - build_document_number 去掉时间戳段,统一生成 A+token 等紧凑单号,is_application_claim_no 兼容旧 AP-/APP- 前缀 - access_policy/status_registry/reimbursements/expense_claims/budget_support 统一复用 is_application_claim_no 判定申请单 - 同步 document_numbering 单元测试覆盖新旧两种格式
This commit is contained in:
@@ -11,6 +11,8 @@ from sqlalchemy.pool import StaticPool
|
||||
from app.db.base import Base
|
||||
from app.models.financial_record import ExpenseClaim
|
||||
from app.services.document_numbering import (
|
||||
DOCUMENT_NUMBER_EXTRACT_PATTERN,
|
||||
DOCUMENT_NUMBER_PATTERN,
|
||||
build_document_number,
|
||||
generate_unique_expense_claim_no,
|
||||
is_application_claim_no,
|
||||
@@ -32,19 +34,37 @@ def test_build_document_number_uses_kind_prefix_timestamp_and_token() -> None:
|
||||
timestamp = datetime(2026, 5, 25, 10, 30, 45, tzinfo=UTC)
|
||||
|
||||
assert (
|
||||
build_document_number("application", timestamp=timestamp, token="ABCDEFGH")
|
||||
== "AP-20260525103045-ABCDEFGH"
|
||||
build_document_number("application", timestamp=timestamp, token="7K3M9Q2P")
|
||||
== "A7K3M9Q2P"
|
||||
)
|
||||
assert (
|
||||
build_document_number("reimbursement", timestamp=timestamp, token="ABCDEFGH")
|
||||
== "RE-20260525103045-ABCDEFGH"
|
||||
build_document_number("reimbursement", timestamp=timestamp, token="7K3M9Q2P")
|
||||
== "R7K3M9Q2P"
|
||||
)
|
||||
assert (
|
||||
build_document_number("audit", timestamp=timestamp, token="ABCDEFGH")
|
||||
== "AD-20260525103045-ABCDEFGH"
|
||||
build_document_number("audit", timestamp=timestamp, token="7K3M9Q2P")
|
||||
== "D7K3M9Q2P"
|
||||
)
|
||||
|
||||
|
||||
def test_document_number_pattern_accepts_short_and_legacy_numbers() -> None:
|
||||
assert DOCUMENT_NUMBER_PATTERN.fullmatch("A7K3M9Q2P")
|
||||
assert DOCUMENT_NUMBER_PATTERN.fullmatch("R7K3M9Q2P")
|
||||
assert DOCUMENT_NUMBER_PATTERN.fullmatch("D7K3M9Q2P")
|
||||
assert DOCUMENT_NUMBER_PATTERN.fullmatch("AP-20260525103045-ABCDEFGH")
|
||||
assert DOCUMENT_NUMBER_PATTERN.fullmatch("RE-20260525103045-HGFEDCBA")
|
||||
|
||||
|
||||
def test_document_number_extract_pattern_finds_short_and_legacy_numbers() -> None:
|
||||
query = "查看 A7K3M9Q2P、R7K3M9Q2P 和 AP-20260525103045-ABCDEFGH 的状态"
|
||||
|
||||
assert [match.group(0) for match in DOCUMENT_NUMBER_EXTRACT_PATTERN.finditer(query)] == [
|
||||
"A7K3M9Q2P",
|
||||
"R7K3M9Q2P",
|
||||
"AP-20260525103045-ABCDEFGH",
|
||||
]
|
||||
|
||||
|
||||
def test_build_document_number_rejects_ambiguous_token_chars() -> None:
|
||||
timestamp = datetime(2026, 5, 25, 10, 30, 45, tzinfo=UTC)
|
||||
|
||||
@@ -57,7 +77,7 @@ def test_generate_unique_expense_claim_no_retries_existing_candidate() -> None:
|
||||
with build_session() as db:
|
||||
db.add(
|
||||
ExpenseClaim(
|
||||
claim_no="RE-20260525103045-ABCDEFGH",
|
||||
claim_no="RABCDEFGH",
|
||||
employee_name="张三",
|
||||
department_name="市场部",
|
||||
project_code=None,
|
||||
@@ -84,11 +104,13 @@ def test_generate_unique_expense_claim_no_retries_existing_candidate() -> None:
|
||||
timestamp=timestamp,
|
||||
token_factory=lambda: next(tokens),
|
||||
)
|
||||
== "RE-20260525103045-HGFEDCBA"
|
||||
== "RHGFEDCBA"
|
||||
)
|
||||
|
||||
|
||||
def test_is_application_claim_no_supports_new_and_legacy_prefixes() -> None:
|
||||
assert is_application_claim_no("A7K3M9Q2P")
|
||||
assert is_application_claim_no("AP-20260525103045-ABCDEFGH")
|
||||
assert is_application_claim_no("APP-20260525-ABC123")
|
||||
assert not is_application_claim_no("R7K3M9Q2P")
|
||||
assert not is_application_claim_no("RE-20260525103045-ABCDEFGH")
|
||||
|
||||
Reference in New Issue
Block a user