Browse Source

refactor: standardize terminology by renaming "Program Track" to "Academic Program".

master
Syarif Ubaidillah 2 months ago
parent
commit
782dbd4aaa
  1. 2
      README.md
  2. 2
      app/Domains/Academic/Providers/RelationshipServiceProvider.php
  3. 18
      app/Domains/Admission/Actions/StudyModeProvisioning/DefineAcademicProgram.php
  4. 18
      app/Domains/Admission/Actions/StudyModeProvisioning/DefineProgramTrack.php
  5. 2
      app/Domains/Admission/DTOs/AdmissionPricing/AdmissionFeeRatesDTO.php
  6. 2
      app/Domains/Admission/DTOs/StudyModeProvisioning/DefineAcademicProgramDTO.php
  7. 10
      app/Domains/Admission/Models/AcademicProgram.php
  8. 6
      app/Domains/Admission/Models/FeeRate.php
  9. 49
      app/Domains/Admission/Policies/AcademicProgramPolicy.php
  10. 49
      app/Domains/Admission/Policies/ProgramTrackPolicy.php
  11. 2
      app/Domains/Identity/Enums/RoleType.php
  12. 4
      app/Domains/System/Models/File.php
  13. 32
      app/Http/Controllers/Web/Admission/AcademicProgramController.php
  14. 32
      app/Http/Controllers/Web/Admission/ProgramTrackController.php
  15. 38
      app/Http/DataTables/Admission/AcademicProgramDataTable.php
  16. 6
      app/Livewire/Forms/Admission/AcademicProgramForm.php
  17. 8
      database/factories/Academic/AcademicProgramFactory.php
  18. 4
      database/migrations/2026_07_06_031133_create_academic_programs_table.php
  19. 2
      database/migrations/2026_07_06_034032_create_fee_rates_table.php
  20. 6
      lang/en/domains/admission/field.php
  21. 8
      lang/en/domains/admission/seo.php
  22. 2
      lang/en/resources.php
  23. 2
      lang/en/ui.php
  24. 6
      lang/id/domains/admission/field.php
  25. 8
      lang/id/domains/admission/seo.php
  26. 2
      lang/id/resources.php
  27. 2
      lang/id/ui.php
  28. 4
      resources/views/components/layouts/nav/sidebar.blade.php
  29. 11
      resources/views/pages/admission/academic-program/index.blade.php
  30. 15
      resources/views/pages/admission/academic-program/⚡form-modal/form-modal.blade.php
  31. 29
      resources/views/pages/admission/academic-program/⚡form-modal/form-modal.php
  32. 13
      resources/views/pages/admission/program-track/⚡form-modal/form-modal.blade.php
  33. 4
      routes/web.php
  34. 12
      tests/Feature/Domains/System/Actions/Backup/ArchiveSystemBackupTest.php
  35. 54
      tests/Unit/Domains/System/Actions/Files/DeleteModelFileTest.php
  36. 14
      tests/Unit/Domains/System/Casts/ByteHumanReadableTest.php

2
README.md

@ -270,7 +270,7 @@ To keep domain actions aligned with authentic business intent rather than techni
| **`Initialize`** / **`Register`** | Declares the setup of an operational business track or engine. | `InitializeAdmissionTrack`, `RegisterStudyProgram` | | **`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` | | **`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` | | **`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` | | **`Define`** | Configures static lookups or structural reference dictionary elements. | `DefineAcademicProgram`, `DefineFeeType` |
| **`Adjust`** / **`Modify`** | Performs precise partial modifications or data tuning on active records. | `AdjustAdmissionScheduleDuration` | | **`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` | | **`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` | | **`Synchronize`** | Forces full state matching with an authoritative external system registry. | `SynchronizeFacultyData` |

2
app/Domains/Academic/Providers/RelationshipServiceProvider.php

@ -41,7 +41,7 @@ class RelationshipServiceProvider extends ServiceProvider
localKey: 'id' localKey: 'id'
)); ));
FeeRate::resolveRelationUsing('study_program', fn (FeeRate $programTrack) => $programTrack->belongsTo( FeeRate::resolveRelationUsing('study_program', fn (FeeRate $feeRate) => $feeRate->belongsTo(
related: StudyProgram::class, related: StudyProgram::class,
foreignKey: 'study_program_id' foreignKey: 'study_program_id'
)); ));

18
app/Domains/Admission/Actions/StudyModeProvisioning/DefineAcademicProgram.php

@ -0,0 +1,18 @@
<?php
namespace App\Domains\Admission\Actions\StudyModeProvisioning;
use App\Domains\Admission\DTOs\StudyModeProvisioning\DefineAcademicProgramDTO;
use App\Domains\Admission\Models\AcademicProgram;
class DefineAcademicProgram
{
public function execute(DefineAcademicProgramDTO $dto): void
{
AcademicProgram::create([
'name' => $dto->name,
'code' => $dto->code,
'status' => $dto->status,
]);
}
}

18
app/Domains/Admission/Actions/StudyModeProvisioning/DefineProgramTrack.php

@ -1,18 +0,0 @@
<?php
namespace App\Domains\Admission\Actions\StudyModeProvisioning;
use App\Domains\Admission\DTOs\StudyModeProvisioning\DefineProgramTrackDTO;
use App\Domains\Admission\Models\ProgramTrack;
class DefineProgramTrack
{
public function execute(DefineProgramTrackDTO $dto): void
{
ProgramTrack::create([
'name' => $dto->name,
'code' => $dto->code,
'status' => $dto->status,
]);
}
}

