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 d915f95..264db31 100644 --- a/app/Console/Commands/PollNeonParticipants.php +++ b/app/Console/Commands/PollNeonParticipants.php @@ -4,8 +4,11 @@ use Illuminate\Console\Command; use App\Services\Integrations\NeonApiService; +use App\Services\NeonDTOTransformer; use App\Jobs\GenerateParticipantPdfJob; use App\Models\NeonHash; +use Carbon\Carbon; +use Illuminate\Support\Facades\Log; class PollNeonParticipants extends Command { @@ -39,21 +42,29 @@ public function __construct(NeonApiService $neonApi) */ public function handle() { - $participants = $this->neonApi->getTodaysParticipants(); + + $participantIds = $this->neonApi->getTodaysParticipantIds(); + + foreach ($participantIds as $participantId) { + // Get the full participant record + $fullRecord = $this->neonApi->buildFullParticipantRecord($participantId); - foreach ($participants as $person) { - $participantId = (int) $person['persons_id']['value']; - - // Build 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 new file mode 100644 index 0000000..b30289c --- /dev/null +++ b/app/DTOs/AssessmentDTO.php @@ -0,0 +1,47 @@ + $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/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/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 new file mode 100644 index 0000000..16dbdc5 --- /dev/null +++ b/app/DTOs/DisclosureDTO.php @@ -0,0 +1,83 @@ + $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/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 new file mode 100644 index 0000000..30982ac --- /dev/null +++ b/app/DTOs/ServicePlanDTO.php @@ -0,0 +1,47 @@ + $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 new file mode 100644 index 0000000..38a7515 --- /dev/null +++ b/app/DTOs/SurveyDTO.php @@ -0,0 +1,31 @@ + $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 8cc3ee7..f1e8cdd 100644 --- a/app/Jobs/GenerateParticipantPdfJob.php +++ b/app/Jobs/GenerateParticipantPdfJob.php @@ -3,57 +3,54 @@ 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; 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, - NeonDataTransformer $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 b6ac867..c793042 100644 --- a/app/Services/Integrations/NeonApiService.php +++ b/app/Services/Integrations/NeonApiService.php @@ -2,7 +2,9 @@ namespace App\Services\Integrations; +use Carbon\Carbon; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Log; use Exception; @@ -60,29 +62,86 @@ 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('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; + } - $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 +210,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", @@ -252,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 new file mode 100644 index 0000000..e61585d --- /dev/null +++ b/app/Services/NeonDTOTransformer.php @@ -0,0 +1,278 @@ +diffInYears(Carbon::now()) : '', + dob: $dob ? $dob->format('m/d/Y') : '', + ); + } + return $result; + } + + private static 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( + fullName: $d['persons_id']['displayValue'] ?? '', + phone: $d['homeCellPhone']['value'] ?? '', + dob: self::parseDateString($d['dateOfBirth']['value'] ?? null), + ## We should not collect this information + // ssn: null, + address: $d['fullAddress']['displayValue'] ?? '', + email: $d['email']['value'] ?? '', + 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, + 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), + 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), + 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), + 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), + 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 + 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 static function transformAssessment(array $a): AssessmentDTO + { + $otherValue = $a['other']['displayValue'] ?? null; + + return new AssessmentDTO( + fullName: $a['persons_id']['displayValue'] ?? '', + dob: $a['dateOfBirth']['displayValue'] ?? '', + ## We should not collect this information + // ssn: null, + 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 static function transformSurvey(array $s): SurveyDTO + { + $reasons = explode(',', $s['reasons']['value'] ?? ''); + $howHeardAbout = explode(',', $s['hearAboutUs']['value'] ?? ''); + $expectedGain = explode(',', $s['expectToGain']['value'] ?? ''); + + return new SurveyDTO( + clientDob: $s['dateOfBirth']['displayValue'] ?? '', + deliveryMethod: '', // not in Neon — filled in by participant on paper + why: match(true) { + 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', + }, + whyOther: $s['reasonsOther']['value'] ?? '', + how: match(true) { + 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', + }, + howOther: $s['hearAboutUsOther']['value'] ?? '', + gain: match(true) { + 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', + }, + gainOther: $s['expectToGainOther']['value'] ?? '', + ); + } + + private static function transformServicePlan(array $sp): ServicePlanDTO + { + return new ServicePlanDTO( + 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 static function parseDate(?string $value): ?Carbon + { + return $value ? Carbon::createFromFormat('Y-m-d', $value) : null; + } + + 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 static 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 static function yesNo(?string $value): string + { + return match($value) { + '1' => 'Yes', + '0' => 'No', + default => 'Off', + }; + } + + private static function inList(string $list, string $id): string + { + return in_array($id, explode(',', $list)) ? 'Yes' : 'Off'; + } + + private static 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..ee16c77 100644 --- a/app/Services/PdfIntakeFormService.php +++ b/app/Services/PdfIntakeFormService.php @@ -6,35 +6,37 @@ use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use mikehaertl\pdftk\Pdf; +use App\DTOs\ParticipantUpdateData; 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 + public function generate(ParticipantUpdateData $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}/"; $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}"); // Ensure directory exists Storage::makeDirectory($storagePath); + $data = $participant->toPdfArray(); // Load and fill the PDF $pdf = new Pdf(storage_path("app/{$this->pdfTemplatePath}")); 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.