ixg_platform/apps/batches/serializers.py
ixgadmin 88eede5d7a Restrict the processing-batch API to ops/admin
Any authenticated user could PATCH a batch, and the write serializer accepted
status, so a buyer could move a qc_fail batch to delivered. The buyer portal
reads batches through the ORM, not this API, so the whole viewset is closed to
ops/admin and the write serializer refuses a hand-set QC status and any
transition out of qc_fail.
2026-09-17 14:48:09 +00:00

85 lines
3.1 KiB
Python

from rest_framework import serializers
from .models import ProcessingBatch
class ProcessingBatchListSerializer(serializers.ModelSerializer):
qc_status = serializers.SerializerMethodField()
shipment_status = serializers.SerializerMethodField()
class Meta:
model = ProcessingBatch
fields = ["id", "batch_reference", "origin_facility", "grade", "weight_kg", "harvest_date", "status", "qc_status", "shipment_status", "created_at"]
def get_qc_status(self, obj):
qc = obj.qc_results.first()
if qc:
return "pass" if qc.overall_pass else "fail"
return "pending"
def get_shipment_status(self, obj):
shipment = obj.shipments.first()
return shipment.status if shipment else None
class ProcessingBatchDetailSerializer(serializers.ModelSerializer):
created_by_name = serializers.CharField(source="created_by.full_name", read_only=True)
qc_results = serializers.SerializerMethodField()
shipments = serializers.SerializerMethodField()
documents = serializers.SerializerMethodField()
class Meta:
model = ProcessingBatch
fields = "__all__"
def get_qc_results(self, obj):
qc = obj.qc_results.first()
if not qc:
return None
from apps.qc.serializers import QCResultSerializer
return QCResultSerializer(qc).data
def get_shipments(self, obj):
shipments = obj.shipments.all()
from apps.shipments.serializers import ShipmentSerializer
return ShipmentSerializer(shipments, many=True).data
def get_documents(self, obj):
docs = obj.documents.all()
from apps.documents.serializers import DocumentSerializer
return DocumentSerializer(docs, many=True).data
# Mirrors apps/ops/forms.py: a QC status is set by recording a QC result, not by
# hand, and a failed batch is sticky — only a new result moves it. The portal
# enforces this in BatchForm; without the same rule here the write serializer is
# a way around the screen.
QC_SET_STATUSES = {
ProcessingBatch.Status.QC_PASS,
ProcessingBatch.Status.QC_FAIL,
}
class ProcessingBatchWriteSerializer(serializers.ModelSerializer):
class Meta:
model = ProcessingBatch
fields = ["id", "batch_reference", "harvest_date", "weight_kg", "grade", "origin_facility", "notes", "status"]
read_only_fields = ["id"]
def validate_status(self, status):
instance = self.instance
unchanged = instance is not None and status == instance.status
if status in QC_SET_STATUSES and not unchanged:
raise serializers.ValidationError(
f"Batch status {ProcessingBatch.Status(status).label} is set by the "
"QC result, not by hand."
)
if (
instance is not None
and instance.status == ProcessingBatch.Status.QC_FAIL
and status != ProcessingBatch.Status.QC_FAIL
):
raise serializers.ValidationError(
"A batch that has failed QC cannot be moved by hand; a new QC "
"result is what moves it."
)
return status