feat(server): 报销单输出工号/邮箱并扩展申请人邮箱前缀匹配
- ExpenseClaimRead 新增 employee_no/employee_email 字段,ExpenseClaim 模型补对应只读属性 - expense_claim_access_policy 在姓名匹配未果时,按 candidate@% 邮箱前缀匹配 Employee.email,命中唯一记录即返回 - test_backend_pagination/test_expense_claim_service 补充工号/邮箱字段断言与邮箱前缀匹配用例 - 更新公司通信费报销规则表
This commit is contained in:
Binary file not shown.
@@ -58,6 +58,14 @@ class ExpenseClaim(Base):
|
||||
def employee_position(self) -> str | None:
|
||||
return str(self.employee.position).strip() if self.employee is not None and self.employee.position else None
|
||||
|
||||
@property
|
||||
def employee_no(self) -> str | None:
|
||||
return str(self.employee.employee_no).strip() if self.employee is not None and self.employee.employee_no else None
|
||||
|
||||
@property
|
||||
def employee_email(self) -> str | None:
|
||||
return str(self.employee.email).strip() if self.employee is not None and self.employee.email else None
|
||||
|
||||
@property
|
||||
def employee_grade(self) -> str | None:
|
||||
return str(self.employee.grade).strip() if self.employee is not None and self.employee.grade else None
|
||||
|
||||
@@ -144,6 +144,8 @@ class ExpenseClaimRead(BaseModel):
|
||||
claim_no: str
|
||||
employee_id: str | None
|
||||
employee_name: str
|
||||
employee_no: str | None = None
|
||||
employee_email: str | None = None
|
||||
department_id: str | None
|
||||
department_name: str
|
||||
employee_position: str | None = None
|
||||
|
||||
@@ -509,6 +509,20 @@ class ExpenseClaimAccessPolicy:
|
||||
if employee is not None:
|
||||
return employee
|
||||
|
||||
for candidate in normalized_candidates:
|
||||
if self.is_email_like(candidate):
|
||||
continue
|
||||
matches = list(
|
||||
self.db.scalars(
|
||||
select(Employee)
|
||||
.options(*load_options)
|
||||
.where(func.lower(Employee.email).like(f"{candidate.lower()}@%"))
|
||||
.limit(2)
|
||||
).all()
|
||||
)
|
||||
if len(matches) == 1:
|
||||
return matches[0]
|
||||
|
||||
for candidate in normalized_candidates:
|
||||
matches = list(
|
||||
self.db.scalars(
|
||||
|
||||
@@ -105,6 +105,8 @@ def test_expense_claims_support_page_envelope_and_keep_legacy_list() -> None:
|
||||
assert payload["total_pages"] == 2
|
||||
assert payload["has_next"] is True
|
||||
assert payload["has_previous"] is False
|
||||
assert payload["items"][0]["employee_no"] == "E-PAGE"
|
||||
assert payload["items"][0]["employee_email"] == "page-user@example.com"
|
||||
|
||||
|
||||
def test_employee_directory_supports_backend_pagination() -> None:
|
||||
|
||||
@@ -4301,6 +4301,50 @@ def test_list_claims_scopes_to_current_user_id_even_when_names_duplicate() -> No
|
||||
assert claims[0].claim_no == "EXP-DUP-001"
|
||||
|
||||
|
||||
def test_list_claims_resolves_short_username_to_unique_employee_email_prefix() -> None:
|
||||
current_user = CurrentUserContext(
|
||||
username="caoxiaozhu",
|
||||
name="caoxiaozhu",
|
||||
role_codes=["employee"],
|
||||
is_admin=False,
|
||||
)
|
||||
|
||||
with build_session() as db:
|
||||
employee = Employee(
|
||||
employee_no="E90919",
|
||||
name="曹笑竹",
|
||||
email="caoxiaozhu@xf.com",
|
||||
)
|
||||
db.add(employee)
|
||||
db.flush()
|
||||
db.add(
|
||||
ExpenseClaim(
|
||||
claim_no="AP-SHORT-USERNAME-001",
|
||||
employee_id=employee.id,
|
||||
employee_name=employee.name,
|
||||
department_name="研发部",
|
||||
project_code="PRJ-APP",
|
||||
expense_type="travel_application",
|
||||
reason="客户现场实施",
|
||||
location="北京",
|
||||
amount=Decimal("1600.00"),
|
||||
currency="CNY",
|
||||
invoice_count=0,
|
||||
occurred_at=datetime(2026, 6, 23, 9, 0, tzinfo=UTC),
|
||||
submitted_at=datetime(2026, 6, 23, 10, 0, tzinfo=UTC),
|
||||
status="approved",
|
||||
approval_stage=APPROVAL_DONE_STAGE,
|
||||
risk_flags_json=[],
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
claims = ExpenseClaimService(db).list_claims(current_user)
|
||||
|
||||
assert len(claims) == 1
|
||||
assert claims[0].claim_no == "AP-SHORT-USERNAME-001"
|
||||
|
||||
|
||||
def test_list_claims_limits_finance_to_personal_records() -> None:
|
||||
current_user = CurrentUserContext(
|
||||
username="finance@example.com",
|
||||
|
||||
Reference in New Issue
Block a user