"""Forms for the ops portal. The intake form is deliberately **stricter than the model**. ``HarvestIntake`` allows almost everything to be blank, because records also arrive through the API, and the platform copes by flagging the gaps later: the EUDR completeness check has an "intake data" test whose whole job is to catch a missing date or weight. A portal form that let the same blanks through would simply move that problem to a screen where a person could have prevented it. So every field the checks read is required here — reference, collector group, origin zone, date, weight and grade — while the fields that are genuinely optional at intake (receiving officer, the supporting file's name) stay optional. """ from django import forms from apps.compliance.models import CollectorGroup, HarvestIntake, OriginZone # The visual grades the product specification defines. GRADE_CHOICES = [("A", "Grade A"), ("B", "Grade B"), ("C", "Grade C")] class ZoneChoiceField(forms.ModelChoiceField): """Zone dropdown that says which zones are not verified yet. Geolocation in the EUDR case passes only for a verified zone, so an operator choosing one should be able to see that before it fails a check. It is a label, not a block: an intake can legitimately be recorded against a zone that is still awaiting verification. """ def label_from_instance(self, zone): where = zone.region or zone.country or "" mark = "" if zone.verification_status != OriginZone.VerificationStatus.VERIFIED: mark = f" — {zone.get_verification_status_display().lower()}" return f"{zone.zone_code}{f' ({where})' if where else ''}{mark}" class CollectorGroupChoiceField(forms.ModelChoiceField): """Group dropdown that names the zone each group draws from.""" def label_from_instance(self, group): if not group.origin_zone: return f"{group.name} — no origin zone set" return f"{group.name} — {group.origin_zone.zone_code}" class HarvestIntakeForm(forms.ModelForm): collector_group = CollectorGroupChoiceField( queryset=CollectorGroup.objects.select_related("origin_zone").order_by("name"), label="Collector group", help_text="The registered group that delivered the material.", empty_label="Select the collector group", ) origin_zone = ZoneChoiceField( queryset=OriginZone.objects.order_by("zone_code"), label="Origin zone", help_text="Where it was collected. Filled in from the group, and can be changed.", empty_label="Select the origin zone", ) grade = forms.ChoiceField( choices=GRADE_CHOICES, label="Grade", help_text="Visual grade recorded at intake, as defined by the product specification.", widget=forms.Select(attrs={"class": "form-select"}), ) class Meta: model = HarvestIntake fields = [ "intake_reference", "collector_group", "origin_zone", "intake_date", "weight_kg", "grade", "receiving_officer", "status", "evidence_file", ] labels = { "intake_reference": "Intake reference", "intake_date": "Intake date", "weight_kg": "Weight (kg)", "receiving_officer": "Receiving officer", "evidence_file": "Supporting file name", } help_texts = { "intake_reference": "Must be unique. Suggested from the last intake recorded, and can be overwritten to match the weighbridge ticket.", "intake_date": "The day the material was received.", "weight_kg": "Weight received, in kilograms.", "receiving_officer": "Who received and weighed it. Defaults to you.", "status": "Received is the normal state for a new intake; Verified is set once the record has been checked.", "evidence_file": "The name of the supporting record, typically the weighbridge ticket. Files are not uploaded by the platform yet, so this is the name it is filed under.", } widgets = { "intake_reference": forms.TextInput( attrs={"class": "form-control", "autocomplete": "off"} ), "collector_group": forms.Select(attrs={"class": "form-select"}), "origin_zone": forms.Select(attrs={"class": "form-select"}), "intake_date": forms.DateInput( attrs={"class": "form-control", "type": "date"} ), "weight_kg": forms.NumberInput( attrs={"class": "form-control", "step": "0.01", "min": "0.01"} ), "receiving_officer": forms.TextInput(attrs={"class": "form-control"}), "status": forms.Select(attrs={"class": "form-select"}), "evidence_file": forms.TextInput(attrs={"class": "form-control"}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # The model tolerates blanks; the portal form does not. See the module # docstring: these are the fields the completeness checks read. for name in ("collector_group", "origin_zone", "intake_date", "weight_kg"): self.fields[name].required = True self.fields["status"].required = True def clean_intake_reference(self): reference = (self.cleaned_data["intake_reference"] or "").strip() if not reference: raise forms.ValidationError("An intake reference is required.") clashes = HarvestIntake.objects.filter(intake_reference__iexact=reference) if self.instance.pk: clashes = clashes.exclude(pk=self.instance.pk) if clashes.exists(): raise forms.ValidationError( f"Intake {reference} already exists. References must be unique." ) return reference def clean_weight_kg(self): weight = self.cleaned_data["weight_kg"] if weight is None: raise forms.ValidationError("A weight is required.") if weight <= 0: raise forms.ValidationError("Weight must be greater than zero.") return weight def clean(self): cleaned = super().clean() group = cleaned.get("collector_group") zone = cleaned.get("origin_zone") if group and zone and group.origin_zone_id and group.origin_zone_id != zone.pk: # Not an error — a group can deliver from more than one area — but it # is worth surfacing, because the two records will disagree. self.add_error( "origin_zone", forms.ValidationError( f"{group.name} is registered to {group.origin_zone.zone_code}, " f"not {zone.zone_code}. Save it this way only if that is intended." ), ) return cleaned