getFileName()); if (!is_array($lines)) { throw new RuntimeException('method source must be readable'); } return implode('', array_slice( $lines, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1 )); } $diagnosisLists = prescriptionAppointmentScopeMethod( (new ReflectionClass(DiagnosisLists::class))->getMethod('lists') ); $appointmentLists = prescriptionAppointmentScopeMethod( (new ReflectionClass(AppointmentLists::class))->getMethod('lists') ); $getByAppointment = prescriptionAppointmentScopeMethod( (new ReflectionClass(PrescriptionLogic::class))->getMethod('getByAppointment') ); foreach ([$diagnosisLists, $appointmentLists] as $listSource) { prescriptionAppointmentScopeExpect( str_contains($listSource, "whereIn('appointment_id', \$appointmentIds)"), 'list action state must be selected by the current appointment ids' ); prescriptionAppointmentScopeExpect( str_contains($listSource, "['current_has_prescription']"), 'list DTO must expose an appointment-scoped prescription flag' ); prescriptionAppointmentScopeExpect( str_contains($listSource, "['current_prescription_id']"), 'list DTO must expose the exact appointment-scoped prescription id' ); } prescriptionAppointmentScopeExpect( !str_contains( $diagnosisLists, "['prescription_audit_status'] = (\$item['has_prescription'] && \$fu)" ), 'diagnosis history must not drive the current appointment action state' ); prescriptionAppointmentScopeExpect( str_contains($getByAppointment, "(int) (\$candidate->void_status ?? 0) === 0") && str_contains($getByAppointment, '$row = $row ?? $fallback;'), 'detail lookup must prefer the same active prescription and only fall back when all are voided' ); echo "Prescription appointment scope contract: OK\n";