Browse Source
- Implement mappers for Academic (Faculty, StudyProgram, Term) and Admission (AcademicProgram, AdmissionTrack, AdmissionSchedule, FeeType) domains. - Add registry bindings in service providers for API synchronization. - Enhance models to use HasPublicUlid and clean up ULID-related traits. - Update DTOs and actions to accommodate integration features and improve mapping flexibility. - Standardize billing cycle parsing with PMB label support.master
51 changed files with 732 additions and 222 deletions
@ -0,0 +1,64 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Academic\Integration\Mappers; |
|||
|
|||
use App\Domains\Academic\Actions\Curriculum\EstablishFaculty; |
|||
use App\Domains\Academic\Actions\Curriculum\ModifyFacultyDetails; |
|||
use App\Domains\Academic\DTOs\Curriculum\EstablishFacultyDTO; |
|||
use App\Domains\Academic\DTOs\Curriculum\ModifyFacultyDetailsDTO; |
|||
use App\Domains\Academic\Models\Faculty; |
|||
use App\Domains\System\Support\Integration\DataPayloadMapper; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
class FacultyDataMapper implements DataPayloadMapper |
|||
{ |
|||
public function __construct( |
|||
protected EstablishFaculty $establishFaculty, |
|||
protected ModifyFacultyDetails $modifyFacultyDetails, |
|||
) |
|||
{ |
|||
// Inject required Domain and Cross-Domain Actions via constructor composition |
|||
} |
|||
|
|||
public function getLookupKey(): string |
|||
{ |
|||
// Return the unique string key used to identify existing records (e.g., 'email') |
|||
return 'external_id'; |
|||
} |
|||
|
|||
public function transform(array $rawData): array |
|||
{ |
|||
// Normalize incoming data array formats into an internal domain-safe layout |
|||
return [ |
|||
'name' => $rawData['nama_fakultas'], |
|||
'code' => $rawData['kode_fakultas'], |
|||
'external_id' => $rawData['id_fakultas'], |
|||
]; |
|||
} |
|||
|
|||
public function updateOrCreateDomainState(array $payload, ?Model $existingModel = null): void |
|||
{ |
|||
/** @var Faculty $faculty */ |
|||
$faculty = $existingModel; |
|||
if ($faculty) { |
|||
$this->modifyFacultyDetails->execute($faculty, new ModifyFacultyDetailsDTO( |
|||
name: $payload['name'], |
|||
code: $payload['code'], |
|||
)); |
|||
} else { |
|||
$this->establishFaculty->execute(dto: new EstablishFacultyDTO( |
|||
name: $payload['name'], |
|||
code: $payload['code'], |
|||
externalId: $payload['external_id'], |
|||
)); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @return class-string |
|||
*/ |
|||
public function getModelClass(): string |
|||
{ |
|||
return Faculty::class; |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Academic\Integration\Mappers; |
|||
|
|||
use App\Domains\Academic\Actions\Curriculum\AdjustStudyProgramDetails; |
|||
use App\Domains\Academic\Actions\Curriculum\RegisterStudyProgram; |
|||
use App\Domains\Academic\DTOs\Curriculum\AdjustStudyProgramDTO; |
|||
use App\Domains\Academic\DTOs\Curriculum\RegisterStudyProgramDTO; |
|||
use App\Domains\Academic\Models\Faculty; |
|||
use App\Domains\Academic\Models\StudyProgram; |
|||
use App\Domains\System\Enums\LifecycleStatus; |
|||
use App\Domains\System\Support\Integration\DataPayloadMapper; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
class StudyProgramDataMapper implements DataPayloadMapper |
|||
{ |
|||
private static array $facultyCache = []; |
|||
|
|||
public function __construct( |
|||
protected RegisterStudyProgram $registerStudyProgram, |
|||
protected AdjustStudyProgramDetails $adjustStudyProgramDetails, |
|||
) |
|||
{ |
|||
// Inject required Domain and Cross-Domain Actions via constructor composition |
|||
} |
|||
|
|||
public function getLookupKey(): string |
|||
{ |
|||
// Return the unique string key used to identify existing records (e.g., 'email') |
|||
return 'external_id'; |
|||
} |
|||
|
|||
public function transform(array $rawData): array |
|||
{ |
|||
// Normalize incoming data array formats into an internal domain-safe layout |
|||
return [ |
|||
'name' => $rawData['nama_program_studi'], |
|||
'code' => $rawData['kode_program_studi'], |
|||
'level' => $rawData['nama_jenjang_pendidikan'], |
|||
'faculty_id' => self::resolveFacultyId($rawData['id_fakultas']), |
|||
'external_id' => $rawData['id_prodi'], |
|||
]; |
|||
} |
|||
|
|||
public function updateOrCreateDomainState(array $payload, ?Model $existingModel = null): void |
|||
{ |
|||
/** @var StudyProgram $studyProgram */ |
|||
$studyProgram = $existingModel; |
|||
|
|||
if ($studyProgram) { |
|||
$this->adjustStudyProgramDetails->execute($studyProgram, new AdjustStudyProgramDTO( |
|||
level: $payload['level'], |
|||
status: LifecycleStatus::ACTIVE, |
|||
)); |
|||
} else { |
|||
$this->registerStudyProgram->execute(new RegisterStudyProgramDTO( |
|||
name: $payload['name'], |
|||
code: $payload['code'], |
|||
level: $payload['level'], |
|||
status: LifecycleStatus::ACTIVE, |
|||
facultyId: $payload['faculty_id'], |
|||
externalId: $payload['external_id'], |
|||
)); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @return class-string |
|||
*/ |
|||
public function getModelClass(): string |
|||
{ |
|||
return StudyProgram::class; |
|||
} |
|||
|
|||
private function resolveFacultyId(string $externalId): int { |
|||
if (!isset(self::$facultyCache[$externalId])) { |
|||
self::$facultyCache[$externalId] = Faculty::where('external_id', $externalId)->first()->id; |
|||
} |
|||
|
|||
return self::$facultyCache[$externalId]; |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Academic\Integration\Mappers; |
|||
|
|||
use App\Domains\Academic\Actions\Calendar\InitializeAcademicTerm; |
|||
use App\Domains\Academic\Actions\Calendar\ModifyTermDate; |
|||
use App\Domains\Academic\DTOs\Calendar\InitializeAcademicTermDTO; |
|||
use App\Domains\Academic\DTOs\Calendar\ModifyTermDateDTO; |
|||
use App\Domains\Academic\Models\Term; |
|||
use App\Domains\System\Support\Integration\DataPayloadMapper; |
|||
use Carbon\CarbonImmutable; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
class TermDataMapper implements DataPayloadMapper |
|||
{ |
|||
public function __construct( |
|||
protected InitializeAcademicTerm $initializeAcademicTerm, |
|||
protected ModifyTermDate $modifyTermDate, |
|||
) |
|||
{ |
|||
// Inject required Domain and Cross-Domain Actions via constructor composition |
|||
} |
|||
|
|||
public function getLookupKey(): string |
|||
{ |
|||
// Return the unique string key used to identify existing records (e.g., 'email') |
|||
return 'code'; |
|||
} |
|||
|
|||
public function transform(array $rawData): array |
|||
{ |
|||
// Normalize incoming data array formats into an internal domain-safe layout |
|||
return [ |
|||
'name' => $rawData['nama_semester'], |
|||
'code' => $rawData['id_semester'], |
|||
'year_study' => $rawData['id_tahun_ajaran'], |
|||
'semester' => $rawData['semester'], |
|||
'date_start' => $rawData['tanggal_mulai'], |
|||
'date_end' => $rawData['tanggal_selesai'], |
|||
]; |
|||
} |
|||
|
|||
public function updateOrCreateDomainState(array $payload, ?Model $existingModel = null): void |
|||
{ |
|||
/** @var Term $term */ |
|||
$term = $existingModel; |
|||
|
|||
if(isset($term)) { |
|||
$this->modifyTermDate->execute($term, new ModifyTermDateDTO( |
|||
startDate: CarbonImmutable::parse($payload['date_start']), |
|||
endDate: CarbonImmutable::parse($payload['date_end']), |
|||
)); |
|||
} else { |
|||
$this->initializeAcademicTerm->execute(new InitializeAcademicTermDTO( |
|||
name: $payload['name'], |
|||
code: $payload['code'], |
|||
yearStudy: $payload['year_study'], |
|||
semester: $payload['semester'], |
|||
startDate: CarbonImmutable::parse($payload['date_start']), |
|||
endDate: CarbonImmutable::parse($payload['date_end']), |
|||
)); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @return class-string |
|||
*/ |
|||
public function getModelClass(): string |
|||
{ |
|||
return Term::class; |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Channel\Support\Mapper; |
|||
|
|||
use App\Domains\Channel\Enums\Api\ApiType; |
|||
use App\Domains\System\Support\Integration\DataPayloadMapper; |
|||
use InvalidArgumentException; |
|||
|
|||
class ApiMapperRegistry |
|||
{ |
|||
/** |
|||
* @var array<string, class-string<DataPayloadMapper>> |
|||
*/ |
|||
protected static array $mappers = []; |
|||
|
|||
/** |
|||
* Register mapper with type-safe using enum ApiType |
|||
*/ |
|||
public static function register(ApiType $type, string $mapperClass): void |
|||
{ |
|||
static::$mappers[$type->value] = $mapperClass; |
|||
} |
|||
|
|||
/** |
|||
* @param ApiType $type |
|||
* @throws InvalidArgumentException |
|||
* @return DataPayloadMapper |
|||
*/ |
|||
public static function resolve(ApiType $type): DataPayloadMapper |
|||
{ |
|||
$mapperClass = static::$mappers[$type->value] ?? null; |
|||
|
|||
if (! $mapperClass || ! class_exists($mapperClass)) { |
|||
throw new InvalidArgumentException("No DataMapper registered for ApiType: {$type->value}"); |
|||
} |
|||
|
|||
return app($mapperClass); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\System\Traits\Model; |
|||
|
|||
use Illuminate\Support\Str; |
|||
|
|||
trait HasPublicUlid |
|||
{ |
|||
public static function bootHasPublicUlid(): void |
|||
{ |
|||
static::creating(function ($model) { |
|||
$model->ulid = Str::ulid(); |
|||
}); |
|||
} |
|||
|
|||
public function getRouteKeyName(): string |
|||
{ |
|||
return 'ulid'; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
'clients' => [ |
|||
'synced' => 'Clients synced successfully.', |
|||
'fail_to_sync' => 'Clients failed to sync: :error.' |
|||
] |
|||
]; |
|||
Loading…
Reference in new issue