update sync data from site dashboard and testing closing cycles

This commit is contained in:
Alberto-Audrix committed 2026-09-15 16:52:41 +07:00
1 parent 36639d8c38
commit f49e72592c
42 files changed
+1966 -688

No files matched your search

+29 -135
View File
@@ -15,14 +15,6 @@ from apps.accounts.scoping import (
from apps.farms.models import Cycle, Flock, Kandang, Site
from apps.farms.serializers import CycleSerializer, FlockSerializer, KandangSerializer, SiteSerializer
from apps.farms.services.site_close import SiteClosePushError, push_close_decision
from apps.operations.services.feed_sacks import (
cleanup_opening_balance_feed_rows,
recompute_feed_sack_in_totals,
)
from apps.operations.services.karung_web import (
initial_balance_accuracy,
pull_initial_balance_iot,
)
from apps.farms.services.active_site_registry import ensure_hq_site_for_active_site
from apps.sync.models import ActiveSite
@@ -61,11 +53,35 @@ def _upsert_mirror_cycle_from_ingest(active_site: ActiveSite, payload: dict) ->
# Prefer ingest site_name when it does not collide.
site.site_name = site_name
site.save(update_fields=["site_name", "updated_at"])
kandang, _ = Kandang.objects.get_or_create(
site=site,
kandang_name=kandang_name,
defaults={},
)
source_kandang_raw = payload.get("source_kandang_id", payload.get("kandang_id"))
source_kandang_id = None
if source_kandang_raw not in (None, ""):
try:
source_kandang_id = int(source_kandang_raw)
except (TypeError, ValueError) as exc:
raise ValidationError("source_kandang_id must be an integer.") from exc
kandang = None
if source_kandang_id is not None:
kandang = Kandang.objects.filter(
site=site, source_kandang_id=source_kandang_id
).first()
if kandang is None:
kandang = Kandang.objects.create(
site=site,
kandang_name=kandang_name,
source_kandang_id=source_kandang_id,
)
elif kandang.kandang_name != kandang_name:
kandang.kandang_name = kandang_name
kandang.save(update_fields=["kandang_name", "updated_at"])
if kandang is None:
kandang, _ = Kandang.objects.get_or_create(
site=site,
kandang_name=kandang_name,
defaults={},
)
cycle = Cycle.objects.filter(
active_site=active_site, source_cycle_id=source_cycle_id
@@ -266,125 +282,3 @@ class CycleViewSet(viewsets.ModelViewSet):
if site_error:
return Response({"cycle": data, "site_error": site_error})
return Response(data)
@action(detail=True, methods=["get"], url_path="initial-balance-compare")
def compare_initial_balance(self, request, pk=None):
"""Pull IoT masuk for a date and compare against the cycle saldo awal."""
cycle = self.get_object()
date_str = request.query_params.get("date")
if not date_str:
return Response({"detail": "date is required."}, status=status.HTTP_400_BAD_REQUEST)
balance_date = parse_date(str(date_str))
if balance_date is None:
return Response(
{"detail": "date must be in YYYY-MM-DD format."},
status=status.HTTP_400_BAD_REQUEST,
)
manual_raw = request.query_params.get("manual")
if manual_raw is None:
feed_in_manual = cycle.feed_initial_balance
else:
try:
feed_in_manual = int(manual_raw)
except (TypeError, ValueError):
return Response(
{"detail": "manual must be an integer."},
status=status.HTTP_400_BAD_REQUEST,
)
iot_in, sync_error = pull_initial_balance_iot(cycle, balance_date)
return Response(
{
"cycle": cycle.pk,
"date": balance_date.isoformat(),
"feed_initial_balance": cycle.feed_initial_balance,
"feed_initial_balance_date": (
cycle.feed_initial_balance_date.isoformat()
if cycle.feed_initial_balance_date
else None
),
"manual": feed_in_manual,
"manual_total": feed_in_manual,
"iot": iot_in,
"accuracy": initial_balance_accuracy(feed_in_manual, iot_in),
"sync_error": sync_error,
}
)
@action(detail=True, methods=["post"], url_path="initial-balance")
def set_initial_balance(self, request, pk=None):
"""Save the feed-in balance for the cycle (Saldo Awal).
Sets ``feed_initial_balance`` on the cycle (the only cycle field this
feature writes — never during cycle creation). Opening balance is kept on
the cycle for saldo only and is not written into daily FeedSacks rows.
Pulls the IoT-detected "in" for that date from karung-web-admin
``/api/combined`` and returns a manual vs IoT accuracy figure.
"""
cycle = self.get_object()
date_str = request.data.get("date")
if not date_str:
return Response({"detail": "date is required."}, status=status.HTTP_400_BAD_REQUEST)
balance_date = parse_date(str(date_str))
if balance_date is None:
return Response(
{"detail": "date must be in YYYY-MM-DD format."},
status=status.HTTP_400_BAD_REQUEST,
)
try:
feed_in_manual = int(request.data.get("feed_in_manual"))
except (TypeError, ValueError):
return Response(
{"detail": "feed_in_manual must be an integer."},
status=status.HTTP_400_BAD_REQUEST,
)
if feed_in_manual < 0:
return Response(
{"detail": "feed_in_manual cannot be negative."},
status=status.HTTP_400_BAD_REQUEST,
)
previous_date = cycle.feed_initial_balance_date
previous_balance = cycle.feed_initial_balance
cycle.feed_initial_balance = feed_in_manual
cycle.feed_initial_balance_date = balance_date
cycle.save(
update_fields=["feed_initial_balance", "feed_initial_balance_date", "updated_at"]
)
# Pull IoT "in" at the balance date from karung-web-admin /api/combined.
iot_in, sync_error = pull_initial_balance_iot(cycle, balance_date)
cleanup_opening_balance_feed_rows(
cycle,
previous_date=previous_date,
previous_balance=previous_balance,
balance_date=balance_date,
balance_amount=feed_in_manual,
)
recompute_feed_sack_in_totals(cycle)
accuracy = initial_balance_accuracy(feed_in_manual, iot_in)
return Response(
{
"cycle": cycle.pk,
"date": balance_date.isoformat(),
"feed_initial_balance": cycle.feed_initial_balance,
"feed_initial_balance_date": (
cycle.feed_initial_balance_date.isoformat()
if cycle.feed_initial_balance_date
else None
),
"manual": feed_in_manual,
"manual_total": feed_in_manual,
"iot": iot_in,
"accuracy": accuracy,
"sync_error": sync_error,
}
)