update api export for executive and fixing closing cycle flow
This commit is contained in:
1 parent
d0d1ae7d24
commit
07d36081ce
23 files changed
+1378
-101
No files matched your search
+67
-20
@@ -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)
|
||||
Reference in new issue
Block a user