DONE: membership application to generate letter along with member_number and attach to email, change to dropdown for company or job selection, redesign profile ui, change to incremental ids for jobs; WIP: member digital card
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Modules\MembershipApplication\Services;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Services\DocumentService;
|
||||
use App\Services\LetterService;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
@@ -137,15 +138,38 @@ class MembershipApplicationService
|
||||
|
||||
$this->documentService->validateFile($file);
|
||||
|
||||
$application->documentsOfType($type)->get()->each(
|
||||
fn ($document) => $this->documentService->deleteDocument($document->id)
|
||||
);
|
||||
if ($type !== MembershipApplication::ADMIN_ATTACHMENT_DOCUMENT_TYPE) {
|
||||
$application->documentsOfType($type)->get()->each(
|
||||
fn ($document) => $this->documentService->deleteDocument($document->id)
|
||||
);
|
||||
}
|
||||
|
||||
$this->documentService->uploadDocument($application, $file, $type);
|
||||
|
||||
return $this->repository->findByIdWithRelations($application->id);
|
||||
}
|
||||
|
||||
public function deleteDocument(MembershipApplication $application, string $documentId): MembershipApplication
|
||||
{
|
||||
if ($application->status === ApplicationStatus::Completed) {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => ['Permohonan yang telah selesai tidak boleh dikemaskini.'],
|
||||
]);
|
||||
}
|
||||
|
||||
$document = $application->documents()->findOrFail($documentId);
|
||||
|
||||
if ($document->type !== MembershipApplication::ADMIN_ATTACHMENT_DOCUMENT_TYPE) {
|
||||
throw ValidationException::withMessages([
|
||||
'document' => ['Hanya lampiran pentadbir boleh dipadam.'],
|
||||
]);
|
||||
}
|
||||
|
||||
$this->documentService->deleteDocument($document->id);
|
||||
|
||||
return $this->repository->findByIdWithRelations($application->id);
|
||||
}
|
||||
|
||||
public function managementReview(MembershipApplication $application, ManagementDecision $decision, ?string $remarks, User $reviewer): MembershipApplication {
|
||||
if ($application->status !== ApplicationStatus::Submitted) {
|
||||
throw ValidationException::withMessages([
|
||||
@@ -230,8 +254,11 @@ class MembershipApplicationService
|
||||
});
|
||||
}
|
||||
|
||||
public function complete(MembershipApplication $application, User $processedBy): MembershipApplication
|
||||
{
|
||||
public function complete(
|
||||
MembershipApplication $application,
|
||||
User $processedBy,
|
||||
string $boardMeetingReference,
|
||||
): MembershipApplication {
|
||||
if ($application->status !== ApplicationStatus::PendingNotification) {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => ['Permohonan ini tidak boleh diselesaikan pada masa ini.'],
|
||||
@@ -244,13 +271,16 @@ class MembershipApplicationService
|
||||
]);
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($application) {
|
||||
return DB::transaction(function () use ($application, $boardMeetingReference) {
|
||||
if ($application->board_result === BoardResult::Pass->value && ! $application->user_id) {
|
||||
$user = $this->createMemberFromApplication($application);
|
||||
$application->update(['user_id' => $user->id]);
|
||||
$application->refresh();
|
||||
}
|
||||
|
||||
$this->sendResultNotification($application);
|
||||
$resultLetter = $this->storeResultLetter($application, $boardMeetingReference);
|
||||
|
||||
$this->sendResultNotification($application, $resultLetter);
|
||||
|
||||
$application->update([
|
||||
'status' => ApplicationStatus::Completed,
|
||||
@@ -268,7 +298,7 @@ class MembershipApplicationService
|
||||
* failed: list<array{id: string, application_number: string|null, message: string}>
|
||||
* }
|
||||
*/
|
||||
public function completeBatch(array $applicationIds, User $processedBy): array
|
||||
public function completeBatch(array $applicationIds, User $processedBy, string $boardMeetingReference): array
|
||||
{
|
||||
$succeeded = [];
|
||||
$failed = [];
|
||||
@@ -287,7 +317,7 @@ class MembershipApplicationService
|
||||
}
|
||||
|
||||
try {
|
||||
$succeeded[] = $this->complete($application, $processedBy);
|
||||
$succeeded[] = $this->complete($application, $processedBy, $boardMeetingReference);
|
||||
} catch (ValidationException $e) {
|
||||
$failed[] = [
|
||||
'id' => $id,
|
||||
@@ -317,40 +347,47 @@ class MembershipApplicationService
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($application, $boardMeetingReference) {
|
||||
$lockedApplication = MembershipApplication::query()
|
||||
->whereKey($application->id)
|
||||
->lockForUpdate()
|
||||
->firstOrFail();
|
||||
|
||||
if ($lockedApplication->hasDocumentsOfType(MembershipApplication::RESULT_LETTER_DOCUMENT_TYPE)) {
|
||||
throw ValidationException::withMessages([
|
||||
'result_letter' => ['Surat keputusan telah dijana dan tidak boleh dijana semula.'],
|
||||
]);
|
||||
}
|
||||
|
||||
$application = $this->repository->findByIdWithRelations($lockedApplication->id);
|
||||
|
||||
if (! $application || ! $application->applicant) {
|
||||
throw ValidationException::withMessages([
|
||||
'application' => ['Maklumat pemohon tidak lengkap.'],
|
||||
]);
|
||||
}
|
||||
|
||||
$viewData = $this->buildResultLetterViewData($application, $boardMeetingReference);
|
||||
$pdf = $this->letterService->renderPdf('membershipapplication::pdf.approved-letter', $viewData);
|
||||
$fileName = 'surat-keputusan-'.$application->application_number.'.pdf';
|
||||
|
||||
$this->documentService->storeGeneratedDocument(
|
||||
$application,
|
||||
$pdf,
|
||||
$fileName,
|
||||
MembershipApplication::RESULT_LETTER_DOCUMENT_TYPE,
|
||||
);
|
||||
$this->storeResultLetter($application, $boardMeetingReference);
|
||||
|
||||
return $this->repository->findByIdWithRelations($application->id);
|
||||
});
|
||||
}
|
||||
|
||||
protected function storeResultLetter(
|
||||
MembershipApplication $application,
|
||||
string $boardMeetingReference,
|
||||
): Document {
|
||||
$lockedApplication = MembershipApplication::query()
|
||||
->whereKey($application->id)
|
||||
->lockForUpdate()
|
||||
->firstOrFail();
|
||||
|
||||
if ($lockedApplication->hasDocumentsOfType(MembershipApplication::RESULT_LETTER_DOCUMENT_TYPE)) {
|
||||
throw ValidationException::withMessages([
|
||||
'result_letter' => ['Surat keputusan telah dijana dan tidak boleh dijana semula.'],
|
||||
]);
|
||||
}
|
||||
|
||||
$application = $this->repository->findByIdWithRelations($lockedApplication->id);
|
||||
|
||||
if (! $application || ! $application->applicant) {
|
||||
throw ValidationException::withMessages([
|
||||
'application' => ['Maklumat pemohon tidak lengkap.'],
|
||||
]);
|
||||
}
|
||||
|
||||
$viewData = $this->buildResultLetterViewData($application, $boardMeetingReference);
|
||||
$pdf = $this->letterService->renderPdf('membershipapplication::pdf.approved-letter', $viewData);
|
||||
$fileName = 'surat-keputusan-'.$application->application_number.'.pdf';
|
||||
|
||||
return $this->documentService->storeGeneratedDocument(
|
||||
$application,
|
||||
$pdf,
|
||||
$fileName,
|
||||
MembershipApplication::RESULT_LETTER_DOCUMENT_TYPE,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
@@ -378,12 +415,13 @@ class MembershipApplicationService
|
||||
): array {
|
||||
$applicant = $application->applicant;
|
||||
$isPassed = $application->board_result === BoardResult::Pass->value;
|
||||
$monthlyShareInstallment = number_format(
|
||||
MembershipApplication::MINIMUM_SHARE_CAPITAL / MembershipApplication::SHARE_INSTALLMENT_MONTHS,
|
||||
2,
|
||||
'.',
|
||||
'',
|
||||
);
|
||||
// $monthlyShareInstallment = number_format(
|
||||
// MembershipApplication::MINIMUM_SHARE_CAPITAL / MembershipApplication::SHARE_INSTALLMENT_MONTHS,
|
||||
// 2,
|
||||
// '.',
|
||||
// '',
|
||||
// );
|
||||
$monthlyShareInstallment = 84;
|
||||
|
||||
return [
|
||||
'application' => $application,
|
||||
@@ -400,6 +438,7 @@ class MembershipApplicationService
|
||||
'feeMonthlyContribution' => number_format((float) $applicant->fee_monthly_contribution, 2, '.', ''),
|
||||
'shareInstallmentMonths' => MembershipApplication::SHARE_INSTALLMENT_MONTHS,
|
||||
'minimumShareCapital' => number_format(MembershipApplication::MINIMUM_SHARE_CAPITAL, 2, '.', ''),
|
||||
'memberNumber' => $isPassed ? $application->user?->member_number : null,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -488,20 +527,30 @@ class MembershipApplicationService
|
||||
->notify(new MembershipApplicationSubmittedNotification($application));
|
||||
}
|
||||
|
||||
protected function sendResultNotification(MembershipApplication $application): void
|
||||
protected function sendResultNotification(MembershipApplication $application, Document $resultLetter): void
|
||||
{
|
||||
$application->loadMissing('applicant');
|
||||
|
||||
$notification = $application->board_result === BoardResult::Pass->value
|
||||
? new MembershipApplicationPassedNotification($application)
|
||||
: new MembershipApplicationFailedNotification($application);
|
||||
? new MembershipApplicationPassedNotification($application, $resultLetter)
|
||||
: new MembershipApplicationFailedNotification($application, $resultLetter);
|
||||
|
||||
Notification::route('mail', $application->applicant->email)->notify($notification);
|
||||
}
|
||||
|
||||
protected function generateMemberNumber(): int
|
||||
{
|
||||
return ((int) User::max('member_number')) + 1;
|
||||
$latestUser = User::query()
|
||||
->whereNotNull('member_number')
|
||||
->orderByDesc('member_number')
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
if ($latestUser === null && DB::getDriverName() === 'pgsql') {
|
||||
DB::selectOne('SELECT pg_advisory_xact_lock(?) AS locked', [742891035]);
|
||||
}
|
||||
|
||||
return ((int) ($latestUser?->member_number ?? 0)) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user