update add feed initial balance on cycle and fix its feature
This commit is contained in:
1 parent
91444faa31
commit
7a9d600aa6
16 files changed
+439
-88
No files matched your search
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from datetime import date
|
||||
from typing import Any
|
||||
from urllib.parse import urljoin
|
||||
@@ -18,17 +19,34 @@ class KarungWebError(Exception):
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
def masuk_source_for_kandang(kandang_name: str) -> str:
|
||||
"""Map a kandang label like ``Kandang 1`` to the karung-web-admin source ``K1 - In``."""
|
||||
match = re.search(r"(\d+)", kandang_name or "")
|
||||
if not match:
|
||||
raise ValidationError(
|
||||
f"Cannot derive IoT masuk source from kandang name: {kandang_name!r}"
|
||||
)
|
||||
return f"K{match.group(1)} - In"
|
||||
|
||||
|
||||
def _normalize_source(value: str | None) -> str:
|
||||
return (value or "").strip().casefold()
|
||||
|
||||
|
||||
class KarungWebClient:
|
||||
"""Outbound client for karung-web-admin — base URL from settings only."""
|
||||
|
||||
PATH = "/api/v1/cycles/active/sections"
|
||||
SECTIONS_PATH = "/api/v1/cycles/active/sections"
|
||||
COMBINED_PATH = "/api/combined"
|
||||
|
||||
def __init__(self, base_url: str | None = None, timeout: float | None = None):
|
||||
self.base_url = (base_url or settings.KARUNG_WEB_ADMIN_BASE_URL).rstrip("/")
|
||||
self.timeout = timeout or settings.KARUNG_WEB_ADMIN_TIMEOUT_SECONDS
|
||||
|
||||
def fetch_active_sections(self) -> dict[str, Any]:
|
||||
url = urljoin(self.base_url + "/", self.PATH.lstrip("/"))
|
||||
def _get_json(self, path: str) -> dict[str, Any]:
|
||||
if not self.base_url:
|
||||
raise KarungWebError("KARUNG_WEB_ADMIN_BASE_URL is not configured")
|
||||
url = urljoin(self.base_url + "/", path.lstrip("/"))
|
||||
try:
|
||||
with httpx.Client(timeout=self.timeout) as client:
|
||||
response = client.get(url)
|
||||
@@ -40,11 +58,59 @@ class KarungWebClient:
|
||||
status_code=response.status_code,
|
||||
)
|
||||
payload = response.json()
|
||||
if not payload.get("success", True):
|
||||
if not payload.get("success", True) and payload.get("error"):
|
||||
error = payload.get("error") or {}
|
||||
raise KarungWebError(error.get("message") or "karung-web-admin returned failure")
|
||||
return payload.get("data") or payload
|
||||
|
||||
def fetch_active_sections(self) -> dict[str, Any]:
|
||||
return self._get_json(self.SECTIONS_PATH)
|
||||
|
||||
def fetch_combined(self) -> dict[str, Any]:
|
||||
return self._get_json(self.COMBINED_PATH)
|
||||
|
||||
|
||||
def fetch_iot_masuk_for_date(target_date: date, *, kandang_name: str) -> int | None:
|
||||
"""Return IoT karung-masuk for ``target_date`` and kandang from karung-web-admin /api/combined."""
|
||||
expected_source = _normalize_source(masuk_source_for_kandang(kandang_name))
|
||||
target = target_date.isoformat()
|
||||
|
||||
client = KarungWebClient()
|
||||
data = client.fetch_combined()
|
||||
masuk = data.get("masuk") or {}
|
||||
|
||||
for row in masuk.get("history") or []:
|
||||
if row.get("date") == target and _normalize_source(row.get("source")) == expected_source:
|
||||
return int(row.get("counter_value") or 0)
|
||||
|
||||
if target_date == date.today():
|
||||
for row in masuk.get("today") or []:
|
||||
if _normalize_source(row.get("source")) == expected_source:
|
||||
return int(row.get("karung") or 0)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def initial_balance_accuracy(manual: int, iot_in: int | None) -> float | None:
|
||||
if manual > 0 and iot_in is not None and iot_in > 0:
|
||||
return round(min(manual, iot_in) / max(manual, iot_in) * 1000) / 10
|
||||
if manual == 0 and iot_in == 0:
|
||||
return 100.0
|
||||
return None
|
||||
|
||||
|
||||
def pull_initial_balance_iot(cycle: Cycle, balance_date: date) -> tuple[int | None, str | None]:
|
||||
sync_error = None
|
||||
iot_in = None
|
||||
try:
|
||||
iot_in = fetch_iot_masuk_for_date(
|
||||
balance_date,
|
||||
kandang_name=cycle.kandang.kandang_name,
|
||||
)
|
||||
except KarungWebError as exc:
|
||||
sync_error = str(exc)
|
||||
return iot_in, sync_error
|
||||
|
||||
|
||||
def request_karung(
|
||||
cycle: Cycle,
|
||||
|
||||
Reference in new issue
Block a user