feat(expense): add persistent zero-entry receipt association
This commit is contained in:
110
server/src/app/models/attachment_association_job.py
Normal file
110
server/src/app/models/attachment_association_job.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
CheckConstraint,
|
||||
DateTime,
|
||||
Float,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.types import JSON
|
||||
|
||||
from app.db.base_class import Base
|
||||
|
||||
|
||||
class AttachmentAssociationJob(Base):
|
||||
"""可跨进程恢复的票据归集任务状态。"""
|
||||
|
||||
__tablename__ = "attachment_association_jobs"
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"owner_username",
|
||||
"dedupe_key",
|
||||
"generation",
|
||||
name="uq_attachment_association_jobs_owner_dedupe",
|
||||
),
|
||||
CheckConstraint(
|
||||
"status IN ('queued', 'running', 'succeeded', 'failed')",
|
||||
name="ck_attachment_association_jobs_status",
|
||||
),
|
||||
CheckConstraint(
|
||||
"attempt_count >= 0",
|
||||
name="ck_attachment_association_jobs_attempt_count",
|
||||
),
|
||||
CheckConstraint(
|
||||
"generation >= 1",
|
||||
name="ck_attachment_association_jobs_generation",
|
||||
),
|
||||
CheckConstraint(
|
||||
"status != 'running' OR lease_expires_at IS NOT NULL",
|
||||
name="ck_attachment_association_jobs_running_lease",
|
||||
),
|
||||
Index(
|
||||
"ix_attachment_association_jobs_owner_time",
|
||||
"tenant_id",
|
||||
"owner_username",
|
||||
"created_at",
|
||||
),
|
||||
Index(
|
||||
"ix_attachment_association_jobs_status_lease",
|
||||
"status",
|
||||
"lease_expires_at",
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[str] = mapped_column(String(80), primary_key=True)
|
||||
tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
owner_username: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
owner_name: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|
||||
owner_context_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
||||
dedupe_key: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
generation: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
||||
receipt_ids_json: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
||||
prompt: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||||
conversation_id: Mapped[str] = mapped_column(String(120), nullable=False, default="")
|
||||
status: Mapped[str] = mapped_column(String(20), nullable=False, default="queued")
|
||||
message: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||||
claim_id: Mapped[str] = mapped_column(String(36), nullable=False, default="")
|
||||
claim_no: Mapped[str] = mapped_column(String(80), nullable=False, default="")
|
||||
uploaded_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
skipped_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
resolution: Mapped[str] = mapped_column(String(40), nullable=False, default="pending")
|
||||
requires_confirmation: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
expense_case_id: Mapped[str] = mapped_column(String(36), nullable=False, default="")
|
||||
application_claim_id: Mapped[str] = mapped_column(String(36), nullable=False, default="")
|
||||
application_claim_no: Mapped[str] = mapped_column(String(80), nullable=False, default="")
|
||||
confidence: Mapped[str] = mapped_column(String(20), nullable=False, default="")
|
||||
confidence_score: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
|
||||
match_reasons_json: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
||||
exceptions_json: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
||||
missing_fields_json: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
||||
risk_items_json: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
|
||||
candidates_json: Mapped[list[dict[str, Any]]] = mapped_column(JSON, nullable=False, default=list)
|
||||
draft_payload_json: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
|
||||
error: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||||
attempt_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
lease_expires_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
nullable=False,
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
nullable=False,
|
||||
)
|
||||
Reference in New Issue
Block a user