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:
+26
-9
@@ -2,19 +2,28 @@
|
||||
|
||||
namespace Modules\MembershipApplication\Emails;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Notifications\Concerns\BuildsMailMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Modules\MembershipApplication\Entities\MembershipApplication;
|
||||
|
||||
class MembershipApplicationFailedNotification extends Notification
|
||||
class MembershipApplicationFailedNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use BuildsMailMessage;
|
||||
use Queueable;
|
||||
|
||||
public function __construct(
|
||||
protected MembershipApplication $application
|
||||
public MembershipApplication $application,
|
||||
public Document $resultLetter,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
@@ -23,13 +32,21 @@ class MembershipApplicationFailedNotification extends Notification
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
$this->application->loadMissing('applicant');
|
||||
$applicant = $this->application->applicant;
|
||||
|
||||
return (new MailMessage)
|
||||
->subject('Keputusan Permohonan Keahlian')
|
||||
->markdown('membershipapplication::emails.failed', [
|
||||
'name' => $this->application->applicant->name,
|
||||
'applicationNumber' => $this->application->application_number,
|
||||
'logoPath' => public_path('images/logo-kopkb.svg'),
|
||||
]);
|
||||
return $this->mailMessage(
|
||||
subject: 'Keputusan Permohonan Keahlian KOPKB - TIDAK LULUS',
|
||||
view: 'membershipapplication::emails.failed',
|
||||
data: [
|
||||
'application' => $this->application,
|
||||
'applicant' => $applicant,
|
||||
],
|
||||
)->attach(
|
||||
Storage::disk(Document::STORAGE_DISK)->path($this->resultLetter->path),
|
||||
[
|
||||
'as' => $this->resultLetter->name,
|
||||
'mime' => $this->resultLetter->mime_type,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+26
-9
@@ -2,19 +2,28 @@
|
||||
|
||||
namespace Modules\MembershipApplication\Emails;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Notifications\Concerns\BuildsMailMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Modules\MembershipApplication\Entities\MembershipApplication;
|
||||
|
||||
class MembershipApplicationPassedNotification extends Notification
|
||||
class MembershipApplicationPassedNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use BuildsMailMessage;
|
||||
use Queueable;
|
||||
|
||||
public function __construct(
|
||||
protected MembershipApplication $application
|
||||
public MembershipApplication $application,
|
||||
public Document $resultLetter,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
@@ -23,13 +32,21 @@ class MembershipApplicationPassedNotification extends Notification
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
$this->application->loadMissing('applicant');
|
||||
$applicant = $this->application->applicant;
|
||||
|
||||
return (new MailMessage)
|
||||
->subject('Permohonan Keahlian Diluluskan')
|
||||
->markdown('membershipapplication::emails.passed', [
|
||||
'name' => $this->application->applicant->name,
|
||||
'applicationNumber' => $this->application->application_number,
|
||||
'logoPath' => public_path('images/logo-kopkb.svg'),
|
||||
]);
|
||||
return $this->mailMessage(
|
||||
subject: 'Keputusan Permohonan Keahlian KOPKB - LULUS',
|
||||
view: 'membershipapplication::emails.passed',
|
||||
data: [
|
||||
'application' => $this->application,
|
||||
'applicant' => $applicant,
|
||||
],
|
||||
)->attach(
|
||||
Storage::disk(Document::STORAGE_DISK)->path($this->resultLetter->path),
|
||||
[
|
||||
'as' => $this->resultLetter->name,
|
||||
'mime' => $this->resultLetter->mime_type,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ class MembershipApplication extends Model
|
||||
|
||||
public const RESULT_LETTER_DOCUMENT_TYPE = 'result_letter';
|
||||
|
||||
public const ADMIN_ATTACHMENT_DOCUMENT_TYPE = 'admin_attachment';
|
||||
|
||||
public const MINIMUM_SHARE_CAPITAL = 500;
|
||||
|
||||
public const SHARE_INSTALLMENT_MONTHS = 6;
|
||||
|
||||
+24
-5
@@ -17,6 +17,7 @@ use Modules\MembershipApplication\Enums\ManagementDecision;
|
||||
use Modules\MembershipApplication\Http\Requests\AssignReferencesRequest;
|
||||
use Modules\MembershipApplication\Http\Requests\BatchCompleteRequest;
|
||||
use Modules\MembershipApplication\Http\Requests\BoardReviewRequest;
|
||||
use Modules\MembershipApplication\Http\Requests\CompleteMembershipApplicationRequest;
|
||||
use Modules\MembershipApplication\Http\Requests\GenerateResultLetterRequest;
|
||||
use Modules\MembershipApplication\Http\Requests\ManagementReviewRequest;
|
||||
use Modules\MembershipApplication\Http\Requests\UpdateMembershipApplicationRequest;
|
||||
@@ -180,18 +181,19 @@ class MembershipApplicationController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function complete(Request $request, MembershipApplication $membershipApplication): JsonResponse
|
||||
public function complete(CompleteMembershipApplicationRequest $request, MembershipApplication $membershipApplication): JsonResponse
|
||||
{
|
||||
$this->authorize('complete', $membershipApplication);
|
||||
|
||||
$application = $this->membershipApplicationService->complete(
|
||||
$membershipApplication,
|
||||
$request->user()
|
||||
$request->user(),
|
||||
$request->validated('board_meeting_reference'),
|
||||
);
|
||||
|
||||
$message = $application->board_result === BoardResult::Pass->value
|
||||
? 'Permohonan selesai. Akaun ahli telah dicipta dan e-mel dihantar.'
|
||||
: 'Permohonan selesai. E-mel keputusan dihantar.';
|
||||
? 'Permohonan selesai. Surat keputusan dijana, akaun ahli dicipta dan e-mel dihantar.'
|
||||
: 'Permohonan selesai. Surat keputusan dijana dan e-mel dihantar.';
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
@@ -206,7 +208,8 @@ class MembershipApplicationController extends Controller
|
||||
|
||||
$result = $this->membershipApplicationService->completeBatch(
|
||||
$request->validated('application_ids'),
|
||||
$request->user()
|
||||
$request->user(),
|
||||
$request->validated('board_meeting_reference'),
|
||||
);
|
||||
|
||||
$succeededCount = count($result['succeeded']);
|
||||
@@ -245,6 +248,22 @@ class MembershipApplicationController extends Controller
|
||||
return $this->documentService->downloadDocument($document->id);
|
||||
}
|
||||
|
||||
public function deleteDocument(MembershipApplication $membershipApplication, string $documentId): JsonResponse
|
||||
{
|
||||
$this->authorize('update', $membershipApplication);
|
||||
|
||||
$application = $this->membershipApplicationService->deleteDocument(
|
||||
$membershipApplication,
|
||||
$documentId,
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Lampiran pentadbir berjaya dipadam.',
|
||||
'data' => new MembershipApplicationResource($application),
|
||||
]);
|
||||
}
|
||||
|
||||
public function generateResultLetter(GenerateResultLetterRequest $request, MembershipApplication $membershipApplication,): JsonResponse {
|
||||
$this->authorize('generateResultLetter', $membershipApplication);
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ class BatchCompleteRequest extends FormRequest
|
||||
return [
|
||||
'application_ids' => 'required|array|min:1|max:100',
|
||||
'application_ids.*' => 'required|uuid|distinct|exists:membership_applications,id',
|
||||
'board_meeting_reference' => 'required|string|max:255',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -33,6 +34,8 @@ class BatchCompleteRequest extends FormRequest
|
||||
'application_ids.max' => 'Maksimum 100 permohonan setiap batch.',
|
||||
'application_ids.*.exists' => 'Salah satu permohonan tidak dijumpai.',
|
||||
'application_ids.*.distinct' => 'Permohonan duplikat tidak dibenarkan.',
|
||||
'board_meeting_reference.required' => 'Rujukan mesyuarat lembaga diperlukan.',
|
||||
'board_meeting_reference.max' => 'Rujukan mesyuarat lembaga tidak boleh melebihi 255 aksara.',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\MembershipApplication\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CompleteMembershipApplicationRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'board_meeting_reference' => 'required|string|max:255',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'board_meeting_reference.required' => 'Rujukan mesyuarat lembaga diperlukan.',
|
||||
'board_meeting_reference.max' => 'Rujukan mesyuarat lembaga tidak boleh melebihi 255 aksara.',
|
||||
];
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -48,7 +48,7 @@ class StoreMembershipApplicationRequest extends FormRequest
|
||||
'documents.ic_copy' => 'required|file|mimes:pdf,jpg,jpeg,png|max:10240',
|
||||
'documents.photo' => 'nullable|file|mimes:jpg,jpeg,png|max:10240',
|
||||
'documents.salary_slip' => 'nullable|file|mimes:pdf,jpg,jpeg,png|max:10240',
|
||||
'documents.employer_letter' => 'nullable|file|mimes:pdf,jpg,jpeg,png|max:10240',
|
||||
'documents.employer_letter' => 'required|file|mimes:pdf,jpg,jpeg,png|max:10240',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ class StoreMembershipApplicationRequest extends FormRequest
|
||||
|
||||
'documents.ic_copy.required' => 'Salinan kad pengenalan diperlukan.',
|
||||
'documents.ic_copy.mimes' => 'Salinan kad pengenalan mestilah PDF, JPG, JPEG atau PNG.',
|
||||
'documents.employer_letter.required' => 'Surat pengesahan majikan diperlukan.',
|
||||
'documents.employer_letter.mimes' => 'Surat pengesahan majikan mestilah PDF, JPG, JPEG atau PNG.',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -4,6 +4,7 @@ namespace Modules\MembershipApplication\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Modules\MembershipApplication\Entities\MembershipApplication;
|
||||
|
||||
class UploadMembershipApplicationDocumentRequest extends FormRequest
|
||||
{
|
||||
@@ -18,7 +19,13 @@ class UploadMembershipApplicationDocumentRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'type' => ['required', Rule::in(['ic_copy', 'photo', 'salary_slip', 'employer_letter'])],
|
||||
'type' => ['required', Rule::in([
|
||||
'ic_copy',
|
||||
'photo',
|
||||
'salary_slip',
|
||||
'employer_letter',
|
||||
MembershipApplication::ADMIN_ATTACHMENT_DOCUMENT_TYPE,
|
||||
])],
|
||||
'file' => [
|
||||
'required',
|
||||
'file',
|
||||
|
||||
@@ -23,6 +23,7 @@ class MembershipApplicationRepository implements MembershipApplicationRepository
|
||||
return MembershipApplication::with([
|
||||
'applicant',
|
||||
'heirs',
|
||||
'user:id,member_number',
|
||||
'references.member:id,name,ic_number',
|
||||
'documents',
|
||||
'reviews.reviewer:id,name',
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
@component('mail::message')
|
||||
@if (! empty($logoPath) && file_exists($logoPath))
|
||||
<div style="text-align: center; margin-bottom: 24px;">
|
||||
<img src="{{ $message->embed($logoPath) }}" alt="{{ config('app.name') }}" style="max-height: 80px; width: auto;">
|
||||
</div>
|
||||
@endif
|
||||
@include('emails.partials.header')
|
||||
|
||||
# Keputusan Permohonan Keahlian
|
||||
|
||||
Assalamualaikum **{{ $name }}**,
|
||||
Assalamualaikum **{{ $applicant->name }}**,
|
||||
|
||||
Permohonan keahlian anda (**{{ $applicationNumber }}**) **tidak diluluskan** pada peringkat lembaga.
|
||||
Permohonan keahlian anda (**{{ $application->application_number }}**) **tidak diluluskan** pada peringkat lembaga.
|
||||
|
||||
Surat keputusan rasmi dilampirkan dalam e-mel ini.
|
||||
|
||||
Terima kasih atas minat anda. Untuk sebarang pertanyaan, sila hubungi pejabat koperasi.
|
||||
|
||||
Terima kasih,<br>
|
||||
{{ config('app.name') }}
|
||||
@include('emails.partials.footer')
|
||||
@endcomponent
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
@component('mail::message')
|
||||
@if (! empty($logoPath) && file_exists($logoPath))
|
||||
<div style="text-align: center; margin-bottom: 24px;">
|
||||
<img src="{{ $message->embed($logoPath) }}" alt="{{ config('app.name') }}" style="max-height: 80px; width: auto;">
|
||||
</div>
|
||||
@endif
|
||||
@include('emails.partials.header')
|
||||
|
||||
# Keputusan Permohonan Keahlian
|
||||
|
||||
Assalamualaikum **{{ $name }}**,
|
||||
Assalamualaikum **{{ $applicant->name }}**,
|
||||
|
||||
Permohonan keahlian anda (**{{ $applicationNumber }}**) **diluluskan** pada peringkat lembaga.
|
||||
Permohonan keahlian anda (**{{ $application->application_number }}**) **diluluskan** pada peringkat lembaga.
|
||||
|
||||
Surat keputusan rasmi dilampirkan dalam e-mel ini.
|
||||
|
||||
Terima kasih atas minat anda. Untuk sebarang pertanyaan, sila hubungi pejabat koperasi.
|
||||
|
||||
Terima kasih,<br>
|
||||
{{ config('app.name') }}
|
||||
@include('emails.partials.footer')
|
||||
@endcomponent
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</p>
|
||||
<p style="line-height: 1;">
|
||||
2. {{ $openingTone }} dimaklumkan bahawa, permohonan tuan untuk menjadi anggota Koperasi
|
||||
Permodalan Kelantan Berhad (KoPKB) telah <strong>{{ $boardResultLabel }}</strong> dalam {{ $boardMeetingReference }}.
|
||||
Permodalan Kelantan Berhad (KoPKB) telah <strong>{{ $boardResultLabel }}</strong> dalam {{ $boardMeetingReference }}@if ($isPassed && $memberNumber). Nombor keanggotaan tuan adalah <strong>{{ $memberNumber }}</strong>@endif.
|
||||
</p>
|
||||
|
||||
@if ($isPassed)
|
||||
@@ -67,7 +67,7 @@
|
||||
<div class="signature-header">Dengan hormatnya,</div>
|
||||
<div class="signature-company">KOPERASI PERMODALAN KELANTAN BERHAD</div>
|
||||
<div class="signature-space"></div>
|
||||
<div class="signatory-name">MUHAMMAD NAFIS BIN ZAINUDDIN</div>
|
||||
<div class="signatory-name">MUHAMMAD NAFIS BIN ZAINUDIN</div>
|
||||
<div class="signatory-title">Pengurus Besar</div>
|
||||
<div class="signatory-title">b/p Setiausaha</div>
|
||||
@endsection
|
||||
|
||||
@@ -38,6 +38,7 @@ Route::middleware(['auth:sanctum', 'single.session'])->prefix('v1')->group(funct
|
||||
|
||||
// Membership application upload document routes
|
||||
Route::post('membership-applications/{membershipApplication}/documents', [MembershipApplicationController::class, 'uploadDocument'])->name('membership-application.upload-document');
|
||||
Route::delete('membership-applications/{membershipApplication}/documents/{documentId}', [MembershipApplicationController::class, 'deleteDocument'])->name('membership-application.delete-document');
|
||||
// download document routes
|
||||
Route::get('membership-applications/{membershipApplication}/documents/{documentId}/download', [MembershipApplicationController::class, 'downloadDocument'])->name('membership-application.download-document');
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,7 @@ return new class extends Migration
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
$table->id();
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
@@ -22,7 +22,7 @@ return new class extends Migration
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
@@ -35,7 +35,7 @@ return new class extends Migration
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
|
||||
Reference in New Issue
Block a user