"""The ops intake screens. Two things matter here and neither is cosmetic: that only operations can create intake records, and that the form refuses the incomplete records the platform's own checks exist to catch. The model tolerates a missing date, weight or zone because records also arrive by API; the portal form does not. """ from decimal import Decimal from django.contrib.auth import get_user_model from django.test import Client, TestCase from apps.compliance.models import CollectorGroup, Facility, HarvestIntake, OriginZone from .views import suggested_intake_reference User = get_user_model() class IntakeTestCase(TestCase): def setUp(self): self.ops = User.objects.create_user( email="intake-ops@ixg.local", password="pass1234", role="ops", full_name="Intake Officer", is_staff=True, ) self.buyer = User.objects.create_user( email="intake-buyer@ixg.local", password="pass1234", role="buyer", full_name="Intake Buyer", is_staff=True, ) self.facility = Facility.objects.create(name="IXG Test Facility") self.zone = OriginZone.objects.create( facility=self.facility, zone_code="NG-KT-ZTEST", country="Nigeria", region="Katsina State", verification_status="verified", ) self.other_zone = OriginZone.objects.create( facility=self.facility, zone_code="NG-KT-ZTEST2", country="Nigeria", region="Katsina State", verification_status="unverified", ) self.group = CollectorGroup.objects.create( name="Test Collective", contact_reference="CG-TEST-001", origin_zone=self.zone, ) self.client_ops = Client(SERVER_NAME="ops.ixgltd.com") self.client_ops.force_login(self.ops) self.client_buyer = Client(SERVER_NAME="ops.ixgltd.com") self.client_buyer.force_login(self.buyer) self.intake = HarvestIntake.objects.create( intake_reference="IXG-INTAKE-2026-9001", collector_group=self.group, origin_zone=self.zone, intake_date="2026-09-01", weight_kg=Decimal("500.00"), grade="A", status="received", ) def payload(self, **overrides): data = { "intake_reference": "IXG-INTAKE-2026-9002", "collector_group": str(self.group.pk), "origin_zone": str(self.zone.pk), "intake_date": "2026-09-10", "weight_kg": "640.00", "grade": "A", "receiving_officer": "Intake Officer", "status": "received", "evidence_file": "weighbridge-ticket-9002.pdf", } data.update(overrides) return data def errors_for(self, response, field=None): form = response.context["form"] if field: return " ".join(form.errors.get(field, [])) return " ".join(str(v) for v in form.errors.values()) class IntakeAccessTests(IntakeTestCase): def test_ops_can_open_the_list(self): r = self.client_ops.get("/ops/intakes/") self.assertEqual(r.status_code, 200) self.assertContains(r, "IXG-INTAKE-2026-9001") def test_ops_can_open_the_create_form(self): self.assertEqual(self.client_ops.get("/ops/intakes/new/").status_code, 200) def test_ops_can_open_the_edit_form(self): r = self.client_ops.get(f"/ops/intakes/{self.intake.pk}/edit/") self.assertEqual(r.status_code, 200) self.assertContains(r, "IXG-INTAKE-2026-9001") def test_a_buyer_cannot_open_the_intake_screens(self): for path in ("/ops/intakes/", "/ops/intakes/new/", f"/ops/intakes/{self.intake.pk}/edit/"): with self.subTest(path=path): self.assertEqual(self.client_buyer.get(path).status_code, 403) def test_the_intake_list_appears_in_the_sidebar(self): self.assertContains(self.client_ops.get("/ops/intakes/"), "Harvest Intakes") class IntakeCreateTests(IntakeTestCase): def test_a_complete_intake_is_recorded(self): r = self.client_ops.post("/ops/intakes/new/", self.payload(), follow=True) self.assertEqual(r.status_code, 200) intake = HarvestIntake.objects.get(intake_reference="IXG-INTAKE-2026-9002") self.assertEqual(intake.weight_kg, Decimal("640.00")) self.assertEqual(intake.origin_zone, self.zone) self.assertEqual(intake.collector_group, self.group) self.assertContains(r, "recorded") def test_the_form_suggests_the_next_reference_and_today(self): r = self.client_ops.get("/ops/intakes/new/") self.assertContains(r, 'value="IXG-INTAKE-2026-9002"') self.assertContains(r, "Intake Officer") def test_a_duplicate_reference_is_refused(self): r = self.client_ops.post( "/ops/intakes/new/", self.payload(intake_reference="IXG-INTAKE-2026-9001") ) self.assertEqual(r.status_code, 200) self.assertIn("already exists", self.errors_for(r, "intake_reference")) self.assertEqual(HarvestIntake.objects.count(), 1) def test_a_duplicate_differing_only_in_case_is_refused(self): r = self.client_ops.post( "/ops/intakes/new/", self.payload(intake_reference="ixg-intake-2026-9001") ) self.assertIn("already exists", self.errors_for(r, "intake_reference")) def test_a_zero_or_negative_weight_is_refused(self): for weight in ("0", "-5"): with self.subTest(weight=weight): r = self.client_ops.post("/ops/intakes/new/", self.payload(weight_kg=weight)) self.assertIn("greater than zero", self.errors_for(r, "weight_kg")) def test_the_fields_the_checks_read_are_required(self): cases = { "intake_reference": "", "collector_group": "", "origin_zone": "", "intake_date": "", "weight_kg": "", "grade": "", } for field, blank in cases.items(): with self.subTest(field=field): r = self.client_ops.post("/ops/intakes/new/", self.payload(**{field: blank})) self.assertEqual(r.status_code, 200) self.assertTrue( self.errors_for(r, field), f"{field} was accepted blank" ) def test_an_unknown_grade_is_refused(self): r = self.client_ops.post("/ops/intakes/new/", self.payload(grade="Z")) self.assertTrue(self.errors_for(r, "grade")) def test_a_zone_that_does_not_match_the_group_is_queried(self): r = self.client_ops.post( "/ops/intakes/new/", self.payload(origin_zone=str(self.other_zone.pk)) ) self.assertIn("registered to", self.errors_for(r, "origin_zone")) def test_an_unverified_zone_is_allowed_but_flagged(self): """Recording against a zone still awaiting verification must stay possible.""" group_without_zone = CollectorGroup.objects.create(name="Unzoned Collective") r = self.client_ops.post( "/ops/intakes/new/", self.payload(collector_group=str(group_without_zone.pk), origin_zone=str(self.other_zone.pk)), ) self.assertEqual(r.status_code, 302) self.assertTrue( HarvestIntake.objects.filter(intake_reference="IXG-INTAKE-2026-9002").exists() ) def test_the_form_labels_an_unverified_zone(self): r = self.client_ops.get("/ops/intakes/new/") self.assertContains(r, "unverified") class IntakeEditTests(IntakeTestCase): def test_an_intake_can_be_corrected(self): data = self.payload(intake_reference="IXG-INTAKE-2026-9001", weight_kg="510.00") r = self.client_ops.post(f"/ops/intakes/{self.intake.pk}/edit/", data, follow=True) self.assertEqual(r.status_code, 200) self.intake.refresh_from_db() self.assertEqual(self.intake.weight_kg, Decimal("510.00")) self.assertContains(r, "updated") def test_saving_an_intake_under_its_own_reference_is_not_a_duplicate(self): data = self.payload(intake_reference="IXG-INTAKE-2026-9001") r = self.client_ops.post(f"/ops/intakes/{self.intake.pk}/edit/", data) self.assertEqual(r.status_code, 302) def test_correcting_does_not_create_a_second_record(self): data = self.payload(intake_reference="IXG-INTAKE-2026-9001", grade="B") self.client_ops.post(f"/ops/intakes/{self.intake.pk}/edit/", data) self.assertEqual(HarvestIntake.objects.count(), 1) class SuggestedReferenceTests(IntakeTestCase): def test_the_suggestion_continues_the_year_sequence(self): HarvestIntake.objects.create( intake_reference="IXG-INTAKE-2026-9042", intake_date="2026-09-02", weight_kg=Decimal("10.00"), grade="A", ) self.assertEqual(suggested_intake_reference(), "IXG-INTAKE-2026-9043") def test_the_suggestion_ignores_other_years_and_odd_references(self): HarvestIntake.objects.create( intake_reference="IXG-INTAKE-2025-9999", intake_date="2025-09-02", weight_kg=Decimal("10.00"), grade="A", ) HarvestIntake.objects.create( intake_reference="IXG-INTAKE-2026-ABC", intake_date="2026-09-02", weight_kg=Decimal("10.00"), grade="A", ) self.assertEqual(suggested_intake_reference(), "IXG-INTAKE-2026-9002")