update api export for executive and fixing closing cycle flow

This commit is contained in:
Alberto-Audrix committed 2026-09-15 16:53:25 +07:00
1 parent d0d1ae7d24
commit 07d36081ce
23 files changed
+1378 -101

No files matched your search

+67 -20
View File
@@ -1,16 +1,22 @@
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.dateparse import parse_date
from rest_framework import permissions, status, viewsets
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.response import Response
from rest_framework.views import APIView
import secrets
from apps.accounts.authentication import ApiKeyAuthentication
from apps.accounts.models import User
from apps.farms.models import Cycle, Flock, Kandang, Site
from apps.farms.serializers import CycleSerializer, FlockSerializer, KandangSerializer, SiteSerializer
from apps.farms.services.pusat_close import PusatCloseError, notify_pusat_close_request
from apps.farms.services.pusat_export import (
PusatExportError,
build_iot_export,
build_ops_export,
resolve_iot_export_params,
resolve_ops_export_params,
)
from apps.farms.services.pusat_register import PusatRegisterError, register_site_to_pusat
from apps.operations.services.feed_sacks import (
cleanup_opening_balance_feed_rows,
@@ -30,23 +36,19 @@ def _validation_detail(exc: ValidationError) -> str | dict:
return str(exc.message) if hasattr(exc, "message") else str(exc)
def _pusat_api_key_from_request(request) -> str | None:
return request.headers.get("X-API-Key") or request.META.get("HTTP_X_API_KEY")
class IsApiKeyAuthenticated(permissions.BasePermission):
"""Require authentication via hashed site ApiKey (reject session-only users)."""
class IsPusatCloseCallback(permissions.BasePermission):
"""Allow only the shared pusat callback key (not session users)."""
message = "Pusat API key required."
message = "Site API key required."
def has_permission(self, request, view):
expected = (getattr(settings, "PUSAT_API_KEY", "") or "").strip()
if not expected:
return False
provided = (_pusat_api_key_from_request(request) or "").strip()
if not provided:
return False
return secrets.compare_digest(provided, expected)
from apps.accounts.models import ApiKey
return bool(
request.user
and request.user.is_authenticated
and isinstance(getattr(request, "auth", None), ApiKey)
)
def _revert_pending_close(cycle: Cycle) -> None:
@@ -286,11 +288,11 @@ class CycleViewSet(viewsets.ModelViewSet):
detail=True,
methods=["post"],
url_path="apply-close-decision",
authentication_classes=[],
permission_classes=[IsPusatCloseCallback],
authentication_classes=[ApiKeyAuthentication],
permission_classes=[IsApiKeyAuthenticated],
)
def apply_close_decision(self, request, pk=None):
"""Machine endpoint: pusat pushes approve/reject via shared PUSAT_API_KEY."""
"""Machine endpoint: pusat pushes approve/reject with the site SITE_API_KEY."""
cycle = self.get_object()
decision = str(request.data.get("decision", "")).strip().lower()
if decision not in {"approve", "reject"}:
@@ -434,3 +436,48 @@ class CycleViewSet(viewsets.ModelViewSet):
"sync_error": sync_error,
}
)
class PusatOpsExportView(APIView):
"""Machine pull: sites → kandangs → cycles + ops time-series (no publish cutoff)."""
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
params = request.query_params
try:
resolved = resolve_ops_export_params(
site_id=params.get("site_id"),
cycle_id=params.get("cycle_id"),
date_from=params.get("from"),
date_to=params.get("to"),
page=params.get("page"),
page_size=params.get("page_size"),
)
payload = build_ops_export(**resolved)
except PusatExportError as exc:
return Response({"detail": exc.message}, status=status.HTTP_400_BAD_REQUEST)
return Response(payload)
class PusatIotExportView(APIView):
"""Machine pull: sites → kandangs → flocks + iot_panels (no publish cutoff)."""
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
params = request.query_params
try:
resolved = resolve_iot_export_params(
site_id=params.get("site_id"),
kandang_id=params.get("kandang_id"),
flock_id=params.get("flock_id"),
date_from=params.get("from"),
date_to=params.get("to"),
page=params.get("page"),
page_size=params.get("page_size"),
)
payload = build_iot_export(**resolved)
except PusatExportError as exc:
return Response({"detail": exc.message}, status=status.HTTP_400_BAD_REQUEST)
return Response(payload)