107 lines
3.5 KiB
Python
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
|