diff --git a/README.md b/README.md index e104974..af74bb3 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ cd laravel-base # 2. Run the automated setup # This installs PHP/JS deps, creates .env, generates key, and runs migrations composer setup + ``` --- @@ -50,6 +51,7 @@ Use the pre-configured development command which runs the server, queue listener ```bash composer dev + ``` The application will be available at `http://localhost:8000`. @@ -59,14 +61,16 @@ The application will be available at `http://localhost:8000`. ## 5. Scripts & Commands Available via Composer: -- `composer setup`: Initial project bootstrap. -- `composer dev`: Start development environment (Server + Queue + Logs + Vite). -- `composer test`: Run the test suite. -- `php artisan domain:make`: Custom generator for the DDD architecture (see Section 12). + +* `composer setup`: Initial project bootstrap. +* `composer dev`: Start development environment (Server + Queue + Logs + Vite). +* `composer test`: Run the test suite. +* `php artisan domain:make`: Custom generator for the DDD architecture (see Section 12). Available via NPM: -- `npm run dev`: Start Vite dev server. -- `npm run build`: Build assets for production. + +* `npm run dev`: Start Vite dev server. +* `npm run build`: Build assets for production. --- @@ -93,6 +97,7 @@ Available via NPM: ├── tests/ <-- Pest test suite ├── storage/ <-- Logs, file uploads, and cache └── vite.config.js <-- Vite configuration + ``` --- @@ -181,6 +186,7 @@ app/ ├── Traits/ <-- Domain-specific traits (HasFile) └── ... + ``` --- @@ -222,7 +228,7 @@ This architecture uses a strict, intentional naming language. Every name must co Domain names are **Business Concepts**, not technical layers. They must be a singular noun that describes a bounded context. | ✅ Correct | ❌ Wrong | Why | -|---|---|---| +| --- | --- | --- | | `Identity` | `Users` | Identity covers auth, roles, and user lifecycle — not just a table | | `Account` | `Profile` | Account owns the full user account surface, not one model | | `System` | `Utils` / `Helpers` | System is a real bounded context for cross-cutting infrastructure | @@ -232,7 +238,7 @@ Domain names are **Business Concepts**, not technical layers. They must be a sin Subdirectories inside `Actions/`, `DTOs/`, `Events/`, and `Listeners/` must be named after **Business Capabilities**, not database nouns. | ✅ Correct | ❌ Wrong | Why | -|---|---|---| +| --- | --- | --- | | `Onboarding/` | `Users/` | Describes the lifecycle stage, not the DB table | | `AccessControl/` | `Roles/` | Describes the capability, not the resource | | `Governance/` | `Admin/` | Describes the compliance intent | @@ -246,7 +252,7 @@ Subdirectories inside `Actions/`, `DTOs/`, `Events/`, and `Listeners/` must be n Actions must be named after the **specific Business Intent** they fulfill. Use an active verb + business noun pattern. | ✅ Correct | ❌ Wrong | Why | -|---|---|---| +| --- | --- | --- | | `ProvisionNewUser` | `CreateUser` | Describes *who* triggers it and *why* | | `SuspendUser` | `DeleteUser` | Reveals the business consequence (soft revoke, not destroy) | | `UpdateUserRole` | `SaveRole` | Explicit about the subject and property being changed | @@ -255,22 +261,38 @@ Actions must be named after the **specific Business Intent** they fulfill. Use a CRUD names (`CreateCategory`, `UpdateSetting`) are only acceptable for trivial lookup tables with **no side effects**. -### 10.4 DTO Class Names +### 10.4 Mutation Verbs Selection Matrix (The Cheat Sheet) + +To keep domain actions aligned with authentic business intent rather than technical database scripts, choose the execution prefix according to this operational lifecycle matrix: + +| Verb Prefix | Intent Scope | Real-World Context Example | +| --- | --- | --- | +| **`Initialize`** / **`Register`** | Declares the setup of an operational business track or engine. | `InitializeAdmissionTrack`, `RegisterStudyProgram` | +| **`Draft`** | Spawns a new entity row but locks it out of live visibility as a pending draft. | `DraftAdmissionSchedule` | +| **`Publish`** / **`Activate`** | Handles state transition to shift an existing record to a live production state. | `PublishAdmissionSchedule` | +| **`Define`** | Configures static lookups or structural reference dictionary elements. | `DefineProgramTrack`, `DefineFeeType` | +| **`Adjust`** / **`Modify`** | Performs precise partial modifications or data tuning on active records. | `AdjustAdmissionScheduleDuration` | +| **`Replace`** / **`Overwrite`** | Performs a full destructive replacement of an entry's total data layout. | `ReplaceTrackSpecification` | +| **`Synchronize`** | Forces full state matching with an authoritative external system registry. | `SynchronizeFacultyData` | +| **`Suspend`** / **`Pause`** | Halts access temporarily while leaving underlying structures fully intact. | `SuspendUser`, `PauseAdmissionSchedule` | +| **`Archive`** | Executes soft-deletion, shifting records permanently to history ledgers. | `ArchiveStudyProgram` | + +### 10.5 DTO Class Names DTOs are named after the Action they serve, with a `DTO` suffix. | Action | DTO | -|---|---| +| --- | --- | | `ProvisionNewUser` | `ProvisionUserDTO` | | `UpdateUser` | `UpdateUserDTO` | | `CreateSystemRole` | `CreateRoleDTO` | -### 10.5 Event Class Names +### 10.6 Event Class Names Events are **past-tense facts** about something that already happened in the domain. The class name must be grammatically a completed truth. | ✅ Correct | ❌ Wrong | Why | -|---|---|---| +| --- | --- | --- | | `UserWasProvisioned` | `UserProvisioned` | Explicit past-tense removes ambiguity | | `UserWasSuspended` | `UserSuspended` | Reads as a state, not a completed fact | | `UserLoggedIn` | `LoginEvent` | Noun + verb pattern; avoids the `Event` suffix | @@ -278,12 +300,12 @@ Events are **past-tense facts** about something that already happened in the dom **Rule:** Never suffix Events with `Event` (e.g., `UserRegisteredEvent` is wrong). The namespace `Events\` already communicates the type. -### 10.6 Listener Class Names +### 10.7 Listener Class Names Listeners describe the **active reaction** to an event using an imperative verb phrase. | ✅ Correct | ❌ Wrong | Why | -|---|---|---| +| --- | --- | --- | | `SendSignInActivityNotification` | `UserLoggedInListener` | Describes what the listener *does*, not what it reacts to | | `DispatchWelcomeNotification` | `WelcomeListener` | Imperative verb makes the intent crystal clear | @@ -341,6 +363,7 @@ You are an autonomous Senior Laravel Architect specializing in Pragmatic Domain- Write modern PHP 8.4+ code with strict typing. Ensure all PSR-4 namespaces perfectly match the directory structure. + ``` --- @@ -356,6 +379,7 @@ To maintain the strict folder structure of the Antigravity architecture, **do no ```bash php artisan domain:make {type} {domain} {name} [options] + ``` **Arguments:** @@ -409,6 +433,7 @@ class User extends Model } } + ``` ### File Actions (Metadata via DTO) @@ -434,6 +459,7 @@ $action->execute( ) ); + ``` ### System Asset Helper @@ -486,7 +512,6 @@ The component relies on three collaborating layers: ### How It Works * **Import:** The user uploads an `.xlsx` file via the FilePond modal. The file is stored to `local/excel/import/{resourceName}` and a new import instance is constructed with a UUID (`$importId`) and the authenticated user's ID (`$initiatorId`) before being dispatched to the queue via `Excel::queueImport()`. The Ingestion class lives in `app/Http/Ingestion/` (Gateway layer) and implements `WithChunkReading` (chunk size: 200 rows) to stay within shared-hosting memory limits. A success toast (`ui.excel.import.success`) is shown immediately upon queuing. - * **Export:** A Livewire browser event (`export-excel`) — dispatched by the DataTable's Export button — triggers the `export()` method via `#[On('export-excel')]`. The domain Export is wrapped in `StyledExport` and queued via `Excel::queue()`. The job chain appends `NotifyExportReady`, which dispatches `ExportCompleted`, which is handled by `SendExportReportEmail` to send the file as an email attachment to the authenticated user. A success toast (`ui.excel.export.success`) is shown immediately upon queuing. ### Component Props @@ -507,6 +532,7 @@ Embed the component in any DataTable page view. All props must be provided as fu :import-class="\App\Http\Ingestion\Excel\Identity\UserImport::class" resource-name="user" /> + ``` The DataTable's Export button should dispatch the `export-excel` Livewire event, and the Import button should open the `#excel-import-modal` Bootstrap modal: @@ -518,6 +544,7 @@ Button::make('excel') Button::make('excel') ->action("$('#excel-import-modal').modal('show')"), + ``` ### Generating Export & Mapper Classes @@ -531,6 +558,7 @@ php artisan domain:make export Identity UserExport --model=User # Generate an Integration Mapper (auto-appends DataMapper suffix) php artisan domain:make mapper Identity User # → app/Domains/Identity/Integration/Mappers/UserDataMapper.php + ``` Domain Export classes must implement `FromQuery & WithHeadings & WithMapping & WithColumnFormatting`. The `StyledExport` decorator will apply all visual styling automatically at queue time — do **not** implement `WithStyles` directly on domain Exports. @@ -547,6 +575,7 @@ Excel::queue(StyledExport, $path) └─> ExportCompleted::dispatch (Event) [app/Domains/System/Events/] └─> SendExportReportEmail (Listener) [app/Domains/System/Listeners/] └─> ExcelExportEmail (Mailable) [app/Domains/System/Mail/] + ``` The import notification is sent by the domain Import class itself upon completion, using `ExcelImportEmail` from the same `App\Domains\System\Mail\` namespace. @@ -581,12 +610,12 @@ We use [Pest PHP](https://pestphp.com) for our test suite. Please refer to [TEST Key variables used in `.env`: -- `APP_NAME`: Name of the application. -- `APP_ENV`: Application environment (`local`, `production`, etc.). -- `APP_KEY`: Application encryption key. -- `DB_CONNECTION`: Database driver (`sqlite`, `mysql`, `pgsql`). -- `QUEUE_CONNECTION`: Queue driver (default: `database`). -- `MAIL_MAILER`: Mail driver (default: `log`). +* `APP_NAME`: Name of the application. +* `APP_ENV`: Application environment (`local`, `production`, etc.). +* `APP_KEY`: Application encryption key. +* `DB_CONNECTION`: Database driver (`sqlite`, `mysql`, `pgsql`). +* `QUEUE_CONNECTION`: Queue driver (default: `database`). +* `MAIL_MAILER`: Mail driver (default: `log`). See `.env.example` for the full list of available options. @@ -595,5 +624,3 @@ See `.env.example` for the full list of available options. ## 20. License This project is licensed under the **MIT License**. - - diff --git a/app/Console/Commands/DomainMakeCommand.php b/app/Console/Commands/DomainMakeCommand.php index c5552ab..fa6bc36 100644 --- a/app/Console/Commands/DomainMakeCommand.php +++ b/app/Console/Commands/DomainMakeCommand.php @@ -20,24 +20,24 @@ class DomainMakeCommand extends Command /** @var array type → subdirectory */ protected array $types = [ - 'model' => 'Models', - 'action' => 'Actions', - 'dto' => 'DTOs', - 'enum' => 'Enums', - 'event' => 'Events', - 'listener' => 'Listeners', - 'notification' => 'Notifications', - 'policy' => 'Policies', - 'scope' => 'Scopes', - 'trait' => 'Traits', - 'query' => 'Queries', - 'provider' => 'Providers', + 'model' => 'Models', + 'action' => 'Actions', + 'dto' => 'DTOs', + 'enum' => 'Enums', + 'event' => 'Events', + 'listener' => 'Listeners', + 'notification' => 'Notifications', + 'policy' => 'Policies', + 'scope' => 'Scopes', + 'trait' => 'Traits', + 'query' => 'Queries', + 'provider' => 'Providers', 'relationship-provider' => 'Providers', 'view-provider' => 'Providers', - 'export' => 'Exports', + 'export' => 'Exports', // Integration layer — files live under Integration// - 'mapper' => 'Integration/Mappers', - 'mailable' => 'Mail', + 'mapper' => 'Integration/Mappers', + 'mailable' => 'Mail', ]; public function __construct(protected Filesystem $files) diff --git a/app/Console/Commands/DomainNewCommand.php b/app/Console/Commands/DomainNewCommand.php index 622a9fb..fa7d7b6 100644 --- a/app/Console/Commands/DomainNewCommand.php +++ b/app/Console/Commands/DomainNewCommand.php @@ -26,7 +26,7 @@ class DomainNewCommand extends Command if ($this->files->isDirectory($providerDir) && count($this->files->files($providerDir)) > 0) { $this->components->error( "Domain [{$domain}] already has providers. ". - "Use [domain:make provider] to add individual providers." + 'Use [domain:make provider] to add individual providers.' ); return self::FAILURE; diff --git a/app/Domains/Academic/Actions/Calendar/InitializeAcademicTerm.php b/app/Domains/Academic/Actions/Calendar/InitializeAcademicTerm.php new file mode 100644 index 0000000..cc9b601 --- /dev/null +++ b/app/Domains/Academic/Actions/Calendar/InitializeAcademicTerm.php @@ -0,0 +1,21 @@ + $dto->name, + 'code' => $dto->code, + 'year_study' => $dto->yearStudy, + 'semester' => $dto->semester, + 'date_start' => $dto->startDate, + 'date_end' => $dto->endDate, + ]); + } +} diff --git a/app/Domains/Academic/Actions/Calendar/ModifyTerm.php b/app/Domains/Academic/Actions/Calendar/ModifyTerm.php new file mode 100644 index 0000000..75b09b1 --- /dev/null +++ b/app/Domains/Academic/Actions/Calendar/ModifyTerm.php @@ -0,0 +1,17 @@ +update([ + 'date_start' => $dto->startDate, + 'date_end' => $dto->endDate, + ]); + } +} diff --git a/app/Domains/Academic/Actions/Calendar/ModifyTermDate.php b/app/Domains/Academic/Actions/Calendar/ModifyTermDate.php new file mode 100644 index 0000000..74972f9 --- /dev/null +++ b/app/Domains/Academic/Actions/Calendar/ModifyTermDate.php @@ -0,0 +1,17 @@ +update([ + 'date_start' => $dto->startDate, + 'date_end' => $dto->endDate, + ]); + } +} diff --git a/app/Domains/Academic/Actions/Curriculum/AdjustStudyProgramDetails.php b/app/Domains/Academic/Actions/Curriculum/AdjustStudyProgramDetails.php new file mode 100644 index 0000000..0442ec9 --- /dev/null +++ b/app/Domains/Academic/Actions/Curriculum/AdjustStudyProgramDetails.php @@ -0,0 +1,17 @@ +update([ + 'level' => $dto->level, + 'status' => $dto->status, + ]); + } +} diff --git a/app/Domains/Academic/Actions/Curriculum/EstablishFaculty.php b/app/Domains/Academic/Actions/Curriculum/EstablishFaculty.php new file mode 100644 index 0000000..9b69842 --- /dev/null +++ b/app/Domains/Academic/Actions/Curriculum/EstablishFaculty.php @@ -0,0 +1,18 @@ + $dto->name, + 'code' => $dto->code, + 'external_id' => $dto->externalId, + ]); + } +} diff --git a/app/Domains/Academic/Actions/Curriculum/ModifyFacultyDetails.php b/app/Domains/Academic/Actions/Curriculum/ModifyFacultyDetails.php new file mode 100644 index 0000000..8224048 --- /dev/null +++ b/app/Domains/Academic/Actions/Curriculum/ModifyFacultyDetails.php @@ -0,0 +1,17 @@ +update([ + 'name' => $dto->name, + 'code' => $dto->code, + ]); + } +} diff --git a/app/Domains/Academic/Actions/Curriculum/RegisterStudyProgram.php b/app/Domains/Academic/Actions/Curriculum/RegisterStudyProgram.php new file mode 100644 index 0000000..c2550a9 --- /dev/null +++ b/app/Domains/Academic/Actions/Curriculum/RegisterStudyProgram.php @@ -0,0 +1,21 @@ + $dto->name, + 'code' => $dto->code, + 'level' => $dto->level, + 'status' => $dto->status, + 'faculty_id' => $dto->facultyId, + 'external_id' => $dto->externalId, + ]); + } +} diff --git a/app/Domains/Academic/DTOs/Calendar/InitializeAcademicTermDTO.php b/app/Domains/Academic/DTOs/Calendar/InitializeAcademicTermDTO.php new file mode 100644 index 0000000..37f1b5e --- /dev/null +++ b/app/Domains/Academic/DTOs/Calendar/InitializeAcademicTermDTO.php @@ -0,0 +1,17 @@ + LifecycleStatus::class, + ]; + + protected static function newFactory(): Factory + { + return FacultyFactory::new(); + } + + public function uniqueIds(): array + { + return ['ulid']; + } +} diff --git a/app/Domains/Academic/Models/StudyProgram.php b/app/Domains/Academic/Models/StudyProgram.php new file mode 100644 index 0000000..944f9a4 --- /dev/null +++ b/app/Domains/Academic/Models/StudyProgram.php @@ -0,0 +1,48 @@ + LifecycleStatus::class, + ]; + + protected static function newFactory(): Factory + { + return StudyProgramFactory::new(); + } + + public function uniqueIds(): array + { + return ['ulid']; + } + + public function faculty(): BelongsTo + { + return $this->belongsTo(Faculty::class); + } +} diff --git a/app/Domains/Academic/Models/Term.php b/app/Domains/Academic/Models/Term.php new file mode 100644 index 0000000..182c50f --- /dev/null +++ b/app/Domains/Academic/Models/Term.php @@ -0,0 +1,30 @@ +hasPermissionTo('faculty.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Faculty $model): bool + { + return $user->hasPermissionTo('faculty.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('faculty.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Faculty $model): bool + { + return $user->hasPermissionTo('faculty.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Faculty $model): bool + { + return $user->hasPermissionTo('faculty.delete'); + } +} diff --git a/app/Domains/Academic/Policies/StudyProgramPolicy.php b/app/Domains/Academic/Policies/StudyProgramPolicy.php new file mode 100644 index 0000000..90207b5 --- /dev/null +++ b/app/Domains/Academic/Policies/StudyProgramPolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('study-program.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, StudyProgram $model): bool + { + return $user->hasPermissionTo('study-program.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('study-program.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, StudyProgram $model): bool + { + return $user->hasPermissionTo('study-program.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, StudyProgram $model): bool + { + return $user->hasPermissionTo('study-program.delete'); + } +} diff --git a/app/Domains/Academic/Policies/TermPolicy.php b/app/Domains/Academic/Policies/TermPolicy.php new file mode 100644 index 0000000..078c0bb --- /dev/null +++ b/app/Domains/Academic/Policies/TermPolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('term.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Term $model): bool + { + return $user->hasPermissionTo('term.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('term.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Term $model): bool + { + return $user->hasPermissionTo('term.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Term $model): bool + { + return $user->hasPermissionTo('term.delete'); + } +} diff --git a/app/Domains/Academic/Providers/AcademicServiceProvider.php b/app/Domains/Academic/Providers/AcademicServiceProvider.php new file mode 100644 index 0000000..9ddef20 --- /dev/null +++ b/app/Domains/Academic/Providers/AcademicServiceProvider.php @@ -0,0 +1,23 @@ +app->register(RelationshipServiceProvider::class); + } + + public function boot(): void + { + $this->registerEvents(); + } +} diff --git a/app/Domains/Academic/Providers/RelationshipServiceProvider.php b/app/Domains/Academic/Providers/RelationshipServiceProvider.php new file mode 100644 index 0000000..cc29b1d --- /dev/null +++ b/app/Domains/Academic/Providers/RelationshipServiceProvider.php @@ -0,0 +1,54 @@ + + * $order->belongsTo(Customer::class, 'customer_id') + * ); + */ + public function boot(): void + { + // Register cross-domain relationships below. + AdmissionSchedule::resolveRelationUsing('term', fn (AdmissionSchedule $admissionSchedule) => $admissionSchedule->belongsTo( + related: Term::class, + foreignKey: 'term_id' + )); + Term::resolveRelationUsing('admission_schedules', fn (Term $term) => $term->hasMany( + related: AdmissionSchedule::class, + foreignKey: 'term_id', + localKey: 'id' + )); + + FeeRate::resolveRelationUsing('study_program', fn (FeeRate $programTrack) => $programTrack->belongsTo( + related: StudyProgram::class, + foreignKey: 'study_program_id' + )); + StudyProgram::resolveRelationUsing('fee_rates', fn (StudyProgram $studyProgram) => $studyProgram->hasMany( + related: FeeRate::class, + foreignKey: 'study_program_id', + localKey: 'id' + )); + } +} diff --git a/app/Domains/Account/Enums/GenderOption.php b/app/Domains/Account/Enums/GenderOption.php index 57d76de..8212202 100644 --- a/app/Domains/Account/Enums/GenderOption.php +++ b/app/Domains/Account/Enums/GenderOption.php @@ -2,16 +2,17 @@ namespace App\Domains\Account\Enums; -enum GenderOption: string +use App\UI\Enums\Concerns\InteractsWithLabels; +use App\UI\Enums\Contracts\HasLabel; +use App\UI\Enums\Contracts\HasUiBadge; + +enum GenderOption: string implements HasLabel, HasUiBadge { + use InteractsWithLabels; + case MALE = 'male'; case FEMALE = 'female'; - public function label(): string - { - return __("domains/account/enum.gender.{$this->value}"); - } - public static function fromLabel($value): self { return match ($value) { @@ -19,4 +20,12 @@ enum GenderOption: string self::FEMALE->label() => self::FEMALE, }; } + + public function variant(): string + { + return match ($this) { + self::MALE => 'primary', + self::FEMALE => 'secondary', + }; + } } diff --git a/app/Domains/Admission/Actions/AdmissionPricing/DefineFeeType.php b/app/Domains/Admission/Actions/AdmissionPricing/DefineFeeType.php new file mode 100644 index 0000000..6201794 --- /dev/null +++ b/app/Domains/Admission/Actions/AdmissionPricing/DefineFeeType.php @@ -0,0 +1,17 @@ + $dto->name, + 'billing_cycle' => $dto->billingCycle, + ]); + } +} diff --git a/app/Domains/Admission/Actions/AdmissionPricing/SetUpAdmissionFeeRates.php b/app/Domains/Admission/Actions/AdmissionPricing/SetUpAdmissionFeeRates.php new file mode 100644 index 0000000..7ad7d92 --- /dev/null +++ b/app/Domains/Admission/Actions/AdmissionPricing/SetUpAdmissionFeeRates.php @@ -0,0 +1,11 @@ +update([ + 'date_start' => $dto->startData, + 'date_end' => $dto->endData, + 're_registration_date_start' => $dto->reRegisterStartData, + 're_registration_date_end' => $dto->reRegisterEndData, + ]); + } +} diff --git a/app/Domains/Admission/Actions/IntakeScheduling/PublishAdmissionSchedule.php b/app/Domains/Admission/Actions/IntakeScheduling/PublishAdmissionSchedule.php new file mode 100644 index 0000000..cdcbdbc --- /dev/null +++ b/app/Domains/Admission/Actions/IntakeScheduling/PublishAdmissionSchedule.php @@ -0,0 +1,22 @@ + $dto->startData, + 'date_end' => $dto->endData, + 're_registration_date_start' => $dto->reRegisterStartData, + 're_registration_date_end' => $dto->reRegisterEndData, + 'status' => $dto->status, + 'term_id' => $dto->termId, + 'admission_track_id' => $dto->admissionTrackId, + ]); + } +} diff --git a/app/Domains/Admission/Actions/IntakeSourcing/InitializeAdmissionTrack.php b/app/Domains/Admission/Actions/IntakeSourcing/InitializeAdmissionTrack.php new file mode 100644 index 0000000..818bd11 --- /dev/null +++ b/app/Domains/Admission/Actions/IntakeSourcing/InitializeAdmissionTrack.php @@ -0,0 +1,17 @@ + $dto->name, + 'status' => $dto->status, + ]); + } +} diff --git a/app/Domains/Admission/Actions/StudyModeProvisioning/DefineProgramTrack.php b/app/Domains/Admission/Actions/StudyModeProvisioning/DefineProgramTrack.php new file mode 100644 index 0000000..098eec3 --- /dev/null +++ b/app/Domains/Admission/Actions/StudyModeProvisioning/DefineProgramTrack.php @@ -0,0 +1,18 @@ + $dto->name, + 'code' => $dto->code, + 'status' => $dto->status, + ]); + } +} diff --git a/app/Domains/Admission/DTOs/AdmissionPricing/AdmissionFeeRatesDTO.php b/app/Domains/Admission/DTOs/AdmissionPricing/AdmissionFeeRatesDTO.php new file mode 100644 index 0000000..e81ca33 --- /dev/null +++ b/app/Domains/Admission/DTOs/AdmissionPricing/AdmissionFeeRatesDTO.php @@ -0,0 +1,14 @@ + 'success', + self::DRAFT => 'warning', + self::ARCHIVED => 'danger', + }; + } +} diff --git a/app/Domains/Admission/Enums/BillingCycle.php b/app/Domains/Admission/Enums/BillingCycle.php new file mode 100644 index 0000000..5bc8c56 --- /dev/null +++ b/app/Domains/Admission/Enums/BillingCycle.php @@ -0,0 +1,18 @@ + 'date', + 'date_end' => 'date', + 're_registration_date_start' => 'date', + 're_registration_date_end' => 'date', + 'status' => AdmissionStatus::class, + ]; + + public function uniqueIds(): array + { + return ['ulid']; + } + + protected static function newFactory(): Factory + { + return AdmissionScheduleFactory::new(); + } + + public function admissionTrack(): BelongsTo + { + return $this->belongsTo(AdmissionTrack::class); + } +} diff --git a/app/Domains/Admission/Models/AdmissionTrack.php b/app/Domains/Admission/Models/AdmissionTrack.php new file mode 100644 index 0000000..4ae4247 --- /dev/null +++ b/app/Domains/Admission/Models/AdmissionTrack.php @@ -0,0 +1,42 @@ + LifecycleStatus::class, + ]; + + public function uniqueIds(): array + { + return ['ulid']; + } + + protected static function newFactory(): Factory + { + return AdmissionTrackFactory::new(); + } + + public function sluggable(): string + { + return 'name'; + } +} diff --git a/app/Domains/Admission/Models/FeeRate.php b/app/Domains/Admission/Models/FeeRate.php new file mode 100644 index 0000000..d0fed5a --- /dev/null +++ b/app/Domains/Admission/Models/FeeRate.php @@ -0,0 +1,52 @@ +belongsTo(AdmissionSchedule::class); + } + + public function programTrack(): BelongsTo + { + return $this->belongsTo(ProgramTrack::class); + } + + public function feeType(): BelongsTo + { + return $this->belongsTo(FeeType::class); + } +} diff --git a/app/Domains/Admission/Models/FeeType.php b/app/Domains/Admission/Models/FeeType.php new file mode 100644 index 0000000..02aa20e --- /dev/null +++ b/app/Domains/Admission/Models/FeeType.php @@ -0,0 +1,35 @@ + BillingCycle::class, + ]; + + public function uniqueIds(): array + { + return ['ulid']; + } + + protected static function newFactory(): Factory + { + return FeeTypeFactory::new(); + } +} diff --git a/app/Domains/Admission/Models/ProgramTrack.php b/app/Domains/Admission/Models/ProgramTrack.php new file mode 100644 index 0000000..e26d42f --- /dev/null +++ b/app/Domains/Admission/Models/ProgramTrack.php @@ -0,0 +1,35 @@ + LifecycleStatus::class, + ]; + + protected static function newFactory(): Factory + { + return ProgramTrackFactory::new(); + } + + public function uniqueIds(): array + { + return ['ulid']; + } +} diff --git a/app/Domains/Admission/Policies/AdmissionSchedulePolicy.php b/app/Domains/Admission/Policies/AdmissionSchedulePolicy.php new file mode 100644 index 0000000..514ef23 --- /dev/null +++ b/app/Domains/Admission/Policies/AdmissionSchedulePolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('admission-schedule.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, AdmissionSchedule $model): bool + { + return $user->hasPermissionTo('admission-schedule.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('admission-schedule.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, AdmissionSchedule $model): bool + { + return $user->hasPermissionTo('admission-schedule.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, AdmissionSchedule $model): bool + { + return $user->hasPermissionTo('admission-schedule.delete'); + } +} diff --git a/app/Domains/Admission/Policies/AdmissionTrackPolicy.php b/app/Domains/Admission/Policies/AdmissionTrackPolicy.php new file mode 100644 index 0000000..4359bd5 --- /dev/null +++ b/app/Domains/Admission/Policies/AdmissionTrackPolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('admission-track.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, AdmissionTrack $model): bool + { + return $user->hasPermissionTo('admission-track.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('admission-track.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, AdmissionTrack $model): bool + { + return $user->hasPermissionTo('admission-track.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, AdmissionTrack $model): bool + { + return $user->hasPermissionTo('admission-track.delete'); + } +} diff --git a/app/Domains/Admission/Policies/FeeRatePolicy.php b/app/Domains/Admission/Policies/FeeRatePolicy.php new file mode 100644 index 0000000..e4c12ec --- /dev/null +++ b/app/Domains/Admission/Policies/FeeRatePolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('fee-rate.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, FeeRate $model): bool + { + return $user->hasPermissionTo('fee-rate.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('fee-rate.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, FeeRate $model): bool + { + return $user->hasPermissionTo('fee-rate.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, FeeRate $model): bool + { + return $user->hasPermissionTo('fee-rate.delete'); + } +} diff --git a/app/Domains/Admission/Policies/FeeTypePolicy.php b/app/Domains/Admission/Policies/FeeTypePolicy.php new file mode 100644 index 0000000..075c8f7 --- /dev/null +++ b/app/Domains/Admission/Policies/FeeTypePolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('fee-type.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, FeeType $model): bool + { + return $user->hasPermissionTo('fee-type.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('fee-type.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, FeeType $model): bool + { + return $user->hasPermissionTo('fee-type.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, FeeType $model): bool + { + return $user->hasPermissionTo('fee-type.delete'); + } +} diff --git a/app/Domains/Admission/Policies/ProgramTrackPolicy.php b/app/Domains/Admission/Policies/ProgramTrackPolicy.php new file mode 100644 index 0000000..b7bbc6c --- /dev/null +++ b/app/Domains/Admission/Policies/ProgramTrackPolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('program-track.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, ProgramTrack $model): bool + { + return $user->hasPermissionTo('program-track.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('program-track.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, ProgramTrack $model): bool + { + return $user->hasPermissionTo('program-track.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, ProgramTrack $model): bool + { + return $user->hasPermissionTo('program-track.delete'); + } +} diff --git a/app/Domains/Admission/Providers/AdmissionServiceProvider.php b/app/Domains/Admission/Providers/AdmissionServiceProvider.php new file mode 100644 index 0000000..ed97be2 --- /dev/null +++ b/app/Domains/Admission/Providers/AdmissionServiceProvider.php @@ -0,0 +1,23 @@ +app->register(RelationshipServiceProvider::class); + } + + public function boot(): void + { + $this->registerEvents(); + } +} diff --git a/app/Domains/Admission/Providers/RelationshipServiceProvider.php b/app/Domains/Admission/Providers/RelationshipServiceProvider.php new file mode 100644 index 0000000..7d2d710 --- /dev/null +++ b/app/Domains/Admission/Providers/RelationshipServiceProvider.php @@ -0,0 +1,31 @@ + + * $order->belongsTo(Customer::class, 'customer_id') + * ); + */ + public function boot(): void + { + // Register cross-domain relationships below. + } +} diff --git a/app/Domains/Channel/Actions/AppProvisioning/DeauthorizConsumerApp.php b/app/Domains/Channel/Actions/AppProvisioning/DeauthorizConsumerApp.php new file mode 100644 index 0000000..8fd1bdd --- /dev/null +++ b/app/Domains/Channel/Actions/AppProvisioning/DeauthorizConsumerApp.php @@ -0,0 +1,13 @@ + $dto->name, + 'code' => $dto->code, + 'url' => $dto->url, + 'secret' => encrypt(uuid_create()), + ]); + } +} diff --git a/app/Domains/Channel/Actions/AppProvisioning/RotateSecretKey.php b/app/Domains/Channel/Actions/AppProvisioning/RotateSecretKey.php new file mode 100644 index 0000000..ebca4e5 --- /dev/null +++ b/app/Domains/Channel/Actions/AppProvisioning/RotateSecretKey.php @@ -0,0 +1,14 @@ +update(['secret' => $key]); + } +} diff --git a/app/Domains/Channel/DTOs/AppProvisioning/ProvisionConsumerAppDTO.php b/app/Domains/Channel/DTOs/AppProvisioning/ProvisionConsumerAppDTO.php new file mode 100644 index 0000000..f4390be --- /dev/null +++ b/app/Domains/Channel/DTOs/AppProvisioning/ProvisionConsumerAppDTO.php @@ -0,0 +1,12 @@ + 'encrypted', + ]; + + protected static function newFactory(): Factory + { + return ClientFactory::new(); + } + + public function uniqueIds(): array + { + return ['ulid']; + } +} diff --git a/app/Domains/Channel/Policies/ClientPolicy.php b/app/Domains/Channel/Policies/ClientPolicy.php new file mode 100644 index 0000000..b7952bc --- /dev/null +++ b/app/Domains/Channel/Policies/ClientPolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('client.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Client $model): bool + { + return $user->hasPermissionTo('client.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('client.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Client $model): bool + { + return $user->hasPermissionTo('client.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Client $model): bool + { + return $user->hasPermissionTo('client.delete'); + } +} diff --git a/app/Domains/Channel/Providers/ChannelServiceProvider.php b/app/Domains/Channel/Providers/ChannelServiceProvider.php new file mode 100644 index 0000000..fef6bf9 --- /dev/null +++ b/app/Domains/Channel/Providers/ChannelServiceProvider.php @@ -0,0 +1,23 @@ +app->register(RelationshipServiceProvider::class); + } + + public function boot(): void + { + $this->registerEvents(); + } +} diff --git a/app/Domains/Channel/Providers/RelationshipServiceProvider.php b/app/Domains/Channel/Providers/RelationshipServiceProvider.php new file mode 100644 index 0000000..db337be --- /dev/null +++ b/app/Domains/Channel/Providers/RelationshipServiceProvider.php @@ -0,0 +1,51 @@ + + * $order->belongsTo(Customer::class, 'customer_id') + * ); + */ + public function boot(): void + { + // Register cross-domain relationships below. + Client::resolveRelationUsing('transaction', fn (Client $client) => $client->hasMany( + related: Invoice::class, + foreignKey: 'client_id' + )); + Invoice::resolveRelationUsing('client', fn (Invoice $transaction) => $transaction->belongsTo( + related: Client::class, + foreignKey: 'client_id' + )); + + Client::resolveRelationUsing('product_mapping', fn (Client $client) => $client->hasMany( + related: ProductMapping::class, + foreignKey: 'client_id' + )); + ProductMapping::resolveRelationUsing('client', fn (ProductMapping $productMapping) => $productMapping->belongsTo( + related: Client::class, + foreignKey: 'client_id' + )); + } +} diff --git a/app/Domains/Finance/Actions/DisbursementProcessing/DisburseFunds.php b/app/Domains/Finance/Actions/DisbursementProcessing/DisburseFunds.php new file mode 100644 index 0000000..5168e9a --- /dev/null +++ b/app/Domains/Finance/Actions/DisbursementProcessing/DisburseFunds.php @@ -0,0 +1,11 @@ +update([ + 'code' => $dto->code, + 'name' => $dto->name, + 'classification' => $dto->classification, + 'parent_id' => $dto->parentId, + ]); + } +} diff --git a/app/Domains/Finance/Actions/LedgerConfiguration/RegisterChartOfAccount.php b/app/Domains/Finance/Actions/LedgerConfiguration/RegisterChartOfAccount.php new file mode 100644 index 0000000..2c7594e --- /dev/null +++ b/app/Domains/Finance/Actions/LedgerConfiguration/RegisterChartOfAccount.php @@ -0,0 +1,20 @@ + $dto->code, + 'name' => $dto->name, + 'classification' => $dto->classification, + 'status' => $dto->status, + 'parent_id' => $dto->parentId, + ]); + } +} diff --git a/app/Domains/Finance/Actions/LedgerConfiguration/SuspendAccountPostings.php b/app/Domains/Finance/Actions/LedgerConfiguration/SuspendAccountPostings.php new file mode 100644 index 0000000..3dde2dd --- /dev/null +++ b/app/Domains/Finance/Actions/LedgerConfiguration/SuspendAccountPostings.php @@ -0,0 +1,16 @@ +update([ + 'status' => LifecycleStatus::ARCHIVED, + ]); + } +} diff --git a/app/Domains/Finance/Actions/PaymentProcessing/InitializeInvoice.php b/app/Domains/Finance/Actions/PaymentProcessing/InitializeInvoice.php new file mode 100644 index 0000000..97f0aff --- /dev/null +++ b/app/Domains/Finance/Actions/PaymentProcessing/InitializeInvoice.php @@ -0,0 +1,11 @@ +amount; + } + + if (is_numeric($value)) { + return (float) $value; + } + + throw new InvalidArgumentException("The {$key} attribute must be an float or an instance of Money."); + } +} diff --git a/app/Domains/Finance/DTOs/DisbursementProcessing/DisburseFundsDTO.php b/app/Domains/Finance/DTOs/DisbursementProcessing/DisburseFundsDTO.php new file mode 100644 index 0000000..5921d12 --- /dev/null +++ b/app/Domains/Finance/DTOs/DisbursementProcessing/DisburseFundsDTO.php @@ -0,0 +1,10 @@ + AccountClassification::class, + 'status' => LifecycleStatus::class, + ]; + + public function uniqueIds(): array + { + return ['ulid']; + } + + protected static function newFactory(): Factory + { + return ChartOfAccountFactory::new(); + } +} diff --git a/app/Domains/Finance/Models/Invoice.php b/app/Domains/Finance/Models/Invoice.php new file mode 100644 index 0000000..f4ed1cf --- /dev/null +++ b/app/Domains/Finance/Models/Invoice.php @@ -0,0 +1,60 @@ + MoneyCurrency::class, + 'type' => InvoiceType::class, + 'status' => InvoiceStatus::class, + ]; + + public function uniqueIds(): array + { + return ['ulid']; + } + + protected static function newFactory(): Factory + { + return InvoiceFactory::new(); + } + + public function chartOfAccount(): BelongsTo + { + return $this->belongsTo(ChartOfAccount::class); + } + + public function payments(): HasMany + { + return $this->hasMany(Payment::class); + } +} diff --git a/app/Domains/Finance/Models/InvoiceReport.php b/app/Domains/Finance/Models/InvoiceReport.php new file mode 100644 index 0000000..73d6886 --- /dev/null +++ b/app/Domains/Finance/Models/InvoiceReport.php @@ -0,0 +1,42 @@ + throw new BadMethodCallException('Cannot create invoice report from here, use event instead.')); + static::deleting(fn (InvoiceReport $invoiceReport) => throw new BadMethodCallException('Cannot delete invoice report.')); + static::updating(fn (InvoiceReport $invoiceReport) => throw new BadMethodCallException('Cannot update invoice report.')); + } + + protected $casts = [ + 'amount' => MoneyCurrency::class, + 'paid_amount' => MoneyCurrency::class, + 'issued_at' => 'datetime', + 'paid_at' => 'datetime', + 'status' => InvoiceStatus::class, + ]; +} diff --git a/app/Domains/Finance/Models/Payment.php b/app/Domains/Finance/Models/Payment.php new file mode 100644 index 0000000..a6d1237 --- /dev/null +++ b/app/Domains/Finance/Models/Payment.php @@ -0,0 +1,54 @@ + MoneyCurrency::class, + 'valid_until' => 'datetime_immutable', + 'direction' => PaymentDirection::class, + 'status' => PaymentStatus::class, + ]; + + public function uniqueIds(): array + { + return ['ulid']; + } + + protected static function newFactory(): Factory + { + return PaymentFactory::new(); + } + + public function transaction(): BelongsTo + { + return $this->belongsTo(Invoice::class); + } +} diff --git a/app/Domains/Finance/Models/ProductMapping.php b/app/Domains/Finance/Models/ProductMapping.php new file mode 100644 index 0000000..a5dd18b --- /dev/null +++ b/app/Domains/Finance/Models/ProductMapping.php @@ -0,0 +1,24 @@ +belongsTo(ChartOfAccount::class); + } +} diff --git a/app/Domains/Finance/Models/StudentInvoiceReport.php b/app/Domains/Finance/Models/StudentInvoiceReport.php new file mode 100644 index 0000000..f4f3020 --- /dev/null +++ b/app/Domains/Finance/Models/StudentInvoiceReport.php @@ -0,0 +1,47 @@ + throw new BadMethodCallException('Cannot create invoice report from here, use event instead.')); + static::deleting(fn (InvoiceReport $invoiceReport) => throw new BadMethodCallException('Cannot delete invoice report.')); + static::updating(fn (InvoiceReport $invoiceReport) => throw new BadMethodCallException('Cannot update invoice report.')); + } + + protected $casts = [ + 'amount' => MoneyCurrency::class, + 'paid_amount' => MoneyCurrency::class, + 'issued_at' => 'datetime', + 'paid_at' => 'datetime', + 'status' => InvoiceStatus::class, + ]; +} diff --git a/app/Domains/Finance/Policies/ChartOfAccountPolicy.php b/app/Domains/Finance/Policies/ChartOfAccountPolicy.php new file mode 100644 index 0000000..57a1752 --- /dev/null +++ b/app/Domains/Finance/Policies/ChartOfAccountPolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('chart-of-account.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, ChartOfAccount $model): bool + { + return $user->hasPermissionTo('chart-of-account.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('chart-of-account.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, ChartOfAccount $model): bool + { + return $user->hasPermissionTo('chart-of-account.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, ChartOfAccount $model): bool + { + return $user->hasPermissionTo('chart-of-account.delete'); + } +} diff --git a/app/Domains/Finance/Policies/InvoicePolicy.php b/app/Domains/Finance/Policies/InvoicePolicy.php new file mode 100644 index 0000000..0287aa8 --- /dev/null +++ b/app/Domains/Finance/Policies/InvoicePolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('invoice.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Invoice $model): bool + { + return $user->hasPermissionTo('invoice.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('invoice.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Invoice $model): bool + { + return $user->hasPermissionTo('invoice.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Invoice $model): bool + { + return $user->hasPermissionTo('invoice.delete'); + } +} diff --git a/app/Domains/Finance/Policies/InvoiceReportPolicy.php b/app/Domains/Finance/Policies/InvoiceReportPolicy.php new file mode 100644 index 0000000..e7bf312 --- /dev/null +++ b/app/Domains/Finance/Policies/InvoiceReportPolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('invoice-report.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, InvoiceReport $model): bool + { + return $user->hasPermissionTo('invoice-report.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return false; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, InvoiceReport $model): bool + { + return false; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, InvoiceReport $model): bool + { + return false; + } +} diff --git a/app/Domains/Finance/Policies/PaymentPolicy.php b/app/Domains/Finance/Policies/PaymentPolicy.php new file mode 100644 index 0000000..a1d826c --- /dev/null +++ b/app/Domains/Finance/Policies/PaymentPolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('payment.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Payment $model): bool + { + return $user->hasPermissionTo('payment.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('payment.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Payment $model): bool + { + return $user->hasPermissionTo('payment.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Payment $model): bool + { + return $user->hasPermissionTo('payment.delete'); + } +} diff --git a/app/Domains/Finance/Policies/ProductMappingPolicy.php b/app/Domains/Finance/Policies/ProductMappingPolicy.php new file mode 100644 index 0000000..fd1cc9e --- /dev/null +++ b/app/Domains/Finance/Policies/ProductMappingPolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('product-mapping.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, ProductMapping $model): bool + { + return $user->hasPermissionTo('product-mapping.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->hasPermissionTo('product-mapping.create'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, ProductMapping $model): bool + { + return $user->hasPermissionTo('product-mapping.update'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, ProductMapping $model): bool + { + return $user->hasPermissionTo('product-mapping.delete'); + } +} diff --git a/app/Domains/Finance/Policies/StudentInvoiceReportPolicy.php b/app/Domains/Finance/Policies/StudentInvoiceReportPolicy.php new file mode 100644 index 0000000..ab1c3de --- /dev/null +++ b/app/Domains/Finance/Policies/StudentInvoiceReportPolicy.php @@ -0,0 +1,49 @@ +hasPermissionTo('student-invoice-report.viewAny'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, StudentInvoiceReport $model): bool + { + return $user->hasPermissionTo('student-invoice-report.view'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return false; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, StudentInvoiceReport $model): bool + { + return false; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, StudentInvoiceReport $model): bool + { + return false; + } +} diff --git a/app/Domains/Finance/Providers/FinanceServiceProvider.php b/app/Domains/Finance/Providers/FinanceServiceProvider.php new file mode 100644 index 0000000..650a1a6 --- /dev/null +++ b/app/Domains/Finance/Providers/FinanceServiceProvider.php @@ -0,0 +1,23 @@ +app->register(RelationshipServiceProvider::class); + } + + public function boot(): void + { + $this->registerEvents(); + } +} diff --git a/app/Domains/Finance/Providers/RelationshipServiceProvider.php b/app/Domains/Finance/Providers/RelationshipServiceProvider.php new file mode 100644 index 0000000..52b2e72 --- /dev/null +++ b/app/Domains/Finance/Providers/RelationshipServiceProvider.php @@ -0,0 +1,31 @@ + + * $order->belongsTo(Customer::class, 'customer_id') + * ); + */ + public function boot(): void + { + // Register cross-domain relationships below. + } +} diff --git a/app/Domains/Finance/Support/ValueObjects/Money.php b/app/Domains/Finance/Support/ValueObjects/Money.php new file mode 100644 index 0000000..38d13fc --- /dev/null +++ b/app/Domains/Finance/Support/ValueObjects/Money.php @@ -0,0 +1,35 @@ +amount < 0) { + throw new InvalidArgumentException('Money cannot be negative.'); + } + } + + /** + * Format the bytes into a human-readable string. + */ + public function format(string $currency = 'IDR'): string + { + $format = new NumberFormatter(app()->getLocale(), NumberFormatter::CURRENCY); + + return $format->formatCurrency($this->amount, $currency); + } + + /** + * Automatically format when echoed in Blade (e.g., {{ $model->amount }}). + */ + public function __toString(): string + { + return $this->format(); + } +} diff --git a/app/Domains/Identity/Actions/AccessControl/DefineSystemRole.php b/app/Domains/Identity/Actions/AccessControl/DefineSystemRole.php new file mode 100644 index 0000000..6ddc1ae --- /dev/null +++ b/app/Domains/Identity/Actions/AccessControl/DefineSystemRole.php @@ -0,0 +1,26 @@ + $dto->name, + ]); + $role->syncPermissions($dto->permissions); + + return true; + }); + } +} diff --git a/app/Domains/Identity/Actions/AccessControl/DeleteSystemRole.php b/app/Domains/Identity/Actions/AccessControl/DeleteSystemRole.php new file mode 100644 index 0000000..1083a52 --- /dev/null +++ b/app/Domains/Identity/Actions/AccessControl/DeleteSystemRole.php @@ -0,0 +1,26 @@ +name, [RoleType::ADMIN->value, RoleType::SYSTEM_ADMIN->value])) { + throw new Exception(__('domains/identity/messages.exceptions.cannot_remove_system_role')); + } + + if ($role->users()->exists()) { + throw new Exception(__('domains/identity/messages.exceptions.role_has_users')); + } + } +} diff --git a/app/Domains/Identity/Actions/Governance/DeleteUser.php b/app/Domains/Identity/Actions/Governance/DeleteUser.php new file mode 100644 index 0000000..8b8c17d --- /dev/null +++ b/app/Domains/Identity/Actions/Governance/DeleteUser.php @@ -0,0 +1,22 @@ +status->isActive()) { + $this->suspendUser->execute($user); + } else { + $this->purgeUser->execute($user); + } + } +} diff --git a/app/Domains/Identity/Enums/RoleType.php b/app/Domains/Identity/Enums/RoleType.php index 45ca16a..3f6bbb5 100644 --- a/app/Domains/Identity/Enums/RoleType.php +++ b/app/Domains/Identity/Enums/RoleType.php @@ -37,6 +37,22 @@ enum RoleType: string ...self::generatePolicy('role', [self::SYSTEM_ADMIN, self::ADMIN]), ...self::generatePolicy('user', [self::SYSTEM_ADMIN, self::ADMIN]), + ...self::generatePolicy('faculty', [self::SYSTEM_ADMIN, self::ADMIN]), + ...self::generatePolicy('study-program', [self::SYSTEM_ADMIN, self::ADMIN]), + ...self::generatePolicy('term', [self::SYSTEM_ADMIN, self::ADMIN]), + + ...self::generatePolicy('client', [self::SYSTEM_ADMIN, self::ADMIN]), + + ...self::generatePolicy('admission-track', [self::SYSTEM_ADMIN, self::ADMIN]), + ...self::generatePolicy('program-track', [self::SYSTEM_ADMIN, self::ADMIN]), + ...self::generatePolicy('admission-schedule', [self::SYSTEM_ADMIN, self::ADMIN]), + ...self::generatePolicy('fee-type', [self::SYSTEM_ADMIN, self::ADMIN]), + + ...self::generatePolicy('chart-of-account', [self::SYSTEM_ADMIN, self::ADMIN]), + ...self::generatePolicy('invoice', [self::SYSTEM_ADMIN, self::ADMIN]), + ...self::generatePolicy('product-mapping', [self::SYSTEM_ADMIN, self::ADMIN]), + ...self::generatePolicy('payment', [self::SYSTEM_ADMIN, self::ADMIN]), + // System Setting [ 'name' => 'system-setting.manage', diff --git a/app/Domains/Identity/Enums/UserStatus.php b/app/Domains/Identity/Enums/UserStatus.php index 7794e8d..368e4b3 100644 --- a/app/Domains/Identity/Enums/UserStatus.php +++ b/app/Domains/Identity/Enums/UserStatus.php @@ -2,8 +2,16 @@ namespace App\Domains\Identity\Enums; -enum UserStatus: string +use App\Domains\System\Traits\Enum\HasPredicateMethod; +use App\UI\Enums\Concerns\InteractsWithLabels; +use App\UI\Enums\Contracts\HasLabel; +use Illuminate\Foundation\Testing\Concerns\InteractsWithAuthentication; + +enum UserStatus: string implements HasLabel { + use HasPredicateMethod; + use InteractsWithLabels; + case ACTIVE = 'active'; case INACTIVE = 'inactive'; @@ -12,16 +20,11 @@ enum UserStatus: string return __('domains/identity/enum.user_status.'.$this->value); } - public function badgeVariant(): string + public function badge(): string { return match ($this) { self::ACTIVE => 'success', self::INACTIVE => 'danger', }; } - - public function isActive(): bool - { - return $this == self::ACTIVE; - } } diff --git a/app/Domains/Identity/Models/User.php b/app/Domains/Identity/Models/User.php index 6989edd..0f3f80f 100644 --- a/app/Domains/Identity/Models/User.php +++ b/app/Domains/Identity/Models/User.php @@ -2,7 +2,6 @@ namespace App\Domains\Identity\Models; -use App\Domains\Account\Models\Profile; use App\Domains\Identity\Enums\UserStatus; use App\Domains\Identity\Notifications\ResetPasswordNotification; use App\Domains\Identity\Notifications\VerifyEmailNotification; @@ -16,7 +15,6 @@ use Illuminate\Database\Eloquent\Attributes\UsePolicy; use Illuminate\Database\Eloquent\Concerns\HasUlids; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -29,13 +27,14 @@ use Spatie\Permission\Traits\HasRoles; #[UsePolicy(UserPolicy::class)] class User extends Authenticatable implements Auditable, MustVerifyEmail { + use HasApiTokens; + /** @use HasFactory */ use HasFactory; use HasFile; use HasRoles; use HasUlids; - use HasApiTokens; use Notifiable; use \OwenIt\Auditing\Auditable; diff --git a/app/Domains/Identity/Notifications/ResetPasswordNotification.php b/app/Domains/Identity/Notifications/ResetPasswordNotification.php index c3da566..087560b 100644 --- a/app/Domains/Identity/Notifications/ResetPasswordNotification.php +++ b/app/Domains/Identity/Notifications/ResetPasswordNotification.php @@ -11,8 +11,7 @@ class ResetPasswordNotification extends Notification implements ShouldQueue { use Queueable; - public function __construct(public string $token) - {} + public function __construct(public string $token) {} public function via(object $notifiable): array { @@ -25,11 +24,12 @@ class ResetPasswordNotification extends Notification implements ShouldQueue 'token' => $this->token, 'email' => $notifiable->getEmailForPasswordReset(), ])); + return (new MailMessage) - ->subject(__('domains/auth/notifications.reset_password.subject')) - ->line(__('domains/auth/notifications.reset_password.intro')) - ->action(__('domains/auth/notifications.reset_password.action'), $url) - ->line(__('domains/auth/notifications.reset_password.outro')); + ->subject(__('domains/auth/notifications.reset_password.subject')) + ->line(__('domains/auth/notifications.reset_password.intro')) + ->action(__('domains/auth/notifications.reset_password.action'), $url) + ->line(__('domains/auth/notifications.reset_password.outro')); } public function toArray(object $notifiable): array diff --git a/app/Domains/System/Actions/Backup/ArchiveSystemBackup.php b/app/Domains/System/Actions/Backup/ArchiveSystemBackup.php new file mode 100644 index 0000000..2864a56 --- /dev/null +++ b/app/Domains/System/Actions/Backup/ArchiveSystemBackup.php @@ -0,0 +1,26 @@ +syncBackupCatalog->execute(); + } +} diff --git a/app/Domains/System/Actions/Backup/SystemBackup.php b/app/Domains/System/Actions/Backup/SystemBackup.php deleted file mode 100644 index 4992d93..0000000 --- a/app/Domains/System/Actions/Backup/SystemBackup.php +++ /dev/null @@ -1,42 +0,0 @@ -allFiles($backupName); - if (empty($files)) { - throw new Exception(__('domains/system/messages.backup.verification_error')); - } - - $latestFile = collect($files)->last(); - $fileName = $backupName.' '.basename($latestFile); - $sizeInBytes = Storage::disk($disk)->size($latestFile); - - return Backup::create([ - 'file_name' => $fileName, - 'disk' => $disk, - 'path' => $latestFile, - 'size' => $sizeInBytes, - 'type' => 'full', - ]); - } -} diff --git a/app/Domains/System/Enums/LifecycleStatus.php b/app/Domains/System/Enums/LifecycleStatus.php new file mode 100644 index 0000000..081d43e --- /dev/null +++ b/app/Domains/System/Enums/LifecycleStatus.php @@ -0,0 +1,25 @@ + 'success', + self::ARCHIVED => 'secondary', + }; + } +} diff --git a/app/Domains/System/Models/File.php b/app/Domains/System/Models/File.php index 6491f65..a1867b1 100644 --- a/app/Domains/System/Models/File.php +++ b/app/Domains/System/Models/File.php @@ -2,7 +2,7 @@ namespace App\Domains\System\Models; -use App\Domains\System\Casts\ByteHumanReadable; +use App\Domains\System\Casts\MoneyCurrency; use Database\Factories\System\FileFactory; use Exception; use Illuminate\Database\Eloquent\Attributes\Fillable; @@ -22,14 +22,14 @@ use Storage; 'disk', 'mime_type', 'options', - 'uploader_id' + 'uploader_id', ])] class File extends Model { use HasFactory; protected $casts = [ - 'size' => ByteHumanReadable::class, + 'size' => MoneyCurrency::class, 'options' => 'array', ]; diff --git a/app/Domains/System/Providers/SystemServiceProvider.php b/app/Domains/System/Providers/SystemServiceProvider.php index cd6c08f..6dc3c8e 100644 --- a/app/Domains/System/Providers/SystemServiceProvider.php +++ b/app/Domains/System/Providers/SystemServiceProvider.php @@ -3,7 +3,6 @@ namespace App\Domains\System\Providers; use App\Domains\Identity\Events\Governance\UserWasPurged; -use App\Domains\System\Enums\SystemSettingKey; use App\Domains\System\Events\ExportCompleted; use App\Domains\System\Events\ImportCompleted; use App\Domains\System\Listeners\Excel\SendExportReportEmail; @@ -13,8 +12,6 @@ use App\Domains\System\Queries\GetSystemSettings; use App\Domains\System\Traits\Provider\RegistersDomainEvents; use Illuminate\Support\Carbon; use Illuminate\Support\ServiceProvider; -use OwenIt\Auditing\Models\Audit; -use View; class SystemServiceProvider extends ServiceProvider { diff --git a/app/Domains/System/Traits/Enum/HasPredicateMethod.php b/app/Domains/System/Traits/Enum/HasPredicateMethod.php new file mode 100644 index 0000000..9a7add4 --- /dev/null +++ b/app/Domains/System/Traits/Enum/HasPredicateMethod.php @@ -0,0 +1,23 @@ +name === $expectedCase) { + return $this === $case; + } + } + + throw new \BadMethodCallException("Method {$method} does not exist on " . self::class); + } + + return true; + } +} diff --git a/app/Domains/System/Traits/Model/HasSlugs.php b/app/Domains/System/Traits/Model/HasSlugs.php new file mode 100644 index 0000000..239c474 --- /dev/null +++ b/app/Domains/System/Traits/Model/HasSlugs.php @@ -0,0 +1,18 @@ + $model->slug = str($model->{$model->sluggable()})->slug()); + } +} diff --git a/app/Http/Controllers/Api/V1/Lookup/RoleLookupController.php b/app/Http/Controllers/Api/V1/Lookup/RoleLookupController.php index c8a7797..87db07c 100644 --- a/app/Http/Controllers/Api/V1/Lookup/RoleLookupController.php +++ b/app/Http/Controllers/Api/V1/Lookup/RoleLookupController.php @@ -15,7 +15,7 @@ class RoleLookupController extends Controller public function __invoke(Request $request, RoleLookup $lookup) { $result = $lookup->fetch($request->input('search')) - ->map(fn($res) => (object) [ + ->map(fn ($res) => (object) [ 'id' => $res->name, 'text' => $res->name, ]); diff --git a/app/Http/Controllers/Web/Academic/FacultyController.php b/app/Http/Controllers/Web/Academic/FacultyController.php new file mode 100644 index 0000000..a1bafec --- /dev/null +++ b/app/Http/Controllers/Web/Academic/FacultyController.php @@ -0,0 +1,32 @@ + 'dashboard', + 'domains/academic/seo.faculty.title' => '', + ], + )] + public function __invoke(FacultyDataTable $dataTable) + { + return $dataTable->render('pages.academic.faculty.index'); + } +} diff --git a/app/Http/Controllers/Web/Academic/StudyProgramController.php b/app/Http/Controllers/Web/Academic/StudyProgramController.php new file mode 100644 index 0000000..6ea40f4 --- /dev/null +++ b/app/Http/Controllers/Web/Academic/StudyProgramController.php @@ -0,0 +1,32 @@ + 'dashboard', + 'domains/academic/seo.study-program.title' => '', + ], + )] + public function __invoke(StudyProgramDataTable $dataTable) + { + return $dataTable->render('pages.academic.study-program.index'); + } +} diff --git a/app/Http/Controllers/Web/Academic/TermController.php b/app/Http/Controllers/Web/Academic/TermController.php new file mode 100644 index 0000000..92c60fd --- /dev/null +++ b/app/Http/Controllers/Web/Academic/TermController.php @@ -0,0 +1,32 @@ + 'dashboard', + 'domains/academic/seo.term.title' => '', + ], + )] + public function __invoke(TermDataTable $dataTable) + { + return $dataTable->render('pages.academic.term.index'); + } +} diff --git a/app/Http/Controllers/Web/Admission/AdmissionScheduleController.php b/app/Http/Controllers/Web/Admission/AdmissionScheduleController.php new file mode 100644 index 0000000..bf10060 --- /dev/null +++ b/app/Http/Controllers/Web/Admission/AdmissionScheduleController.php @@ -0,0 +1,32 @@ + 'dashboard', + 'domains/admission/seo.admission-schedule.title' => '', + ], + )] + public function __invoke(AdmissionScheduleDataTable $dataTable) + { + return $dataTable->render('pages.admission.admission-schedule.index'); + } +} diff --git a/app/Http/Controllers/Web/Admission/AdmissionTrackController.php b/app/Http/Controllers/Web/Admission/AdmissionTrackController.php new file mode 100644 index 0000000..ff3662b --- /dev/null +++ b/app/Http/Controllers/Web/Admission/AdmissionTrackController.php @@ -0,0 +1,32 @@ + 'dashboard', + 'domains/admission/seo.admission-track.title' => '', + ], + )] + public function __invoke(AdmissionTrackDataTable $dataTable) + { + return $dataTable->render('pages.admission.admission-track.index'); + } +} diff --git a/app/Http/Controllers/Web/Admission/FeeTypeController.php b/app/Http/Controllers/Web/Admission/FeeTypeController.php new file mode 100644 index 0000000..cad43b4 --- /dev/null +++ b/app/Http/Controllers/Web/Admission/FeeTypeController.php @@ -0,0 +1,32 @@ + 'dashboard', + 'domains/admission/seo.fee-type.title' => '', + ], + )] + public function __invoke(FeeTypeDataTable $dataTable) + { + return $dataTable->render('pages.admission.fee-type.index'); + } +} diff --git a/app/Http/Controllers/Web/Admission/ProgramTrackController.php b/app/Http/Controllers/Web/Admission/ProgramTrackController.php new file mode 100644 index 0000000..93dc8e1 --- /dev/null +++ b/app/Http/Controllers/Web/Admission/ProgramTrackController.php @@ -0,0 +1,32 @@ + 'dashboard', + 'domains/admission/seo.program-track.title' => '', + ], + )] + public function __invoke(ProgramTrackDataTable $dataTable) + { + return $dataTable->render('pages.admission.program-track.index'); + } +} diff --git a/app/Http/Controllers/Web/Channel/ClientController.php b/app/Http/Controllers/Web/Channel/ClientController.php new file mode 100644 index 0000000..20e30d0 --- /dev/null +++ b/app/Http/Controllers/Web/Channel/ClientController.php @@ -0,0 +1,32 @@ + 'dashboard', + 'domains/channel/seo.client.title' => '', + ], + )] + public function __invoke(ClientDataTable $dataTable) + { + return $dataTable->render('pages.channel.client.index'); + } +} diff --git a/app/Http/Controllers/Web/Finance/ChartOfAccountController.php b/app/Http/Controllers/Web/Finance/ChartOfAccountController.php new file mode 100644 index 0000000..e2044b4 --- /dev/null +++ b/app/Http/Controllers/Web/Finance/ChartOfAccountController.php @@ -0,0 +1,32 @@ + 'dashboard', + 'domains/finance/seo.coa.title' => '', + ], + )] + public function __invoke(ChartOfAccountDataTable $dataTable) + { + return $dataTable->render('pages.finance.chart-of-account.index'); + } +} diff --git a/app/Http/Controllers/Web/Finance/InvoiceController.php b/app/Http/Controllers/Web/Finance/InvoiceController.php new file mode 100644 index 0000000..f98e262 --- /dev/null +++ b/app/Http/Controllers/Web/Finance/InvoiceController.php @@ -0,0 +1,32 @@ + 'dashboard', + 'domains/finance/seo.invoice.title' => '', + ], + )] + public function __invoke(InvoiceDataTable $dataTable) + { + return $dataTable->render('pages.finance.invoice.index'); + } +} diff --git a/app/Http/Controllers/Web/Finance/ProductMappingController.php b/app/Http/Controllers/Web/Finance/ProductMappingController.php new file mode 100644 index 0000000..0e4b4b6 --- /dev/null +++ b/app/Http/Controllers/Web/Finance/ProductMappingController.php @@ -0,0 +1,32 @@ + 'dashboard', + 'domains/finance/seo.product-mapping.title' => '', + ], + )] + public function __invoke(ProductMappingDataTable $dataTable) + { + return $dataTable->render('pages.finance.product-mapping.index'); + } +} diff --git a/app/Http/DataTables/Academic/FacultyDataTable.php b/app/Http/DataTables/Academic/FacultyDataTable.php new file mode 100644 index 0000000..c0b1fc7 --- /dev/null +++ b/app/Http/DataTables/Academic/FacultyDataTable.php @@ -0,0 +1,133 @@ + $query Results from query() method. + */ + public function dataTable(QueryBuilder $query): EloquentDataTable + { + return (new EloquentDataTable($query)) + ->addColumn( + 'action', + fn ($admissionSchedule) => view('components.datatables.action-button', [ + 'log' => true, + 'edit' => [ + 'modal' => 'admissionschedule-form-modal', + 'permission' => auth()->user()->can('update', $admissionSchedule), + ], + 'delete' => [ + 'url' => null, + 'title' => __('ui.button.delete'), + 'message' => __('ui.confirmation.delete', ['resource' => __('resources.admission-schedule')]), + 'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.admission-schedule')]), + 'permission' => auth()->user()->can('delete', $admissionSchedule), + ], + 'table_name' => 'admissionschedule-table', + 'id' => $admissionSchedule->ulid, + ]) + ) + ->rawColumns(['action']) + ->addIndexColumn(); + } + + /** + * Get the query source of dataTable. + * + * @return QueryBuilder + */ + public function query(Faculty $model): QueryBuilder + { + return $model->newQuery(); + } + + /** + * Optional method if you want to use the html builder. + */ + public function html(): HtmlBuilder + { + return $this->builder() + ->setTableId('faculty-table') + ->columns($this->getColumns()) + ->minifiedAjax() + ->orderBy(-1) + ->layout([ + 'topStart' => [ + 'rowClass' => 'row gap-1', + 'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', + 'features' => ['buttons', 'pageLength'], + ], + 'topEnd' => [ + 'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', + 'features' => ['search'], + ], + + 'bottomStart' => 'info', + 'bottomEnd' => 'paging', + ]) + ->parameters([ + 'language' => [ + 'search' => '', + 'searchPlaceholder' => __('ui.button.lookup'), + ], + 'fixedColumns' => [ + 'start' => 2, + ], + 'scrollX' => true, + 'scrollCollapse' => true, + 'responsive' => true, + ]) + ->buttons([ + Button::make('add') + ->action('$("#faculty-form-modal").modal("show");') + ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + Button::make('reload') + ->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + ]); + } + + /** + * Get the dataTable columns definition. + */ + public function getColumns(): array + { + return [ + Column::computed('DT_RowIndex') + ->width(10) + ->title('#'), + Column::make('name') + ->title(__('domains/academic/field.faculty.name')), + Column::make('code') + ->width(100) + ->title(__('domains/academic/field.faculty.code')), + Column::computed('action') + ->title(__('ui.label.actions')) + ->exportable(false) + ->printable(false) + ->width(60) + ->addClass('text-center'), + ]; + } + + /** + * Get the filename for export. + */ + protected function filename(): string + { + return 'Faculty_'.date('YmdHis'); + } +} diff --git a/app/Http/DataTables/Academic/StudyProgramDataTable.php b/app/Http/DataTables/Academic/StudyProgramDataTable.php new file mode 100644 index 0000000..9302247 --- /dev/null +++ b/app/Http/DataTables/Academic/StudyProgramDataTable.php @@ -0,0 +1,142 @@ + $query Results from query() method. + */ + public function dataTable(QueryBuilder $query): EloquentDataTable + { + return (new EloquentDataTable($query)) + ->editColumn('status', fn (StudyProgram $studyProgram) => view('components.badge', [ + 'label' => $studyProgram->status->label(), + 'variant' => $studyProgram->status->color(), + ])) + ->addColumn( + 'action', + fn ($admissionSchedule) => view('components.datatables.action-button', [ + 'log' => true, + 'edit' => [ + 'modal' => 'studyprogram-form-modal', + 'permission' => auth()->user()->can('update', $admissionSchedule), + ], + 'delete' => [ + 'url' => null, + 'title' => __('ui.button.delete'), + 'message' => __('ui.confirmation.delete', ['resource' => __('resources.admission-schedule')]), + 'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.admission-schedule')]), + 'permission' => auth()->user()->can('delete', $admissionSchedule), + ], + 'table_name' => 'studyprogram-table', + 'id' => $admissionSchedule->ulid, + ]) + ) + ->rawColumns(['action']) + ->addIndexColumn(); + } + + /** + * Get the query source of dataTable. + * + * @return QueryBuilder + */ + public function query(StudyProgram $model): QueryBuilder + { + return $model->with('faculty')->newQuery(); + } + + /** + * Optional method if you want to use the html builder. + */ + public function html(): HtmlBuilder + { + return $this->builder() + ->setTableId('studyprogram-table') + ->columns($this->getColumns()) + ->minifiedAjax() + ->orderBy(-1) + ->layout([ + 'topStart' => [ + 'rowClass' => 'row gap-1', + 'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', + 'features' => ['buttons', 'pageLength'], + ], + 'topEnd' => [ + 'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', + 'features' => ['search'], + ], + + 'bottomStart' => 'info', + 'bottomEnd' => 'paging', + ]) + ->parameters([ + 'language' => [ + 'search' => '', + 'searchPlaceholder' => __('ui.button.lookup'), + ], + 'fixedColumns' => [ + 'start' => 2, + ], + 'scrollX' => true, + 'scrollCollapse' => true, + 'responsive' => true, + ]) + ->buttons([ + Button::make('add') + ->action('$("#studyprogram-form-modal").modal("show");') + ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + Button::make('reload') + ->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + ]); + } + + /** + * Get the dataTable columns definition. + */ + public function getColumns(): array + { + return [ + Column::computed('DT_RowIndex') + ->width(10) + ->title('#'), + Column::make('name') + ->title(__('domains/academic/field.study-program.name')), + Column::make('code') + ->width(100) + ->title(__('domains/academic/field.study-program.code')), + Column::make('faculty.name') + ->title(__('domains/academic/field.faculty.name')), + Column::make('status') + ->width(100) + ->title(__('domains/academic/field.study-program.status')), + Column::computed('action') + ->title(__('ui.label.actions')) + ->exportable(false) + ->printable(false) + ->width(60) + ->addClass('text-center'), + ]; + } + + /** + * Get the filename for export. + */ + protected function filename(): string + { + return 'StudyProgram_'.date('YmdHis'); + } +} diff --git a/app/Http/DataTables/Academic/TermDataTable.php b/app/Http/DataTables/Academic/TermDataTable.php new file mode 100644 index 0000000..0f67fe7 --- /dev/null +++ b/app/Http/DataTables/Academic/TermDataTable.php @@ -0,0 +1,140 @@ + $query Results from query() method. + */ + public function dataTable(QueryBuilder $query): EloquentDataTable + { + return (new EloquentDataTable($query)) + ->addColumn( + 'action', + fn ($admissionSchedule) => view('components.datatables.action-button', [ + 'log' => true, + 'edit' => [ + 'modal' => 'admissionschedule-form-modal', + 'permission' => auth()->user()->can('update', $admissionSchedule), + ], + 'delete' => [ + 'url' => null, + 'title' => __('ui.button.delete'), + 'message' => __('ui.confirmation.delete', ['resource' => __('resources.admission-schedule')]), + 'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.admission-schedule')]), + 'permission' => auth()->user()->can('delete', $admissionSchedule), + ], + 'table_name' => 'admissionschedule-table', + 'id' => $admissionSchedule->ulid, + ]) + ) + ->rawColumns(['action']) + ->addIndexColumn(); + } + + /** + * Get the query source of dataTable. + * + * @return QueryBuilder + */ + public function query(Term $model): QueryBuilder + { + return $model->newQuery(); + } + + /** + * Optional method if you want to use the html builder. + */ + public function html(): HtmlBuilder + { + return $this->builder() + ->setTableId('term-table') + ->columns($this->getColumns()) + ->minifiedAjax() + ->orderBy(-1) + ->layout([ + 'topStart' => [ + 'rowClass' => 'row gap-1', + 'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', + 'features' => ['buttons', 'pageLength'], + ], + 'topEnd' => [ + 'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', + 'features' => ['search'], + ], + + 'bottomStart' => 'info', + 'bottomEnd' => 'paging', + ]) + ->parameters([ + 'language' => [ + 'search' => '', + 'searchPlaceholder' => __('ui.button.lookup'), + ], + 'fixedColumns' => [ + 'start' => 2, + ], + 'scrollX' => true, + 'scrollCollapse' => true, + 'responsive' => true, + ]) + ->buttons([ + Button::make('add') + ->action('$("#term-form-modal").modal("show");') + ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + Button::make('reload') + ->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + ]); + } + + /** + * Get the dataTable columns definition. + */ + public function getColumns(): array + { + return [ + Column::computed('DT_RowIndex') + ->width(10) + ->title('#'), + Column::make('name') + ->title(__('domains/academic/field.term.name')), + Column::make('code') + ->title(__('domains/academic/field.term.code')), + Column::make('year_study') + ->title(__('domains/academic/field.term.year_study')), + Column::make('semester') + ->title(__('domains/academic/field.term.semester')), + Column::make('date_start') + ->title(__('domains/academic/field.term.date_start')), + Column::make('date_end') + ->title(__('domains/academic/field.term.date_end')), + Column::computed('action') + ->title(__('ui.label.actions')) + ->exportable(false) + ->printable(false) + ->width(60) + ->addClass('text-center'), + ]; + } + + /** + * Get the filename for export. + */ + protected function filename(): string + { + return 'Term_'.date('YmdHis'); + } +} diff --git a/app/Http/DataTables/Admission/AdmissionScheduleDataTable.php b/app/Http/DataTables/Admission/AdmissionScheduleDataTable.php new file mode 100644 index 0000000..9ee19c2 --- /dev/null +++ b/app/Http/DataTables/Admission/AdmissionScheduleDataTable.php @@ -0,0 +1,143 @@ + $query Results from query() method. + */ + public function dataTable(QueryBuilder $query): EloquentDataTable + { + return (new EloquentDataTable($query)) + ->addColumn( + 'action', + fn ($admissionSchedule) => view('components.datatables.action-button', [ + 'log' => true, + 'edit' => [ + 'modal' => 'admissionschedule-form-modal', + 'permission' => auth()->user()->can('update', $admissionSchedule), + ], + 'delete' => [ + 'url' => null, + 'title' => __('ui.button.delete'), + 'message' => __('ui.confirmation.delete', ['resource' => __('resources.admission-schedule')]), + 'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.admission-schedule')]), + 'permission' => auth()->user()->can('delete', $admissionSchedule), + ], + 'table_name' => 'admissionschedule-table', + 'id' => $admissionSchedule->ulid, + ]) + ) + ->rawColumns(['action']) + ->addIndexColumn(); + } + + /** + * Get the query source of dataTable. + * + * @return QueryBuilder + */ + public function query(AdmissionSchedule $model): QueryBuilder + { + return $model->newQuery(); + } + + /** + * Optional method if you want to use the html builder. + */ + public function html(): HtmlBuilder + { + return $this->builder() + ->setTableId('admissionschedule-table') + ->columns($this->getColumns()) + ->minifiedAjax() + ->orderBy(-1) + ->layout([ + 'topStart' => [ + 'rowClass' => 'row gap-1', + 'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', + 'features' => ['buttons', 'pageLength'], + ], + 'topEnd' => [ + 'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', + 'features' => ['search'], + ], + + 'bottomStart' => 'info', + 'bottomEnd' => 'paging', + ]) + ->parameters([ + 'language' => [ + 'search' => '', + 'searchPlaceholder' => __('ui.button.lookup'), + ], + 'fixedColumns' => [ + 'start' => 2, + ], + 'scrollX' => true, + 'scrollCollapse' => true, + 'responsive' => true, + ]) + ->buttons([ + Button::make('add') + ->action('$("#admissionschedule-form-modal").modal("show");') + ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + Button::make('reload') + ->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + ]); + } + + /** + * Get the dataTable columns definition. + */ + public function getColumns(): array + { + return [ + Column::computed('DT_RowIndex') + ->width(10) + ->title('#'), + Column::make('date_start') + ->title(__('domains/admission/field.admission-schedule.date-start')), + Column::make('date_end') + ->title(__('domains/admission/field.admission-schedule.date-end')), + Column::make('re_registration_date_start') + ->title(__('domains/admission/field.admission-schedule.re-registration-date-start')), + Column::make('re_registration_date_end') + ->title(__('domains/admission/field.admission-schedule.re-registration-date-end')), + Column::make('admission_track.name') + ->title(__('domains/admission/field.admission-schedule.track_name')), + Column::make('status') + ->width(100) + ->title(__('domains/admission/field.admission-schedule.status')), + Column::make('term.name') + ->title(__('domains/admission/field.admission-schedule.term_name')), + Column::computed('action') + ->title(__('ui.label.actions')) + ->exportable(false) + ->printable(false) + ->width(60) + ->addClass('text-center'), + ]; + } + + /** + * Get the filename for export. + */ + protected function filename(): string + { + return 'AdmissionSchedule_'.date('YmdHis'); + } +} diff --git a/app/Http/DataTables/Admission/AdmissionTrackDataTable.php b/app/Http/DataTables/Admission/AdmissionTrackDataTable.php new file mode 100644 index 0000000..beca23e --- /dev/null +++ b/app/Http/DataTables/Admission/AdmissionTrackDataTable.php @@ -0,0 +1,137 @@ + $query Results from query() method. + */ + public function dataTable(QueryBuilder $query): EloquentDataTable + { + return (new EloquentDataTable($query)) + ->editColumn('status', fn(AdmissionTrack $track) => view('components.badge', [ + 'label' => $track->status->label(), + 'variant' => $track->status->variant(), + ])) + ->addColumn( + 'action', + fn ($admissionTrack) => view('components.datatables.action-button', [ + 'log' => true, + 'edit' => [ + 'modal' => 'admissiontrack-form-modal', + 'permission' => auth()->user()->can('update', $admissionTrack), + ], + 'delete' => [ + 'url' => null, + 'title' => __('ui.button.delete'), + 'message' => __('ui.confirmation.delete', ['resource' => __('resources.admission-track')]), + 'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.admission-track')]), + 'permission' => auth()->user()->can('delete', $admissionTrack), + ], + 'table_name' => 'admissiontrack-table', + 'id' => $admissionTrack->ulid, + ]) + ) + ->rawColumns(['action']) + ->addIndexColumn(); + } + + /** + * Get the query source of dataTable. + * + * @return QueryBuilder + */ + public function query(AdmissionTrack $model): QueryBuilder + { + return $model->newQuery(); + } + + /** + * Optional method if you want to use the html builder. + */ + public function html(): HtmlBuilder + { + return $this->builder() + ->setTableId('admissiontrack-table') + ->columns($this->getColumns()) + ->minifiedAjax() + ->orderBy(-1) + ->layout([ + 'topStart' => [ + 'rowClass' => 'row gap-1', + 'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', + 'features' => ['buttons', 'pageLength'], + ], + 'topEnd' => [ + 'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', + 'features' => ['search'], + ], + + 'bottomStart' => 'info', + 'bottomEnd' => 'paging', + ]) + ->parameters([ + 'language' => [ + 'search' => '', + 'searchPlaceholder' => __('ui.button.lookup'), + ], + 'fixedColumns' => [ + 'start' => 2, + ], + 'scrollX' => true, + 'scrollCollapse' => true, + 'responsive' => true, + ]) + ->buttons([ + Button::make('add') + ->action('$("#admissiontrack-form-modal").modal("show");') + ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + Button::make('reload') + ->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + ]); + } + + /** + * Get the dataTable columns definition. + */ + public function getColumns(): array + { + return [ + Column::computed('DT_RowIndex') + ->width(10) + ->title('#'), + Column::make('name') + ->title(__('domains/admission/field.admission-track.name')), + Column::computed('status') + ->width(100) + ->title(__('domains/admission/field.admission-track.status')), + Column::computed('action') + ->title(__('ui.label.actions')) + ->exportable(false) + ->printable(false) + ->width(30) + ->addClass('text-center'), + ]; + } + + /** + * Get the filename for export. + */ + protected function filename(): string + { + return 'AdmissionTrack_'.date('YmdHis'); + } +} diff --git a/app/Http/DataTables/Admission/FeeTypeDataTable.php b/app/Http/DataTables/Admission/FeeTypeDataTable.php new file mode 100644 index 0000000..d4ee61a --- /dev/null +++ b/app/Http/DataTables/Admission/FeeTypeDataTable.php @@ -0,0 +1,129 @@ + $query Results from query() method. + */ + public function dataTable(QueryBuilder $query): EloquentDataTable + { + return (new EloquentDataTable($query)) + ->editColumn('billing_cycle', fn(FeeType $feeType) => $feeType->billing_cycle->label()) + ->addColumn( + 'action', + fn ($feeType) => view('components.datatables.action-button', [ + 'log' => true, + 'delete' => [ + 'url' => null, + 'title' => __('ui.button.delete'), + 'message' => __('ui.confirmation.delete', ['resource' => __('resources.fee-type')]), + 'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.fee-type')]), + 'permission' => auth()->user()->can('delete', $feeType), + ], + 'table_name' => 'feetype-table', + 'id' => $feeType->ulid, + ]) + ) + ->rawColumns(['action']) + ->addIndexColumn(); + } + + /** + * Get the query source of dataTable. + * + * @return QueryBuilder + */ + public function query(FeeType $model): QueryBuilder + { + return $model->newQuery(); + } + + /** + * Optional method if you want to use the html builder. + */ + public function html(): HtmlBuilder + { + return $this->builder() + ->setTableId('feetype-table') + ->columns($this->getColumns()) + ->minifiedAjax() + ->orderBy(-1) + ->layout([ + 'topStart' => [ + 'rowClass' => 'row gap-1', + 'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', + 'features' => ['buttons', 'pageLength'], + ], + 'topEnd' => [ + 'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', + 'features' => ['search'], + ], + + 'bottomStart' => 'info', + 'bottomEnd' => 'paging', + ]) + ->parameters([ + 'language' => [ + 'search' => '', + 'searchPlaceholder' => __('ui.button.lookup'), + ], + 'fixedColumns' => [ + 'start' => 2, + ], + 'scrollX' => true, + 'scrollCollapse' => true, + 'responsive' => true, + ]) + ->buttons([ + Button::make('add') + ->action('$("#feetype-form-modal").modal("show");') + ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + Button::make('reload') + ->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + ]); + } + + /** + * Get the dataTable columns definition. + */ + public function getColumns(): array + { + return [ + Column::computed('DT_RowIndex') + ->width(10) + ->title('#'), + Column::make('name') + ->title(__('domains/admission/field.fee-type.name')), + Column::make('billing_cycle') + ->title(__('domains/admission/field.fee-type.billing_cycle')), + Column::computed('action') + ->title(__('ui.label.actions')) + ->exportable(false) + ->printable(false) + ->width(60) + ->addClass('text-center'), + ]; + } + + /** + * Get the filename for export. + */ + protected function filename(): string + { + return 'FeeType_'.date('YmdHis'); + } +} diff --git a/app/Http/DataTables/Admission/ProgramTrackDataTable.php b/app/Http/DataTables/Admission/ProgramTrackDataTable.php new file mode 100644 index 0000000..6447f53 --- /dev/null +++ b/app/Http/DataTables/Admission/ProgramTrackDataTable.php @@ -0,0 +1,139 @@ + $query Results from query() method. + */ + public function dataTable(QueryBuilder $query): EloquentDataTable + { + return (new EloquentDataTable($query)) + ->editColumn('status', fn ($model) => view('components.badge', [ + 'label' => $model->status->label(), + 'variant' => $model->status->variant(), + ])) + ->addColumn( + 'action', + fn ($programTrack) => view('components.datatables.action-button', [ + 'log' => true, + 'edit' => [ + 'modal' => 'programtrack-form-modal', + 'permission' => auth()->user()->can('update', $programTrack), + ], + 'delete' => [ + 'url' => null, + 'title' => __('ui.button.delete'), + 'message' => __('ui.confirmation.delete', ['resource' => __('resources.program-track')]), + 'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.program-track')]), + 'permission' => auth()->user()->can('delete', $programTrack), + ], + 'table_name' => 'programtrack-table', + 'id' => $programTrack->ulid, + ]) + ) + ->rawColumns(['action', 'status']) + ->addIndexColumn(); + } + + /** + * Get the query source of dataTable. + * + * @return QueryBuilder + */ + public function query(ProgramTrack $model): QueryBuilder + { + return $model->newQuery(); + } + + /** + * Optional method if you want to use the html builder. + */ + public function html(): HtmlBuilder + { + return $this->builder() + ->setTableId('programtrack-table') + ->columns($this->getColumns()) + ->minifiedAjax() + ->orderBy(-1) + ->layout([ + 'topStart' => [ + 'rowClass' => 'row gap-1', + 'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', + 'features' => ['buttons', 'pageLength'], + ], + 'topEnd' => [ + 'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', + 'features' => ['search'], + ], + + 'bottomStart' => 'info', + 'bottomEnd' => 'paging', + ]) + ->parameters([ + 'language' => [ + 'search' => '', + 'searchPlaceholder' => __('ui.button.lookup'), + ], + 'fixedColumns' => [ + 'start' => 2, + ], + 'scrollX' => true, + 'scrollCollapse' => true, + 'responsive' => true, + ]) + ->buttons([ + Button::make('add') + ->action('$("#programtrack-form-modal").modal("show");') + ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + Button::make('reload') + ->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + ]); + } + + /** + * Get the dataTable columns definition. + */ + public function getColumns(): array + { + return [ + Column::computed('DT_RowIndex') + ->width(10) + ->title('#'), + Column::make('code') + ->title(__('domains/admission/field.program-track.code')), + Column::make('name') + ->title(__('domains/admission/field.program-track.name')), + Column::make('status') + ->width(100) + ->title(__('domains/admission/field.program-track.status')), + Column::computed('action') + ->title(__('ui.label.actions')) + ->exportable(false) + ->printable(false) + ->width(60) + ->addClass('text-center'), + ]; + } + + /** + * Get the filename for export. + */ + protected function filename(): string + { + return 'ProgramTrack_'.date('YmdHis'); + } +} diff --git a/app/Http/DataTables/Channel/ClientDataTable.php b/app/Http/DataTables/Channel/ClientDataTable.php new file mode 100644 index 0000000..2e540df --- /dev/null +++ b/app/Http/DataTables/Channel/ClientDataTable.php @@ -0,0 +1,134 @@ + $query Results from query() method. + */ + public function dataTable(QueryBuilder $query): EloquentDataTable + { + return (new EloquentDataTable($query)) + ->addColumn( + 'action', + fn ($client) => view('components.datatables.action-button', [ + 'log' => true, + 'edit' => [ + 'modal' => 'client-form-modal', + 'permission' => auth()->user()->can('update', $client), + ], + 'delete' => [ + 'url' => null, + 'title' => __('ui.button.delete'), + 'message' => __('ui.confirmation.delete', ['resource' => __('resources.client')]), + 'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.client')]), + 'permission' => auth()->user()->can('delete', $client), + ], + 'table_name' => 'client-table', + 'id' => $client->ulid, + ]) + ) + ->rawColumns(['action']) + ->addIndexColumn(); + } + + /** + * Get the query source of dataTable. + * + * @return QueryBuilder + */ + public function query(Client $model): QueryBuilder + { + return $model->newQuery(); + } + + /** + * Optional method if you want to use the html builder. + */ + public function html(): HtmlBuilder + { + return $this->builder() + ->setTableId('client-table') + ->columns($this->getColumns()) + ->minifiedAjax() + ->orderBy(-1) + ->layout([ + 'topStart' => [ + 'rowClass' => 'row gap-1', + 'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', + 'features' => ['buttons', 'pageLength'], + ], + 'topEnd' => [ + 'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', + 'features' => ['search'], + ], + + 'bottomStart' => 'info', + 'bottomEnd' => 'paging', + ]) + ->parameters([ + 'language' => [ + 'search' => '', + 'searchPlaceholder' => __('ui.button.lookup'), + ], + 'fixedColumns' => [ + 'start' => 2, + ], + 'scrollX' => true, + 'scrollCollapse' => true, + 'responsive' => true, + ]) + ->buttons([ + Button::make('add') + ->action('$("#client-form-modal").modal("show");') + ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + Button::make('reload') + ->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + ]); + } + + /** + * Get the dataTable columns definition. + */ + public function getColumns(): array + { + return [ + Column::computed('DT_RowIndex') + ->width(10) + ->title('#'), + Column::make('name') + ->title(__('domains/channel/field.client.name')), + Column::make('code') + ->title(__('domains/channel/field.client.code')), + Column::make('url') + ->title(__('domains/channel/field.client.url')), + Column::computed('action') + ->title(__('ui.label.actions')) + ->exportable(false) + ->printable(false) + ->width(60) + ->addClass('text-center'), + ]; + } + + /** + * Get the filename for export. + */ + protected function filename(): string + { + return 'Client_'.date('YmdHis'); + } +} diff --git a/app/Http/DataTables/Finance/ChartOfAccountDataTable.php b/app/Http/DataTables/Finance/ChartOfAccountDataTable.php new file mode 100644 index 0000000..33cb946 --- /dev/null +++ b/app/Http/DataTables/Finance/ChartOfAccountDataTable.php @@ -0,0 +1,137 @@ + $query Results from query() method. + */ + public function dataTable(QueryBuilder $query): EloquentDataTable + { + return (new EloquentDataTable($query)) + ->addColumn( + 'action', + fn ($coa) => view('components.datatables.action-button', [ + 'log' => true, + 'edit' => [ + 'modal' => 'coa-form-modal', + 'permission' => auth()->user()->can('update', $coa), + ], + 'delete' => [ + 'url' => null, + 'title' => __('ui.button.delete'), + 'message' => __('ui.confirmation.delete', ['resource' => __('resources.chart-of-account')]), + 'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.chart-of-account')]), + 'permission' => auth()->user()->can('delete', $coa), + ], + 'table_name' => 'chartofaccount-table', + 'id' => $coa->ulid, + ]) + ) + ->rawColumns(['action']) + ->addIndexColumn(); + } + + /** + * Get the query source of dataTable. + * + * @return QueryBuilder + */ + public function query(ChartOfAccount $model): QueryBuilder + { + return $model->newQuery(); + } + + /** + * Optional method if you want to use the html builder. + */ + public function html(): HtmlBuilder + { + return $this->builder() + ->setTableId('chartofaccount-table') + ->columns($this->getColumns()) + ->minifiedAjax() + ->orderBy(-1) + ->layout([ + 'topStart' => [ + 'rowClass' => 'row gap-1', + 'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', + 'features' => ['buttons', 'pageLength'], + ], + 'topEnd' => [ + 'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', + 'features' => ['search'], + ], + + 'bottomStart' => 'info', + 'bottomEnd' => 'paging', + ]) + ->parameters([ + 'language' => [ + 'search' => '', + 'searchPlaceholder' => __('ui.button.lookup'), + ], + 'fixedColumns' => [ + 'start' => 2, + ], + 'scrollX' => true, + 'scrollCollapse' => true, + 'responsive' => true, + ]) + ->buttons([ + Button::make('add') + ->action('$("#coa-form-modal").modal("show");') + ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + Button::make('reload') + ->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + ]); + } + + /** + * Get the dataTable columns definition. + */ + public function getColumns(): array + { + return [ + Column::computed('DT_RowIndex') + ->width(10) + ->title('#'), + Column::make('code') + ->title(__('domains/finance/field.coa.code')), + Column::make('name') + ->title(__('domains/finance/field.coa.name')), + Column::make('classification') + ->title(__('domains/finance/field.coa.classification')), + Column::make('status') + ->width(100) + ->title(__('domains/finance/field.coa.status')), + Column::computed('action') + ->title(__('ui.label.actions')) + ->exportable(false) + ->printable(false) + ->width(60) + ->addClass('text-center'), + ]; + } + + /** + * Get the filename for export. + */ + protected function filename(): string + { + return 'ChartOfAccount_'.date('YmdHis'); + } +} diff --git a/app/Http/DataTables/Finance/InvoiceDataTable.php b/app/Http/DataTables/Finance/InvoiceDataTable.php new file mode 100644 index 0000000..c8395bd --- /dev/null +++ b/app/Http/DataTables/Finance/InvoiceDataTable.php @@ -0,0 +1,114 @@ + $query Results from query() method. + */ + public function dataTable(QueryBuilder $query): EloquentDataTable + { + return (new EloquentDataTable($query)) + ->addColumn('action', 'invoice.action') + ->setRowId('id'); + } + + /** + * Get the query source of dataTable. + * + * @return QueryBuilder + */ + public function query(Invoice $model): QueryBuilder + { + return $model->newQuery(); + } + + /** + * Optional method if you want to use the html builder. + */ + public function html(): HtmlBuilder + { + return $this->builder() + ->setTableId('invoice-table') + ->columns($this->getColumns()) + ->minifiedAjax() + ->orderBy(-1) + ->layout([ + 'topStart' => [ + 'rowClass' => 'row gap-1', + 'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', + 'features' => ['buttons', 'pageLength'], + ], + 'topEnd' => [ + 'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', + 'features' => ['search'], + ], + + 'bottomStart' => 'info', + 'bottomEnd' => 'paging', + ]) + ->parameters([ + 'language' => [ + 'search' => '', + 'searchPlaceholder' => __('ui.button.lookup'), + ], + 'fixedColumns' => [ + 'start' => 2, + ], + 'scrollX' => true, + 'scrollCollapse' => true, + 'responsive' => true, + ]) + ->buttons([ + Button::make('add') + ->action('$("#invoice-form-modal").modal("show");') + ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + Button::make('reload') + ->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + ]); + } + + /** + * Get the dataTable columns definition. + */ + public function getColumns(): array + { + return [ + Column::computed('DT_RowIndex') + ->width(10) + ->title('#'), + Column::make('name') + ->title(__('domains/finance/field.invoice.name')), + Column::make('type') + ->title(__('domains/finance/field.invoice.type')), + Column::make('amount') + ->title(__('domains/finance/field.invoice.amount')), + Column::make('client.name') + ->title(__('domains/finance/field.invoice.client_name')), + Column::make('status') + ->width(100) + ->title(__('domains/finance/field.invoice.status')), + ]; + } + + /** + * Get the filename for export. + */ + protected function filename(): string + { + return 'Invoice_'.date('YmdHis'); + } +} diff --git a/app/Http/DataTables/Finance/ProductMappingDataTable.php b/app/Http/DataTables/Finance/ProductMappingDataTable.php new file mode 100644 index 0000000..79e5631 --- /dev/null +++ b/app/Http/DataTables/Finance/ProductMappingDataTable.php @@ -0,0 +1,136 @@ + $query Results from query() method. + */ + public function dataTable(QueryBuilder $query): EloquentDataTable + { + return (new EloquentDataTable($query)) + ->addColumn( + 'action', + fn ($productMapping) => view('components.datatables.action-button', [ + 'log' => true, + 'edit' => [ + 'modal' => 'productmapping-form-modal', + 'permission' => auth()->user()->can('update', $productMapping), + ], + 'delete' => [ + 'url' => null, + 'title' => __('ui.button.delete'), + 'message' => __('ui.confirmation.delete', ['resource' => __('resources.product-mapping')]), + 'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.product-mapping')]), + 'permission' => auth()->user()->can('delete', $productMapping), + ], + 'table_name' => 'productmapping-table', + 'id' => $productMapping->ulid, + ]) + ) + ->rawColumns(['action']) + ->addIndexColumn(); + } + + /** + * Get the query source of dataTable. + * + * @return QueryBuilder + */ + public function query(ProductMapping $model): QueryBuilder + { + return $model->newQuery(); + } + + /** + * Optional method if you want to use the html builder. + */ + public function html(): HtmlBuilder + { + return $this->builder() + ->setTableId('productmapping-table') + ->columns($this->getColumns()) + ->minifiedAjax() + ->orderBy(-1) + ->layout([ + 'topStart' => [ + 'rowClass' => 'row gap-1', + 'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', + 'features' => ['buttons', 'pageLength'], + ], + 'topEnd' => [ + 'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', + 'features' => ['search'], + ], + + 'bottomStart' => 'info', + 'bottomEnd' => 'paging', + ]) + ->parameters([ + 'language' => [ + 'search' => '', + 'searchPlaceholder' => __('ui.button.lookup'), + ], + 'fixedColumns' => [ + 'start' => 2, + ], + 'scrollX' => true, + 'scrollCollapse' => true, + 'responsive' => true, + ]) + ->buttons([ + Button::make('add') + ->action('$("#productmapping-form-modal").modal("show");') + ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + Button::make('reload') + ->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) + ->addClass('btn-sm'), + ]); + } + + /** + * Get the dataTable columns definition. + */ + public function getColumns(): array + { + return [ + Column::computed('DT_RowIndex') + ->width(10) + ->title('#'), + Column::make('code') + ->title(__('domains/finance/field.product_mapping.code')), + Column::make('name') + ->title(__('domains/finance/field.product_mapping.name')), + Column::make('chart_of_account.name') + ->title(__('domains/finance/field.product_mapping.coa_name')), + Column::make('client.name') + ->title(__('domains/finance/field.product_mapping.client_name')), + Column::computed('action') + ->title(__('ui.label.actions')) + ->exportable(false) + ->printable(false) + ->width(60) + ->addClass('text-center'), + ]; + } + + /** + * Get the filename for export. + */ + protected function filename(): string + { + return 'ProductMapping_'.date('YmdHis'); + } +} diff --git a/app/Http/DataTables/Identity/UserDataTable.php b/app/Http/DataTables/Identity/UserDataTable.php index f96f5ab..bfb1b20 100644 --- a/app/Http/DataTables/Identity/UserDataTable.php +++ b/app/Http/DataTables/Identity/UserDataTable.php @@ -29,7 +29,7 @@ class UserDataTable extends DataTable return (new EloquentDataTable($query)) ->editColumn('status', fn ($model) => view('components.badge', [ 'label' => $model->status->label(), - 'variant' => $model->status->badgeVariant(), + 'variant' => $model->status->variant(), ])) ->addColumn( 'action', @@ -114,18 +114,18 @@ class UserDataTable extends DataTable [ 'custom-features' => [ 'targetId' => 'template-role-filter', - 'style' => 'width: 200px;' - ] + 'style' => 'width: 200px;', + ], ], // 2. Pass your second custom filter layout option [ 'custom-features' => [ 'targetId' => 'template-status-filter', - 'style' => 'width: 200px;' - ] + 'style' => 'width: 200px;', + ], ], // Keep your original native layout features running seamlessly alongside them - 'search' + 'search', ], ], diff --git a/app/Livewire/Forms/Academic/FacultyForm.php b/app/Livewire/Forms/Academic/FacultyForm.php new file mode 100644 index 0000000..c671043 --- /dev/null +++ b/app/Livewire/Forms/Academic/FacultyForm.php @@ -0,0 +1,18 @@ +textResolver->execute($labelKey, $dataContext, $contextTarget); $url = null; - if (! empty($routeConfig)) { $routeName = ''; $routeParams = []; @@ -70,7 +69,7 @@ class ApplyLayoutMetadata // 4. Inject properties into the layout view instance memory View::composer([ - 'components.layouts.app', + 'components.layouts.nav.topbar', 'components.layouts.guest', ], fn ($view) => $view->with([ 'breadcrumbs' => $breadcrumbs, diff --git a/app/UI/Enums/Concerns/InteractsWithLabels.php b/app/UI/Enums/Concerns/InteractsWithLabels.php new file mode 100644 index 0000000..cc9a1c1 --- /dev/null +++ b/app/UI/Enums/Concerns/InteractsWithLabels.php @@ -0,0 +1,41 @@ +/enum.enum-slugs. + * + * @mixin BackedEnum + */ +trait InteractsWithLabels +{ + public function label(): string + { + $fqcn = static::class; + + $domain = Str::between($fqcn, 'App\\Domains\\', '\\Enums\\'); + $domainSlug = Str::kebab($domain); + + $className = Str::afterLast($fqcn, '\\'); + $enumSlug = Str::kebab($className); + + $translationKey = "domains/{$domainSlug}/enum.{$enumSlug}.{$this->value}"; + + return __($translationKey); + } + + public static function options(): array + { + $mappedArray = array_map(fn(self $self) => [ + $self->value => $self->label(), + ], self::cases()); + + return array_merge(...$mappedArray); + } +} diff --git a/app/UI/Enums/Contracts/HasLabel.php b/app/UI/Enums/Contracts/HasLabel.php new file mode 100644 index 0000000..b248868 --- /dev/null +++ b/app/UI/Enums/Contracts/HasLabel.php @@ -0,0 +1,16 @@ +statefulApi(); $middleware->api(prepend: [ - EnsureFrontendRequestsAreStateful::class + EnsureFrontendRequestsAreStateful::class, ]); }) ->withExceptions(function (Exceptions $exceptions): void { diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 406473f..b00e89e 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -1,29 +1,39 @@ + */ +class FacultyFactory extends Factory +{ + protected $model = Faculty::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/factories/Academic/ProgramTrackFactory.php b/database/factories/Academic/ProgramTrackFactory.php new file mode 100644 index 0000000..74d7db4 --- /dev/null +++ b/database/factories/Academic/ProgramTrackFactory.php @@ -0,0 +1,19 @@ + + */ +class ProgramTrackFactory extends Factory +{ + protected $model = ProgramTrack::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/factories/Academic/StudyProgramFactory.php b/database/factories/Academic/StudyProgramFactory.php new file mode 100644 index 0000000..5d96b1a --- /dev/null +++ b/database/factories/Academic/StudyProgramFactory.php @@ -0,0 +1,19 @@ + + */ +class StudyProgramFactory extends Factory +{ + protected $model = StudyProgram::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/factories/Academic/TermFactory.php b/database/factories/Academic/TermFactory.php new file mode 100644 index 0000000..4aa4bc5 --- /dev/null +++ b/database/factories/Academic/TermFactory.php @@ -0,0 +1,19 @@ + + */ +class TermFactory extends Factory +{ + protected $model = Term::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/factories/Admission/AdmissionScheduleFactory.php b/database/factories/Admission/AdmissionScheduleFactory.php new file mode 100644 index 0000000..39bd7f5 --- /dev/null +++ b/database/factories/Admission/AdmissionScheduleFactory.php @@ -0,0 +1,19 @@ + + */ +class AdmissionScheduleFactory extends Factory +{ + protected $model = AdmissionSchedule::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/factories/Admission/AdmissionTrackFactory.php b/database/factories/Admission/AdmissionTrackFactory.php new file mode 100644 index 0000000..2c7c32f --- /dev/null +++ b/database/factories/Admission/AdmissionTrackFactory.php @@ -0,0 +1,19 @@ + + */ +class AdmissionTrackFactory extends Factory +{ + protected $model = AdmissionTrack::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/factories/Admission/FeeRateFactory.php b/database/factories/Admission/FeeRateFactory.php new file mode 100644 index 0000000..13d8c9a --- /dev/null +++ b/database/factories/Admission/FeeRateFactory.php @@ -0,0 +1,19 @@ + + */ +class FeeRateFactory extends Factory +{ + protected $model = FeeRate::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/factories/Admission/FeeTypeFactory.php b/database/factories/Admission/FeeTypeFactory.php new file mode 100644 index 0000000..b6e9601 --- /dev/null +++ b/database/factories/Admission/FeeTypeFactory.php @@ -0,0 +1,19 @@ + + */ +class FeeTypeFactory extends Factory +{ + protected $model = FeeType::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/factories/Channel/ClientFactory.php b/database/factories/Channel/ClientFactory.php new file mode 100644 index 0000000..a1c3381 --- /dev/null +++ b/database/factories/Channel/ClientFactory.php @@ -0,0 +1,19 @@ + + */ +class ClientFactory extends Factory +{ + protected $model = Client::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/factories/Finance/ChartOfAccountFactory.php b/database/factories/Finance/ChartOfAccountFactory.php new file mode 100644 index 0000000..e398355 --- /dev/null +++ b/database/factories/Finance/ChartOfAccountFactory.php @@ -0,0 +1,19 @@ + + */ +class ChartOfAccountFactory extends Factory +{ + protected $model = ChartOfAccount::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/factories/Finance/InvoiceFactory.php b/database/factories/Finance/InvoiceFactory.php new file mode 100644 index 0000000..1906a3d --- /dev/null +++ b/database/factories/Finance/InvoiceFactory.php @@ -0,0 +1,19 @@ + + */ +class InvoiceFactory extends Factory +{ + protected $model = Invoice::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/factories/Finance/PaymentFactory.php b/database/factories/Finance/PaymentFactory.php new file mode 100644 index 0000000..d3b562d --- /dev/null +++ b/database/factories/Finance/PaymentFactory.php @@ -0,0 +1,19 @@ + + */ +class PaymentFactory extends Factory +{ + protected $model = Payment::class; + + public function definition(): array + { + return []; + } +} diff --git a/database/migrations/2026_07_06_031003_create_clients_table.php b/database/migrations/2026_07_06_031003_create_clients_table.php new file mode 100644 index 0000000..b13507e --- /dev/null +++ b/database/migrations/2026_07_06_031003_create_clients_table.php @@ -0,0 +1,32 @@ +id(); + $table->ulid(); + $table->string('name'); + $table->string('url')->nullable(); + $table->string('code')->nullable(); + $table->string('secret'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('clients'); + } +}; diff --git a/database/migrations/2026_07_06_031009_create_fee_types_table.php b/database/migrations/2026_07_06_031009_create_fee_types_table.php new file mode 100644 index 0000000..8eefa29 --- /dev/null +++ b/database/migrations/2026_07_06_031009_create_fee_types_table.php @@ -0,0 +1,30 @@ +id(); + $table->ulid(); + $table->string('name'); + $table->string('billing_cycle'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('fee_types'); + } +}; diff --git a/database/migrations/2026_07_06_031024_create_faculties_table.php b/database/migrations/2026_07_06_031024_create_faculties_table.php new file mode 100644 index 0000000..0b7bb8b --- /dev/null +++ b/database/migrations/2026_07_06_031024_create_faculties_table.php @@ -0,0 +1,31 @@ +id(); + $table->ulid(); + $table->string('code')->nullable(); + $table->string('name')->nullable(); + $table->string('external_id')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('faculties'); + } +}; diff --git a/database/migrations/2026_07_06_031030_create_study_programs_table.php b/database/migrations/2026_07_06_031030_create_study_programs_table.php new file mode 100644 index 0000000..91e043d --- /dev/null +++ b/database/migrations/2026_07_06_031030_create_study_programs_table.php @@ -0,0 +1,34 @@ +id(); + $table->ulid(); + $table->string('code'); + $table->string('name'); + $table->string('level'); + $table->string('status'); + $table->string('external_id')->nullable(); + $table->foreignId('faculty_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('study_programs'); + } +}; diff --git a/database/migrations/2026_07_06_031133_create_program_tracks_table.php b/database/migrations/2026_07_06_031133_create_program_tracks_table.php new file mode 100644 index 0000000..3cf89fe --- /dev/null +++ b/database/migrations/2026_07_06_031133_create_program_tracks_table.php @@ -0,0 +1,31 @@ +id(); + $table->ulid(); + $table->string('code'); + $table->string('name'); + $table->string('status'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('program_tracks'); + } +}; diff --git a/database/migrations/2026_07_06_031349_create_terms_table.php b/database/migrations/2026_07_06_031349_create_terms_table.php new file mode 100644 index 0000000..fb44f32 --- /dev/null +++ b/database/migrations/2026_07_06_031349_create_terms_table.php @@ -0,0 +1,34 @@ +id(); + $table->ulid(); + $table->string('code'); + $table->string('name'); + $table->string('year_study'); + $table->string('semester'); + $table->date('date_start'); + $table->date('date_end'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('terms'); + } +}; diff --git a/database/migrations/2026_07_06_031436_create_admission_tracks_table.php b/database/migrations/2026_07_06_031436_create_admission_tracks_table.php new file mode 100644 index 0000000..77ac1b3 --- /dev/null +++ b/database/migrations/2026_07_06_031436_create_admission_tracks_table.php @@ -0,0 +1,31 @@ +id(); + $table->ulid(); + $table->string('name'); + $table->string('slug'); + $table->string('status'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('admission_tracks'); + } +}; diff --git a/database/migrations/2026_07_06_031445_create_admission_schedules_table.php b/database/migrations/2026_07_06_031445_create_admission_schedules_table.php new file mode 100644 index 0000000..b80bf51 --- /dev/null +++ b/database/migrations/2026_07_06_031445_create_admission_schedules_table.php @@ -0,0 +1,36 @@ +id(); + $table->ulid(); + $table->date('date_start'); + $table->date('date_end'); + $table->date('re_registration_date_start'); + $table->date('re_registration_date_end'); + $table->foreignId('admission_track_id') + ->constrained() + ->restrictOnDelete(); + $table->unsignedBigInteger('term_id')->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('admission_schedules'); + } +}; diff --git a/database/migrations/2026_07_06_034032_create_fee_rates_table.php b/database/migrations/2026_07_06_034032_create_fee_rates_table.php new file mode 100644 index 0000000..d79b7f2 --- /dev/null +++ b/database/migrations/2026_07_06_034032_create_fee_rates_table.php @@ -0,0 +1,33 @@ +id(); + $table->ulid(); + $table->integer('amount'); + $table->foreignId('admission_schedule_id')->constrained(); + $table->foreignId('program_track_id')->constrained(); + $table->foreignId('study_program_id')->index(); + $table->foreignId('fee_type_id')->constrained(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('fee_rates'); + } +}; diff --git a/database/migrations/2026_07_06_034148_create_chart_of_accounts_table.php b/database/migrations/2026_07_06_034148_create_chart_of_accounts_table.php new file mode 100644 index 0000000..d2342a9 --- /dev/null +++ b/database/migrations/2026_07_06_034148_create_chart_of_accounts_table.php @@ -0,0 +1,35 @@ +id(); + $table->ulid(); + $table->string('code'); + $table->string('name'); + $table->string('classification'); + $table->string('status'); + $table->unsignedBigInteger('parent_id') + ->nullable() + ->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('chart_of_accounts'); + } +}; diff --git a/database/migrations/2026_07_06_035903_create_invoices_table.php b/database/migrations/2026_07_06_035903_create_invoices_table.php new file mode 100644 index 0000000..fe1a2a8 --- /dev/null +++ b/database/migrations/2026_07_06_035903_create_invoices_table.php @@ -0,0 +1,38 @@ +id(); + $table->ulid(); + $table->string('name'); + $table->string('detail'); + $table->string('type'); + $table->string('amount'); + $table->string('status'); + $table->string('client_ref_id'); + $table->string('client_id')->index(); + $table->foreignId('chart_of_account_id') + ->constrained() + ->restrictOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('invoices'); + } +}; diff --git a/database/migrations/2026_07_06_035952_create_payments_table.php b/database/migrations/2026_07_06_035952_create_payments_table.php new file mode 100644 index 0000000..0eb4f73 --- /dev/null +++ b/database/migrations/2026_07_06_035952_create_payments_table.php @@ -0,0 +1,35 @@ +id(); + $table->ulid(); + $table->integer('virtual_account'); + $table->string('bank_ref_id'); + $table->string('bank'); + $table->string('nominal'); + $table->date('valid_until'); + $table->string('status'); + $table->foreignId('invoice_id')->constrained()->restrictOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('payments'); + } +}; diff --git a/database/migrations/2026_07_13_045042_create_product_mappings_table.php b/database/migrations/2026_07_13_045042_create_product_mappings_table.php new file mode 100644 index 0000000..8525708 --- /dev/null +++ b/database/migrations/2026_07_13_045042_create_product_mappings_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('code'); + $table->string('name'); + $table->foreignId('chart_of_account_id')->constrained(); + $table->unsignedBigInteger('client_id')->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('product_mappings'); + } +}; diff --git a/database/migrations/2026_07_15_071124_create_student_invoice_reports_table.php b/database/migrations/2026_07_15_071124_create_student_invoice_reports_table.php new file mode 100644 index 0000000..10b9e71 --- /dev/null +++ b/database/migrations/2026_07_15_071124_create_student_invoice_reports_table.php @@ -0,0 +1,43 @@ +id(); + $table->foreignId('invoice_id') + ->constrained('invoices') + ->restrictOnDelete(); + $table->string('invoice_name'); + $table->string('student_name'); + $table->string('faculty_id'); + $table->string('faculty_name'); + $table->string('study_program_id'); + $table->string('study_program_name'); + $table->string('term_id'); + $table->string('term_name'); + $table->integer('amount'); + $table->integer('paid_amount'); + $table->dateTime('issued_at'); + $table->dateTime('paid_at'); + $table->string('status'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('student_invoice_reports'); + } +}; diff --git a/database/migrations/2026_07_15_071129_create_invoice_reports_table.php b/database/migrations/2026_07_15_071129_create_invoice_reports_table.php new file mode 100644 index 0000000..c4bf66c --- /dev/null +++ b/database/migrations/2026_07_15_071129_create_invoice_reports_table.php @@ -0,0 +1,38 @@ +id(); + $table->foreignId('invoice_id') + ->constrained('invoices') + ->restrictOnDelete(); + $table->string('invoice_name'); + $table->string('customer_name'); + $table->string('customer_id'); + $table->integer('amount'); + $table->integer('paid_amount'); + $table->dateTime('issued_at'); + $table->dateTime('paid_at'); + $table->string('status'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('invoice_reports'); + } +}; diff --git a/lang/en/domains/academic/field.php b/lang/en/domains/academic/field.php new file mode 100644 index 0000000..02bb44a --- /dev/null +++ b/lang/en/domains/academic/field.php @@ -0,0 +1,22 @@ + [ + 'name' => 'Faculty Name', + 'code' => 'Faculty Code', + 'status' => 'Status', + ], + 'study-program' => [ + 'name' => 'Study Program Name', + 'code' => 'Study Program Code', + 'status' => 'Status', + ], + 'term' => [ + 'name' => 'Term Name', + 'code' => 'Term Code', + 'year_study' => 'Study Year', + 'semester' => 'Semester', + 'date_start' => 'Start Date', + 'date_end' => 'End Date', + ], +]; diff --git a/lang/en/domains/academic/seo.php b/lang/en/domains/academic/seo.php new file mode 100644 index 0000000..327c86f --- /dev/null +++ b/lang/en/domains/academic/seo.php @@ -0,0 +1,19 @@ + [ + 'title' => 'Faculty Data', + 'description' => 'Manage faculty data in the system.', + 'keywords' => 'faculty, academic, university', + ], + 'study-program' => [ + 'title' => 'Study Program Data', + 'description' => 'Manage study program data in the system.', + 'keywords' => 'study program, academic, university', + ], + 'term' => [ + 'title' => 'Term Data', + 'description' => 'Manage term and semester data.', + 'keywords' => 'term, semester, academic', + ], +]; diff --git a/lang/en/domains/account/enum.php b/lang/en/domains/account/enum.php index 25812ba..26c5ede 100644 --- a/lang/en/domains/account/enum.php +++ b/lang/en/domains/account/enum.php @@ -3,7 +3,7 @@ use App\Domains\Account\Enums\GenderOption; return [ - 'gender' => [ + 'gender-option' => [ GenderOption::MALE->value => 'Male', GenderOption::FEMALE->value => 'Female', ], diff --git a/lang/en/domains/admission/enum.php b/lang/en/domains/admission/enum.php new file mode 100644 index 0000000..e103261 --- /dev/null +++ b/lang/en/domains/admission/enum.php @@ -0,0 +1,15 @@ + [ + 'published' => 'Published', + 'draft' => 'Draft', + 'archived' => 'Archived', + ], + 'billing-cycle' => [ + 'once' => 'Once', + 'monthly' => 'Monthly', + 'semester' => 'Semester', + 'annual' => 'Annual', + ], +]; diff --git a/lang/en/domains/admission/field.php b/lang/en/domains/admission/field.php new file mode 100644 index 0000000..5f61f10 --- /dev/null +++ b/lang/en/domains/admission/field.php @@ -0,0 +1,27 @@ + [ + 'date-start' => 'Start Date', + 'date-end' => 'End Date', + 're-registration-date-start' => 'Re-registration Start Date', + 're-registration-date-end' => 'Re-registration End Date', + 'track_name' => 'Track Name', + 'status' => 'Status', + 'term_name' => 'Term', + ], + 'admission-track' => [ + 'code' => 'Track Code', + 'name' => 'Admission Track Name', + 'status' => 'Status', + ], + 'fee-type' => [ + 'name' => 'Fee Type Name', + 'billing_cycle' => 'Billing Cycle', + ], + 'program-track' => [ + 'code' => 'Program Track Code', + 'name' => 'Program Track Name', + 'status' => 'Status', + ], +]; diff --git a/lang/en/domains/admission/seo.php b/lang/en/domains/admission/seo.php new file mode 100644 index 0000000..b955c8f --- /dev/null +++ b/lang/en/domains/admission/seo.php @@ -0,0 +1,24 @@ + [ + 'title' => 'Admission Schedule Data', + 'description' => 'Manage new student admission schedules.', + 'keywords' => 'admission schedule, admission, new student', + ], + 'admission-track' => [ + 'title' => 'Admission Track Data', + 'description' => 'Manage new student admission tracks.', + 'keywords' => 'admission track, admission, new student', + ], + 'fee-type' => [ + 'title' => 'Fee Type Data', + 'description' => 'Manage admission fee types.', + 'keywords' => 'fee type, fee, admission', + ], + 'program-track' => [ + 'title' => 'Program Track Data', + 'description' => 'Manage admission program tracks.', + 'keywords' => 'program track, program, admission', + ], +]; diff --git a/lang/en/domains/channel/field.php b/lang/en/domains/channel/field.php new file mode 100644 index 0000000..2297c98 --- /dev/null +++ b/lang/en/domains/channel/field.php @@ -0,0 +1,9 @@ + [ + 'name' => 'Client Name', + 'code' => 'Client Code', + 'url' => 'Integration URL', + ], +]; diff --git a/lang/en/domains/channel/seo.php b/lang/en/domains/channel/seo.php new file mode 100644 index 0000000..885c7dc --- /dev/null +++ b/lang/en/domains/channel/seo.php @@ -0,0 +1,9 @@ + [ + 'title' => 'Client Data', + 'description' => 'Manage client/integration channel data.', + 'keywords' => 'client, channel, integration', + ], +]; diff --git a/lang/en/domains/finance/enum.php b/lang/en/domains/finance/enum.php new file mode 100644 index 0000000..58329d0 --- /dev/null +++ b/lang/en/domains/finance/enum.php @@ -0,0 +1,32 @@ + [ + 'paid' => 'Paid', + 'pending' => 'Pending', + 'partially_paid' => 'Partially Paid', + 'overdue' => 'Overdue', + ], + 'invoice-type' => [ + 'customer_invoice' => 'Customer Invoice', + 'vendor_bill' => 'Vendor Bill', + 'credit_note' => 'Credit Note', + ], + 'payment-status' => [ + 'pending' => 'Pending', + 'success' => 'Success', + 'failed' => 'Failed', + 'expired' => 'Expired', + ], + 'payment-direction' => [ + 'receipt' => 'Receipt', + 'disbursement' => 'Disbursement', + ], + 'account-classification' => [ + 'asset' => 'Asset', + 'liability' => 'Liability', + 'equity' => 'Equity', + 'revenue' => 'Revenue', + 'expense' => 'Expense', + ], +]; diff --git a/lang/en/domains/finance/field.php b/lang/en/domains/finance/field.php new file mode 100644 index 0000000..368974c --- /dev/null +++ b/lang/en/domains/finance/field.php @@ -0,0 +1,23 @@ + [ + 'code' => 'Account Code', + 'name' => 'Account Name', + 'classification' => 'Classification', + 'status' => 'Status', + ], + 'invoice' => [ + 'name' => 'Payer Name', + 'type' => 'Invoice Type', + 'amount' => 'Invoice Amount', + 'client_name' => 'Client Name', + 'status' => 'Status', + ], + 'product_mapping' => [ + 'code' => 'Product Code', + 'name' => 'Product Name', + 'coa_name' => 'Account Name (COA)', + 'client_name' => 'Client Name', + ], +]; diff --git a/lang/en/domains/finance/seo.php b/lang/en/domains/finance/seo.php new file mode 100644 index 0000000..d51aefd --- /dev/null +++ b/lang/en/domains/finance/seo.php @@ -0,0 +1,19 @@ + [ + 'title' => 'Chart of Accounts (COA) Data', + 'description' => 'Manage chart of accounts for financial records.', + 'keywords' => 'chart of accounts, coa, accounting, finance', + ], + 'invoice' => [ + 'title' => 'Invoice Data', + 'description' => 'Manage student payment invoices.', + 'keywords' => 'invoice, payment, finance', + ], + 'product-mapping' => [ + 'title' => 'Product Mapping Data', + 'description' => 'Manage cost product mappings with Chart of Accounts (COA).', + 'keywords' => 'product mapping, product, coa, finance', + ], +]; diff --git a/lang/en/domains/system/enum.php b/lang/en/domains/system/enum.php new file mode 100644 index 0000000..4ef4ff3 --- /dev/null +++ b/lang/en/domains/system/enum.php @@ -0,0 +1,8 @@ + [ + 'active' => 'Active', + 'archived' => 'Archived', + ], +]; diff --git a/lang/en/resources.php b/lang/en/resources.php index 9957695..f2fbc89 100644 --- a/lang/en/resources.php +++ b/lang/en/resources.php @@ -11,4 +11,12 @@ return [ 'avatar' => 'Avatar', 'system_settings' => 'System Settings', 'audit' => 'Audit', + 'study_program' => 'Study Program', + 'admission_schedule' => 'Admission Schedule', + 'admission_track' => 'Admission Track', + 'fee_type' => 'Fee Type', + 'program_track' => 'Program Track', + 'client' => 'Client', + 'faculty' => 'Faculty', + 'term' => 'Term', ]; diff --git a/lang/en/ui.php b/lang/en/ui.php index 50eee7a..5bc264d 100644 --- a/lang/en/ui.php +++ b/lang/en/ui.php @@ -8,6 +8,21 @@ return [ 'roles' => 'Roles & Permissions', 'settings' => 'System Settings', 'system_backup' => 'System Backup', + 'academic' => 'Academic', + 'faculties' => 'Faculties', + 'study-programs' => 'Study Programs', + 'terms' => 'Terms', + 'admission' => 'Admission', + 'admission-tracks' => 'Admission Tracks', + 'program-tracks' => 'Program Tracks', + 'fee-types' => 'Fee Types', + 'admission-schedules' => 'Admission Schedules', + 'finance' => 'Finance', + 'invoices' => 'Invoices', + 'chart-of-accounts' => 'Chart of Accounts', + 'product-mappings' => 'Product Mappings', + 'management' => 'Management', + 'profile' => 'Profile', ], 'title' => [ 'index' => ':resource Data', diff --git a/lang/id/domains/academic/field.php b/lang/id/domains/academic/field.php new file mode 100644 index 0000000..2a28e37 --- /dev/null +++ b/lang/id/domains/academic/field.php @@ -0,0 +1,22 @@ + [ + 'name' => 'Nama Fakultas', + 'code' => 'Kode Fakultas', + 'status' => 'Status', + ], + 'study-program' => [ + 'name' => 'Nama Program Studi', + 'code' => 'Kode Program Studi', + 'status' => 'Status', + ], + 'term' => [ + 'name' => 'Nama Tahun Ajaran', + 'code' => 'Kode Tahun Ajaran', + 'year_study' => 'Tahun Studi', + 'semester' => 'Semester', + 'date_start' => 'Tanggal Mulai', + 'date_end' => 'Tanggal Selesai', + ], +]; diff --git a/lang/id/domains/academic/seo.php b/lang/id/domains/academic/seo.php new file mode 100644 index 0000000..695b516 --- /dev/null +++ b/lang/id/domains/academic/seo.php @@ -0,0 +1,19 @@ + [ + 'title' => 'Data Fakultas', + 'description' => 'Kelola data fakultas dalam sistem.', + 'keywords' => 'fakultas, akademik, universitas', + ], + 'study-program' => [ + 'title' => 'Data Program Studi', + 'description' => 'Kelola data program studi dalam sistem.', + 'keywords' => 'program studi, akademik, universitas', + ], + 'term' => [ + 'title' => 'Data Tahun Ajaran', + 'description' => 'Kelola data tahun ajaran dan semester.', + 'keywords' => 'tahun ajaran, semester, akademik', + ], +]; diff --git a/lang/id/domains/account/enum.php b/lang/id/domains/account/enum.php index e8095c4..aed1d25 100644 --- a/lang/id/domains/account/enum.php +++ b/lang/id/domains/account/enum.php @@ -3,7 +3,7 @@ use App\Domains\Account\Enums\GenderOption; return [ - 'gender' => [ + 'gender-option' => [ GenderOption::MALE->value => 'Laki-laki', GenderOption::FEMALE->value => 'Perempuan', ], diff --git a/lang/id/domains/admission/enum.php b/lang/id/domains/admission/enum.php new file mode 100644 index 0000000..dc88621 --- /dev/null +++ b/lang/id/domains/admission/enum.php @@ -0,0 +1,15 @@ + [ + 'published' => 'Dipublikasikan', + 'draft' => 'Draf', + 'archived' => 'Diarsipkan', + ], + 'billing-cycle' => [ + 'once' => 'Sekali', + 'monthly' => 'Bulanan', + 'semester' => 'Semester', + 'annual' => 'Tahunan', + ], +]; diff --git a/lang/id/domains/admission/field.php b/lang/id/domains/admission/field.php new file mode 100644 index 0000000..83c55e0 --- /dev/null +++ b/lang/id/domains/admission/field.php @@ -0,0 +1,27 @@ + [ + 'date-start' => 'Tanggal Mulai', + 'date-end' => 'Tanggal Selesai', + 're-registration-date-start' => 'Tanggal Mulai Daftar Ulang', + 're-registration-date-end' => 'Tanggal Selesai Daftar Ulang', + 'track_name' => 'Nama Jalur', + 'status' => 'Status', + 'term_name' => 'Tahun Ajaran', + ], + 'admission-track' => [ + 'code' => 'Kode Jalur', + 'name' => 'Nama Jalur Penerimaan', + 'status' => 'Status', + ], + 'fee-type' => [ + 'name' => 'Nama Jenis Biaya', + 'billing_cycle' => 'Siklus Tagihan', + ], + 'program-track' => [ + 'code' => 'Kode Jalur Program', + 'name' => 'Nama Jalur Program', + 'status' => 'Status', + ], +]; diff --git a/lang/id/domains/admission/seo.php b/lang/id/domains/admission/seo.php new file mode 100644 index 0000000..7e8a2a1 --- /dev/null +++ b/lang/id/domains/admission/seo.php @@ -0,0 +1,24 @@ + [ + 'title' => 'Data Jadwal Penerimaan', + 'description' => 'Kelola jadwal penerimaan mahasiswa baru.', + 'keywords' => 'jadwal penerimaan, penerimaan, mahasiswa baru', + ], + 'admission-track' => [ + 'title' => 'Data Jalur Penerimaan', + 'description' => 'Kelola jalur penerimaan mahasiswa baru.', + 'keywords' => 'jalur penerimaan, penerimaan, mahasiswa baru', + ], + 'fee-type' => [ + 'title' => 'Data Jenis Biaya', + 'description' => 'Kelola jenis biaya penerimaan.', + 'keywords' => 'jenis biaya, biaya, penerimaan', + ], + 'program-track' => [ + 'title' => 'Data Jalur Program', + 'description' => 'Kelola jalur program penerimaan.', + 'keywords' => 'jalur program, program, penerimaan', + ], +]; diff --git a/lang/id/domains/channel/field.php b/lang/id/domains/channel/field.php new file mode 100644 index 0000000..5c0af5f --- /dev/null +++ b/lang/id/domains/channel/field.php @@ -0,0 +1,9 @@ + [ + 'name' => 'Nama Klien', + 'code' => 'Kode Klien', + 'url' => 'URL Integrasi', + ], +]; diff --git a/lang/id/domains/channel/seo.php b/lang/id/domains/channel/seo.php new file mode 100644 index 0000000..73d57ba --- /dev/null +++ b/lang/id/domains/channel/seo.php @@ -0,0 +1,9 @@ + [ + 'title' => 'Data Klien', + 'description' => 'Kelola data klien/saluran integrasi.', + 'keywords' => 'klien, saluran, integrasi', + ], +]; diff --git a/lang/id/domains/finance/enum.php b/lang/id/domains/finance/enum.php new file mode 100644 index 0000000..1f4a163 --- /dev/null +++ b/lang/id/domains/finance/enum.php @@ -0,0 +1,32 @@ + [ + 'paid' => 'Lunas', + 'pending' => 'Tertunda', + 'partially_paid' => 'Dibayar Sebagian', + 'overdue' => 'Jatuh Tempo', + ], + 'invoice-type' => [ + 'customer_invoice' => 'Faktur Pelanggan', + 'vendor_bill' => 'Tagihan Vendor', + 'credit_note' => 'Nota Kredit', + ], + 'payment-status' => [ + 'pending' => 'Tertunda', + 'success' => 'Berhasil', + 'failed' => 'Gagal', + 'expired' => 'Kedaluwarsa', + ], + 'payment-direction' => [ + 'receipt' => 'Penerimaan', + 'disbursement' => 'Pengeluaran', + ], + 'account-classification' => [ + 'asset' => 'Aset', + 'liability' => 'Kewajiban', + 'equity' => 'Ekuitas', + 'revenue' => 'Pendapatan', + 'expense' => 'Beban', + ], +]; diff --git a/lang/id/domains/finance/field.php b/lang/id/domains/finance/field.php new file mode 100644 index 0000000..dc32980 --- /dev/null +++ b/lang/id/domains/finance/field.php @@ -0,0 +1,23 @@ + [ + 'code' => 'Kode Akun', + 'name' => 'Nama Akun', + 'classification' => 'Klasifikasi', + 'status' => 'Status', + ], + 'invoice' => [ + 'name' => 'Nama Pembayar', + 'type' => 'Jenis Tagihan', + 'amount' => 'Jumlah Tagihan', + 'client_name' => 'Nama Klien', + 'status' => 'Status', + ], + 'product_mapping' => [ + 'code' => 'Kode Produk', + 'name' => 'Nama Produk', + 'coa_name' => 'Nama Akun (COA)', + 'client_name' => 'Nama Klien', + ], +]; diff --git a/lang/id/domains/finance/seo.php b/lang/id/domains/finance/seo.php new file mode 100644 index 0000000..44c6318 --- /dev/null +++ b/lang/id/domains/finance/seo.php @@ -0,0 +1,19 @@ + [ + 'title' => 'Data Bagan Akun (COA)', + 'description' => 'Kelola bagan akun untuk pencatatan keuangan.', + 'keywords' => 'bagan akun, coa, akuntansi, keuangan', + ], + 'invoice' => [ + 'title' => 'Data Tagihan', + 'description' => 'Kelola tagihan pembayaran mahasiswa.', + 'keywords' => 'tagihan, invoice, pembayaran, keuangan', + ], + 'product-mapping' => [ + 'title' => 'Data Pemetaan Produk', + 'description' => 'Kelola pemetaan produk biaya dengan Bagan Akun (COA).', + 'keywords' => 'pemetaan produk, produk, coa, keuangan', + ], +]; diff --git a/lang/id/domains/system/enum.php b/lang/id/domains/system/enum.php new file mode 100644 index 0000000..9da35f6 --- /dev/null +++ b/lang/id/domains/system/enum.php @@ -0,0 +1,8 @@ + [ + 'active' => 'Aktif', + 'archived' => 'Diarsipkan', + ], +]; diff --git a/lang/id/resources.php b/lang/id/resources.php index 2d86d5d..93ecc95 100644 --- a/lang/id/resources.php +++ b/lang/id/resources.php @@ -11,4 +11,12 @@ return [ 'avatar' => 'Avatar', 'system_settings' => 'Pengaturan Sistem', 'audit' => 'Audit', + 'study_program' => 'Program Studi', + 'admission_schedule' => 'Jadwal Penerimaan', + 'admission_track' => 'Jalur Penerimaan', + 'fee_type' => 'Jenis Biaya', + 'program_track' => 'Jalur Program', + 'client' => 'Klien', + 'faculty' => 'Fakultas', + 'term' => 'Tahun Ajaran', ]; diff --git a/lang/id/ui.php b/lang/id/ui.php index 66f0dd6..12aa18c 100644 --- a/lang/id/ui.php +++ b/lang/id/ui.php @@ -8,6 +8,21 @@ return [ 'roles' => 'Peran & Izin', 'settings' => 'Pengaturan Sistem', 'system_backup' => 'Cadangkan Sistem', + 'academic' => 'Akademik', + 'faculties' => 'Fakultas', + 'study-programs' => 'Program Studi', + 'terms' => 'Tahun Ajaran', + 'admission' => 'Penerimaan', + 'admission-tracks' => 'Jalur Penerimaan', + 'program-tracks' => 'Jalur Program', + 'fee-types' => 'Jenis Biaya', + 'admission-schedules' => 'Jadwal Penerimaan', + 'finance' => 'Keuangan', + 'invoices' => 'Tagihan', + 'chart-of-accounts' => 'Bagan Akun', + 'product-mappings' => 'Pemetaan Produk', + 'management' => 'Manajemen', + 'profile' => 'Profil', ], 'title' => [ 'index' => 'Data :resource', diff --git a/resources/scss/app.scss b/resources/scss/app.scss index 6d57b51..c4a1fd5 100644 --- a/resources/scss/app.scss +++ b/resources/scss/app.scss @@ -6,7 +6,8 @@ body { background-color: var(--cui-tertiary-bg); } -.icon { +.icon, +.nav-icon{ fill: none !important; } diff --git a/resources/views/components/layouts/nav/sidebar.blade.php b/resources/views/components/layouts/nav/sidebar.blade.php index 8fd30b2..62c80a6 100644 --- a/resources/views/components/layouts/nav/sidebar.blade.php +++ b/resources/views/components/layouts/nav/sidebar.blade.php @@ -13,6 +13,34 @@ +
+ +
diff --git a/resources/views/components/link.blade.php b/resources/views/components/link.blade.php index c82724c..cc6085e 100644 --- a/resources/views/components/link.blade.php +++ b/resources/views/components/link.blade.php @@ -17,6 +17,9 @@ @if ($icon && !str_contains($icon, 'svg')) @svg($icon, $iconConfig) @endif + @isset($slot) + {{ $slot }} + @endisset @if (is_string($label)) {{ $label }} @else diff --git a/resources/views/pages/academic/faculty/index.blade.php b/resources/views/pages/academic/faculty/index.blade.php new file mode 100644 index 0000000..a891d71 --- /dev/null +++ b/resources/views/pages/academic/faculty/index.blade.php @@ -0,0 +1,19 @@ + + + {{ $dataTable->table() }} + + + + + + + @can('faculty.delete') + + @endcan + + @push('page-scripts') + @vite('resources/js/plugin/datatables.js') + {{ $dataTable->scripts(attributes: ['type' => 'module']) }} + @endpush + diff --git a/resources/views/pages/academic/faculty/⚡form-modal/form-modal.blade.php b/resources/views/pages/academic/faculty/⚡form-modal/form-modal.blade.php new file mode 100644 index 0000000..6a7dbf4 --- /dev/null +++ b/resources/views/pages/academic/faculty/⚡form-modal/form-modal.blade.php @@ -0,0 +1,15 @@ + + +
+ + + + + +
+ + + + + +
diff --git a/resources/views/pages/academic/faculty/⚡form-modal/form-modal.php b/resources/views/pages/academic/faculty/⚡form-modal/form-modal.php new file mode 100644 index 0000000..400bc0f --- /dev/null +++ b/resources/views/pages/academic/faculty/⚡form-modal/form-modal.php @@ -0,0 +1,74 @@ +form->validate(); + + if ($this->mode === 'create') { + $create->execute(new EstablishFacultyDTO( + name: $this->form->name, + code: $this->form->code, + externalId: $this->form->external_id, + )); + } elseif ($this->mode === 'update') { + $update->execute($this->faculty, new ModifyFacultyDetailsDTO( + name: $this->form->name, + code: $this->form->code, + )); + } + + $this->success($this->message); + $this->dispatch('hide-faculty-form-modal'); + $this->js("LaravelDataTables['faculty-table'].ajax.reload(null, false)"); + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } + + #[Computed] + public function faculty(): ?Faculty + { + return $this->id ? Faculty::where('ulid', $this->id)->first() : null; + } + + public function show(int|string $id): void + { + $this->id = $id; + $this->mode = 'update'; + $this->form->fill($this->faculty->only(['name', 'code', 'external_id'])); + } + + public function hide(): void + { + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } +}; diff --git a/resources/views/pages/academic/study-program/index.blade.php b/resources/views/pages/academic/study-program/index.blade.php new file mode 100644 index 0000000..7d7cfa2 --- /dev/null +++ b/resources/views/pages/academic/study-program/index.blade.php @@ -0,0 +1,19 @@ + + + {{ $dataTable->table() }} + + + + + + + @can('study-program.delete') + + @endcan + + @push('page-scripts') + @vite('resources/js/plugin/datatables.js') + {{ $dataTable->scripts(attributes: ['type' => 'module']) }} + @endpush + diff --git a/resources/views/pages/academic/study-program/⚡form-modal/form-modal.blade.php b/resources/views/pages/academic/study-program/⚡form-modal/form-modal.blade.php new file mode 100644 index 0000000..5daefe7 --- /dev/null +++ b/resources/views/pages/academic/study-program/⚡form-modal/form-modal.blade.php @@ -0,0 +1,21 @@ + + +
+ + + + + + + + + + + +
+ + + + + +
diff --git a/resources/views/pages/academic/study-program/⚡form-modal/form-modal.php b/resources/views/pages/academic/study-program/⚡form-modal/form-modal.php new file mode 100644 index 0000000..1206199 --- /dev/null +++ b/resources/views/pages/academic/study-program/⚡form-modal/form-modal.php @@ -0,0 +1,86 @@ +form->validate(); + + if ($this->mode === 'create') { + $create->execute(new RegisterStudyProgramDTO( + name: $this->form->name, + code: $this->form->code, + level: $this->form->level, + status: $this->form->status, + facultyId: (int) $this->form->faculty_id, + externalId: (int) $this->form->external_id, + )); + } elseif ($this->mode === 'update') { + $update->execute($this->studyProgram, new AdjustStudyProgramDTO( + level: $this->form->level, + status: $this->form->status, + )); + } + + $this->success($this->message); + $this->dispatch('hide-studyprogram-form-modal'); + $this->js("LaravelDataTables['studyprogram-table'].ajax.reload(null, false)"); + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } + + #[Computed] + public function studyProgram(): ?StudyProgram + { + return $this->id ? StudyProgram::where('ulid', $this->id)->first() : null; + } + + public function show(int|string $id): void + { + $this->id = $id; + $this->mode = 'update'; + $this->form->fill($this->studyProgram->only(['name', 'code', 'level', 'status', 'faculty_id', 'external_id'])); + } + + public function hide(): void + { + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } +}; diff --git a/resources/views/pages/academic/term/index.blade.php b/resources/views/pages/academic/term/index.blade.php new file mode 100644 index 0000000..345a728 --- /dev/null +++ b/resources/views/pages/academic/term/index.blade.php @@ -0,0 +1,19 @@ + + + {{ $dataTable->table() }} + + + + + + + @can('term.delete') + + @endcan + + @push('page-scripts') + @vite('resources/js/plugin/datatables.js') + {{ $dataTable->scripts(attributes: ['type' => 'module']) }} + @endpush + diff --git a/resources/views/pages/academic/term/⚡form-modal/form-modal.blade.php b/resources/views/pages/academic/term/⚡form-modal/form-modal.blade.php new file mode 100644 index 0000000..60a42a7 --- /dev/null +++ b/resources/views/pages/academic/term/⚡form-modal/form-modal.blade.php @@ -0,0 +1,21 @@ + + +
+ + + + + + + + + + + +
+ + + + + +
diff --git a/resources/views/pages/academic/term/⚡form-modal/form-modal.php b/resources/views/pages/academic/term/⚡form-modal/form-modal.php new file mode 100644 index 0000000..62f6304 --- /dev/null +++ b/resources/views/pages/academic/term/⚡form-modal/form-modal.php @@ -0,0 +1,83 @@ +form->validate(); + + $startDate = CarbonImmutable::parse($this->form->date_start); + $endDate = CarbonImmutable::parse($this->form->date_end); + + if ($this->mode === 'create') { + $create->execute(new InitializeAcademicTermDTO( + name: $this->form->name, + code: $this->form->code, + yearStudy: $this->form->year_study, + semester: $this->form->semester, + startDate: $startDate, + endDate: $endDate, + )); + } elseif ($this->mode === 'update') { + $update->execute($this->term, new ModifyTermDateDTO( + startDate: $startDate, + endDate: $endDate, + )); + } + + $this->success($this->message); + $this->dispatch('hide-term-form-modal'); + $this->js("LaravelDataTables['term-table'].ajax.reload(null, false)"); + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } + + #[Computed] + public function term(): ?Term + { + return $this->id ? Term::where('ulid', $this->id)->first() : null; + } + + public function show(int|string $id): void + { + $this->id = $id; + $this->mode = 'update'; + $this->form->fill($this->term->only(['name', 'code', 'year_study', 'semester'])); + $this->form->date_start = $this->term->date_start->format('Y-m-d'); + $this->form->date_end = $this->term->date_end->format('Y-m-d'); + } + + public function hide(): void + { + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } +}; diff --git a/resources/views/pages/admission/admission-schedule/index.blade.php b/resources/views/pages/admission/admission-schedule/index.blade.php new file mode 100644 index 0000000..90c0038 --- /dev/null +++ b/resources/views/pages/admission/admission-schedule/index.blade.php @@ -0,0 +1,19 @@ + + + {{ $dataTable->table() }} + + + + + + + @can('admission-schedule.delete') + + @endcan + + @push('page-scripts') + @vite('resources/js/plugin/datatables.js') + {{ $dataTable->scripts(attributes: ['type' => 'module']) }} + @endpush + diff --git a/resources/views/pages/admission/admission-schedule/⚡form-modal/form-modal.blade.php b/resources/views/pages/admission/admission-schedule/⚡form-modal/form-modal.blade.php new file mode 100644 index 0000000..8fac024 --- /dev/null +++ b/resources/views/pages/admission/admission-schedule/⚡form-modal/form-modal.blade.php @@ -0,0 +1,23 @@ + + +
+ + + + + + + + + + + + + +
+ + + + + +
diff --git a/resources/views/pages/admission/admission-schedule/⚡form-modal/form-modal.php b/resources/views/pages/admission/admission-schedule/⚡form-modal/form-modal.php new file mode 100644 index 0000000..13e88e3 --- /dev/null +++ b/resources/views/pages/admission/admission-schedule/⚡form-modal/form-modal.php @@ -0,0 +1,106 @@ +form->validate(); + + $startData = CarbonImmutable::parse($this->form->date_start); + $endData = CarbonImmutable::parse($this->form->date_end); + $reRegisterStartData = CarbonImmutable::parse($this->form->re_registration_date_start); + $reRegisterEndData = CarbonImmutable::parse($this->form->re_registration_date_end); + + if ($this->mode === 'create') { + $create->execute(new PublishAdmissionScheduleDTO( + startData: $startData, + endData: $endData, + reRegisterStartData: $reRegisterStartData, + reRegisterEndData: $reRegisterEndData, + status: AdmissionStatus::from($this->form->status), + termId: (int) $this->form->term_id, + admissionTrackId: (int) $this->form->admission_track_id, + )); + } elseif ($this->mode === 'update') { + $update->execute($this->admissionSchedule, new AdjustAdmissionScheduleDurationDTO( + startData: $startData, + endData: $endData, + reRegisterStartData: $reRegisterStartData, + reRegisterEndData: $reRegisterEndData, + )); + } + + $this->success($this->message); + $this->dispatch('hide-admissionschedule-form-modal'); + $this->js("LaravelDataTables['admissionschedule-table'].ajax.reload(null, false)"); + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } + + #[Computed] + public function admissionSchedule(): ?AdmissionSchedule + { + return $this->id ? AdmissionSchedule::where('ulid', $this->id)->first() : null; + } + + public function show(int|string $id): void + { + $this->id = $id; + $this->mode = 'update'; + $this->form->fill($this->admissionSchedule->only(['status', 'term_id', 'admission_track_id'])); + $this->form->date_start = $this->admissionSchedule->date_start->format('Y-m-d'); + $this->form->date_end = $this->admissionSchedule->date_end->format('Y-m-d'); + $this->form->re_registration_date_start = $this->admissionSchedule->re_registration_date_start->format('Y-m-d'); + $this->form->re_registration_date_end = $this->admissionSchedule->re_registration_date_end->format('Y-m-d'); + } + + public function hide(): void + { + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } +}; diff --git a/resources/views/pages/admission/admission-track/index.blade.php b/resources/views/pages/admission/admission-track/index.blade.php new file mode 100644 index 0000000..d05c13d --- /dev/null +++ b/resources/views/pages/admission/admission-track/index.blade.php @@ -0,0 +1,19 @@ + + + {{ $dataTable->table() }} + + + + + + + @can('admission-track.delete') + + @endcan + + @push('page-scripts') + @vite('resources/js/plugin/datatables.js') + {{ $dataTable->scripts(attributes: ['type' => 'module']) }} + @endpush + diff --git a/resources/views/pages/admission/admission-track/⚡form-modal/form-modal.blade.php b/resources/views/pages/admission/admission-track/⚡form-modal/form-modal.blade.php new file mode 100644 index 0000000..10ce53e --- /dev/null +++ b/resources/views/pages/admission/admission-track/⚡form-modal/form-modal.blade.php @@ -0,0 +1,13 @@ + + +
+ + + +
+ + + + + +
diff --git a/resources/views/pages/admission/admission-track/⚡form-modal/form-modal.php b/resources/views/pages/admission/admission-track/⚡form-modal/form-modal.php new file mode 100644 index 0000000..624a4d0 --- /dev/null +++ b/resources/views/pages/admission/admission-track/⚡form-modal/form-modal.php @@ -0,0 +1,72 @@ +form->validate(); + + if ($this->mode === 'create') { + $create->execute(new InitializeAdmissionTrackDTO( + name: $this->form->name, + status: $this->form->status, + )); + } elseif ($this->mode === 'update') { + $this->admissionTrack->update([ + 'name' => $this->form->name, + 'status' => $this->form->status, + ]); + } + + $this->success($this->message); + $this->dispatch('hide-admissiontrack-form-modal'); + $this->js("LaravelDataTables['admissiontrack-table'].ajax.reload(null, false)"); + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } + + #[Computed] + public function admissionTrack(): ?AdmissionTrack + { + return $this->id ? AdmissionTrack::where('ulid', $this->id)->first() : null; + } + + public function show(int|string $id): void + { + $this->id = $id; + $this->mode = 'update'; + $this->form->fill($this->admissionTrack->only(['name', 'status'])); + } + + public function hide(): void + { + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } +}; diff --git a/resources/views/pages/admission/fee-type/index.blade.php b/resources/views/pages/admission/fee-type/index.blade.php new file mode 100644 index 0000000..e99f882 --- /dev/null +++ b/resources/views/pages/admission/fee-type/index.blade.php @@ -0,0 +1,19 @@ + + + {{ $dataTable->table() }} + + + + + + + @can('fee-type.delete') + + @endcan + + @push('page-scripts') + @vite('resources/js/plugin/datatables.js') + {{ $dataTable->scripts(attributes: ['type' => 'module']) }} + @endpush + diff --git a/resources/views/pages/admission/fee-type/⚡form-modal/form-modal.blade.php b/resources/views/pages/admission/fee-type/⚡form-modal/form-modal.blade.php new file mode 100644 index 0000000..a42cf60 --- /dev/null +++ b/resources/views/pages/admission/fee-type/⚡form-modal/form-modal.blade.php @@ -0,0 +1,14 @@ +@use(App\Domains\Admission\Enums\BillingCycle) + + +
+ + + +
+ + + + + +
diff --git a/resources/views/pages/admission/fee-type/⚡form-modal/form-modal.php b/resources/views/pages/admission/fee-type/⚡form-modal/form-modal.php new file mode 100644 index 0000000..0c28c8c --- /dev/null +++ b/resources/views/pages/admission/fee-type/⚡form-modal/form-modal.php @@ -0,0 +1,64 @@ +form->validate(); + + $create->execute(new FeeTypeDTO( + name: $this->form->name, + billingCycle: $this->form->billing_cycle, + )); + + $this->success($this->message); + $this->dispatch('hide-feetype-form-modal'); + $this->js("LaravelDataTables['feetype-table'].ajax.reload(null, false)"); + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } + + #[Computed] + public function feeType(): ?FeeType + { + return $this->id ? FeeType::where('ulid', $this->id)->first() : null; + } + + public function show(int|string $id): void + { + $this->id = $id; + $this->mode = 'update'; + $this->form->fill($this->feeType->only(['name', 'billing_cycle'])); + } + + public function hide(): void + { + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } +}; diff --git a/resources/views/pages/admission/program-track/index.blade.php b/resources/views/pages/admission/program-track/index.blade.php new file mode 100644 index 0000000..8376e92 --- /dev/null +++ b/resources/views/pages/admission/program-track/index.blade.php @@ -0,0 +1,19 @@ + + + {{ $dataTable->table() }} + + + + + + + @can('program-track.delete') + + @endcan + + @push('page-scripts') + @vite('resources/js/plugin/datatables.js') + {{ $dataTable->scripts(attributes: ['type' => 'module']) }} + @endpush + diff --git a/resources/views/pages/admission/program-track/⚡form-modal/form-modal.blade.php b/resources/views/pages/admission/program-track/⚡form-modal/form-modal.blade.php new file mode 100644 index 0000000..10f34c0 --- /dev/null +++ b/resources/views/pages/admission/program-track/⚡form-modal/form-modal.blade.php @@ -0,0 +1,13 @@ + + +
+ + + +
+ + + + + +
diff --git a/resources/views/pages/admission/program-track/⚡form-modal/form-modal.php b/resources/views/pages/admission/program-track/⚡form-modal/form-modal.php new file mode 100644 index 0000000..95edee8 --- /dev/null +++ b/resources/views/pages/admission/program-track/⚡form-modal/form-modal.php @@ -0,0 +1,74 @@ +form->validate(); + + if ($this->mode === 'create') { + $create->execute(new DefineProgramTrackDTO( + name: $this->form->name, + code: $this->form->code, + status: $this->form->status, + )); + } elseif ($this->mode === 'update') { + $this->programTrack->update([ + 'name' => $this->form->name, + 'code' => $this->form->code, + 'status' => $this->form->status, + ]); + } + + $this->success($this->message); + $this->dispatch('hide-programtrack-form-modal'); + $this->js("LaravelDataTables['programtrack-table'].ajax.reload(null, false)"); + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } + + #[Computed] + public function programTrack(): ?ProgramTrack + { + return $this->id ? ProgramTrack::where('ulid', $this->id)->first() : null; + } + + public function show(int|string $id): void + { + $this->id = $id; + $this->mode = 'update'; + $this->form->fill($this->programTrack->only(['name', 'code', 'status'])); + } + + public function hide(): void + { + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } +}; diff --git a/resources/views/pages/channel/client/index.blade.php b/resources/views/pages/channel/client/index.blade.php new file mode 100644 index 0000000..5eaa093 --- /dev/null +++ b/resources/views/pages/channel/client/index.blade.php @@ -0,0 +1,17 @@ + + + {{ $dataTable->table() }} + + + + + @can('client.delete') + + @endcan + + @push('page-scripts') + @vite('resources/js/plugin/datatables.js') + {{ $dataTable->scripts(attributes: ['type' => 'module']) }} + @endpush + diff --git a/resources/views/pages/channel/client/⚡form-modal/form-modal.blade.php b/resources/views/pages/channel/client/⚡form-modal/form-modal.blade.php new file mode 100644 index 0000000..46c3abb --- /dev/null +++ b/resources/views/pages/channel/client/⚡form-modal/form-modal.blade.php @@ -0,0 +1,15 @@ + + +
+ + + + + +
+ + + + + +
diff --git a/resources/views/pages/channel/client/⚡form-modal/form-modal.php b/resources/views/pages/channel/client/⚡form-modal/form-modal.php new file mode 100644 index 0000000..221d8bd --- /dev/null +++ b/resources/views/pages/channel/client/⚡form-modal/form-modal.php @@ -0,0 +1,73 @@ +form->validate(); + + if ($this->mode === 'create') { + $create->execute(new ProvisionConsumerAppDTO( + name: $this->form->name, + code: $this->form->code, + url: $this->form->url, + )); + } elseif ($this->mode === 'update') { + $this->client->update([ + 'name' => $this->form->name, + 'code' => $this->form->code, + 'url' => $this->form->url, + ]); + } + + $this->success($this->message); + $this->dispatch('hide-client-form-modal'); + $this->js("LaravelDataTables['client-table'].ajax.reload(null, false)"); + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } + + #[Computed] + public function client(): ?Client + { + return $this->id ? Client::where('ulid', $this->id)->first() : null; + } + + public function show(int|string $id): void + { + $this->id = $id; + $this->mode = 'update'; + $this->form->fill($this->client->only(['name', 'code', 'url'])); + } + + public function hide(): void + { + $this->form->reset(); + $this->form->resetValidation(); + $this->reset('id', 'mode'); + } +}; diff --git a/resources/views/pages/finance/chart-of-account/index.blade.php b/resources/views/pages/finance/chart-of-account/index.blade.php new file mode 100644 index 0000000..01ae838 --- /dev/null +++ b/resources/views/pages/finance/chart-of-account/index.blade.php @@ -0,0 +1,17 @@ + + + {{ $dataTable->table() }} + + + + + @can('chart-of-account.delete') + + @endcan + + @push('page-scripts') + @vite('resources/js/plugin/datatables.js') + {{ $dataTable->scripts(attributes: ['type' => 'module']) }} + @endpush + diff --git a/resources/views/pages/finance/invoice/index.blade.php b/resources/views/pages/finance/invoice/index.blade.php new file mode 100644 index 0000000..405fe70 --- /dev/null +++ b/resources/views/pages/finance/invoice/index.blade.php @@ -0,0 +1,17 @@ + + + {{ $dataTable->table() }} + + + + + @can('invoice.delete') + + @endcan + + @push('page-scripts') + @vite('resources/js/plugin/datatables.js') + {{ $dataTable->scripts(attributes: ['type' => 'module']) }} + @endpush + diff --git a/resources/views/pages/finance/product-mapping/index.blade.php b/resources/views/pages/finance/product-mapping/index.blade.php new file mode 100644 index 0000000..093b4fb --- /dev/null +++ b/resources/views/pages/finance/product-mapping/index.blade.php @@ -0,0 +1,17 @@ + + + {{ $dataTable->table() }} + + + + + @can('product-mapping.delete') + + @endcan + + @push('page-scripts') + @vite('resources/js/plugin/datatables.js') + {{ $dataTable->scripts(attributes: ['type' => 'module']) }} + @endpush + diff --git a/resources/views/pages/identity/users/⚡detail-view/detail-view.blade.php b/resources/views/pages/identity/users/⚡detail-view/detail-view.blade.php index 7ce3e78..1eb5439 100644 --- a/resources/views/pages/identity/users/⚡detail-view/detail-view.blade.php +++ b/resources/views/pages/identity/users/⚡detail-view/detail-view.blade.php @@ -4,7 +4,7 @@
- + @if(!$this->user->hasRole([RoleType::SYSTEM_ADMIN, RoleType::ADMIN]))