Files
dashboard-cpsp/backend/apps/operations/services/karung_web.py
T
2026-08-26 13:46:05 +07:00

107 lines
3.5 KiB
Python

from __future__ import annotations
from datetime import date
from typing import Any
from urllib.parse import urljoin
import httpx
from django.conf import settings
from django.core.exceptions import ValidationError
from apps.farms.models import Cycle
from apps.operations.models import FeedSacks
class KarungWebError(Exception):
def __init__(self, message: str, status_code: int | None = None):
super().__init__(message)
self.status_code = status_code
class KarungWebClient:
"""Outbound client for karung-web-admin — base URL from settings only."""
PATH = "/api/v1/cycles/active/sections"
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("/"))
try:
with httpx.Client(timeout=self.timeout) as client:
response = client.get(url)
except httpx.HTTPError as exc:
raise KarungWebError(f"karung-web-admin request failed: {exc}") from exc
if response.status_code >= 400:
raise KarungWebError(
f"karung-web-admin HTTP {response.status_code}: {response.text[:300]}",
status_code=response.status_code,
)
payload = response.json()
if not payload.get("success", True):
error = payload.get("error") or {}
raise KarungWebError(error.get("message") or "karung-web-admin returned failure")
return payload.get("data") or payload
def request_karung(
cycle: Cycle,
*,
section: str = "today",
) -> tuple[FeedSacks, dict[str, Any]]:
if section not in {"today", "yesterday_history"}:
raise ValidationError("section must be today or yesterday_history")
client = KarungWebClient()
data = client.fetch_active_sections()
sections = data.get("sections") or {}
block = sections.get(section) or {}
totals = block.get("totals") or {}
remote_cycle = data.get("cycle") or {}
in_today = int(totals.get("masuk") or 0)
out_today = int(totals.get("tuang") or 0)
feed_use_today = out_today
saldo_awal = int(remote_cycle.get("saldo_awal") or 0)
snapshot_raw = block.get("date")
snapshot_date = date.fromisoformat(str(snapshot_raw)) if snapshot_raw else date.today()
prior = (
FeedSacks.objects.filter(cycle=cycle)
.exclude(date=snapshot_date)
.order_by("-date", "-pk")
.first()
)
if prior:
in_total = prior.in_total + in_today
out_total = prior.out_total + out_today
feed_use_total = prior.feed_use_total + feed_use_today
else:
in_total = cycle.feed_initial_balance + in_today
out_total = out_today
feed_use_total = feed_use_today
feed_sack, _ = FeedSacks.objects.update_or_create(
cycle=cycle,
date=snapshot_date,
defaults={
"in_today": in_today,
"out_today": out_today,
"in_total": in_total,
"out_total": out_total,
"feed_use_today": feed_use_today,
"feed_use_total": feed_use_total,
},
)
summary = {
"section": section,
"snapshot_date": snapshot_date.isoformat(),
"totals": totals,
"saldo_awal": saldo_awal,
"remote_cycle": remote_cycle,
}
return feed_sack, summary