refactor(backend): update and add service layers
- services/ontology.py: update ontology service - services/orchestrator.py: update orchestrator service - services/user_agent.py: update user agent service - services/settings.py: update settings service - services/expense_claims.py: update expense claims service - services/agent_conversations.py: add new agent conversations service
This commit is contained in:
@@ -40,6 +40,7 @@ class ExpenseClaimService:
|
||||
self._ensure_ready()
|
||||
|
||||
claim = self._find_target_claim(ontology=ontology, context_json=context_json)
|
||||
is_new_claim = claim is None
|
||||
before_json = self._serialize_claim(claim) if claim is not None else None
|
||||
|
||||
employee = self._resolve_employee(ontology=ontology, context_json=context_json)
|
||||
@@ -47,12 +48,30 @@ class ExpenseClaimService:
|
||||
occurred_at = self._resolve_occurred_at(ontology)
|
||||
expense_type = self._resolve_expense_type(ontology.entities)
|
||||
location = self._resolve_location(message=message, context_json=context_json)
|
||||
reason = self._resolve_reason(message=message, context_json=context_json)
|
||||
reason = self._resolve_reason(
|
||||
message=message,
|
||||
context_json=context_json,
|
||||
allow_message_fallback=is_new_claim,
|
||||
)
|
||||
attachment_count = self._resolve_attachment_count(context_json)
|
||||
|
||||
final_amount = amount if amount is not None else (claim.amount if claim is not None else Decimal("0.00"))
|
||||
final_occurred_at = (
|
||||
occurred_at if occurred_at is not None else (claim.occurred_at if claim is not None else datetime.now(UTC))
|
||||
)
|
||||
final_expense_type = expense_type or (claim.expense_type if claim is not None else "other")
|
||||
final_location = location or (claim.location if claim is not None else "待补充")
|
||||
final_reason = reason or (claim.reason if claim is not None else "待补充")
|
||||
final_attachment_count = (
|
||||
attachment_count if attachment_count > 0 else int(claim.invoice_count or 0) if claim is not None else 0
|
||||
)
|
||||
final_risk_flags = list(ontology.risk_flags) or (
|
||||
list(claim.risk_flags_json or []) if claim is not None else []
|
||||
)
|
||||
|
||||
if claim is None:
|
||||
claim = ExpenseClaim(
|
||||
claim_no=self._generate_claim_no(occurred_at),
|
||||
claim_no=self._generate_claim_no(final_occurred_at),
|
||||
employee_id=employee.id if employee is not None else None,
|
||||
employee_name=employee.name if employee is not None else self._resolve_employee_name(
|
||||
ontology=ontology,
|
||||
@@ -65,16 +84,16 @@ class ExpenseClaimService:
|
||||
context_json=context_json,
|
||||
),
|
||||
project_code=self._resolve_project_code(ontology.entities),
|
||||
expense_type=expense_type,
|
||||
reason=reason,
|
||||
location=location,
|
||||
amount=amount,
|
||||
expense_type=final_expense_type,
|
||||
reason=final_reason,
|
||||
location=final_location,
|
||||
amount=final_amount,
|
||||
currency="CNY",
|
||||
invoice_count=attachment_count,
|
||||
occurred_at=occurred_at,
|
||||
invoice_count=final_attachment_count,
|
||||
occurred_at=final_occurred_at,
|
||||
status="draft",
|
||||
approval_stage="待补充",
|
||||
risk_flags_json=list(ontology.risk_flags),
|
||||
risk_flags_json=final_risk_flags,
|
||||
)
|
||||
self.db.add(claim)
|
||||
else:
|
||||
@@ -86,6 +105,7 @@ class ExpenseClaimService:
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
user_id=user_id,
|
||||
fallback=claim.employee_name,
|
||||
)
|
||||
)
|
||||
claim.department_id = employee.organization_unit_id if employee is not None else claim.department_id
|
||||
@@ -95,24 +115,24 @@ class ExpenseClaimService:
|
||||
fallback=claim.department_name,
|
||||
)
|
||||
claim.project_code = self._resolve_project_code(ontology.entities) or claim.project_code
|
||||
claim.expense_type = expense_type or claim.expense_type
|
||||
claim.reason = reason
|
||||
claim.location = location
|
||||
claim.amount = amount
|
||||
claim.invoice_count = attachment_count
|
||||
claim.occurred_at = occurred_at
|
||||
claim.expense_type = final_expense_type
|
||||
claim.reason = final_reason
|
||||
claim.location = final_location
|
||||
claim.amount = final_amount
|
||||
claim.invoice_count = final_attachment_count
|
||||
claim.occurred_at = final_occurred_at
|
||||
claim.status = "draft"
|
||||
claim.approval_stage = "待补充"
|
||||
claim.risk_flags_json = list(ontology.risk_flags)
|
||||
claim.risk_flags_json = final_risk_flags
|
||||
|
||||
self.db.flush()
|
||||
self._upsert_primary_item(
|
||||
claim=claim,
|
||||
occurred_at=occurred_at,
|
||||
expense_type=expense_type,
|
||||
amount=amount,
|
||||
reason=reason,
|
||||
location=location,
|
||||
occurred_at=final_occurred_at,
|
||||
expense_type=final_expense_type,
|
||||
amount=final_amount,
|
||||
reason=final_reason,
|
||||
location=final_location,
|
||||
attachment_names=self._resolve_attachment_names(context_json),
|
||||
)
|
||||
self.db.commit()
|
||||
@@ -130,7 +150,7 @@ class ExpenseClaimService:
|
||||
|
||||
return {
|
||||
"message": (
|
||||
f"已创建报销草稿 {claim.claim_no},当前状态为 draft。"
|
||||
f"已{'创建' if is_new_claim else '更新'}报销草稿 {claim.claim_no},当前状态为 draft。"
|
||||
"你可以继续补充费用明细、客户单位和票据附件。"
|
||||
),
|
||||
"draft_only": True,
|
||||
@@ -229,6 +249,7 @@ class ExpenseClaimService:
|
||||
ontology: OntologyParseResult,
|
||||
context_json: dict[str, Any],
|
||||
user_id: str | None,
|
||||
fallback: str = "待补充",
|
||||
) -> str:
|
||||
for item in ontology.entities:
|
||||
if item.type == "employee" and item.value.strip():
|
||||
@@ -237,7 +258,7 @@ class ExpenseClaimService:
|
||||
value = str(context_json.get(key) or "").strip()
|
||||
if value:
|
||||
return value
|
||||
return str(user_id or "待补充").strip() or "待补充"
|
||||
return str(user_id or fallback).strip() or fallback
|
||||
|
||||
@staticmethod
|
||||
def _resolve_department_name(
|
||||
@@ -270,26 +291,33 @@ class ExpenseClaimService:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_expense_type(entities: list[OntologyEntity]) -> str:
|
||||
def _resolve_expense_type(entities: list[OntologyEntity]) -> str | None:
|
||||
for item in entities:
|
||||
if item.type == "expense_type":
|
||||
normalized = item.normalized_value.strip()
|
||||
if normalized:
|
||||
return normalized
|
||||
return "other"
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_reason(*, message: str, context_json: dict[str, Any]) -> str:
|
||||
def _resolve_reason(
|
||||
*,
|
||||
message: str,
|
||||
context_json: dict[str, Any],
|
||||
allow_message_fallback: bool,
|
||||
) -> str | None:
|
||||
request_context = context_json.get("request_context")
|
||||
if isinstance(request_context, dict):
|
||||
for key in ("reason", "title"):
|
||||
value = str(request_context.get(key) or "").strip()
|
||||
if value:
|
||||
return value
|
||||
return str(message or "").strip()[:500] or "待补充"
|
||||
if not allow_message_fallback:
|
||||
return None
|
||||
return str(message or "").strip()[:500] or None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_location(*, message: str, context_json: dict[str, Any]) -> str:
|
||||
def _resolve_location(*, message: str, context_json: dict[str, Any]) -> str | None:
|
||||
request_context = context_json.get("request_context")
|
||||
if isinstance(request_context, dict):
|
||||
for key in ("city", "location"):
|
||||
@@ -299,10 +327,10 @@ class ExpenseClaimService:
|
||||
compact = str(message or "").replace(" ", "")
|
||||
if "客户现场" in compact:
|
||||
return "客户现场"
|
||||
return "待补充"
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_occurred_at(ontology: OntologyParseResult) -> datetime:
|
||||
def _resolve_occurred_at(ontology: OntologyParseResult) -> datetime | None:
|
||||
start_date = ontology.time_range.start_date
|
||||
if start_date:
|
||||
try:
|
||||
@@ -310,10 +338,10 @@ class ExpenseClaimService:
|
||||
return datetime(parsed.year, parsed.month, parsed.day, tzinfo=UTC)
|
||||
except ValueError:
|
||||
pass
|
||||
return datetime.now(UTC)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_amount(entities: list[OntologyEntity]) -> Decimal:
|
||||
def _resolve_amount(entities: list[OntologyEntity]) -> Decimal | None:
|
||||
for item in entities:
|
||||
if item.type != "amount" or item.role == "threshold":
|
||||
continue
|
||||
@@ -321,7 +349,7 @@ class ExpenseClaimService:
|
||||
return Decimal(item.normalized_value).quantize(Decimal("0.01"))
|
||||
except (InvalidOperation, ValueError):
|
||||
continue
|
||||
return Decimal("0.00")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_attachment_names(context_json: dict[str, Any]) -> list[str]:
|
||||
|
||||
Reference in New Issue
Block a user