- 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
59 lines
2.2 KiB
Python
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
|