2
app/Domains/Admission/DTOs/AdmissionPricing/AdmissionFeeRatesDTO.php

@ -6,7 +6,7 @@ readonly class AdmissionFeeRatesDTO
{ {
public function __construct( public function __construct(
public int $termId, public int $termId,
public int $programTrackId, public int $academicProgramId,
public int $admissionTrackId, public int $admissionTrackId,
public int $feeTypeId, public int $feeTypeId,
public float $amount, public float $amount,

2
app/Domains/Admission/DTOs/StudyModeProvisioning/DefineProgramTrackDTO.php → app/Domains/Admission/DTOs/StudyModeProvisioning/DefineAcademicProgramDTO.php

@ -4,7 +4,7 @@ namespace App\Domains\Admission\DTOs\StudyModeProvisioning;
use App\Domains\System\Enums\LifecycleStatus; use App\Domains\System\Enums\LifecycleStatus;
readonly class DefineProgramTrackDTO readonly class DefineAcademicProgramDTO
{ {
public function __construct( public function __construct(
public string $name, public string $name,

10
app/Domains/Admission/Models/ProgramTrack.php → app/Domains/Admission/Models/AcademicProgram.php

@ -2,9 +2,9 @@
namespace App\Domains\Admission\Models; namespace App\Domains\Admission\Models;
use App\Domains\Admission\Policies\ProgramTrackPolicy; use App\Domains\Admission\Policies\AcademicProgramPolicy;
use App\Domains\System\Enums\LifecycleStatus; use App\Domains\System\Enums\LifecycleStatus;
use Database\Factories\Academic\ProgramTrackFactory; use Database\Factories\Academic\AcademicProgramFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\UsePolicy; use Illuminate\Database\Eloquent\Attributes\UsePolicy;
use Illuminate\Database\Eloquent\Concerns\HasUlids; use Illuminate\Database\Eloquent\Concerns\HasUlids;
@ -13,8 +13,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
#[Fillable(['code', 'name', 'status'])] #[Fillable(['code', 'name', 'status'])]
#[UsePolicy(ProgramTrackPolicy::class)] #[UsePolicy(AcademicProgramPolicy::class)]
class ProgramTrack extends Model class AcademicProgram extends Model
{ {
use HasFactory; use HasFactory;
use HasUlids; use HasUlids;
@ -25,7 +25,7 @@ class ProgramTrack extends Model
protected static function newFactory(): Factory protected static function newFactory(): Factory
{ {
return ProgramTrackFactory::new(); return AcademicProgramFactory::new();
} }
public function uniqueIds(): array public function uniqueIds(): array

6
app/Domains/Admission/Models/FeeRate.php

@ -14,7 +14,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable([ #[Fillable([
'admission_schedule_id', 'admission_schedule_id',
'program_track_id', 'academic_program_id',
'study_program_id', 'study_program_id',
'fee_type_id', 'fee_type_id',
'amount', 'amount',
@ -40,9 +40,9 @@ class FeeRate extends Model
return $this->belongsTo(AdmissionSchedule::class); return $this->belongsTo(AdmissionSchedule::class);
} }
public function programTrack(): BelongsTo public function academicProgram(): BelongsTo
{ {
return $this->belongsTo(ProgramTrack::class); return $this->belongsTo(AcademicProgram::class);
} }
public function feeType(): BelongsTo public function feeType(): BelongsTo

49
app/Domains/Admission/Policies/AcademicProgramPolicy.php

@ -0,0 +1,49 @@
<?php
namespace App\Domains\Admission\Policies;
use App\Domains\Admission\Models\AcademicProgram;
use App\Domains\Identity\Models\User;
class AcademicProgramPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->hasPermissionTo('academic-program.viewAny');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, AcademicProgram $model): bool
{
return $user->hasPermissionTo('academic-program.view');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->hasPermissionTo('academic-program.create');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, AcademicProgram $model): bool
{
return $user->hasPermissionTo('academic-program.update');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, AcademicProgram $model): bool
{
return $user->hasPermissionTo('academic-program.delete');
}
}

49
app/Domains/Admission/Policies/ProgramTrackPolicy.php

@ -1,49 +0,0 @@
<?php
namespace App\Domains\Admission\Policies;
use App\Domains\Admission\Models\ProgramTrack;
use App\Domains\Identity\Models\User;
class ProgramTrackPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->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');
}
}

2
app/Domains/Identity/Enums/RoleType.php

@ -44,7 +44,7 @@ enum RoleType: string
...self::generatePolicy('client', [self::SYSTEM_ADMIN, self::ADMIN]), ...self::generatePolicy('client', [self::SYSTEM_ADMIN, self::ADMIN]),
...self::generatePolicy('admission-track', [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('academic-program', [self::SYSTEM_ADMIN, self::ADMIN]),
...self::generatePolicy('admission-schedule', [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('fee-type', [self::SYSTEM_ADMIN, self::ADMIN]),

4
app/Domains/System/Models/File.php

@ -2,7 +2,7 @@
namespace App\Domains\System\Models; namespace App\Domains\System\Models;
use App\Domains\System\Casts\MoneyCurrency; use App\Domains\System\Casts\ByteHumanReadable;
use Database\Factories\System\FileFactory; use Database\Factories\System\FileFactory;
use Exception; use Exception;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
@ -29,7 +29,7 @@ class File extends Model
use HasFactory; use HasFactory;
protected $casts = [ protected $casts = [
'size' => MoneyCurrency::class, 'size' => ByteHumanReadable::class,
'options' => 'array', 'options' => 'array',
]; ];

32
app/Http/Controllers/Web/Admission/AcademicProgramController.php

@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Web\Admission;
use App\Attributes\LayoutData;
use App\Attributes\Seo;
use App\Http\Controllers\Controller;
use App\Http\DataTables\Admission\AcademicProgramDataTable;
use Illuminate\Http\Request;
class AcademicProgramController extends Controller
{
/**
* Handle the incoming request.
*/
#[Seo(
title: 'domains/admission/seo.academic-program.title',
description: 'domains/admission/seo.academic-program.description',
keywords: 'domains/admission/seo.academic-program.keywords'
)]
#[LayoutData(
header: 'domains/admission/seo.academic-program.title',
breadcrumbs: [
'ui.menu.dashboard' => 'dashboard',
'domains/admission/seo.academic-program.title' => '',
],
)]
public function __invoke(AcademicProgramDataTable $dataTable)
{
return $dataTable->render('pages.admission.academic-program.index');
}
}

32
app/Http/Controllers/Web/Admission/ProgramTrackController.php

@ -1,32 +0,0 @@
<?php
namespace App\Http\Controllers\Web\Admission;
use App\Attributes\LayoutData;
use App\Attributes\Seo;
use App\Http\Controllers\Controller;
use App\Http\DataTables\Admission\ProgramTrackDataTable;
use Illuminate\Http\Request;
class ProgramTrackController extends Controller
{
/**
* Handle the incoming request.
*/
#[Seo(
title: 'domains/admission/seo.program-track.title',
description: 'domains/admission/seo.program-track.description',
keywords: 'domains/admission/seo.program-track.keywords'
)]
#[LayoutData(
header: 'domains/admission/seo.program-track.title',
breadcrumbs: [
'ui.menu.dashboard' => 'dashboard',
'domains/admission/seo.program-track.title' => '',
],
)]
public function __invoke(ProgramTrackDataTable $dataTable)
{
return $dataTable->render('pages.admission.program-track.index');
}
}

38
app/Http/DataTables/Admission/ProgramTrackDataTable.php → app/Http/DataTables/Admission/AcademicProgramDataTable.php

@ -2,7 +2,7 @@
namespace App\Http\DataTables\Admission; namespace App\Http\DataTables\Admission;
use App\Domains\Admission\Models\ProgramTrack; use App\Domains\Admission\Models\AcademicProgram;
use Illuminate\Database\Eloquent\Builder as QueryBuilder; use Illuminate\Database\Eloquent\Builder as QueryBuilder;
use Yajra\DataTables\EloquentDataTable; use Yajra\DataTables\EloquentDataTable;
use Yajra\DataTables\Html\Builder as HtmlBuilder; use Yajra\DataTables\Html\Builder as HtmlBuilder;
@ -10,12 +10,12 @@ use Yajra\DataTables\Html\Button;
use Yajra\DataTables\Html\Column; use Yajra\DataTables\Html\Column;
use Yajra\DataTables\Services\DataTable; use Yajra\DataTables\Services\DataTable;
class ProgramTrackDataTable extends DataTable class AcademicProgramDataTable extends DataTable
{ {
/** /**
* Build the DataTable class. * Build the DataTable class.
* *
* @param QueryBuilder<ProgramTrack> $query Results from query() method. * @param QueryBuilder<AcademicProgram> $query Results from query() method.
*/ */
public function dataTable(QueryBuilder $query): EloquentDataTable public function dataTable(QueryBuilder $query): EloquentDataTable
{ {
@ -26,21 +26,21 @@ class ProgramTrackDataTable extends DataTable
])) ]))
->addColumn( ->addColumn(
'action', 'action',
fn ($programTrack) => view('components.datatables.action-button', [ fn ($academicProgram) => view('components.datatables.action-button', [
'log' => true, 'log' => true,
'edit' => [ 'edit' => [
'modal' => 'programtrack-form-modal', 'modal' => 'academicprogram-form-modal',
'permission' => auth()->user()->can('update', $programTrack), 'permission' => auth()->user()->can('update', $academicProgram),
], ],
'delete' => [ 'delete' => [
'url' => null, 'url' => null,
'title' => __('ui.button.delete'), 'title' => __('ui.button.delete'),
'message' => __('ui.confirmation.delete', ['resource' => __('resources.program-track')]), 'message' => __('ui.confirmation.delete', ['resource' => __('resources.academic_program')]),
'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.program-track')]), 'success_message' => __('ui.crud.success.deleted', ['resource' => __('resources.academic_program')]),
'permission' => auth()->user()->can('delete', $programTrack), 'permission' => auth()->user()->can('delete', $academicProgram),
], ],
'table_name' => 'programtrack-table', 'table_name' => 'academicprogram-table',
'id' => $programTrack->ulid, 'id' => $academicProgram->ulid,
]) ])
) )
->rawColumns(['action', 'status']) ->rawColumns(['action', 'status'])
@ -50,9 +50,9 @@ class ProgramTrackDataTable extends DataTable
/** /**
* Get the query source of dataTable. * Get the query source of dataTable.
* *
* @return QueryBuilder<ProgramTrack> * @return QueryBuilder<AcademicProgram>
*/ */
public function query(ProgramTrack $model): QueryBuilder public function query(AcademicProgram $model): QueryBuilder
{ {
return $model->newQuery(); return $model->newQuery();
} }
@ -63,7 +63,7 @@ class ProgramTrackDataTable extends DataTable
public function html(): HtmlBuilder public function html(): HtmlBuilder
{ {
return $this->builder() return $this->builder()
->setTableId('programtrack-table') ->setTableId('academicprogram-table')
->columns($this->getColumns()) ->columns($this->getColumns())
->minifiedAjax() ->minifiedAjax()
->orderBy(-1) ->orderBy(-1)
@ -95,7 +95,7 @@ class ProgramTrackDataTable extends DataTable
]) ])
->buttons([ ->buttons([
Button::make('add') Button::make('add')
->action('$("#programtrack-form-modal").modal("show");') ->action('$("#academicprogram-form-modal").modal("show");')
->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml())
->addClass('btn-sm'), ->addClass('btn-sm'),
Button::make('reload') Button::make('reload')
@ -114,12 +114,12 @@ class ProgramTrackDataTable extends DataTable
->width(10) ->width(10)
->title('#'), ->title('#'),
Column::make('code') Column::make('code')
->title(__('domains/admission/field.program-track.code')), ->title(__('domains/admission/field.academic-program.code')),
Column::make('name') Column::make('name')
->title(__('domains/admission/field.program-track.name')), ->title(__('domains/admission/field.academic-program.name')),
Column::make('status') Column::make('status')
->width(100) ->width(100)
->title(__('domains/admission/field.program-track.status')), ->title(__('domains/admission/field.academic-program.status')),
Column::computed('action') Column::computed('action')
->title(__('ui.label.actions')) ->title(__('ui.label.actions'))
->exportable(false) ->exportable(false)
@ -134,6 +134,6 @@ class ProgramTrackDataTable extends DataTable
*/ */
protected function filename(): string protected function filename(): string
{ {
return 'ProgramTrack_'.date('YmdHis'); return 'AcademicProgram_'.date('YmdHis');
} }
} }

6
app/Livewire/Forms/Admission/ProgramTrackForm.php → app/Livewire/Forms/Admission/AcademicProgramForm.php

@ -6,12 +6,12 @@ use App\Domains\System\Enums\LifecycleStatus;
use Livewire\Attributes\Validate; use Livewire\Attributes\Validate;
use Livewire\Form; use Livewire\Form;
class ProgramTrackForm extends Form class AcademicProgramForm extends Form
{ {
#[Validate('required|string|max:255', as: 'domains/admission/field.program-track.name')] #[Validate('required|string|max:255', as: 'domains/admission/field.academic-program.name')]
public string $name = ''; public string $name = '';
#[Validate('required|string|max:255', as: 'domains/admission/field.program-track.code')] #[Validate('required|string|max:255', as: 'domains/admission/field.academic-program.code')]
public string $code = ''; public string $code = '';
#[Validate('required')] #[Validate('required')]

8
database/factories/Academic/ProgramTrackFactory.php → database/factories/Academic/AcademicProgramFactory.php

@ -2,15 +2,15 @@
namespace Database\Factories\Academic; namespace Database\Factories\Academic;
use App\Domains\Admission\Models\ProgramTrack; use App\Domains\Admission\Models\AcademicProgram;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
/** /**
* @extends Factory<ProgramTrack> * @extends Factory<AcademicProgram>
*/ */
class ProgramTrackFactory extends Factory class AcademicProgramFactory extends Factory
{ {
protected $model = ProgramTrack::class; protected $model = AcademicProgram::class;
public function definition(): array public function definition(): array
{ {

4
database/migrations/2026_07_06_031133_create_program_tracks_table.php → database/migrations/2026_07_06_031133_create_academic_programs_table.php

@ -11,7 +11,7 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::create('program_tracks', function (Blueprint $table) { Schema::create('academic_programs', function (Blueprint $table) {
$table->id(); $table->id();
$table->ulid(); $table->ulid();
$table->string('code'); $table->string('code');
@ -26,6 +26,6 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('program_tracks'); Schema::dropIfExists('academic_programs');
} }
}; };

2
database/migrations/2026_07_06_034032_create_fee_rates_table.php

@ -16,7 +16,7 @@ return new class extends Migration
$table->ulid(); $table->ulid();
$table->integer('amount'); $table->integer('amount');
$table->foreignId('admission_schedule_id')->constrained(); $table->foreignId('admission_schedule_id')->constrained();
$table->foreignId('program_track_id')->constrained(); $table->foreignId('academic_program_id')->constrained();
$table->foreignId('study_program_id')->index(); $table->foreignId('study_program_id')->index();
$table->foreignId('fee_type_id')->constrained(); $table->foreignId('fee_type_id')->constrained();
$table->timestamps(); $table->timestamps();

6
lang/en/domains/admission/field.php

@ -19,9 +19,9 @@ return [
'name' => 'Fee Type Name', 'name' => 'Fee Type Name',
'billing_cycle' => 'Billing Cycle', 'billing_cycle' => 'Billing Cycle',
], ],
'program-track' => [ 'academic-program' => [
'code' => 'Program Track Code', 'code' => 'Academic Program Code',
'name' => 'Program Track Name', 'name' => 'Academic Program Name',
'status' => 'Status', 'status' => 'Status',
], ],
]; ];

8
lang/en/domains/admission/seo.php

@ -16,9 +16,9 @@ return [
'description' => 'Manage admission fee types.', 'description' => 'Manage admission fee types.',
'keywords' => 'fee type, fee, admission', 'keywords' => 'fee type, fee, admission',
], ],
'program-track' => [ 'academic-program' => [
'title' => 'Program Track Data', 'title' => 'Academic Program Data',
'description' => 'Manage admission program tracks.', 'description' => 'Manage admission academic programs.',
'keywords' => 'program track, program, admission', 'keywords' => 'academic program, program, admission',
], ],
]; ];

2
lang/en/resources.php

@ -15,7 +15,7 @@ return [
'admission_schedule' => 'Admission Schedule', 'admission_schedule' => 'Admission Schedule',
'admission_track' => 'Admission Track', 'admission_track' => 'Admission Track',
'fee_type' => 'Fee Type', 'fee_type' => 'Fee Type',
'program_track' => 'Program Track', 'academic_program' => 'Academic Program',
'client' => 'Client', 'client' => 'Client',
'faculty' => 'Faculty', 'faculty' => 'Faculty',
'term' => 'Term', 'term' => 'Term',

2
lang/en/ui.php

@ -14,7 +14,7 @@ return [
'terms' => 'Terms', 'terms' => 'Terms',
'admission' => 'Admission', 'admission' => 'Admission',
'admission-tracks' => 'Admission Tracks', 'admission-tracks' => 'Admission Tracks',
'program-tracks' => 'Program Tracks', 'academic-programs' => 'Academic Programs',
'fee-types' => 'Fee Types', 'fee-types' => 'Fee Types',
'admission-schedules' => 'Admission Schedules', 'admission-schedules' => 'Admission Schedules',
'finance' => 'Finance', 'finance' => 'Finance',

6
lang/id/domains/admission/field.php

@ -19,9 +19,9 @@ return [
'name' => 'Nama Jenis Biaya', 'name' => 'Nama Jenis Biaya',
'billing_cycle' => 'Siklus Tagihan', 'billing_cycle' => 'Siklus Tagihan',
], ],
'program-track' => [ 'academic-program' => [
'code' => 'Kode Jalur Program', 'code' => 'Kode Program Akademik',
'name' => 'Nama Jalur Program', 'name' => 'Nama Program Akademik',
'status' => 'Status', 'status' => 'Status',
], ],
]; ];

