From 7d104fb79e9f80a77d7079286e2916a78247a6d9 Mon Sep 17 00:00:00 2001 From: Daniel Rustad Date: Mon, 23 Feb 2026 01:59:41 -0600 Subject: [PATCH 1/2] Added DTOs and polling by date Replaced the rather tedious mapping from Neon-json to associative array to standard object to associative array (via config.php) with some simple DTOs. Also added functionality to only get participantIds that have been update on or after (>=) a specified date. The function checks the fields entered_date and updated_date (WHERE with OR) in the tables persons, persons_applications_children, persons_applications, persons_assessment_worksheet, persons_introductory_survey, and persons_service_plan. The fields release to attorney/legistlator/employer/Governor's staff in the disclosure form still have issues. Neon has the checkbox values (whether or not the box is checked), but no obvious value for the text field for who the information should be disclosed to. We, on the other hand, seem to have both (Josh would have to confirm). Also, we don't seem to have the survey delivery checkboxes at the end of the disclosure form. Haven't checked if Neon has those yet. Lastly, Neon returns '403 Forbidden' at irregular intervals. Haven't figured out what triggers it. Could be late night/early morning issue due to maintenance. --- app/Console/Commands/PollNeonParticipants.php | 14 +- app/DTOs/AssessmentDTO.php | 28 ++ app/DTOs/ChildDTO.php | 13 + app/DTOs/DisclosureDTO.php | 61 ++++ app/DTOs/EnrollmentFormDTO.php | 186 ++++++++++++ app/DTOs/ServicePlanDTO.php | 26 ++ app/DTOs/SurveyDTO.php | 18 ++ app/Jobs/GenerateParticipantPdfJob.php | 4 +- app/Services/Integrations/NeonApiService.php | 86 +++++- app/Services/NeonDTOTransformer.php | 266 ++++++++++++++++++ app/Services/PdfIntakeFormService.php | 23 +- 11 files changed, 693 insertions(+), 32 deletions(-) create mode 100644 app/DTOs/AssessmentDTO.php create mode 100644 app/DTOs/ChildDTO.php create mode 100644 app/DTOs/DisclosureDTO.php create mode 100644 app/DTOs/EnrollmentFormDTO.php create mode 100644 app/DTOs/ServicePlanDTO.php create mode 100644 app/DTOs/SurveyDTO.php create mode 100644 app/Services/NeonDTOTransformer.php diff --git a/app/Console/Commands/PollNeonParticipants.php b/app/Console/Commands/PollNeonParticipants.php index d915f95..902b29b 100644 --- a/app/Console/Commands/PollNeonParticipants.php +++ b/app/Console/Commands/PollNeonParticipants.php @@ -6,6 +6,8 @@ use App\Services\Integrations\NeonApiService; use App\Jobs\GenerateParticipantPdfJob; use App\Models\NeonHash; +use Carbon\Carbon; +use Illuminate\Support\Facades\Log; class PollNeonParticipants extends Command { @@ -39,11 +41,15 @@ public function __construct(NeonApiService $neonApi) */ public function handle() { - $participants = $this->neonApi->getTodaysParticipants(); - - foreach ($participants as $person) { - $participantId = (int) $person['persons_id']['value']; + $today = Carbon::today()->format('m-d-Y'); + Log::info("Collecting participant records that have been added or updated today - {$today}...."); + + $participantIds = $this->neonApi->getTodaysParticipantIds(); + $count = count($participantIds); + Log::info("Found {$count} new or updated participant records."); + + foreach ($participantIds as $participantId) { // Build the full participant record $fullRecord = $this->neonApi->buildFullParticipantRecord($participantId); diff --git a/app/DTOs/AssessmentDTO.php b/app/DTOs/AssessmentDTO.php new file mode 100644 index 0000000..ca6887e --- /dev/null +++ b/app/DTOs/AssessmentDTO.php @@ -0,0 +1,28 @@ + diff --git a/app/DTOs/ChildDTO.php b/app/DTOs/ChildDTO.php new file mode 100644 index 0000000..8e55f91 --- /dev/null +++ b/app/DTOs/ChildDTO.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/app/DTOs/DisclosureDTO.php b/app/DTOs/DisclosureDTO.php new file mode 100644 index 0000000..890a14c --- /dev/null +++ b/app/DTOs/DisclosureDTO.php @@ -0,0 +1,61 @@ + \ No newline at end of file diff --git a/app/DTOs/EnrollmentFormDTO.php b/app/DTOs/EnrollmentFormDTO.php new file mode 100644 index 0000000..d2fde4e --- /dev/null +++ b/app/DTOs/EnrollmentFormDTO.php @@ -0,0 +1,186 @@ + value array for pdftk. + * Keys match PDF field names exactly from getDataFields() output. + */ + public function toPdfArray(): array + { + return [ + // Page 1 - Enrollment + 'title_region' => $this->title_region, + 'full_name' => $this->full_name, + 'entered_date' => $this->entered_date, + 'address' => $this->address, + 'employer' => $this->employer, + 'tshirt_size' => $this->tshirt_size, + 'phone' => $this->phone, + 'work_phone' => $this->work_phone, + 'other_phone' => $this->other_phone, + 'email' => $this->email, + 'case_worker_name' => $this->case_worker_name, + 'case_worker_phone' => $this->case_worker_phone, + 'monthly_child_support' => $this->monthly_child_support, + 'marital_status' => $this->marital_status, + 'ethnicity' => $this->ethnicity, + 'contact_with_children' => $this->contact_with_children, + 'children_custody' => $this->children_custody, + 'children_visitation' => $this->children_visitation, + 'children_phone' => $this->children_phone, + + // Children + 'child_name_1' => $this->children[0]->name ?? '', + 'child_age_1' => $this->children[0]->age ?? '', + 'child_dob_1' => $this->children[0]->dob ?? '', + 'child_name_2' => $this->children[1]->name ?? '', + 'child_age_2' => $this->children[1]->age ?? '', + 'child_dob_2' => $this->children[1]->dob ?? '', + 'child_name_3' => $this->children[2]->name ?? '', + 'child_age_3' => $this->children[2]->age ?? '', + 'child_dob_3' => $this->children[2]->dob ?? '', + 'child_name_4' => $this->children[3]->name ?? '', + 'child_age_4' => $this->children[3]->age ?? '', + 'child_dob_4' => $this->children[3]->dob ?? '', + 'child_name_5' => $this->children[4]->name ?? '', + 'child_age_5' => $this->children[4]->age ?? '', + 'child_dob_5' => $this->children[4]->dob ?? '', + + // Page 2 - Disclosure + 'authorize_full_name' => $this->full_name, + 'authorize_dys' => $this->disclosure->authorize_dys, + 'authorize_mhd' => $this->disclosure->authorize_mhd, + 'authorize_dfas' => $this->disclosure->authorize_dfas, + 'authorize_mmac' => $this->disclosure->authorize_mmac, + 'authorize_other' => $this->disclosure->authorize_other, + 'authorize_discloser_form_other' => $this->disclosure->authorize_discloser_form_other ?? '', + 'authorize_cd' => $this->disclosure->authorize_cd, + 'authorize_dls' => $this->disclosure->authorize_dls, + 'disclose_full_name' => $this->disclosure->full_name, + 'disclose_phone' => $this->disclosure->phone, + 'disclose_dob' => $this->disclosure->dob, + ### Removed - we should not collect this information + // 'disclose_ssn' => $this->disclosure->ssn ?? '', + 'disclose_address' => $this->disclosure->address, + 'disclose_email' => $this->disclosure->email, + // 'select_disclose_to_attorney' => $this->disclosure->select_disclose_to_attorney, + 'disclose_to_attorney' => $this->disclosure->disclose_to_attorney, + 'disclose_to_legislator' => $this->disclosure->disclose_to_legislator, + 'disclose_to_employer' => $this->disclosure->disclose_to_employer, + 'disclose_to_governors_staff' => $this->disclosure->disclose_to_governors_staff, + ## Removed - pre-filled + // 'other_discloser' => $this->disclosure->other_discloser, + ## Removed - pre-filled + // 'disclosure_purpose_eligibility_determination' => $this->disclosure->purpose_eligibility_determination, + ## Removed - pre-filled + // 'disclosure_purpose_employment' => $this->disclosure->purpose_employment, + 'disclosure_purpose_continuity_of_services_care' => $this->disclosure->purpose_continuity_of_services_care, + 'disclosure_purpose_legal_consultation_representation' => $this->disclosure->purpose_legal_consultation_representation, + 'disclosure_purpose_complaint_investigation_resolution' => $this->disclosure->purpose_complaint_investigation_resolution, + 'disclosure_purpose_background_investigation' => $this->disclosure->purpose_background_investigation, + 'disclosure_purpose_legal_proceedings' => $this->disclosure->purpose_legal_proceedings, + 'disclosure_purpose_treatment_planning' => $this->disclosure->purpose_treatment_planning, + 'disclosure_purpose_at_consumers_request' => $this->disclosure->purpose_at_consumers_request, + 'disclosure_purpose_to_share_or_refer' => $this->disclosure->purpose_to_share_or_refer, + ## Removed - pre-filled + // 'disclosure_purpose_other' => $this->disclosure->purpose_other, + 'disclosure_licensure_information' => $this->disclosure->licensure_information, + 'disclosure_medical' => $this->disclosure->disclosure_medical, + 'disclose_hotline_investigations' => $this->disclosure->hotline_investigations, + 'disclosure_home_studies' => $this->disclosure->home_studies, + 'disclosure_eligibility_determinations' => $this->disclosure->eligibility_determinations, + 'disclosure_substance_abuse_treatment' => $this->disclosure->substance_abuse_treatment, + 'disclosure_client_employment_records' => $this->disclosure->client_employment_records, + + // Page 3 + 'accept_text_messages' => $this->disclosure->accept_text_messages, + + // Page 4 - Assessment + 'participant_full_name' => $this->assessment->full_name, + 'participant_dob' => $this->assessment->dob, + ## Removed - we should not collect this information + // 'participant_ssn' => $this->assessment->ssn ?? '', + 'eligibility_missouri_resident' => $this->assessment->eligibility_missouri_resident, + 'eligibility_child_under_18' => $this->assessment->eligibility_child_under_18, + 'financial_assessment_eligibility' => $this->assessment->financial_eligibility, + 'financial_assessment_drivers_licence' => $this->assessment->financial_drivers_licence, + 'financial_assessment_utility_bill' => $this->assessment->financial_utility_bill, + 'financial_assessment_written_employer_statement' => $this->assessment->financial_written_employer_statement, + 'financial_assessment_ss_benefits_statement' => $this->assessment->financial_ss_benefits_statement, + 'financial_assessment_no_employment_income' => $this->assessment->financial_no_employment_income, + 'financial_assessment_unemployment_compensation' => $this->assessment->financial_unemployment_compensation, + 'financial_assessment_other' => $this->assessment->financial_other, + 'financial_assessment_other_description' => $this->assessment->financial_other_description ?? '', + 'poverty_level_monthly_income' => $this->assessment->poverty_monthly_income, + 'poverty_level_number_of_household_members' => $this->assessment->poverty_household_members, + 'poverty_level_percentage_fpl' => $this->assessment->poverty_percentage_fpl, + + // Page 5 - Survey + 'survey_client_dob' => $this->survey->client_dob, + 'survey_delivery_method' => $this->survey->delivery_method, + 'survey_why' => $this->survey->why, + 'survey_other_description' => $this->survey->why_other, + 'survey_how' => $this->survey->how, + 'survey_how_other_description' => $this->survey->how_other, + 'survey_gain' => $this->survey->gain, + 'survey_gain_other_description' => $this->survey->gain_other, + + // Page 6 - Service Plan + 'service_plan_participant_full_name' => $this->servicePlan->participant_full_name, + 'service_plan_client_number' => $this->servicePlan->client_number, + 'service_plan_goal' => $this->servicePlan->goal, + 'service_plan_service_identified' => $this->servicePlan->service_identified, + 'service_plan_strategies_1' => $this->servicePlan->strategies_1, + 'service_plan_person_responsible_1' => $this->servicePlan->person_responsible_1, + 'service_plan_timeline_1' => $this->servicePlan->timeline_1, + 'service_plan_measure_of_success_1' => $this->servicePlan->measure_of_success_1, + 'service_plan_strategies_2' => $this->servicePlan->strategies_2, + 'service_plan_person_responsible_2' => $this->servicePlan->person_responsible_2, + 'service_plan_timeline_2' => $this->servicePlan->timeline_2, + 'service_plan_measure_of_success_2' => $this->servicePlan->measure_of_success_2, + 'service_plan_strategies_3' => $this->servicePlan->strategies_3, + 'service_plan_person_responsible_3' => $this->servicePlan->person_responsible_3, + 'service_plan_timeline_3' => $this->servicePlan->timeline_3, + 'service_plan_measure_of_success_3' => $this->servicePlan->measure_of_success_3, + ]; + } +} +?> \ No newline at end of file diff --git a/app/DTOs/ServicePlanDTO.php b/app/DTOs/ServicePlanDTO.php new file mode 100644 index 0000000..a15476d --- /dev/null +++ b/app/DTOs/ServicePlanDTO.php @@ -0,0 +1,26 @@ + diff --git a/app/DTOs/SurveyDTO.php b/app/DTOs/SurveyDTO.php new file mode 100644 index 0000000..347b08a --- /dev/null +++ b/app/DTOs/SurveyDTO.php @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/app/Jobs/GenerateParticipantPdfJob.php b/app/Jobs/GenerateParticipantPdfJob.php index 8cc3ee7..0e6bb3f 100644 --- a/app/Jobs/GenerateParticipantPdfJob.php +++ b/app/Jobs/GenerateParticipantPdfJob.php @@ -3,7 +3,7 @@ namespace App\Jobs; use App\Services\Integrations\NeonApiService; -use App\Services\NeonDataTransformer; +use App\Services\NeonDTOTransformer; use App\Services\PdfIntakeFormService; use App\Models\NeonHash; use Illuminate\Bus\Queueable; @@ -35,7 +35,7 @@ public function __construct(int $participantId) */ public function handle( NeonApiService $neonApi, - NeonDataTransformer $transformer, + NeonDTOTransformer $transformer, PdfIntakeFormService $pdfService ) { try { diff --git a/app/Services/Integrations/NeonApiService.php b/app/Services/Integrations/NeonApiService.php index b6ac867..d7a1485 100644 --- a/app/Services/Integrations/NeonApiService.php +++ b/app/Services/Integrations/NeonApiService.php @@ -2,6 +2,7 @@ namespace App\Services\Integrations; +use Carbon\Carbon; use Illuminate\Support\Facades\Http; use Exception; @@ -60,29 +61,82 @@ private function fetch(string $endpoint, array $fields = [], ?int $personId = nu return $responseJson; } - public function getTodaysParticipants(): array - { - $url = "{$this->baseUrl}/data/persons"; + public function getTodaysParticipantIds(): array { + $todaysDate = Carbon::today()->format('Y-m-d'); + // $todaysDate = '2026-02-01'; + return $this->getParticipantIdsByDate($todaysDate); + } - $fields = [ - // Contact info - 'fullName', - 'enteredDate', - 'updatedDate', - ]; + private function getParticipantIdsByDate(string $filterDate): array { + $tables = ['persons', 'persons_applications_children', 'persons_applications', 'persons_assessment_worksheet', 'persons_introductory_survey', 'persons_service_plan']; + $fields = ['persons_id', 'entered_date', 'updated_date']; + $baseUrl = "{$this->baseUrl}/data"; - $response = Http::get($url, [ - 'fields' => json_encode($fields), + $params = [ 'key' => $this->apiKey, - ]); + ]; - $response->throw(); // will raise exception if not 200 + $params['fields'] = json_encode($fields); + + $params['where'] = json_encode([ + 'whereType' => 'OR', + 'clauses' => [ + [ + 'fieldName' => 'enteredDate', + 'operator' => '>=', + 'operand' => $filterDate, + ], + [ + 'fieldName' => 'updatedDate', + 'operator' => '>=', + 'operand' => $filterDate, + ] + ], + ]); + + $participantIds = []; + + foreach ($tables as $table) { + $url = "{$baseUrl}/$table"; + $response = Http::get($url, $params); + $data = $response->json() ?? []; + $records = $data['records'] ?? []; + + if (!empty($records)) { + $personsIds = array_column($records, 'persons_id'); + $newIds = array_column($personsIds, 'value'); + $participantIds = array_unique(array_merge($participantIds, $newIds)); + } - $data = $response->json() ?? []; + } - return $data['records'] ?? []; + return $participantIds; } + + // public function getTodaysParticipants(): array + // { + // $url = "{$this->baseUrl}/data/persons"; + + // $fields = [ + // // Contact info + // 'fullName', + // 'enteredDate', + // 'updatedDate', + // ]; + + // $response = Http::get($url, [ + // 'fields' => json_encode($fields), + // 'key' => $this->apiKey, + // ]); + + // $response->throw(); // will raise exception if not 200 + + // $data = $response->json() ?? []; + + // return $data['records'] ?? []; + // } + public function getParticipant(int $id): array { $fields = [ @@ -151,6 +205,8 @@ public function getParticipant(int $id): array public function fetchPersonContactInfo(int $personId, bool $useWhereClause): array { return $this->fetch("persons/{$personId}", [ + "firstName", + "lastName", "regions_id", "enteredDate", "address1", diff --git a/app/Services/NeonDTOTransformer.php b/app/Services/NeonDTOTransformer.php new file mode 100644 index 0000000..34aefd9 --- /dev/null +++ b/app/Services/NeonDTOTransformer.php @@ -0,0 +1,266 @@ +parseDateString($contactInfo['enteredDate']['value'] ?? null), + address: $this->buildAddress($contactInfo), + employer: $contactInfo['employer']['value'] ?? '', + tshirt_size: $contactInfo['tShirtSize']['displayValue'] ?? '', + phone: $contactInfo['homeCellPhone']['value'] ?? '', + work_phone: $contactInfo['workPhone']['value'] ?? '', + other_phone: $contactInfo['otherNumber']['value'] ?? '', + email: $contactInfo['email']['value'] ?? '', + case_worker_name: $contactInfo['probationParoleCaseWorkerName']['value'] ?? '', + case_worker_phone: $contactInfo['probationParoleCaseWorkerPhone']['value'] ?? '', + monthly_child_support: $contactInfo['monthlyChildSupportPayment']['displayValue'] ?? '', + marital_status: $contactInfo['maritalStatus']['displayValue'] ?? '', + ethnicity: $contactInfo['ethnicity']['displayValue'] ?? '', + contact_with_children: $this->yesNo($contactInfo['contactWithChildren']['displayValue'] ?? null), + children_custody: $this->inList($contactInfo['contactType']['value'] ?? '', '763'), + children_visitation: $this->inList($contactInfo['contactType']['value'] ?? '', '762'), + children_phone: $this->inList($contactInfo['contactType']['value'] ?? '', '1483'), + children: $this->transformChildren($data['children']['records'] ?? []), + disclosure: $this->transformDisclosure($data['disclosure']['records'][0]), + assessment: $this->transformAssessment($data['assessment']['records'][0]), + survey: $this->transformSurvey($data['survey']['records'][0]), + servicePlan: $this->transformServicePlan($data['servicePlan']['records'][0]), + ); + } + + private function transformChildren(array $children): array + { + $result = []; + foreach ($children as $child) { + $dob = $this->parseDate($child['dateOfBirth']['value'] ?? null); + $result[] = new ChildDTO( + name: trim(($child['firstName']['value'] ?? '') . ' ' . ($child['lastName']['value'] ?? '')), + age: $dob ? (string) $dob->diffInYears(Carbon::now()) : '', + dob: $dob ? $dob->format('m/d/Y') : '', + ); + } + return $result; + } + + private function transformDisclosure(array $d): DisclosureDTO + { + $divisions = explode(',', $d['division']['value'] ?? ''); + $releaseTo = explode(',', $d['releaseTo']['value'] ?? ''); + $purposes = explode(',', $d['purposeOfDisclosure']['value'] ?? ''); + $disclosed = explode(',', $d['informationToBeDisclosed']['value'] ?? ''); + + return new DisclosureDTO( + full_name: $d['persons_id']['displayValue'] ?? '', + phone: $d['homeCellPhone']['value'] ?? '', + dob: $this->parseDateString($d['dateOfBirth']['value'] ?? null), + ## We should not collect this information + // ssn: null, + address: $d['fullAddress']['displayValue'] ?? '', + email: $d['email']['value'] ?? '', + authorize_dys: $this->inArray('679', $divisions), + authorize_mhd: $this->inArray('684', $divisions), + authorize_dfas: $this->inArray('683', $divisions), + authorize_mmac: $this->inArray('1484', $divisions), + authorize_other: isset($d['divisionOther']['value']) && $d['divisionOther']['value'] ? 'Yes' : 'Off', + authorize_discloser_form_other: $d['divisionOther']['value'] ?? null, + authorize_cd: $this->inArray('682', $divisions), + authorize_dls: $this->inArray('681', $divisions), + ## This is the text field + // disclose_to_attorney: $attorneyInList, + disclose_to_attorney: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", + ## This is the text field + // disclose_to_legislator: $this->inArray('1487', $releaseTo), + disclose_to_legislator: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", + ## This is the text field + // disclose_to_employer: $this->inArray('1486', $releaseTo), + disclose_to_employer: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", + ## This is the text field + // disclose_to_governors_staff: $this->inArray('1488', $releaseTo), + disclose_to_governors_staff: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", + ## Pre-filled + // other_discloser: $d['releaseToOther']['displayValue'] ?? '', + ## Pre-filled + // purpose_eligibility_determination: $this->inArray('585', $purposes), + ## Pre-filled + // purpose_employment: $this->inArray('594', $purposes), + purpose_continuity_of_services_care: $this->inArray('447', $purposes), + purpose_legal_consultation_representation: $this->inArray('1490', $purposes), + purpose_complaint_investigation_resolution: $this->inArray('1491', $purposes), + purpose_background_investigation: $this->inArray('1492', $purposes), + purpose_legal_proceedings: $this->inArray('1493', $purposes), + purpose_treatment_planning: $this->inArray('1494', $purposes), + purpose_at_consumers_request: $this->inArray('1495', $purposes), + purpose_to_share_or_refer: $this->inArray('755', $purposes), + //This is the checkbox for the 'other purpose' field which is pre-filled, but the box is not checked, hence 'Yes' here + purpose_other: "Yes", //$this->inArray('1496', $purposes), + licensure_information: $this->inArray('161', $disclosed), + disclosure_medical: $this->inArray('1497', $disclosed), + hotline_investigations: $this->inArray('1499', $disclosed), + home_studies: $this->inArray('1500', $disclosed), + eligibility_determinations: $this->inArray('1501', $disclosed), + substance_abuse_treatment: $this->inArray('1502', $disclosed), + client_employment_records: $this->inArray('1503', $disclosed), + accept_text_messages: $this->yesNo($d['acceptsTextMessage']['displayValue'] ?? null), + ); + } + + private function transformAssessment(array $a): AssessmentDTO + { + $otherValue = $a['other']['displayValue'] ?? null; + + return new AssessmentDTO( + full_name: $a['persons_id']['displayValue'] ?? '', + dob: $a['dateOfBirth']['displayValue'] ?? '', + ## We should not collect this information + // ssn: null, + eligibility_missouri_resident: $this->yesNo($a['missouriResident']['displayValue'] ?? null), + eligibility_child_under_18: $this->yesNo($a['childUnder18']['displayValue'] ?? null), + financial_eligibility: 'Off', // completed by state agency, not in Neon + financial_drivers_licence: $this->yesNo($a['dL']['displayValue'] ?? null), + financial_utility_bill: $this->yesNo($a['utilityBill']['displayValue'] ?? null), + financial_written_employer_statement: $this->yesNo($a['writtenEmployerStatement']['displayValue'] ?? null), + financial_ss_benefits_statement: $this->yesNo($a['socialSecurityBenefitsStatement']['displayValue'] ?? null), + financial_no_employment_income: $this->yesNo($a['selfAttestationOfNoEmploymentOrIncome']['displayValue'] ?? null), + financial_unemployment_compensation: $this->yesNo($a['unemploymentCompensation']['displayValue'] ?? null), + financial_other: $otherValue ? 'Yes' : 'Off', + financial_other_description: $otherValue ?: null, + poverty_monthly_income: $a['hoseholdIncome']['displayValue'] ?? '', // typo is in Neon field name + poverty_household_members: $a['numberOfFamilyMembersInHousehold']['value'] ?? '', + poverty_percentage_fpl: $a['percentageOfFPL']['value'] ?? '', + ); + } + + private function transformSurvey(array $s): SurveyDTO + { + $reasons = explode(',', $s['reasons']['value'] ?? ''); + $howHeardAbout = explode(',', $s['hearAboutUs']['value'] ?? ''); + $expectedGain = explode(',', $s['expectToGain']['value'] ?? ''); + + return new SurveyDTO( + client_dob: $s['dateOfBirth']['displayValue'] ?? '', + delivery_method: '', // not in Neon — filled in by participant on paper + why: match(true) { + in_array('453', $reasons) => 'Responsible father', + in_array('454', $reasons) => 'Referred', + in_array('1506', $reasons) => 'Child support concerns', + in_array('695', $reasons) => 'Attourney', // matches PDF FieldStateOption spelling + in_array('1507', $reasons) => 'Other', + default => 'Off', + }, + why_other: $s['reasonsOther']['value'] ?? '', + how: match(true) { + in_array('1510', $howHeardAbout) => 'Family support', + in_array('1509', $howHeardAbout) => 'Past participant', + in_array('1512', $howHeardAbout) => 'Marketing', + in_array('1511', $howHeardAbout) => 'Prosecuting attorney', + in_array('1513', $howHeardAbout) => 'The organization', + in_array('1508', $howHeardAbout) => 'Word of mouth', + in_array('1514', $howHeardAbout) => 'Other', + default => 'Off', + }, + how_other: $s['hearAboutUsOther']['value'] ?? '', + gain: match(true) { + in_array('1520', $expectedGain) => 'Access to mentors', + in_array('1524', $expectedGain) => 'Credit repair assistance', + in_array('1521', $expectedGain) => 'Criminal History Assistance', + in_array('1522', $expectedGain) => 'Overcoming homelessness assistance', + in_array('1516', $expectedGain) => 'Abuse assistance', + in_array('1523', $expectedGain) => 'Visitation custody assistance', + in_array('1515', $expectedGain) => 'Emplyment opportunities', // matches PDF FieldStateOption spelling + in_array('1517', $expectedGain) => 'Parenting skills', + in_array('1526', $expectedGain) => 'Increased Understanding of Child Support', + in_array('1525', $expectedGain) => 'Maintaining Hope', + in_array('1518', $expectedGain) => 'Resume building', + in_array('1519', $expectedGain) => 'Legal services', + in_array('1527', $expectedGain) => 'Other', + default => 'Off', + }, + gain_other: $s['expectToGainOther']['value'] ?? '', + ); + } + + private function transformServicePlan(array $sp): ServicePlanDTO + { + return new ServicePlanDTO( + participant_full_name: $sp['persons_id']['displayValue'] ?? '', + client_number: $sp['clientNumber']['value'] ?? '', + goal: '', // database appears to be missing this field per original comment + service_identified: $sp['serviceIdentifiedByTheParticipants']['value'] ?? '', + strategies_1: $sp['goals_custodyVisitationObj']['displayValue'] ?? '', + person_responsible_1: $sp['goals_custodyVisitationPersonRes']['displayValue'] ?? '', + timeline_1: $sp['goals_custodyVisitationTimeline']['displayValue'] ?? '', + measure_of_success_1: $sp['goals_custodyVisitationMeasure']['value'] ?? '', + strategies_2: $sp['goals_educationEmploymentObj']['displayValue'] ?? '', + person_responsible_2: $sp['goals_educationEmploymentPersonRes']['displayValue'] ?? '', + timeline_2: $sp['goals_educationEmploymentTimeline']['displayValue'] ?? '', + measure_of_success_2: $sp['goals_educationEmploymentMeasure']['value'] ?? '', + strategies_3: $sp['goals_housingTransportationObj']['displayValue'] ?? '', + person_responsible_3: $sp['goals_housingTransportationPersonRes']['displayValue'] ?? '', + timeline_3: $sp['goals_housingTransportationTimeline']['displayValue'] ?? '', + measure_of_success_3: $sp['goals_housingTransportationMeasure']['value'] ?? '', + ); + } + + // ─── Helpers ───────────────────────────────────────────────────────────── + + private function parseDate(?string $value): ?Carbon + { + return $value ? Carbon::createFromFormat('Y-m-d', $value) : null; + } + + private function parseDateString(?string $value): string + { + if (!$value) return ''; + $date = Carbon::createFromFormat('Y-m-d', $value); + return $date ? $date->format('m/d/Y') : ''; + } + + private function buildAddress(array $c): string + { + return trim(implode(' ', array_filter([ + $c['address1']['value'] ?? '', + $c['address2']['value'] ?? '', + $c['city']['value'] ?? '', + $c['state']['displayValue'] ?? '', + $c['zip']['value'] ?? '', + ]))); + } + + private function yesNo(?string $value): string + { + return match($value) { + '1' => 'Yes', + '0' => 'No', + default => 'Off', + }; + } + + private function inList(string $list, string $id): string + { + return in_array($id, explode(',', $list)) ? 'Yes' : 'Off'; + } + + private function inArray(string $id, array $arr): string + { + return in_array($id, $arr) ? 'Yes' : 'Off'; + } +} \ No newline at end of file diff --git a/app/Services/PdfIntakeFormService.php b/app/Services/PdfIntakeFormService.php index 2da1f58..ec6910d 100644 --- a/app/Services/PdfIntakeFormService.php +++ b/app/Services/PdfIntakeFormService.php @@ -11,20 +11,20 @@ class PdfIntakeFormService { protected string $formKey = 'dad_intake_form'; - protected string $pdfTemplatePath = 'pdfs/intake-forms/Enrollment_Documents_Fillable_GD_North_Central_Contract_Region.pdf'; + protected string $pdfTemplatePath = 'intake-form/Enrollment_Form_Fillable_2026-01-27.pdf'; public function generate($participant): string { - $fieldMap = config("pdf_forms.{$this->formKey}"); - - $data = []; - foreach ($fieldMap as $pdfField => $participantField) { - $value = data_get($participant, $participantField); - // if ($value instanceof \Carbon\Carbon) { - // $value = $value->format('m/d/Y'); - // } - $data[$pdfField] = $value ?? ''; - } + // $fieldMap = config("pdf_forms.{$this->formKey}"); + + // $data = []; + // foreach ($fieldMap as $pdfField => $participantField) { + // $value = data_get($participant, $participantField); + // // if ($value instanceof \Carbon\Carbon) { + // // $value = $value->format('m/d/Y'); + // // } + // $data[$pdfField] = $value ?? ''; + // } // Build folder structure for each participant $storagePath = "participant-forms/{$participant->id}/"; @@ -35,6 +35,7 @@ public function generate($participant): string // Ensure directory exists Storage::makeDirectory($storagePath); + $data = $participant->toPdfArray(); // Load and fill the PDF $pdf = new Pdf(storage_path("app/{$this->pdfTemplatePath}")); From e63a2d4fc093ff4b6eaff48dd314a8351d463259 Mon Sep 17 00:00:00 2001 From: Daniel Rustad Date: Sun, 8 Mar 2026 15:47:55 -0500 Subject: [PATCH 2/2] Encryption and DTO cleanup - Added ShouldBeEncrypted-interface to GenerateParticipantJob and removed the calls to the Neon APIs, the job now receives the data through the queue - Split EnrollmentDTO into ContactInfoDTO and a container class for the DTOs called ParticipantUpdateData. The container holds the DTOs and does the flattening for the pdf generation. It also has a fullName-function for generating the full name for the email body - Added interface PdfArrayable to the DTOs - Switched to camel case in the DTOs - The ParticipantUpdateData-container is now passed to the job at dispatch in PollNeonParticipants, so there is one less call to Neon APIs - The NeonDTOTransformer is now a static class with static functions - Added type to the generate-function in PdfIntakeFormService - Added some logging to the getTodaysParticipantIds-function in NeonApiService - Updated intak-form.blade to use the fullName-function in ParticipantUpdatedData --- .gitignore | 3 + app/Console/Commands/PdfFillFields.php | 4 +- app/Console/Commands/PollNeonParticipants.php | 23 +- app/DTOs/AssessmentDTO.php | 55 ++-- app/DTOs/ContactInfoDTO.php | 54 ++++ app/DTOs/DisclosureDTO.php | 118 ++++--- app/DTOs/EnrollmentFormDTO.php | 186 ----------- app/DTOs/ParticipantUpdateData.php | 56 ++++ app/DTOs/PdfArrayable.php | 7 + app/DTOs/ServicePlanDTO.php | 41 ++- app/DTOs/SurveyDTO.php | 25 +- app/Jobs/GenerateParticipantPdfJob.php | 29 +- app/Mail/IntakeFormMailable.php | 12 +- app/Services/Integrations/NeonApiService.php | 16 +- app/Services/NeonDTOTransformer.php | 290 +++++++++--------- app/Services/PdfIntakeFormService.php | 5 +- resources/views/emails/intake-form.blade.php | 2 +- 17 files changed, 476 insertions(+), 450 deletions(-) create mode 100644 app/DTOs/ContactInfoDTO.php delete mode 100644 app/DTOs/EnrollmentFormDTO.php create mode 100644 app/DTOs/ParticipantUpdateData.php create mode 100644 app/DTOs/PdfArrayable.php diff --git a/.gitignore b/.gitignore index 55d48f6..ee5bfc8 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ node_modules/ /playwright-report/ /blob-report/ /playwright/.cache/ + +#Bruno collection config with api key +**/meta.bru diff --git a/app/Console/Commands/PdfFillFields.php b/app/Console/Commands/PdfFillFields.php index 00f4894..dfc1e03 100644 --- a/app/Console/Commands/PdfFillFields.php +++ b/app/Console/Commands/PdfFillFields.php @@ -14,9 +14,9 @@ class PdfFillFields extends Command public function handle() { - $inputPath = storage_path('app/pdfs/intake-forms/enrollment_documents_fillable_gd_global_DRAFT_v1.pdf'); + $inputPath = storage_path('pdfs/intake-form/Enrollment_Form_Fillable_2026-01-27.pdf'); $timestamp = now()->format('Ymd_His'); - $outputPath = storage_path("app/pdfs/intake-forms/enrollment_documents_fillable_gd_global_DRAFT_v1-{$timestamp}.pdf"); + $outputPath = storage_path("pdfs/intake-form/enrollment_documents_field_names-{$timestamp}.pdf"); // --- 1️⃣ First instance: Get all field names --- $reader = new Pdf($inputPath); diff --git a/app/Console/Commands/PollNeonParticipants.php b/app/Console/Commands/PollNeonParticipants.php index 902b29b..264db31 100644 --- a/app/Console/Commands/PollNeonParticipants.php +++ b/app/Console/Commands/PollNeonParticipants.php @@ -4,6 +4,7 @@ use Illuminate\Console\Command; use App\Services\Integrations\NeonApiService; +use App\Services\NeonDTOTransformer; use App\Jobs\GenerateParticipantPdfJob; use App\Models\NeonHash; use Carbon\Carbon; @@ -41,25 +42,29 @@ public function __construct(NeonApiService $neonApi) */ public function handle() { - $today = Carbon::today()->format('m-d-Y'); - Log::info("Collecting participant records that have been added or updated today - {$today}...."); $participantIds = $this->neonApi->getTodaysParticipantIds(); - - $count = count($participantIds); - Log::info("Found {$count} new or updated participant records."); - + foreach ($participantIds as $participantId) { - // Build the full participant record - $fullRecord = $this->neonApi->buildFullParticipantRecord($participantId); + // Get the full participant record + $fullRecord = $this->neonApi->buildFullParticipantRecord($participantId); + // Create a hash of the full record $hash = hash('sha256', json_encode($fullRecord)); // Check if hash already exists if (!NeonHash::where('id', $hash)->exists()) { + Log::info("Participant ". $participantId . " has updated data. Queuing pdf regeneration."); + // Store the hash for the participant data for future comparison NeonHash::create(['id' => $hash]); - dispatch(new GenerateParticipantPdfJob($participantId)); + + // Transform the participant data into serializable DTOs + $serializableDTOs = NeonDTOTransformer::transformParticipantData($fullRecord); + // Queue the pdf generation job + dispatch(new GenerateParticipantPdfJob($serializableDTOs)); + } else { + Log::info("Participant ". $participantId . " has no updated data. Skipping pdf regeneration."); } } diff --git a/app/DTOs/AssessmentDTO.php b/app/DTOs/AssessmentDTO.php index ca6887e..b30289c 100644 --- a/app/DTOs/AssessmentDTO.php +++ b/app/DTOs/AssessmentDTO.php @@ -2,27 +2,46 @@ namespace App\DTOs; -class AssessmentDTO +readonly class AssessmentDTO implements PdfArrayable { public function __construct( - public readonly string $full_name, + public readonly string $fullName, public readonly string $dob, - ## Removed - we should not collect this information - // public readonly ?string $ssn, - public readonly string $eligibility_missouri_resident, - public readonly string $eligibility_child_under_18, - public readonly string $financial_eligibility, - public readonly string $financial_drivers_licence, - public readonly string $financial_utility_bill, - public readonly string $financial_written_employer_statement, - public readonly string $financial_ss_benefits_statement, - public readonly string $financial_no_employment_income, - public readonly string $financial_unemployment_compensation, - public readonly string $financial_other, - public readonly ?string $financial_other_description, - public readonly string $poverty_monthly_income, - public readonly string $poverty_household_members, - public readonly string $poverty_percentage_fpl, + public readonly string $eligibilityMissouriResident, + public readonly string $eligibilityChildUnder18, + public readonly string $financialEligibility, + public readonly string $financialDriversLicence, + public readonly string $financialUtilityBill, + public readonly string $financialWrittenEmployerStatement, + public readonly string $financialSsBenefitsStatement, + public readonly string $financialNoEmploymentIncome, + public readonly string $financialUnemploymentCompensation, + public readonly string $financialOther, + public readonly ?string $financialOtherDescription, + public readonly string $povertyMonthlyIncome, + public readonly string $povertyHouseholdMembers, + public readonly string $povertyPercentageFpl, ) {} + + public function toPdfArray(): array { + return [ + 'participant_full_name' => $this->fullName, + 'participant_dob' => $this->dob, + 'eligibility_missouri_resident' => $this->eligibilityMissouriResident, + 'eligibility_child_under_18' => $this->eligibilityChildUnder18, + 'financial_assessment_eligibility' => $this->financialEligibility, + 'financial_assessment_drivers_licence' => $this->financialDriversLicence, + 'financial_assessment_utility_bill' => $this->financialUtilityBill, + 'financial_assessment_written_employer_statement' => $this->financialWrittenEmployerStatement, + 'financial_assessment_ss_benefits_statement' => $this->financialSsBenefitsStatement, + 'financial_assessment_no_employment_income' => $this->financialNoEmploymentIncome, + 'financial_assessment_unemployment_compensation' => $this->financialUnemploymentCompensation, + 'financial_assessment_other' => $this->financialOther, + 'financial_assessment_other_description' => $this->financialOtherDescription, + 'poverty_level_monthly_income' => $this->povertyMonthlyIncome, + 'poverty_level_number_of_household_members' => $this->povertyHouseholdMembers, + 'poverty_level_percentage_fpl' => $this->povertyPercentageFpl + ]; + } } ?> diff --git a/app/DTOs/ContactInfoDTO.php b/app/DTOs/ContactInfoDTO.php new file mode 100644 index 0000000..896d35b --- /dev/null +++ b/app/DTOs/ContactInfoDTO.php @@ -0,0 +1,54 @@ + $this->titleRegion, + 'full_name' => $this->fullName, + 'entered_date' => $this->enteredDate, + 'address' => $this->address, + 'employer' => $this->employer, + 'tshirt_size' => $this->tshirtSize, + 'phone' => $this->phone, + 'work_phone' => $this->workPhone, + 'other_phone' => $this->otherPhone, + 'email' => $this->email, + 'case_worker_name' => $this->caseworkerName, + 'case_worker_phone' => $this->caseworkerPhone, + 'monthly_child_support' => $this->monthlyChildSupport, + 'marital_status' => $this->maritalStatus, + 'ethnicity' => $this->ethnicity, + 'contact_with_children' => $this->contactWithChildren, + 'children_custody' => $this->childrenCustody, + 'children_visitation' => $this->childrenVisitation, + 'children_phone' => $this->childrenPhone, + ]; + } +} +?> \ No newline at end of file diff --git a/app/DTOs/DisclosureDTO.php b/app/DTOs/DisclosureDTO.php index 890a14c..16dbdc5 100644 --- a/app/DTOs/DisclosureDTO.php +++ b/app/DTOs/DisclosureDTO.php @@ -2,60 +2,82 @@ namespace App\DTOs; -class DisclosureDTO +readonly class DisclosureDTO implements PdfArrayable { public function __construct( - public readonly string $full_name, + public readonly string $fullName, public readonly string $phone, public readonly string $dob, - ## Removed - we should not collect this information - // public readonly ?string $ssn, public readonly string $address, public readonly string $email, - - // Divisions - public readonly string $authorize_dys, - public readonly string $authorize_mhd, - public readonly string $authorize_dfas, - public readonly string $authorize_mmac, - public readonly string $authorize_other, - public readonly ?string $authorize_discloser_form_other, - public readonly string $authorize_cd, - public readonly string $authorize_dls, - - // Release to - public readonly string $disclose_to_attorney, // Text field - public readonly string $disclose_to_legislator, - public readonly string $disclose_to_employer, - public readonly string $disclose_to_governors_staff, - ## Removed - pre-filled - // public readonly string $other_discloser, - - // Purpose - ## Removed - pre-filled - // public readonly string $purpose_eligibility_determination, - ## Removed - pre-filled - // public readonly string $purpose_employment, - public readonly string $purpose_continuity_of_services_care, - public readonly string $purpose_legal_consultation_representation, - public readonly string $purpose_complaint_investigation_resolution, - public readonly string $purpose_background_investigation, - public readonly string $purpose_legal_proceedings, - public readonly string $purpose_treatment_planning, - public readonly string $purpose_at_consumers_request, - public readonly string $purpose_to_share_or_refer, - public readonly string $purpose_other, - - // To be disclosed - public readonly string $licensure_information, - public readonly string $disclosure_medical, - public readonly string $hotline_investigations, - public readonly string $home_studies, - public readonly string $eligibility_determinations, - public readonly string $substance_abuse_treatment, - public readonly string $client_employment_records, - - public readonly string $accept_text_messages, + public readonly string $authorizeDys, + public readonly string $authorizeMhd, + public readonly string $authorizeDfas, + public readonly string $authorizeMmac, + public readonly string $authorizeOther, + public readonly ?string $authorizeDiscloserFormOther, + public readonly string $authorizeCd, + public readonly string $authorizeDls, + public readonly string $discloseToAttorney, + public readonly string $discloseToLegislator, + public readonly string $discloseToEmployer, + public readonly string $discloseToGovernorsStaff, + public readonly string $purposeContinuityOfServicesCare, + public readonly string $purposeLegalConsultationRepresentation, + public readonly string $purposeComplaintInvestigationResolution, + public readonly string $purposeBackgroundInvestigation, + public readonly string $purposeLegalProceedings, + public readonly string $purposeTreatmentPlanning, + public readonly string $purposeAtConsumersRequest, + public readonly string $purposeToShareOrRefer, + public readonly string $purposeOther, + public readonly string $licensureInformation, + public readonly string $disclosureMedical, + public readonly string $hotlineInvestigations, + public readonly string $homeStudies, + public readonly string $eligibilityDeterminations, + public readonly string $substanceAbuseTreatment, + public readonly string $clientEmploymentRecords, + public readonly string $acceptTextMessages, ) {} + + public function toPdfArray(): array { + return [ + 'authorize_full_name' => $this->fullName, + 'authorize_dys' => $this->authorizeDys, + 'authorize_mhd' => $this->authorizeMhd, + 'authorize_dfas' => $this->authorizeDfas, + 'authorize_mmac' => $this->authorizeMmac, + 'authorize_other' => $this->authorizeOther, + 'authorize_discloser_form_other' => $this->authorizeDiscloserFormOther, + 'authorize_cd' => $this->authorizeCd, + 'authorize_dls' => $this->authorizeDls, + 'disclose_full_name' => $this->fullName, + 'disclose_phone' => $this->phone, + 'disclose_dob' => $this->dob, + 'disclose_address' => $this->address, + 'disclose_email' => $this->email, + 'disclose_to_attorney' => $this->discloseToAttorney, + 'disclose_to_legislator' => $this->discloseToLegislator, + 'disclose_to_employer' => $this->discloseToEmployer, + 'disclose_to_governors_staff' => $this->discloseToGovernorsStaff, + 'disclosure_purpose_continuity_of_services_care' => $this->purposeContinuityOfServicesCare, + 'disclosure_purpose_legal_consultation_representation' => $this->purposeLegalConsultationRepresentation, + 'disclosure_purpose_complaint_investigation_resolution' => $this->purposeComplaintInvestigationResolution, + 'disclosure_purpose_background_investigation' => $this->purposeBackgroundInvestigation, + 'disclosure_purpose_legal_proceedings' => $this->purposeLegalProceedings, + 'disclosure_purpose_treatment_planning' => $this->purposeTreatmentPlanning, + 'disclosure_purpose_at_consumers_request' => $this->purposeAtConsumersRequest, + 'disclosure_purpose_to_share_or_refer' => $this->purposeToShareOrRefer, + 'disclosure_licensure_information' => $this->licensureInformation, + 'disclosure_medical' => $this->disclosureMedical, + 'disclose_hotline_investigations' => $this->hotlineInvestigations, + 'disclosure_home_studies' => $this->homeStudies, + 'disclosure_eligibility_determinations' => $this->eligibilityDeterminations, + 'disclosure_substance_abuse_treatment' => $this->substanceAbuseTreatment, + 'disclosure_client_employment_records' => $this->clientEmploymentRecords, + 'accept_text_messages' => $this->acceptTextMessages + ]; + } } ?> \ No newline at end of file diff --git a/app/DTOs/EnrollmentFormDTO.php b/app/DTOs/EnrollmentFormDTO.php deleted file mode 100644 index d2fde4e..0000000 --- a/app/DTOs/EnrollmentFormDTO.php +++ /dev/null @@ -1,186 +0,0 @@ - value array for pdftk. - * Keys match PDF field names exactly from getDataFields() output. - */ - public function toPdfArray(): array - { - return [ - // Page 1 - Enrollment - 'title_region' => $this->title_region, - 'full_name' => $this->full_name, - 'entered_date' => $this->entered_date, - 'address' => $this->address, - 'employer' => $this->employer, - 'tshirt_size' => $this->tshirt_size, - 'phone' => $this->phone, - 'work_phone' => $this->work_phone, - 'other_phone' => $this->other_phone, - 'email' => $this->email, - 'case_worker_name' => $this->case_worker_name, - 'case_worker_phone' => $this->case_worker_phone, - 'monthly_child_support' => $this->monthly_child_support, - 'marital_status' => $this->marital_status, - 'ethnicity' => $this->ethnicity, - 'contact_with_children' => $this->contact_with_children, - 'children_custody' => $this->children_custody, - 'children_visitation' => $this->children_visitation, - 'children_phone' => $this->children_phone, - - // Children - 'child_name_1' => $this->children[0]->name ?? '', - 'child_age_1' => $this->children[0]->age ?? '', - 'child_dob_1' => $this->children[0]->dob ?? '', - 'child_name_2' => $this->children[1]->name ?? '', - 'child_age_2' => $this->children[1]->age ?? '', - 'child_dob_2' => $this->children[1]->dob ?? '', - 'child_name_3' => $this->children[2]->name ?? '', - 'child_age_3' => $this->children[2]->age ?? '', - 'child_dob_3' => $this->children[2]->dob ?? '', - 'child_name_4' => $this->children[3]->name ?? '', - 'child_age_4' => $this->children[3]->age ?? '', - 'child_dob_4' => $this->children[3]->dob ?? '', - 'child_name_5' => $this->children[4]->name ?? '', - 'child_age_5' => $this->children[4]->age ?? '', - 'child_dob_5' => $this->children[4]->dob ?? '', - - // Page 2 - Disclosure - 'authorize_full_name' => $this->full_name, - 'authorize_dys' => $this->disclosure->authorize_dys, - 'authorize_mhd' => $this->disclosure->authorize_mhd, - 'authorize_dfas' => $this->disclosure->authorize_dfas, - 'authorize_mmac' => $this->disclosure->authorize_mmac, - 'authorize_other' => $this->disclosure->authorize_other, - 'authorize_discloser_form_other' => $this->disclosure->authorize_discloser_form_other ?? '', - 'authorize_cd' => $this->disclosure->authorize_cd, - 'authorize_dls' => $this->disclosure->authorize_dls, - 'disclose_full_name' => $this->disclosure->full_name, - 'disclose_phone' => $this->disclosure->phone, - 'disclose_dob' => $this->disclosure->dob, - ### Removed - we should not collect this information - // 'disclose_ssn' => $this->disclosure->ssn ?? '', - 'disclose_address' => $this->disclosure->address, - 'disclose_email' => $this->disclosure->email, - // 'select_disclose_to_attorney' => $this->disclosure->select_disclose_to_attorney, - 'disclose_to_attorney' => $this->disclosure->disclose_to_attorney, - 'disclose_to_legislator' => $this->disclosure->disclose_to_legislator, - 'disclose_to_employer' => $this->disclosure->disclose_to_employer, - 'disclose_to_governors_staff' => $this->disclosure->disclose_to_governors_staff, - ## Removed - pre-filled - // 'other_discloser' => $this->disclosure->other_discloser, - ## Removed - pre-filled - // 'disclosure_purpose_eligibility_determination' => $this->disclosure->purpose_eligibility_determination, - ## Removed - pre-filled - // 'disclosure_purpose_employment' => $this->disclosure->purpose_employment, - 'disclosure_purpose_continuity_of_services_care' => $this->disclosure->purpose_continuity_of_services_care, - 'disclosure_purpose_legal_consultation_representation' => $this->disclosure->purpose_legal_consultation_representation, - 'disclosure_purpose_complaint_investigation_resolution' => $this->disclosure->purpose_complaint_investigation_resolution, - 'disclosure_purpose_background_investigation' => $this->disclosure->purpose_background_investigation, - 'disclosure_purpose_legal_proceedings' => $this->disclosure->purpose_legal_proceedings, - 'disclosure_purpose_treatment_planning' => $this->disclosure->purpose_treatment_planning, - 'disclosure_purpose_at_consumers_request' => $this->disclosure->purpose_at_consumers_request, - 'disclosure_purpose_to_share_or_refer' => $this->disclosure->purpose_to_share_or_refer, - ## Removed - pre-filled - // 'disclosure_purpose_other' => $this->disclosure->purpose_other, - 'disclosure_licensure_information' => $this->disclosure->licensure_information, - 'disclosure_medical' => $this->disclosure->disclosure_medical, - 'disclose_hotline_investigations' => $this->disclosure->hotline_investigations, - 'disclosure_home_studies' => $this->disclosure->home_studies, - 'disclosure_eligibility_determinations' => $this->disclosure->eligibility_determinations, - 'disclosure_substance_abuse_treatment' => $this->disclosure->substance_abuse_treatment, - 'disclosure_client_employment_records' => $this->disclosure->client_employment_records, - - // Page 3 - 'accept_text_messages' => $this->disclosure->accept_text_messages, - - // Page 4 - Assessment - 'participant_full_name' => $this->assessment->full_name, - 'participant_dob' => $this->assessment->dob, - ## Removed - we should not collect this information - // 'participant_ssn' => $this->assessment->ssn ?? '', - 'eligibility_missouri_resident' => $this->assessment->eligibility_missouri_resident, - 'eligibility_child_under_18' => $this->assessment->eligibility_child_under_18, - 'financial_assessment_eligibility' => $this->assessment->financial_eligibility, - 'financial_assessment_drivers_licence' => $this->assessment->financial_drivers_licence, - 'financial_assessment_utility_bill' => $this->assessment->financial_utility_bill, - 'financial_assessment_written_employer_statement' => $this->assessment->financial_written_employer_statement, - 'financial_assessment_ss_benefits_statement' => $this->assessment->financial_ss_benefits_statement, - 'financial_assessment_no_employment_income' => $this->assessment->financial_no_employment_income, - 'financial_assessment_unemployment_compensation' => $this->assessment->financial_unemployment_compensation, - 'financial_assessment_other' => $this->assessment->financial_other, - 'financial_assessment_other_description' => $this->assessment->financial_other_description ?? '', - 'poverty_level_monthly_income' => $this->assessment->poverty_monthly_income, - 'poverty_level_number_of_household_members' => $this->assessment->poverty_household_members, - 'poverty_level_percentage_fpl' => $this->assessment->poverty_percentage_fpl, - - // Page 5 - Survey - 'survey_client_dob' => $this->survey->client_dob, - 'survey_delivery_method' => $this->survey->delivery_method, - 'survey_why' => $this->survey->why, - 'survey_other_description' => $this->survey->why_other, - 'survey_how' => $this->survey->how, - 'survey_how_other_description' => $this->survey->how_other, - 'survey_gain' => $this->survey->gain, - 'survey_gain_other_description' => $this->survey->gain_other, - - // Page 6 - Service Plan - 'service_plan_participant_full_name' => $this->servicePlan->participant_full_name, - 'service_plan_client_number' => $this->servicePlan->client_number, - 'service_plan_goal' => $this->servicePlan->goal, - 'service_plan_service_identified' => $this->servicePlan->service_identified, - 'service_plan_strategies_1' => $this->servicePlan->strategies_1, - 'service_plan_person_responsible_1' => $this->servicePlan->person_responsible_1, - 'service_plan_timeline_1' => $this->servicePlan->timeline_1, - 'service_plan_measure_of_success_1' => $this->servicePlan->measure_of_success_1, - 'service_plan_strategies_2' => $this->servicePlan->strategies_2, - 'service_plan_person_responsible_2' => $this->servicePlan->person_responsible_2, - 'service_plan_timeline_2' => $this->servicePlan->timeline_2, - 'service_plan_measure_of_success_2' => $this->servicePlan->measure_of_success_2, - 'service_plan_strategies_3' => $this->servicePlan->strategies_3, - 'service_plan_person_responsible_3' => $this->servicePlan->person_responsible_3, - 'service_plan_timeline_3' => $this->servicePlan->timeline_3, - 'service_plan_measure_of_success_3' => $this->servicePlan->measure_of_success_3, - ]; - } -} -?> \ No newline at end of file diff --git a/app/DTOs/ParticipantUpdateData.php b/app/DTOs/ParticipantUpdateData.php new file mode 100644 index 0000000..2e484c7 --- /dev/null +++ b/app/DTOs/ParticipantUpdateData.php @@ -0,0 +1,56 @@ +firstName . ' ' . $this->lastName; + } + + public function toPdfArray(): array { + + $children = []; + + foreach ($this->children as $index => $child) { + $adjusted_index = $index + 1; + $children['child_name_' . $adjusted_index] = $child->name; + $children['child_age_' . $adjusted_index] = $child->age; + $children['child_dob_' . $adjusted_index] = $child->dob; + + } + + $arrays = [ + $this->contactInfo->toPdfArray(), + $children, + $this->disclosure->toPdfArray(), + $this->assessment->toPdfArray(), + $this->survey->toPdfArray(), + $this->servicePlan->toPdfArray() + ]; + + return array_merge(...$arrays); + } +} +?> \ No newline at end of file diff --git a/app/DTOs/PdfArrayable.php b/app/DTOs/PdfArrayable.php new file mode 100644 index 0000000..92e306f --- /dev/null +++ b/app/DTOs/PdfArrayable.php @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/app/DTOs/ServicePlanDTO.php b/app/DTOs/ServicePlanDTO.php index a15476d..30982ac 100644 --- a/app/DTOs/ServicePlanDTO.php +++ b/app/DTOs/ServicePlanDTO.php @@ -2,25 +2,46 @@ namespace App\DTOs; -class ServicePlanDTO +readonly class ServicePlanDTO implements PdfArrayable { public function __construct( - public readonly string $participant_full_name, - public readonly string $client_number, + public readonly string $participantFullName, + public readonly string $clientNumber, public readonly string $goal, // service_plan_goal - public readonly string $service_identified, + public readonly string $serviceIdentified, public readonly string $strategies_1, - public readonly string $person_responsible_1, + public readonly string $personResponsible_1, public readonly string $timeline_1, - public readonly string $measure_of_success_1, + public readonly string $measureOfSuccess_1, public readonly string $strategies_2, - public readonly string $person_responsible_2, + public readonly string $personResponsible_2, public readonly string $timeline_2, - public readonly string $measure_of_success_2, + public readonly string $measureOfSuccess_2, public readonly string $strategies_3, - public readonly string $person_responsible_3, + public readonly string $personResponsible_3, public readonly string $timeline_3, - public readonly string $measure_of_success_3, + public readonly string $measureOfSuccess_3, ) {} + + public function toPdfArray(): array { + return [ + 'service_plan_participant_full_name' => $this->participantFullName, + 'service_plan_client_number' => $this->clientNumber, + 'service_plan_goal' => $this->goal, + 'service_plan_service_identified' => $this->serviceIdentified, + 'service_plan_strategies_1' => $this->strategies_1, + 'service_plan_person_responsible_1' => $this->personResponsible_1, + 'service_plan_timeline_1' => $this->timeline_1, + 'service_plan_measure_of_success_1' => $this->measureOfSuccess_1, + 'service_plan_strategies_2' => $this->strategies_2, + 'service_plan_person_responsible_2' => $this->personResponsible_2, + 'service_plan_timeline_2' => $this->timeline_2, + 'service_plan_measure_of_success_2' => $this->measureOfSuccess_2, + 'service_plan_strategies_3' => $this->strategies_3, + 'service_plan_person_responsible_3' => $this->personResponsible_3, + 'service_plan_timeline_3' => $this->timeline_3, + 'service_plan_measure_of_success_3' => $this->measureOfSuccess_3 + ]; + } } ?> diff --git a/app/DTOs/SurveyDTO.php b/app/DTOs/SurveyDTO.php index 347b08a..38a7515 100644 --- a/app/DTOs/SurveyDTO.php +++ b/app/DTOs/SurveyDTO.php @@ -2,17 +2,30 @@ namespace App\DTOs; -class SurveyDTO +readonly class SurveyDTO { public function __construct( - public readonly string $client_dob, - public readonly string $delivery_method, + public readonly string $clientDob, + public readonly string $deliveryMethod, public readonly string $why, - public readonly string $why_other, + public readonly string $whyOther, public readonly string $how, - public readonly string $how_other, + public readonly string $howOther, public readonly string $gain, - public readonly string $gain_other, + public readonly string $gainOther, ) {} + + public function toPdfArray(): array { + return [ + 'survey_client_dob' => $this->clientDob, + 'survey_delivery_method' => $this->deliveryMethod, + 'survey_why' => $this->why, + 'survey_other_description' => $this->whyOther, + 'survey_how' => $this->how, + 'survey_how_other_description' => $this->howOther, + 'survey_gain' => $this->gain, + 'survey_gain_other_description' => $this->gainOther + ]; + } } ?> \ No newline at end of file diff --git a/app/Jobs/GenerateParticipantPdfJob.php b/app/Jobs/GenerateParticipantPdfJob.php index 0e6bb3f..f1e8cdd 100644 --- a/app/Jobs/GenerateParticipantPdfJob.php +++ b/app/Jobs/GenerateParticipantPdfJob.php @@ -8,52 +8,49 @@ use App\Models\NeonHash; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Mail; use App\Mail\IntakeFormMailable; use Illuminate\Support\Facades\Log; +use App\DTOs\ParticipantUpdateData; -class GenerateParticipantPdfJob implements ShouldQueue +class GenerateParticipantPdfJob implements ShouldQueue, ShouldBeEncrypted { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - - public int $participantId; - public string $hash; /** * Create a new job instance. + * @param ChildDTO[] $children */ - public function __construct(int $participantId) - { - $this->participantId = $participantId; - } + public function __construct( + public readonly ParticipantUpdateData $updatedParticipantData + ) {} /** * Execute the job. */ public function handle( - NeonApiService $neonApi, - NeonDTOTransformer $transformer, PdfIntakeFormService $pdfService ) { try { // Fetch and transform participant data - $fullRecord = $neonApi->buildFullParticipantRecord($this->participantId); - $participant = $transformer->transformPerson($fullRecord); + // $fullRecord = $neonApi->buildFullParticipantRecord($this->participantId); + // $participant = $transformer->transformPerson($fullRecord); // Generate the PDF - $pdfPath = $pdfService->generate($participant); + $pdfPath = $pdfService->generate($this->updatedParticipantData); // Send email - Log::info('📧 Sending PDF email for participant '.$this->participantId); + Log::info('📧 Sending PDF email for participant ' . $this->updatedParticipantData->id); Mail::to('hello@example.com') - ->send(new IntakeFormMailable($participant, $pdfPath)); + ->send(new IntakeFormMailable($this->updatedParticipantData, $pdfPath)); Log::info('✅ PDF email sent.'); } catch (\Exception $e) { - Log::error('Failed to generate PDF for participant '.$this->participantId.': '.$e->getMessage()); + Log::error('Failed to generate PDF for participant ' . $this->updatedParticipantData->id . ': ' . $e->getMessage()); throw $e; // Let the job retry if needed } } diff --git a/app/Mail/IntakeFormMailable.php b/app/Mail/IntakeFormMailable.php index 2befdf5..a13a962 100644 --- a/app/Mail/IntakeFormMailable.php +++ b/app/Mail/IntakeFormMailable.php @@ -8,23 +8,17 @@ use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; +use App\DTOs\ParticipantUpdateData; class IntakeFormMailable extends Mailable { use Queueable, SerializesModels; - public $participant; - - public $pdfPath; /** * Create a new message instance. */ - public function __construct($participant, $pdfPath) - { - $this->participant = $participant; - $this->pdfPath = $pdfPath; - } + public function __construct(private readonly ParticipantUpdateData $participant, private readonly string $pdfPath){} /** * Get the message envelope. @@ -32,7 +26,7 @@ public function __construct($participant, $pdfPath) public function envelope(): Envelope { return new Envelope( - subject: 'Intake Form for '.($this->participant->full_name ?? 'Participant') + subject: 'Intake Form for '.($this->participant->fullName ?? 'Participant') ); } diff --git a/app/Services/Integrations/NeonApiService.php b/app/Services/Integrations/NeonApiService.php index d7a1485..c793042 100644 --- a/app/Services/Integrations/NeonApiService.php +++ b/app/Services/Integrations/NeonApiService.php @@ -4,6 +4,7 @@ use Carbon\Carbon; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Log; use Exception; @@ -62,9 +63,13 @@ private function fetch(string $endpoint, array $fields = [], ?int $personId = nu } public function getTodaysParticipantIds(): array { - $todaysDate = Carbon::today()->format('Y-m-d'); - // $todaysDate = '2026-02-01'; - return $this->getParticipantIdsByDate($todaysDate); + $todaysDate = Carbon::today('America/Chicago')->format('Y-m-d'); + // $todaysDate = '2026-02-24'; + Log::info("Collecting participant records that have been added or updated today - {$todaysDate}...."); + $toReturn = $this->getParticipantIdsByDate($todaysDate); + $count = count($toReturn); + Log::info("Found {$count} new or updated participant records."); + return $toReturn; } private function getParticipantIdsByDate(string $filterDate): array { @@ -308,7 +313,10 @@ public function fetchPersonServicePlan(int $personId, bool $useWhereClause): arr return $this->fetch('persons_service_plan', [ "persons_id", "programName", - "clientNumber", + /** + * This field is broken at Neon, it changes at every request breaking our hashing + */ + //"clientNumber", "reviewDates", "serviceAreas", "serviceIdentifiedByTheParticipants", diff --git a/app/Services/NeonDTOTransformer.php b/app/Services/NeonDTOTransformer.php index 34aefd9..e61585d 100644 --- a/app/Services/NeonDTOTransformer.php +++ b/app/Services/NeonDTOTransformer.php @@ -2,56 +2,68 @@ namespace App\Services; -use App\DTOs\AssessmentDTO; +use App\DTOs\ParticipantUpdateData; +use App\DTOs\ContactInfoDTO; use App\DTOs\ChildDTO; use App\DTOs\DisclosureDTO; -use App\DTOs\EnrollmentFormDTO; -use App\DTOs\ServicePlanDTO; +use App\DTOs\AssessmentDTO; use App\DTOs\SurveyDTO; +use App\DTOs\ServicePlanDTO; use Carbon\Carbon; -class NeonDTOTransformer +final class NeonDTOTransformer { - public function transformPerson(array $data): EnrollmentFormDTO - { - $contactInfo = $data['contactInfo']['records'][0]; + private function __construct(){} + + public static function transformParticipantData(array $participantData): ParticipantUpdateData { + $contactInfo = $participantData['contactInfo']['records'][0]; - return new EnrollmentFormDTO( - id: $contactInfo['persons_id']['value'], - first_name: $contactInfo['firstName']['displayValue'] ?? '', - last_name: $contactInfo['lastName']['displayValue'] ?? '', - title_region: $contactInfo['regions_id']['displayValue'] ?? '', - full_name: $contactInfo['persons_id']['displayValue'] ?? '', - entered_date: $this->parseDateString($contactInfo['enteredDate']['value'] ?? null), - address: $this->buildAddress($contactInfo), - employer: $contactInfo['employer']['value'] ?? '', - tshirt_size: $contactInfo['tShirtSize']['displayValue'] ?? '', - phone: $contactInfo['homeCellPhone']['value'] ?? '', - work_phone: $contactInfo['workPhone']['value'] ?? '', - other_phone: $contactInfo['otherNumber']['value'] ?? '', - email: $contactInfo['email']['value'] ?? '', - case_worker_name: $contactInfo['probationParoleCaseWorkerName']['value'] ?? '', - case_worker_phone: $contactInfo['probationParoleCaseWorkerPhone']['value'] ?? '', - monthly_child_support: $contactInfo['monthlyChildSupportPayment']['displayValue'] ?? '', - marital_status: $contactInfo['maritalStatus']['displayValue'] ?? '', - ethnicity: $contactInfo['ethnicity']['displayValue'] ?? '', - contact_with_children: $this->yesNo($contactInfo['contactWithChildren']['displayValue'] ?? null), - children_custody: $this->inList($contactInfo['contactType']['value'] ?? '', '763'), - children_visitation: $this->inList($contactInfo['contactType']['value'] ?? '', '762'), - children_phone: $this->inList($contactInfo['contactType']['value'] ?? '', '1483'), - children: $this->transformChildren($data['children']['records'] ?? []), - disclosure: $this->transformDisclosure($data['disclosure']['records'][0]), - assessment: $this->transformAssessment($data['assessment']['records'][0]), - survey: $this->transformSurvey($data['survey']['records'][0]), - servicePlan: $this->transformServicePlan($data['servicePlan']['records'][0]), + return new ParticipantUpdateData( + id: $contactInfo['persons_id']['value'], + firstName: $contactInfo['firstName']['displayValue'] ?? '', + lastName: $contactInfo['lastName']['displayValue'] ?? '', + contactInfo: self::transformContactInfo($contactInfo), + children: self::transformChildren($participantData['children']['records'] ?? []), + disclosure: self::transformDisclosure($participantData['disclosure']['records'][0]), + assessment: self::transformAssessment($participantData['assessment']['records'][0]), + survey: self::transformSurvey($participantData['survey']['records'][0]), + servicePlan: self::transformServicePlan($participantData['servicePlan']['records'][0]) + ); + + } + + private static function transformContactInfo(array $contactInfo): ContactInfoDTO + { + return new ContactInfoDTO( + titleRegion: $contactInfo['regions_id']['displayValue'] ?? '', + fullName: $contactInfo['persons_id']['displayValue'] ?? '', + enteredDate: self::parseDateString($contactInfo['enteredDate']['value'] ?? null), + address: self::buildAddress($contactInfo), + employer: $contactInfo['employer']['value'] ?? '', + tshirtSize: $contactInfo['tShirtSize']['displayValue'] ?? '', + phone: $contactInfo['homeCellPhone']['value'] ?? '', + workPhone: $contactInfo['workPhone']['value'] ?? '', + otherPhone: $contactInfo['otherNumber']['value'] ?? '', + email: $contactInfo['email']['value'] ?? '', + caseworkerName: $contactInfo['probationParoleCaseWorkerName']['value'] ?? '', + caseworkerPhone: $contactInfo['probationParoleCaseWorkerPhone']['value'] ?? '', + monthlyChildSupport: $contactInfo['monthlyChildSupportPayment']['displayValue'] ?? '', + maritalStatus: $contactInfo['maritalStatus']['displayValue'] ?? '', + ethnicity: $contactInfo['ethnicity']['displayValue'] ?? '', + contactWithChildren: self::yesNo($contactInfo['contactWithChildren']['displayValue'] ?? null), + childrenCustody: self::inList($contactInfo['contactType']['value'] ?? '', '763'), + childrenVisitation: self::inList($contactInfo['contactType']['value'] ?? '', '762'), + childrenPhone: self::inList($contactInfo['contactType']['value'] ?? '', '1483') ); } + - private function transformChildren(array $children): array + /** @return ChildDTO[] */ + private static function transformChildren(array $children): array { $result = []; foreach ($children as $child) { - $dob = $this->parseDate($child['dateOfBirth']['value'] ?? null); + $dob = self::parseDate($child['dateOfBirth']['value'] ?? null); $result[] = new ChildDTO( name: trim(($child['firstName']['value'] ?? '') . ' ' . ($child['lastName']['value'] ?? '')), age: $dob ? (string) $dob->diffInYears(Carbon::now()) : '', @@ -61,7 +73,7 @@ private function transformChildren(array $children): array return $result; } - private function transformDisclosure(array $d): DisclosureDTO + private static function transformDisclosure(array $d): DisclosureDTO { $divisions = explode(',', $d['division']['value'] ?? ''); $releaseTo = explode(',', $d['releaseTo']['value'] ?? ''); @@ -69,172 +81,172 @@ private function transformDisclosure(array $d): DisclosureDTO $disclosed = explode(',', $d['informationToBeDisclosed']['value'] ?? ''); return new DisclosureDTO( - full_name: $d['persons_id']['displayValue'] ?? '', + fullName: $d['persons_id']['displayValue'] ?? '', phone: $d['homeCellPhone']['value'] ?? '', - dob: $this->parseDateString($d['dateOfBirth']['value'] ?? null), + dob: self::parseDateString($d['dateOfBirth']['value'] ?? null), ## We should not collect this information // ssn: null, address: $d['fullAddress']['displayValue'] ?? '', email: $d['email']['value'] ?? '', - authorize_dys: $this->inArray('679', $divisions), - authorize_mhd: $this->inArray('684', $divisions), - authorize_dfas: $this->inArray('683', $divisions), - authorize_mmac: $this->inArray('1484', $divisions), - authorize_other: isset($d['divisionOther']['value']) && $d['divisionOther']['value'] ? 'Yes' : 'Off', - authorize_discloser_form_other: $d['divisionOther']['value'] ?? null, - authorize_cd: $this->inArray('682', $divisions), - authorize_dls: $this->inArray('681', $divisions), + authorizeDys: self::inArray('679', $divisions), + authorizeMhd: self::inArray('684', $divisions), + authorizeDfas: self::inArray('683', $divisions), + authorizeMmac: self::inArray('1484', $divisions), + authorizeOther: isset($d['divisionOther']['value']) && $d['divisionOther']['value'] ? 'Yes' : 'Off', + authorizeDiscloserFormOther: $d['divisionOther']['value'] ?? null, + authorizeCd: self::inArray('682', $divisions), + authorizeDls: self::inArray('681', $divisions), ## This is the text field // disclose_to_attorney: $attorneyInList, - disclose_to_attorney: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", + discloseToAttorney: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", ## This is the text field // disclose_to_legislator: $this->inArray('1487', $releaseTo), - disclose_to_legislator: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", + discloseToLegislator: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", ## This is the text field // disclose_to_employer: $this->inArray('1486', $releaseTo), - disclose_to_employer: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", + discloseToEmployer: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", ## This is the text field // disclose_to_governors_staff: $this->inArray('1488', $releaseTo), - disclose_to_governors_staff: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", + discloseToGovernorsStaff: "Neon has the checkbox value, but not associated text; we have no checkbox field, but the text", ## Pre-filled // other_discloser: $d['releaseToOther']['displayValue'] ?? '', ## Pre-filled // purpose_eligibility_determination: $this->inArray('585', $purposes), ## Pre-filled // purpose_employment: $this->inArray('594', $purposes), - purpose_continuity_of_services_care: $this->inArray('447', $purposes), - purpose_legal_consultation_representation: $this->inArray('1490', $purposes), - purpose_complaint_investigation_resolution: $this->inArray('1491', $purposes), - purpose_background_investigation: $this->inArray('1492', $purposes), - purpose_legal_proceedings: $this->inArray('1493', $purposes), - purpose_treatment_planning: $this->inArray('1494', $purposes), - purpose_at_consumers_request: $this->inArray('1495', $purposes), - purpose_to_share_or_refer: $this->inArray('755', $purposes), + purposeContinuityOfServicesCare: self::inArray('447', $purposes), + purposeLegalConsultationRepresentation: self::inArray('1490', $purposes), + purposeComplaintInvestigationResolution: self::inArray('1491', $purposes), + purposeBackgroundInvestigation: self::inArray('1492', $purposes), + purposeLegalProceedings: self::inArray('1493', $purposes), + purposeTreatmentPlanning: self::inArray('1494', $purposes), + purposeAtConsumersRequest: self::inArray('1495', $purposes), + purposeToShareOrRefer: self::inArray('755', $purposes), //This is the checkbox for the 'other purpose' field which is pre-filled, but the box is not checked, hence 'Yes' here - purpose_other: "Yes", //$this->inArray('1496', $purposes), - licensure_information: $this->inArray('161', $disclosed), - disclosure_medical: $this->inArray('1497', $disclosed), - hotline_investigations: $this->inArray('1499', $disclosed), - home_studies: $this->inArray('1500', $disclosed), - eligibility_determinations: $this->inArray('1501', $disclosed), - substance_abuse_treatment: $this->inArray('1502', $disclosed), - client_employment_records: $this->inArray('1503', $disclosed), - accept_text_messages: $this->yesNo($d['acceptsTextMessage']['displayValue'] ?? null), + purposeOther: "Yes", //$this->inArray('1496', $purposes), + licensureInformation: self::inArray('161', $disclosed), + disclosureMedical: self::inArray('1497', $disclosed), + hotlineInvestigations: self::inArray('1499', $disclosed), + homeStudies: self::inArray('1500', $disclosed), + eligibilityDeterminations: self::inArray('1501', $disclosed), + substanceAbuseTreatment: self::inArray('1502', $disclosed), + clientEmploymentRecords: self::inArray('1503', $disclosed), + acceptTextMessages: self::yesNo($d['acceptsTextMessage']['displayValue'] ?? null), ); } - private function transformAssessment(array $a): AssessmentDTO + private static function transformAssessment(array $a): AssessmentDTO { $otherValue = $a['other']['displayValue'] ?? null; return new AssessmentDTO( - full_name: $a['persons_id']['displayValue'] ?? '', + fullName: $a['persons_id']['displayValue'] ?? '', dob: $a['dateOfBirth']['displayValue'] ?? '', ## We should not collect this information // ssn: null, - eligibility_missouri_resident: $this->yesNo($a['missouriResident']['displayValue'] ?? null), - eligibility_child_under_18: $this->yesNo($a['childUnder18']['displayValue'] ?? null), - financial_eligibility: 'Off', // completed by state agency, not in Neon - financial_drivers_licence: $this->yesNo($a['dL']['displayValue'] ?? null), - financial_utility_bill: $this->yesNo($a['utilityBill']['displayValue'] ?? null), - financial_written_employer_statement: $this->yesNo($a['writtenEmployerStatement']['displayValue'] ?? null), - financial_ss_benefits_statement: $this->yesNo($a['socialSecurityBenefitsStatement']['displayValue'] ?? null), - financial_no_employment_income: $this->yesNo($a['selfAttestationOfNoEmploymentOrIncome']['displayValue'] ?? null), - financial_unemployment_compensation: $this->yesNo($a['unemploymentCompensation']['displayValue'] ?? null), - financial_other: $otherValue ? 'Yes' : 'Off', - financial_other_description: $otherValue ?: null, - poverty_monthly_income: $a['hoseholdIncome']['displayValue'] ?? '', // typo is in Neon field name - poverty_household_members: $a['numberOfFamilyMembersInHousehold']['value'] ?? '', - poverty_percentage_fpl: $a['percentageOfFPL']['value'] ?? '', + eligibilityMissouriResident: self::yesNo($a['missouriResident']['displayValue'] ?? null), + eligibilityChildUnder18: self::yesNo($a['childUnder18']['displayValue'] ?? null), + financialEligibility: 'Off', // completed by state agency, not in Neon + financialDriversLicence: self::yesNo($a['dL']['displayValue'] ?? null), + financialUtilityBill: self::yesNo($a['utilityBill']['displayValue'] ?? null), + financialWrittenEmployerStatement: self::yesNo($a['writtenEmployerStatement']['displayValue'] ?? null), + financialSsBenefitsStatement: self::yesNo($a['socialSecurityBenefitsStatement']['displayValue'] ?? null), + financialNoEmploymentIncome: self::yesNo($a['selfAttestationOfNoEmploymentOrIncome']['displayValue'] ?? null), + financialUnemploymentCompensation: self::yesNo($a['unemploymentCompensation']['displayValue'] ?? null), + financialOther: $otherValue ? 'Yes' : 'Off', + financialOtherDescription: $otherValue ?: null, + povertyMonthlyIncome: $a['hoseholdIncome']['displayValue'] ?? '', // typo is in Neon field name + povertyHouseholdMembers: $a['numberOfFamilyMembersInHousehold']['value'] ?? '', + povertyPercentageFpl: $a['percentageOfFPL']['value'] ?? '', ); } - private function transformSurvey(array $s): SurveyDTO + private static function transformSurvey(array $s): SurveyDTO { $reasons = explode(',', $s['reasons']['value'] ?? ''); $howHeardAbout = explode(',', $s['hearAboutUs']['value'] ?? ''); $expectedGain = explode(',', $s['expectToGain']['value'] ?? ''); return new SurveyDTO( - client_dob: $s['dateOfBirth']['displayValue'] ?? '', - delivery_method: '', // not in Neon — filled in by participant on paper + clientDob: $s['dateOfBirth']['displayValue'] ?? '', + deliveryMethod: '', // not in Neon — filled in by participant on paper why: match(true) { - in_array('453', $reasons) => 'Responsible father', - in_array('454', $reasons) => 'Referred', - in_array('1506', $reasons) => 'Child support concerns', - in_array('695', $reasons) => 'Attourney', // matches PDF FieldStateOption spelling - in_array('1507', $reasons) => 'Other', + self::inArray('453', $reasons) => 'Responsible father', + self::inArray('454', $reasons) => 'Referred', + self::inArray('1506', $reasons) => 'Child support concerns', + self::inArray('695', $reasons) => 'Attourney', // matches PDF FieldStateOption spelling + self::inArray('1507', $reasons) => 'Other', default => 'Off', }, - why_other: $s['reasonsOther']['value'] ?? '', + whyOther: $s['reasonsOther']['value'] ?? '', how: match(true) { - in_array('1510', $howHeardAbout) => 'Family support', - in_array('1509', $howHeardAbout) => 'Past participant', - in_array('1512', $howHeardAbout) => 'Marketing', - in_array('1511', $howHeardAbout) => 'Prosecuting attorney', - in_array('1513', $howHeardAbout) => 'The organization', - in_array('1508', $howHeardAbout) => 'Word of mouth', - in_array('1514', $howHeardAbout) => 'Other', + self::inArray('1510', $howHeardAbout) => 'Family support', + self::inArray('1509', $howHeardAbout) => 'Past participant', + self::inArray('1512', $howHeardAbout) => 'Marketing', + self::inArray('1511', $howHeardAbout) => 'Prosecuting attorney', + self::inArray('1513', $howHeardAbout) => 'The organization', + self::inArray('1508', $howHeardAbout) => 'Word of mouth', + self::inArray('1514', $howHeardAbout) => 'Other', default => 'Off', }, - how_other: $s['hearAboutUsOther']['value'] ?? '', + howOther: $s['hearAboutUsOther']['value'] ?? '', gain: match(true) { - in_array('1520', $expectedGain) => 'Access to mentors', - in_array('1524', $expectedGain) => 'Credit repair assistance', - in_array('1521', $expectedGain) => 'Criminal History Assistance', - in_array('1522', $expectedGain) => 'Overcoming homelessness assistance', - in_array('1516', $expectedGain) => 'Abuse assistance', - in_array('1523', $expectedGain) => 'Visitation custody assistance', - in_array('1515', $expectedGain) => 'Emplyment opportunities', // matches PDF FieldStateOption spelling - in_array('1517', $expectedGain) => 'Parenting skills', - in_array('1526', $expectedGain) => 'Increased Understanding of Child Support', - in_array('1525', $expectedGain) => 'Maintaining Hope', - in_array('1518', $expectedGain) => 'Resume building', - in_array('1519', $expectedGain) => 'Legal services', - in_array('1527', $expectedGain) => 'Other', + self::inArray('1520', $expectedGain) => 'Access to mentors', + self::inArray('1524', $expectedGain) => 'Credit repair assistance', + self::inArray('1521', $expectedGain) => 'Criminal History Assistance', + self::inArray('1522', $expectedGain) => 'Overcoming homelessness assistance', + self::inArray('1516', $expectedGain) => 'Abuse assistance', + self::inArray('1523', $expectedGain) => 'Visitation custody assistance', + self::inArray('1515', $expectedGain) => 'Emplyment opportunities', // matches PDF FieldStateOption spelling + self::inArray('1517', $expectedGain) => 'Parenting skills', + self::inArray('1526', $expectedGain) => 'Increased Understanding of Child Support', + self::inArray('1525', $expectedGain) => 'Maintaining Hope', + self::inArray('1518', $expectedGain) => 'Resume building', + self::inArray('1519', $expectedGain) => 'Legal services', + self::inArray('1527', $expectedGain) => 'Other', default => 'Off', }, - gain_other: $s['expectToGainOther']['value'] ?? '', + gainOther: $s['expectToGainOther']['value'] ?? '', ); } - private function transformServicePlan(array $sp): ServicePlanDTO + private static function transformServicePlan(array $sp): ServicePlanDTO { return new ServicePlanDTO( - participant_full_name: $sp['persons_id']['displayValue'] ?? '', - client_number: $sp['clientNumber']['value'] ?? '', - goal: '', // database appears to be missing this field per original comment - service_identified: $sp['serviceIdentifiedByTheParticipants']['value'] ?? '', - strategies_1: $sp['goals_custodyVisitationObj']['displayValue'] ?? '', - person_responsible_1: $sp['goals_custodyVisitationPersonRes']['displayValue'] ?? '', - timeline_1: $sp['goals_custodyVisitationTimeline']['displayValue'] ?? '', - measure_of_success_1: $sp['goals_custodyVisitationMeasure']['value'] ?? '', - strategies_2: $sp['goals_educationEmploymentObj']['displayValue'] ?? '', - person_responsible_2: $sp['goals_educationEmploymentPersonRes']['displayValue'] ?? '', - timeline_2: $sp['goals_educationEmploymentTimeline']['displayValue'] ?? '', - measure_of_success_2: $sp['goals_educationEmploymentMeasure']['value'] ?? '', - strategies_3: $sp['goals_housingTransportationObj']['displayValue'] ?? '', - person_responsible_3: $sp['goals_housingTransportationPersonRes']['displayValue'] ?? '', - timeline_3: $sp['goals_housingTransportationTimeline']['displayValue'] ?? '', - measure_of_success_3: $sp['goals_housingTransportationMeasure']['value'] ?? '', + participantFullName: $sp['persons_id']['displayValue'] ?? '', + clientNumber: $sp['clientNumber']['value'] ?? '', + goal: '', // database appears to be missing this field per original comment + serviceIdentified: $sp['serviceIdentifiedByTheParticipants']['value'] ?? '', + strategies_1: $sp['goals_custodyVisitationObj']['displayValue'] ?? '', + personResponsible_1: $sp['goals_custodyVisitationPersonRes']['displayValue'] ?? '', + timeline_1: $sp['goals_custodyVisitationTimeline']['displayValue'] ?? '', + measureOfSuccess_1: $sp['goals_custodyVisitationMeasure']['value'] ?? '', + strategies_2: $sp['goals_educationEmploymentObj']['displayValue'] ?? '', + personResponsible_2: $sp['goals_educationEmploymentPersonRes']['displayValue'] ?? '', + timeline_2: $sp['goals_educationEmploymentTimeline']['displayValue'] ?? '', + measureOfSuccess_2: $sp['goals_educationEmploymentMeasure']['value'] ?? '', + strategies_3: $sp['goals_housingTransportationObj']['displayValue'] ?? '', + personResponsible_3: $sp['goals_housingTransportationPersonRes']['displayValue'] ?? '', + timeline_3: $sp['goals_housingTransportationTimeline']['displayValue'] ?? '', + measureOfSuccess_3: $sp['goals_housingTransportationMeasure']['value'] ?? '', ); } // ─── Helpers ───────────────────────────────────────────────────────────── - private function parseDate(?string $value): ?Carbon + private static function parseDate(?string $value): ?Carbon { return $value ? Carbon::createFromFormat('Y-m-d', $value) : null; } - private function parseDateString(?string $value): string + private static function parseDateString(?string $value): string { if (!$value) return ''; $date = Carbon::createFromFormat('Y-m-d', $value); return $date ? $date->format('m/d/Y') : ''; } - private function buildAddress(array $c): string + private static function buildAddress(array $c): string { return trim(implode(' ', array_filter([ $c['address1']['value'] ?? '', @@ -245,7 +257,7 @@ private function buildAddress(array $c): string ]))); } - private function yesNo(?string $value): string + private static function yesNo(?string $value): string { return match($value) { '1' => 'Yes', @@ -254,12 +266,12 @@ private function yesNo(?string $value): string }; } - private function inList(string $list, string $id): string + private static function inList(string $list, string $id): string { return in_array($id, explode(',', $list)) ? 'Yes' : 'Off'; } - private function inArray(string $id, array $arr): string + private static function inArray(string $id, array $arr): string { return in_array($id, $arr) ? 'Yes' : 'Off'; } diff --git a/app/Services/PdfIntakeFormService.php b/app/Services/PdfIntakeFormService.php index ec6910d..ee16c77 100644 --- a/app/Services/PdfIntakeFormService.php +++ b/app/Services/PdfIntakeFormService.php @@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use mikehaertl\pdftk\Pdf; +use App\DTOs\ParticipantUpdateData; class PdfIntakeFormService { @@ -13,7 +14,7 @@ class PdfIntakeFormService protected string $pdfTemplatePath = 'intake-form/Enrollment_Form_Fillable_2026-01-27.pdf'; - public function generate($participant): string + public function generate(ParticipantUpdateData $participant): string { // $fieldMap = config("pdf_forms.{$this->formKey}"); @@ -29,7 +30,7 @@ public function generate($participant): string // Build folder structure for each participant $storagePath = "participant-forms/{$participant->id}/"; $timestamp = Carbon::now()->format('Y-m-d_H-i-s'); - $filename = Str::of($participant->last_name)->slug('_')->ucfirst().'_'.Str::of($participant->first_name)->slug('_')->ucfirst().'_Enrollment_'.$timestamp.'.pdf'; + $filename = Str::of($participant->lastName)->slug('_')->ucfirst().'_'.Str::of($participant->firstName)->slug('_')->ucfirst().'_Enrollment_'.$timestamp.'.pdf'; $outputPath = storage_path("app/{$storagePath}{$filename}"); diff --git a/resources/views/emails/intake-form.blade.php b/resources/views/emails/intake-form.blade.php index 6400d2f..41bb4d2 100644 --- a/resources/views/emails/intake-form.blade.php +++ b/resources/views/emails/intake-form.blade.php @@ -9,7 +9,7 @@

Hello,

-

A new intake form has been generated for {{ $participant->full_name }}.

+

A new intake form has been generated for {{ $participant->fullName() }}.

The completed form is attached to this email.