id)) { return ''; } $parts = []; $parts[] = substr((string) $application->id, 0, 4); if (!empty($application->merchant_id)) { $parts[] = (string) $application->merchant_id; } $initials = $this->nameInitials($application->name); if ($initials) { $parts[] = $initials; } // pan_card is decrypted by the Encryptable trait on attribute access. $pan = strtoupper(preg_replace('/[^A-Za-z0-9]/', '', (string) $application->pan_card)); if (strlen($pan) >= 4) { $parts[] = substr($pan, -4); } $parts[] = time(); return implode(self::SEPARATOR, $parts); } /** * First and last alphabet of the customer name, uppercased. */ private function nameInitials($name) { $letters = preg_replace('/[^A-Za-z]/', '', (string) $name); if ($letters === '') { return ''; } return strtoupper(substr($letters, 0, 1) . substr($letters, -1)); } /** * Reference number for a letter of the given type. * * Returns the reference already issued for this application, otherwise a * fresh one that the caller is expected to persist via persist() once the * PDF has been rendered. */ public function resolve($application_id, $file_type, $application = null) { $files = ConsumerApplicationFiles::where('consumer_application_id', $application_id) ->where('file_type', $file_type) ->orderBy('id', 'asc') ->get(['id', 'reference_number']); foreach ($files as $file) { if ($file->reference_number) { return $file->reference_number; } } if (!$application) { $application = ConsumerApplication::where('id', $application_id)->first(['id', 'name', 'merchant_id', 'pan_card']); } $reference_number = $this->build($application); // An entry exists but carries no reference (letter recorded before this // change). Backfill it so the number is stable from here on, since // persist() will not add a second entry. if ($reference_number && count($files)) { ConsumerApplicationFiles::where('id', $files[0]->id)->update(['reference_number' => $reference_number]); } return $reference_number; } /** * Has a letter of this type already been issued for the application? * * Lets callers skip rendering the PDF purely for the sake of recording it. */ public function exists($application_id, $file_type) { return ConsumerApplicationFiles::where('consumer_application_id', $application_id) ->where('file_type', $file_type) ->exists(); } /** * Record the letter against the application the first time it is generated, * storing the reference number printed on that PDF. * * Idempotent — if an entry already exists nothing is uploaded or inserted, * so re-sends and re-downloads neither duplicate rows nor renumber the letter. */ public function persist($application_id, $file_type, $reference_number, $pdfOutput, ConsumerDocument $document = null) { if (!$application_id || !$reference_number || !$pdfOutput) { return false; } if ($this->exists($application_id, $file_type)) { return false; } try { if (!$document) { $request = new Request(); $request->request->add(['bot' => true]); $document = new ConsumerDocument($request); } $document->letter_reference = $reference_number; $document->uploadToS3($file_type, $application_id, $pdfOutput); return true; } catch (\Exception $e) { (new CloudWatchLoggerFactory())->logger(json_encode([ 'exception' => 'Failed to persist letter entry', 'message' => $e->getMessage(), 'line' => $e->getLine(), 'file_type' => $file_type, 'application_id' => $application_id, ])); return false; } } }