8
lang/id/domains/admission/seo.php

@ -16,9 +16,9 @@ return [
'description' => 'Kelola jenis biaya penerimaan.', 'description' => 'Kelola jenis biaya penerimaan.',
'keywords' => 'jenis biaya, biaya, penerimaan', 'keywords' => 'jenis biaya, biaya, penerimaan',
], ],
'program-track' => [ 'academic-program' => [
'title' => 'Data Jalur Program', 'title' => 'Data Program Akademik',
'description' => 'Kelola jalur program penerimaan.', 'description' => 'Kelola program akademik penerimaan.',
'keywords' => 'jalur program, program, penerimaan', 'keywords' => 'program akademik, program, penerimaan',
], ],
]; ];

2
lang/id/resources.php

@ -15,7 +15,7 @@ return [
'admission_schedule' => 'Jadwal Penerimaan', 'admission_schedule' => 'Jadwal Penerimaan',
'admission_track' => 'Jalur Penerimaan', 'admission_track' => 'Jalur Penerimaan',
'fee_type' => 'Jenis Biaya', 'fee_type' => 'Jenis Biaya',
'program_track' => 'Jalur Program', 'academic_program' => 'Program Akademik',
'client' => 'Klien', 'client' => 'Klien',
'faculty' => 'Fakultas', 'faculty' => 'Fakultas',
'term' => 'Tahun Ajaran', 'term' => 'Tahun Ajaran',

2
lang/id/ui.php

@ -14,7 +14,7 @@ return [
'terms' => 'Tahun Ajaran', 'terms' => 'Tahun Ajaran',
'admission' => 'Penerimaan', 'admission' => 'Penerimaan',
'admission-tracks' => 'Jalur Penerimaan', 'admission-tracks' => 'Jalur Penerimaan',
'program-tracks' => 'Jalur Program', 'academic-programs' => 'Program Akademik',
'fee-types' => 'Jenis Biaya', 'fee-types' => 'Jenis Biaya',
'admission-schedules' => 'Jadwal Penerimaan', 'admission-schedules' => 'Jadwal Penerimaan',
'finance' => 'Keuangan', 'finance' => 'Keuangan',

4
resources/views/components/layouts/nav/sidebar.blade.php

@ -25,8 +25,8 @@
<x-layouts.nav.sidebar.nav-title>{{ __('ui.menu.admission') }}</x-layouts.nav.sidebar.nav-title> <x-layouts.nav.sidebar.nav-title>{{ __('ui.menu.admission') }}</x-layouts.nav.sidebar.nav-title>
<x-layouts.nav.sidebar.nav-link :route="route('admission-tracks.index')" icon="tabler-git-pull-request" <x-layouts.nav.sidebar.nav-link :route="route('admission-tracks.index')" icon="tabler-git-pull-request"
:text="__('ui.menu.admission-tracks')" permission="admission-track.viewAny" /> :text="__('ui.menu.admission-tracks')" permission="admission-track.viewAny" />
<x-layouts.nav.sidebar.nav-link :route="route('program-tracks.index')" icon="tabler-route" <x-layouts.nav.sidebar.nav-link :route="route('academic-programs.index')" icon="tabler-route"
:text="__('ui.menu.program-tracks')" permission="program-track.viewAny" /> :text="__('ui.menu.academic-programs')" permission="academic-program.viewAny" />
<x-layouts.nav.sidebar.nav-link :route="route('fee-types.index')" icon="tabler-coin" <x-layouts.nav.sidebar.nav-link :route="route('fee-types.index')" icon="tabler-coin"
:text="__('ui.menu.fee-types')" permission="fee-type.viewAny" /> :text="__('ui.menu.fee-types')" permission="fee-type.viewAny" />
<x-layouts.nav.sidebar.nav-link :route="route('admission-schedules.index')" icon="tabler-calendar-event" <x-layouts.nav.sidebar.nav-link :route="route('admission-schedules.index')" icon="tabler-calendar-event"

11
resources/views/pages/admission/program-track/index.blade.php → resources/views/pages/admission/academic-program/index.blade.php

@ -3,13 +3,12 @@
{{ $dataTable->table() }} {{ $dataTable->table() }}
</x-card> </x-card>
<livewire:pages::admission.program-track.form-modal /> <livewire:pages::admission.academic-program.form-modal />
<livewire:pages::system.audit.audit-view-modal key-name="ulid" :model="\App\Domains\Admission\Models\AcademicProgram::class"
translation="domains/admission/field.academic-program." />
<livewire:pages::system.audit.audit-view-modal key-name="ulid" :model="\App\Domains\Admission\Models\ProgramTrack::class" @can('academic-program.delete')
translation="domains/admission/field.program-track." /> <livewire:datatables.delete-action key-name="ulid" :model="\App\Domains\Admission\Models\AcademicProgram::class" />
@can('program-track.delete')
<livewire:datatables.delete-action key-name="ulid" :model="\App\Domains\Admission\Models\ProgramTrack::class" />
@endcan @endcan
@push('page-scripts') @push('page-scripts')

15
resources/views/pages/admission/academic-program/⚡form-modal/form-modal.blade.php

@ -0,0 +1,15 @@
<x-modal id="academicprogram-form-modal" :title="$this->title" wire:submit="save" wire:loading form livewire>
<div class="d-flex flex-column gap-3">
<x-form.input name="form.name" wire:model="form.name" :label="__('domains/admission/field.academic-program.name')" />
<x-form.input name="form.code" wire:model="form.code" :label="__('domains/admission/field.academic-program.code')" />
<x-form.select name="form.status" wire:model="form.status" :label="__('domains/admission/field.academic-program.status')" :options="['active' => 'Active', 'archived' => 'Archived']" />
</div>
<x-slot:footer>
<x-button type="button" theme="secondary" :label="__('ui.button.cancel')" data-bs-dismiss="modal" />
<x-button type="submit" theme="primary" :label="__('ui.button.save')" />
</x-slot:footer>
</x-modal>

29
resources/views/pages/admission/program-track/⚡form-modal/form-modal.php → resources/views/pages/admission/academic-program/⚡form-modal/form-modal.php

@ -1,12 +1,11 @@
<?php <?php
use App\Domains\Admission\Actions\StudyModeProvisioning\DefineProgramTrack; use App\Domains\Admission\Actions\StudyModeProvisioning\DefineAcademicProgram;
use App\Domains\Admission\DTOs\StudyModeProvisioning\DefineProgramTrackDTO; use App\Domains\Admission\DTOs\StudyModeProvisioning\DefineAcademicProgramDTO;
use App\Domains\Admission\Models\ProgramTrack; use App\Domains\Admission\Models\AcademicProgram;
use App\Domains\System\Enums\LifecycleStatus;
use App\Livewire\Concerns\WithModal; use App\Livewire\Concerns\WithModal;
use App\Livewire\Concerns\WithToast; use App\Livewire\Concerns\WithToast;
use App\Livewire\Forms\Admission\ProgramTrackForm; use App\Livewire\Forms\Admission\AcademicProgramForm;
use Livewire\Attributes\Computed; use Livewire\Attributes\Computed;
use Livewire\Attributes\Locked; use Livewire\Attributes\Locked;
use Livewire\Component; use Livewire\Component;
@ -22,22 +21,22 @@ new class extends Component
#[Locked] #[Locked]
public string $mode = 'create'; public string $mode = 'create';
public ProgramTrackForm $form; public AcademicProgramForm $form;
protected string $resourceName = 'program-track'; protected string $resourceName = 'academic_program';
public function save(DefineProgramTrack $create): void public function save(DefineAcademicProgram $create): void
{ {
$this->form->validate(); $this->form->validate();
if ($this->mode === 'create') { if ($this->mode === 'create') {
$create->execute(new DefineProgramTrackDTO( $create->execute(new DefineAcademicProgramDTO(
name: $this->form->name, name: $this->form->name,
code: $this->form->code, code: $this->form->code,
status: $this->form->status, status: $this->form->status,
)); ));
} elseif ($this->mode === 'update') { } elseif ($this->mode === 'update') {
$this->programTrack->update([ $this->academicProgram->update([
'name' => $this->form->name, 'name' => $this->form->name,
'code' => $this->form->code, 'code' => $this->form->code,
'status' => $this->form->status, 'status' => $this->form->status,
@ -45,24 +44,24 @@ new class extends Component
} }
$this->success($this->message); $this->success($this->message);
$this->dispatch('hide-programtrack-form-modal'); $this->dispatch('hide-academic-program-form-modal');
$this->js("LaravelDataTables['programtrack-table'].ajax.reload(null, false)"); $this->js("LaravelDataTables['academic-program-table'].ajax.reload(null, false)");
$this->form->reset(); $this->form->reset();
$this->form->resetValidation(); $this->form->resetValidation();
$this->reset('id', 'mode'); $this->reset('id', 'mode');
} }
#[Computed] #[Computed]
public function programTrack(): ?ProgramTrack public function academicProgram(): ?AcademicProgram
{ {
return $this->id ? ProgramTrack::where('ulid', $this->id)->first() : null; return $this->id ? AcademicProgram::where('ulid', $this->id)->first() : null;
} }
public function show(int|string $id): void public function show(int|string $id): void
{ {
$this->id = $id; $this->id = $id;
$this->mode = 'update'; $this->mode = 'update';
$this->form->fill($this->programTrack->only(['name', 'code', 'status'])); $this->form->fill($this->academicProgram->only(['name', 'code', 'status']));
} }
public function hide(): void public function hide(): void

13
resources/views/pages/admission/program-track/⚡form-modal/form-modal.blade.php

@ -1,13 +0,0 @@
<x-modal id="programtrack-form-modal" :title="$this->title" wire:submit="save" wire:loading form livewire>
<div class="d-flex flex-column gap-3">
<x-form.input name="form.name" wire:model="form.name" :label="__('domains/admission/field.program-track.name')" />
<x-form.select name="form.status" wire:model="form.status" :label="__('domains/admission/field.program-track.status')" :options="['active' => 'Active', 'archived' => 'Archived']" />
</div>
<x-slot:footer>
<x-button type="button" theme="secondary" :label="__('ui.button.cancel')" data-bs-dismiss="modal" />
<x-button type="submit" theme="primary" :label="__('ui.button.save')" />
</x-slot:footer>
</x-modal>

4
routes/web.php

@ -6,10 +6,10 @@ use App\Http\Controllers\Web\Academic\FacultyController;
use App\Http\Controllers\Web\Academic\StudyProgramController; use App\Http\Controllers\Web\Academic\StudyProgramController;
use App\Http\Controllers\Web\Academic\TermController; use App\Http\Controllers\Web\Academic\TermController;
use App\Http\Controllers\Web\Account\ProfileController; use App\Http\Controllers\Web\Account\ProfileController;
use App\Http\Controllers\Web\Admission\AcademicProgramController;
use App\Http\Controllers\Web\Admission\AdmissionScheduleController; use App\Http\Controllers\Web\Admission\AdmissionScheduleController;
use App\Http\Controllers\Web\Admission\AdmissionTrackController; use App\Http\Controllers\Web\Admission\AdmissionTrackController;
use App\Http\Controllers\Web\Admission\FeeTypeController; use App\Http\Controllers\Web\Admission\FeeTypeController;
use App\Http\Controllers\Web\Admission\ProgramTrackController;
use App\Http\Controllers\Web\Finance\ChartOfAccountController; use App\Http\Controllers\Web\Finance\ChartOfAccountController;
use App\Http\Controllers\Web\Finance\InvoiceController; use App\Http\Controllers\Web\Finance\InvoiceController;
use App\Http\Controllers\Web\Finance\ProductMappingController; use App\Http\Controllers\Web\Finance\ProductMappingController;
@ -41,7 +41,7 @@ Route::middleware(['web', 'auth', 'verified', 'seo', 'layouts'])->group(function
Route::get('/terms', TermController::class)->name('terms.index'); Route::get('/terms', TermController::class)->name('terms.index');
Route::get('/admission-tracks', AdmissionTrackController::class)->name('admission-tracks.index'); Route::get('/admission-tracks', AdmissionTrackController::class)->name('admission-tracks.index');
Route::get('/program-tracks', ProgramTrackController::class)->name('program-tracks.index'); Route::get('/academic-programs', AcademicProgramController::class)->name('academic-programs.index');
Route::get('/fee-types', FeeTypeController::class)->name('fee-types.index'); Route::get('/fee-types', FeeTypeController::class)->name('fee-types.index');
Route::get('/admission-schedules', AdmissionScheduleController::class)->name('admission-schedules.index'); Route::get('/admission-schedules', AdmissionScheduleController::class)->name('admission-schedules.index');

12
tests/Feature/Domains/System/Actions/Backup/ArchiveSystemBackupTest.php

@ -3,6 +3,7 @@
namespace Tests\Feature\Domains\System\Actions\Backup; namespace Tests\Feature\Domains\System\Actions\Backup;
use App\Domains\System\Actions\Backup\ArchiveSystemBackup; use App\Domains\System\Actions\Backup\ArchiveSystemBackup;
use App\Domains\System\Actions\Backup\SyncBackupCatalog;
use App\Domains\System\Models\Backup; use App\Domains\System\Models\Backup;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
@ -32,15 +33,8 @@ class ArchiveSystemBackupTest extends TestCase
Storage::disk('test-disk')->put('test-backup/test-file.zip', 'content'); Storage::disk('test-disk')->put('test-backup/test-file.zip', 'content');
$action = new ArchiveSystemBackup; $action = new ArchiveSystemBackup(new SyncBackupCatalog);
$backup = $action->execute(); $action->execute();
$this->assertInstanceOf(Backup::class, $backup);
$this->assertEquals('test-backup test-file.zip', $backup->file_name);
$this->assertEquals('test-disk', $backup->disk);
$this->assertEquals('test-backup/test-file.zip', $backup->path);
$this->assertEquals(7, $backup->getRawOriginal('size'));
$this->assertDatabaseHas('backups', [ $this->assertDatabaseHas('backups', [
'file_name' => 'test-backup test-file.zip', 'file_name' => 'test-backup test-file.zip',

54
tests/Unit/Domains/System/Actions/Files/DeleteModelFileTest.php

@ -1,54 +0,0 @@
<?php
use App\Domains\System\Actions\Files\DeleteModelFile;
use App\Domains\System\Models\File;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
test('it removes all files associated with a model', function () {
// Create some files
File::create([
'fileable_type' => 'User',
'fileable_id' => '1',
'relation_name' => 'avatar',
'name' => 'file1.txt',
'mime_type' => 'text/plain',
'size' => 1024,
'disk' => 'public',
'path' => 'path/to/file1.txt',
'uploader_id' => 'u1',
]);
File::create([
'fileable_type' => 'User',
'fileable_id' => '1',
'relation_name' => 'documents',
'name' => 'file2.txt',
'mime_type' => 'text/plain',
'size' => 1024,
'disk' => 'public',
'path' => 'path/to/file2.txt',
'uploader_id' => 'u1',
]);
// Create a file for a different model/id to ensure no accidental deletion
File::create([
'fileable_type' => 'Post',
'fileable_id' => '2',
'relation_name' => 'attachments',
'name' => 'file3.txt',
'mime_type' => 'text/plain',
'size' => 1024,
'disk' => 'public',
'path' => 'path/to/file3.txt',
'uploader_id' => 'u1',
]);
$action = new DeleteModelFile;
$action->execute('User', '1');
expect(File::where('fileable_type', 'User')->where('fileable_id', '1')->count())->toBe(0)
->and(File::where('fileable_type', 'Post')->where('fileable_id', '2')->count())->toBe(1);
});

14
tests/Unit/Domains/System/Casts/ByteHumanReadableTest.php

@ -1,11 +1,11 @@
<?php <?php
use App\Domains\System\Casts\MoneyCurrency; use App\Domains\System\Casts\ByteHumanReadable;
use App\Domains\System\Support\ValueObjects\ByteUsage; use App\Domains\System\Support\ValueObjects\ByteUsage;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
test('it casts bytes to ByteUsage', function () { test('it casts bytes to ByteUsage', function () {
$cast = new MoneyCurrency; $cast = new ByteHumanReadable;
$model = new class extends Model {}; $model = new class extends Model {};
$result = $cast->get($model, 'bytes', 1024, []); $result = $cast->get($model, 'bytes', 1024, []);
@ -15,21 +15,21 @@ test('it casts bytes to ByteUsage', function () {
}); });
test('it returns null for null value in get', function () { test('it returns null for null value in get', function () {
$cast = new MoneyCurrency; $cast = new ByteHumanReadable;
$model = new class extends Model {}; $model = new class extends Model {};
expect($cast->get($model, 'bytes', null, []))->toBeNull(); expect($cast->get($model, 'bytes', null, []))->toBeNull();
}); });
test('it sets integer to integer', function () { test('it sets integer to integer', function () {
$cast = new MoneyCurrency; $cast = new ByteHumanReadable;
$model = new class extends Model {}; $model = new class extends Model {};
expect($cast->set($model, 'bytes', 1024, []))->toBe(1024); expect($cast->set($model, 'bytes', 1024, []))->toBe(1024);
}); });
test('it sets ByteUsage to bytes', function () { test('it sets ByteUsage to bytes', function () {
$cast = new MoneyCurrency; $cast = new ByteHumanReadable;
$model = new class extends Model {}; $model = new class extends Model {};
$usage = new ByteUsage(1024); $usage = new ByteUsage(1024);
@ -37,14 +37,14 @@ test('it sets ByteUsage to bytes', function () {
}); });
test('it returns null for null value in set', function () { test('it returns null for null value in set', function () {
$cast = new MoneyCurrency; $cast = new ByteHumanReadable;
$model = new class extends Model {}; $model = new class extends Model {};
expect($cast->set($model, 'bytes', null, []))->toBeNull(); expect($cast->set($model, 'bytes', null, []))->toBeNull();
}); });
test('it throws exception for invalid type in set', function () { test('it throws exception for invalid type in set', function () {
$cast = new MoneyCurrency; $cast = new ByteHumanReadable;
$model = new class extends Model {}; $model = new class extends Model {};
expect(fn () => $cast->set($model, 'bytes', 'invalid', []))->toThrow(InvalidArgumentException::class); expect(fn () => $cast->set($model, 'bytes', 'invalid', []))->toThrow(InvalidArgumentException::class);

Loading…
Cancel
Save