fix: handle risk explanation standard adjustment
This commit is contained in:
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
from datetime import UTC, date, datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
@@ -24,6 +24,7 @@ from app.services.expense_claim_constants import (
|
||||
DOCUMENT_FACT_ITEM_TYPES,
|
||||
LOCATION_REQUIRED_EXPENSE_TYPES,
|
||||
OPTIONAL_ATTACHMENT_ITEM_TYPES,
|
||||
STANDARD_ADJUSTMENT_RISK_SOURCE,
|
||||
SYSTEM_GENERATED_ITEM_TYPES,
|
||||
TRAVEL_ALLOWANCE_TRIGGER_ITEM_TYPES,
|
||||
TRAVEL_POLICY_HOTEL_NIGHT_PATTERN,
|
||||
@@ -329,6 +330,45 @@ class ExpenseClaimItemSyncMixin:
|
||||
return destination
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def _parse_standard_adjustment_amount(value: Any) -> Decimal | None:
|
||||
try:
|
||||
raw_value = "" if value is None else value
|
||||
amount = Decimal(str(raw_value)).quantize(Decimal("0.01"))
|
||||
except (InvalidOperation, ValueError):
|
||||
return None
|
||||
return amount if amount >= Decimal("0.00") else None
|
||||
|
||||
def _collect_standard_adjusted_amounts(self, claim: ExpenseClaim) -> dict[str, Decimal]:
|
||||
adjusted_amounts: dict[str, Decimal] = {}
|
||||
for flag in list(claim.risk_flags_json or []):
|
||||
if not isinstance(flag, dict):
|
||||
continue
|
||||
if str(flag.get("source") or "").strip() != STANDARD_ADJUSTMENT_RISK_SOURCE:
|
||||
continue
|
||||
item_id = str(flag.get("item_id") or flag.get("itemId") or "").strip()
|
||||
if not item_id:
|
||||
continue
|
||||
amount = self._parse_standard_adjustment_amount(
|
||||
flag.get("reimbursable_amount") or flag.get("reimbursableAmount")
|
||||
)
|
||||
if amount is None:
|
||||
continue
|
||||
adjusted_amounts[item_id] = amount
|
||||
return adjusted_amounts
|
||||
|
||||
def _resolve_item_amount_for_claim_total(
|
||||
self,
|
||||
item: ExpenseClaimItem,
|
||||
adjusted_amounts: dict[str, Decimal],
|
||||
) -> Decimal:
|
||||
original_amount = Decimal(item.item_amount or Decimal("0.00")).quantize(Decimal("0.01"))
|
||||
item_id = str(item.id or "").strip()
|
||||
adjusted_amount = adjusted_amounts.get(item_id)
|
||||
if adjusted_amount is None:
|
||||
return original_amount
|
||||
return min(max(adjusted_amount, Decimal("0.00")), original_amount)
|
||||
|
||||
def _sync_claim_from_items(self, claim: ExpenseClaim) -> None:
|
||||
self._sync_travel_allowance_item(claim)
|
||||
if not claim.items:
|
||||
@@ -346,7 +386,11 @@ class ExpenseClaimItemSyncMixin:
|
||||
),
|
||||
)
|
||||
primary_item = ordered_items[0]
|
||||
total_amount = sum((item.item_amount for item in ordered_items), Decimal("0.00"))
|
||||
adjusted_amounts = self._collect_standard_adjusted_amounts(claim)
|
||||
total_amount = sum(
|
||||
(self._resolve_item_amount_for_claim_total(item, adjusted_amounts) for item in ordered_items),
|
||||
Decimal("0.00"),
|
||||
)
|
||||
|
||||
claim.amount = total_amount.quantize(Decimal("0.01"))
|
||||
claim.invoice_count = sum(1 for item in ordered_items if str(item.invoice_id or "").strip())
|
||||
|
||||
Reference in New Issue
Block a user