ixg_platform/apps/qc/serializers.py
ixgadmin 08096e168f P99C: compliance automation - CoA auto-generation and EUDR due diligence
- Versioned product specification table (Unrefined Shea Butter v1.0 seed) with
  spec-based QC evaluation replacing hard-coded thresholds
- Structured QC results with test methods and four-eyes verification
  (submitter cannot self-approve)
- reportlab IXG Quality Summary PDF auto-generated on quality approval,
  SHA-256 hashed; original independent lab PDF kept authoritative
- CertificateOfAnalysis version/status/hash/approval/supersede lifecycle;
  commercial release publishes to buyers and triggers notification
- EUDR evidence workflow: Facility, OriginZone, CollectorGroup, HarvestIntake,
  IntakeBatchLineage, DueDiligenceCase, EUDREvidence, RiskAssessment,
  MitigationAction, DueDiligenceStatementReference with rule-based
  completeness check and risk assessment (no AI legal conclusions)
- Ops portal: QC Approvals, CoA Reviews, EUDR Compliance screens
- Buyer portal: /portal/compliance page
- 22 automated tests covering four-eyes, CAPA, PDF+hash, publish/notify,
  EUDR blocks and buyer isolation
2026-08-05 13:58:39 +00:00

59 lines
2.2 KiB
Python

from rest_framework import serializers
from .models import QCResult
from apps.batches.models import ProcessingBatch
from apps.capa.models import CAPARecord
from apps.compliance.services import evaluate_qc_data
class QCResultSerializer(serializers.ModelSerializer):
entered_by_name = serializers.CharField(source="entered_by.full_name", read_only=True)
verified_by_name = serializers.CharField(source="verified_by.full_name", read_only=True)
class Meta:
model = QCResult
fields = "__all__"
read_only_fields = ["id", "overall_pass", "tested_at", "entered_by", "verified_by"]
class QCResultCreateSerializer(serializers.ModelSerializer):
class Meta:
model = QCResult
fields = [
"batch", "ffa_percentage", "moisture_percentage", "peroxide_value", "visual_grade",
"tested_by", "lab_reference",
"method_ffa", "method_moisture", "method_peroxide",
]
def create(self, validated_data):
values = {
"ffa": validated_data["ffa_percentage"],
"moisture": validated_data["moisture_percentage"],
"peroxide": validated_data["peroxide_value"],
"visual_grade": validated_data["visual_grade"],
}
overall_pass, result_status, _details, spec_version = evaluate_qc_data(values)
validated_data["overall_pass"] = overall_pass
validated_data["result_status"] = result_status
validated_data["spec_version"] = spec_version
user = self.context.get("request").user if self.context.get("request") else None
validated_data["entered_by"] = user
validated_data["is_quarantine"] = not overall_pass
qc_result = super().create(validated_data)
batch = validated_data["batch"]
if overall_pass:
batch.status = ProcessingBatch.Status.QC_PASS
else:
batch.status = ProcessingBatch.Status.QC_FAIL
batch.save(update_fields=["status", "updated_at"])
if not overall_pass:
CAPARecord.objects.create(
batch=batch,
issue_description=f"QC failure on batch {batch.batch_reference}",
status=CAPARecord.Status.OPEN,
)
return qc_result