Compare commits
2 Commits
a496679cb1
...
7fa427ef65
| Author | SHA1 | Date |
|---|---|---|
|
|
7fa427ef65 | 2 months ago |
|
|
33da380c20 | 3 months ago |
159 changed files with 1282 additions and 2914 deletions
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Academic\Queries\Lookup; |
|||
|
|||
use App\Domains\Academic\Models\Faculty; |
|||
use Illuminate\Support\Collection; |
|||
|
|||
class FacultyLookup |
|||
{ |
|||
public static function fetch(): Collection |
|||
{ |
|||
return once(fn () => Faculty::query() |
|||
->select(['id', 'name']) |
|||
->latest() |
|||
->get() |
|||
->pluck('name', 'id')); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Academic\Queries\Lookup; |
|||
|
|||
use App\Domains\Academic\Models\Term; |
|||
use Illuminate\Support\Collection; |
|||
|
|||
class TermLookup |
|||
{ |
|||
public static function fetch($search = null): Collection |
|||
{ |
|||
return once(fn () => Term::query() |
|||
->select(['id', 'name']) |
|||
->when($search, fn ($query) => $query->where('name', 'like', "%{$search}%")) |
|||
->latest('date_start') |
|||
->pluck('name', 'name')); |
|||
} |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Academic\Queries\Selection; |
|||
|
|||
use App\Domains\Academic\Models\Faculty; |
|||
use Illuminate\Support\Collection; |
|||
|
|||
class SelectFacultyQuery |
|||
{ |
|||
public static function fetch(): Collection |
|||
{ |
|||
return once(fn () => Faculty::all(['id', 'name']) |
|||
->pluck('name', 'id')); |
|||
} |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Academic\Queries\Selection; |
|||
|
|||
use App\Domains\Academic\Models\Term; |
|||
use Illuminate\Support\Collection; |
|||
|
|||
class SelectTermQuery |
|||
{ |
|||
public static function fetch(): Collection |
|||
{ |
|||
return once(fn () => Term::all(['id', 'name']) |
|||
->pluck('name', 'id')); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Admission\Events\Pricing; |
|||
|
|||
use Illuminate\Foundation\Events\Dispatchable; |
|||
use Illuminate\Queue\SerializesModels; |
|||
|
|||
class AdmissionFeeRateWasSetUp |
|||
{ |
|||
use Dispatchable, SerializesModels; |
|||
|
|||
public function __construct( |
|||
public string $productName |
|||
) {} |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Admission\Integration\Mappers; |
|||
|
|||
use App\Domains\Academic\Models\StudyProgram; |
|||
use App\Domains\Admission\Actions\AdmissionPricing\SetUpAdmissionFeeRates; |
|||
use App\Domains\Admission\DTOs\AdmissionPricing\AdmissionFeeRatesDTO; |
|||
use App\Domains\Admission\Models\AcademicProgram; |
|||
use App\Domains\Admission\Models\AdmissionSchedule; |
|||
use App\Domains\Admission\Models\FeeRate; |
|||
use App\Domains\Admission\Models\FeeType; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
use App\Domains\System\Support\Integration\DataPayloadMapper; |
|||
|
|||
class FeeRateDataMapper implements DataPayloadMapper |
|||
{ |
|||
private static array $cache = []; |
|||
|
|||
public function __construct( |
|||
protected SetUpAdmissionFeeRates $setUpAdmissionFeeRates, |
|||
) |
|||
{ |
|||
// Inject required Domain and Cross-Domain Actions via constructor composition |
|||
} |
|||
|
|||
public function getLookupKey(): array |
|||
{ |
|||
// Return the unique string key used to identify existing records (e.g., 'email') |
|||
return ['admission_schedule_id', 'fee_type_id', 'academic_program_id']; |
|||
} |
|||
|
|||
public function transform(array $rawData): array |
|||
{ |
|||
if(!isset($rawData['jalur'])){ |
|||
dd($rawData); |
|||
} |
|||
// Normalize incoming data array formats into an internal domain-safe layout |
|||
return [ |
|||
'admission_schedule_id' => $this->resolveAdmissionScheduleId($rawData['jalur'], $rawData['angkatan']), |
|||
'academic_program_id' => $this->resolveAcademicProgramId($rawData['kode_Skuliah']), |
|||
'study_program_id' => $this->resolveStudyProgramId($rawData['kode_prodi']), |
|||
'fee_type_id'=> $this->resolveFeeTypeId($rawData['kode_Jtagihan']), |
|||
'amount' => $rawData['nominal_TRFtagihan'], |
|||
]; |
|||
} |
|||
|
|||
public function updateOrCreateDomainState(array $payload, ?Model $existingModel = null): void |
|||
{ |
|||
$this->setUpAdmissionFeeRates->execute(new AdmissionFeeRatesDTO( |
|||
academicProgramId: $payload['academic_program_id'], |
|||
admissionScheduleId: $payload['admission_schedule_id'], |
|||
studyProgramId: $payload['study_program_id'], |
|||
feeTypeId: $payload['fee_type_id'], |
|||
amount: $payload['amount'], |
|||
)); |
|||
} |
|||
|
|||
/** |
|||
* @return class-string |
|||
*/ |
|||
public function getModelClass(): string |
|||
{ |
|||
return FeeRate::class; |
|||
} |
|||
|
|||
public function resolveAdmissionScheduleId(string $admissionTrackSlug, int $yearStudy): int { |
|||
$key = hex2bin(md5("{$admissionTrackSlug}-{$yearStudy}") ); |
|||
if (!isset(self::$cache['admission_schedule'][$key])) { |
|||
self::$cache['admission_schedule'][$key] = AdmissionSchedule::query() |
|||
->whereHas( |
|||
'admissionTrack', |
|||
fn($builder) => $builder->where('slug', str($admissionTrackSlug)->slug()) |
|||
)->whereHas( |
|||
'term', |
|||
fn($builder) => $builder->where('year_study', $yearStudy) |
|||
)->first()?->id; |
|||
} |
|||
|
|||
return self::$cache['admission_schedule'][$key]; |
|||
} |
|||
|
|||
public function resolveStudyProgramId($code): int { |
|||
if (!isset(self::$cache['study_program'][$code])) { |
|||
self::$cache['study_program'][$code] = StudyProgram::where('code', $code)->first()?->id; |
|||
} |
|||
|
|||
return self::$cache['study_program'][$code]; |
|||
} |
|||
|
|||
public function resolveFeeTypeId(string $code): int { |
|||
$key = hex2bin(md5($code)); |
|||
if (!isset(self::$cache['fee_type'][$key])) { |
|||
self::$cache['fee_type'][$key] = FeeType::where('code', $code)->first()?->id; |
|||
} |
|||
|
|||
return self::$cache['fee_type'][$key]; |
|||
} |
|||
|
|||
|
|||
public function resolveAcademicProgramId(string $code): int { |
|||
$key = hex2bin(md5($code)); |
|||
if (!isset(self::$cache['academic_program'][$key])) { |
|||
self::$cache['academic_program'][$key] = AcademicProgram::where('code', $code)->first()?->id; |
|||
} |
|||
|
|||
return self::$cache['academic_program'][$key]; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Admission\Queries\Lookup; |
|||
|
|||
use App\Domains\Admission\Models\FeeType; |
|||
use Illuminate\Support\Collection; |
|||
|
|||
class FeeTypeLookup |
|||
{ |
|||
public static function fetch(): Collection |
|||
{ |
|||
return once(fn () => FeeType::query() |
|||
->select(['id', 'name']) |
|||
->latest() |
|||
->get() |
|||
->pluck('name', 'id')); |
|||
} |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Admission\Queries\Selection; |
|||
|
|||
use Illuminate\Database\Eloquent\Collection; |
|||
|
|||
class SelectFeeTypeQuery |
|||
{ |
|||
public function fetch(): Collection |
|||
{ |
|||
return new Collection; |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Actions\DisbursementProcessing; |
|||
|
|||
class DisburseFunds |
|||
{ |
|||
public function execute(): void |
|||
{ |
|||
// |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Actions\DisbursementProcessing; |
|||
|
|||
class RecordVendorBill |
|||
{ |
|||
public function execute(): void |
|||
{ |
|||
// |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Actions\LedgerConfiguration; |
|||
|
|||
use App\Domains\Finance\DTOs\LedgerConfiguration\AdjustAccountClassificationDTO; |
|||
use App\Domains\Finance\Models\ChartOfAccount; |
|||
|
|||
class AdjustAccountClassification |
|||
{ |
|||
public function execute(ChartOfAccount $coa, AdjustAccountClassificationDTO $dto): void |
|||
{ |
|||
$coa->update([ |
|||
'code' => $dto->code, |
|||
'classification' => $dto->classification, |
|||
'parent_id' => $dto->parentId, |
|||
]); |
|||
} |
|||
} |
|||
@ -1,20 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Actions\LedgerConfiguration; |
|||
|
|||
use App\Domains\Finance\DTOs\LedgerConfiguration\RegisterChartOfAccountDTO; |
|||
use App\Domains\Finance\Models\ChartOfAccount; |
|||
|
|||
class RegisterChartOfAccount |
|||
{ |
|||
public function execute(RegisterChartOfAccountDTO $dto): void |
|||
{ |
|||
ChartOfAccount::create([ |
|||
'code' => $dto->code, |
|||
'name' => $dto->name, |
|||
'classification' => $dto->classification, |
|||
'status' => $dto->status, |
|||
'parent_id' => $dto->parentId, |
|||
]); |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Actions\LedgerConfiguration; |
|||
|
|||
use App\Domains\Finance\Models\ChartOfAccount; |
|||
use App\Domains\System\Enums\LifecycleStatus; |
|||
|
|||
class SuspendAccountPostings |
|||
{ |
|||
public function execute(ChartOfAccount $coa): void |
|||
{ |
|||
$coa->update([ |
|||
'status' => LifecycleStatus::INACTIVE, |
|||
]); |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Actions\PaymentProcessing; |
|||
|
|||
use App\Domains\Finance\DTOs\PaymentProcessing\IssueInvoiceDTO; |
|||
use App\Domains\Finance\Models\Invoice; |
|||
|
|||
class InitializeInvoice |
|||
{ |
|||
public function execute(IssueInvoiceDTO $dto): void |
|||
{ |
|||
Invoice::create([ |
|||
'name' => $dto->name, |
|||
'detail' => $dto->detail->toArray(), |
|||
'client_ref_id' => $dto->clientRefId, |
|||
'type' => $dto->type, |
|||
'amount' => $dto->amount, |
|||
'status' => $dto->status, |
|||
'client_id' => $dto->clientId, |
|||
'chart_of_account_id' => $dto->coaId, |
|||
]); |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Actions\PaymentProcessing; |
|||
|
|||
class ProcessIncomingPaymentWebhook |
|||
{ |
|||
public function execute(): void |
|||
{ |
|||
// |
|||
} |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Actions\ProductConfiguration; |
|||
|
|||
use App\Domains\Finance\Models\ProductMapping; |
|||
|
|||
class ModifyProductMapping |
|||
{ |
|||
public function execute(ProductMapping $productMapping, string|int $coaId): void |
|||
{ |
|||
$productMapping->update(['chart_of_account_id' => $coaId]); |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Actions\ProductConfiguration; |
|||
|
|||
use App\Domains\Finance\DTOs\ProductConfiguration\RegisterProductMappingDTO; |
|||
use App\Domains\Finance\Models\ProductMapping; |
|||
|
|||
class RegisterProductMapping |
|||
{ |
|||
public function execute(RegisterProductMappingDTO $dto): void |
|||
{ |
|||
ProductMapping::create([ |
|||
'name' => $dto->name, |
|||
'code' => $dto->code, |
|||
'chart_of_account_id' => $dto->coaId, |
|||
]); |
|||
} |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\DTOs\DisbursementProcessing; |
|||
|
|||
readonly class DisburseFundsDTO |
|||
{ |
|||
public function __construct( |
|||
// |
|||
) {} |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\DTOs\DisbursementProcessing; |
|||
|
|||
readonly class VendorBillDTO |
|||
{ |
|||
public function __construct( |
|||
// |
|||
) {} |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\DTOs\LedgerConfiguration; |
|||
|
|||
use App\Domains\Finance\Enums\AccountClassification; |
|||
|
|||
readonly class AdjustAccountClassificationDTO |
|||
{ |
|||
public function __construct( |
|||
public string $code, |
|||
public AccountClassification $classification, |
|||
public ?int $parentId, |
|||
) {} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\DTOs\LedgerConfiguration; |
|||
|
|||
use App\Domains\Finance\Enums\AccountClassification; |
|||
use App\Domains\System\Enums\LifecycleStatus; |
|||
|
|||
readonly class RegisterChartOfAccountDTO |
|||
{ |
|||
public function __construct( |
|||
public string $code, |
|||
public string $name, |
|||
public AccountClassification $classification, |
|||
public LifecycleStatus $status, |
|||
public ?int $parentId, |
|||
) {} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\DTOs\PaymentProcessing; |
|||
|
|||
use Carbon\CarbonImmutable; |
|||
|
|||
readonly class BankWebhookPayloadDTO |
|||
{ |
|||
public function __construct( |
|||
public int $virtualAccount, |
|||
public string $bankRefId, |
|||
public CarbonImmutable $validUntil, |
|||
public float $amount, |
|||
public string $status, |
|||
public string $transactionId |
|||
) {} |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\DTOs\PaymentProcessing\Detail; |
|||
|
|||
interface InvoiceDetailDTO |
|||
{ |
|||
public function toArray(): array; |
|||
|
|||
public static function fromArray(array $data): self; |
|||
} |
|||
@ -1,36 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\DTOs\PaymentProcessing\Detail; |
|||
|
|||
class StudentInvoiceDetailDTO implements InvoiceDetailDTO |
|||
{ |
|||
public function __construct( |
|||
public string $studentId, |
|||
public string $studentName, |
|||
public string $faculty, |
|||
public string $studyProgram, |
|||
public string $term, |
|||
) {} |
|||
|
|||
public function toArray(): array |
|||
{ |
|||
return [ |
|||
'student_id' => $this->studentId, |
|||
'student_name' => $this->studentName, |
|||
'faculty' => $this->faculty, |
|||
'study_program' => $this->studyProgram, |
|||
'term' => $this->term, |
|||
]; |
|||
} |
|||
|
|||
public static function fromArray(array $data): self |
|||
{ |
|||
return new self( |
|||
studentId: $data['student_id'], |
|||
studentName: $data['student_name'], |
|||
faculty: $data['faculty'], |
|||
studyProgram: $data['study_program'], |
|||
term: $data['term'], |
|||
); |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\DTOs\PaymentProcessing; |
|||
|
|||
use App\Domains\Finance\DTOs\PaymentProcessing\Detail\InvoiceDetailDTO; |
|||
use App\Domains\Finance\Enums\InvoiceStatus; |
|||
use App\Domains\Finance\Enums\InvoiceType; |
|||
|
|||
readonly class IssueInvoiceDTO |
|||
{ |
|||
public function __construct( |
|||
public string $name, |
|||
public string $clientRefId, |
|||
public InvoiceDetailDTO $detail, |
|||
public InvoiceType $type, |
|||
public InvoiceStatus $status, |
|||
public int $amount, |
|||
public string $coaId, |
|||
public string $clientId |
|||
) {} |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\DTOs\ProductConfiguration; |
|||
|
|||
readonly class ModifyProductMappingDTO |
|||
{ |
|||
public function __construct( |
|||
// |
|||
) {} |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\DTOs\ProductConfiguration; |
|||
|
|||
readonly class RegisterProductMappingDTO |
|||
{ |
|||
public function __construct( |
|||
public string $name, |
|||
public string $code, |
|||
public int $coaId |
|||
) {} |
|||
} |
|||
@ -1,19 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Enums; |
|||
|
|||
use App\Domains\System\Traits\Enum\HasPredicateMethod; |
|||
use App\UI\Enums\Concerns\InteractsWithLabels; |
|||
use App\UI\Enums\Contracts\HasLabel; |
|||
|
|||
enum AccountClassification: string implements HasLabel |
|||
{ |
|||
use HasPredicateMethod; |
|||
use InteractsWithLabels; |
|||
|
|||
case ASSET = 'asset'; |
|||
case LIABILITY = 'liability'; |
|||
case EQUITY = 'equity'; |
|||
case REVENUE = 'revenue'; |
|||
case EXPENSE = 'expense'; |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Enums; |
|||
|
|||
use App\Domains\System\Traits\Enum\HasPredicateMethod; |
|||
use App\UI\Enums\Concerns\InteractsWithLabels; |
|||
use App\UI\Enums\Contracts\HasLabel; |
|||
|
|||
enum InvoiceStatus: string implements HasLabel |
|||
{ |
|||
use HasPredicateMethod; |
|||
use InteractsWithLabels; |
|||
|
|||
case PAID = 'paid'; |
|||
case PENDING = 'pending'; |
|||
case PARTIALLY_PAID = 'partially_paid'; |
|||
case OVERDUE = 'overdue'; |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Enums; |
|||
|
|||
use App\Domains\Finance\DTOs\PaymentProcessing\Detail\InvoiceDetailDTO; |
|||
use App\Domains\Finance\DTOs\PaymentProcessing\Detail\StudentInvoiceDetailDTO; |
|||
use App\Domains\Finance\Schemas\Invoice\StudentInvoiceSchema; |
|||
use App\Domains\Finance\Support\Schemas\InvoiceSchema; |
|||
use App\Domains\System\Traits\Enum\HasPredicateMethod; |
|||
use App\UI\Enums\Concerns\InteractsWithLabels; |
|||
use App\UI\Enums\Contracts\HasLabel; |
|||
use App\UI\Enums\Contracts\HasSchema; |
|||
use App\UI\Support\Schemas\InputSchemaField; |
|||
|
|||
enum InvoiceType: string implements HasLabel, HasSchema |
|||
{ |
|||
use HasPredicateMethod; |
|||
use InteractsWithLabels; |
|||
|
|||
case STUDENT_INVOICE = 'student_invoice'; |
|||
case VENDOR_BILL = 'vendor_bill'; |
|||
case CREDIT_NOTE = 'credit_note'; |
|||
|
|||
public function schema(): InvoiceSchema |
|||
{ |
|||
return match ($this) { |
|||
self::STUDENT_INVOICE => StudentInvoiceSchema::make(), |
|||
default => InvoiceSchema::make()->withCoaVisibility(), |
|||
}; |
|||
} |
|||
|
|||
public function dtoTransform(array $data): InvoiceDetailDTO |
|||
{ |
|||
return match ($this) { |
|||
self::STUDENT_INVOICE => StudentInvoiceDetailDTO::fromArray($data), |
|||
default => false |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Resolve all field schemas, keyed by their input keys. |
|||
* |
|||
* @return array<string, InputSchemaField> |
|||
*/ |
|||
public function getSchema(): array |
|||
{ |
|||
return $this->schema()->getSchema(); |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Enums; |
|||
|
|||
use App\Domains\System\Traits\Enum\HasPredicateMethod; |
|||
use App\UI\Enums\Concerns\InteractsWithLabels; |
|||
use App\UI\Enums\Contracts\HasLabel; |
|||
|
|||
enum PaymentDirection: string implements HasLabel |
|||
{ |
|||
use HasPredicateMethod; |
|||
use InteractsWithLabels; |
|||
|
|||
case RECEIPT = 'receipt'; |
|||
case DISBURSEMENT = 'disbursement'; |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Enums; |
|||
|
|||
use App\Domains\System\Traits\Enum\HasPredicateMethod; |
|||
use App\UI\Enums\Concerns\InteractsWithLabels; |
|||
use App\UI\Enums\Contracts\HasLabel; |
|||
|
|||
enum PaymentStatus: string implements HasLabel |
|||
{ |
|||
use HasPredicateMethod; |
|||
use InteractsWithLabels; |
|||
|
|||
case PENDING = 'pending'; |
|||
case SUCCESS = 'success'; |
|||
case FAILED = 'failed'; |
|||
case EXPIRED = 'expired'; |
|||
} |
|||
@ -1,38 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Models; |
|||
|
|||
use App\Domains\Finance\Enums\AccountClassification; |
|||
use App\Domains\Finance\Policies\ChartOfAccountPolicy; |
|||
use App\Domains\System\Enums\LifecycleStatus; |
|||
use App\Domains\System\Traits\Model\HasPublicUlid; |
|||
use Database\Factories\Finance\ChartOfAccountFactory; |
|||
use Illuminate\Database\Eloquent\Attributes\Fillable; |
|||
use Illuminate\Database\Eloquent\Attributes\UsePolicy; |
|||
use Illuminate\Database\Eloquent\Factories\Factory; |
|||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
#[Fillable([ |
|||
'code', |
|||
'name', |
|||
'classification', |
|||
'status', |
|||
'parent_id', |
|||
])] |
|||
#[UsePolicy(ChartOfAccountPolicy::class)] |
|||
class ChartOfAccount extends Model |
|||
{ |
|||
use HasFactory; |
|||
use HasPublicUlid; |
|||
|
|||
protected $casts = [ |
|||
'classification' => AccountClassification::class, |
|||
'status' => LifecycleStatus::class, |
|||
]; |
|||
|
|||
protected static function newFactory(): Factory |
|||
{ |
|||
return ChartOfAccountFactory::new(); |
|||
} |
|||
} |
|||
@ -1,55 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Models; |
|||
|
|||
use App\Domains\Finance\Casts\MoneyCurrency; |
|||
use App\Domains\Finance\Enums\InvoiceStatus; |
|||
use App\Domains\Finance\Enums\InvoiceType; |
|||
use App\Domains\Finance\Policies\InvoicePolicy; |
|||
use App\Domains\System\Traits\Model\HasPublicUlid; |
|||
use Database\Factories\Finance\InvoiceFactory; |
|||
use Illuminate\Database\Eloquent\Attributes\Fillable; |
|||
use Illuminate\Database\Eloquent\Attributes\UsePolicy; |
|||
use Illuminate\Database\Eloquent\Factories\Factory; |
|||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
use Illuminate\Database\Eloquent\Relations\BelongsTo; |
|||
use Illuminate\Database\Eloquent\Relations\HasMany; |
|||
|
|||
#[Fillable([ |
|||
'name', |
|||
'detail', |
|||
'client_ref_id', |
|||
'type', |
|||
'amount', |
|||
'status', |
|||
'client_id', |
|||
'chart_of_account_id', |
|||
])] |
|||
#[UsePolicy(InvoicePolicy::class)] |
|||
class Invoice extends Model |
|||
{ |
|||
use HasFactory; |
|||
use HasPublicUlid; |
|||
|
|||
protected $casts = [ |
|||
'amount' => MoneyCurrency::class, |
|||
'type' => InvoiceType::class, |
|||
'status' => InvoiceStatus::class, |
|||
]; |
|||
|
|||
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); |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Models; |
|||
|
|||
use App\Domains\Finance\Casts\MoneyCurrency; |
|||
use App\Domains\Finance\Enums\InvoiceStatus; |
|||
use App\Domains\Finance\Policies\InvoiceReportPolicy; |
|||
use BadMethodCallException; |
|||
use Illuminate\Database\Eloquent\Attributes\Fillable; |
|||
use Illuminate\Database\Eloquent\Attributes\UsePolicy; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
#[Fillable([ |
|||
'invoice_id', |
|||
'invoice_name', |
|||
'customer_id', |
|||
'customer_name', |
|||
'amount', |
|||
'paid_amount', |
|||
'issued_at', |
|||
'paid_at', |
|||
'status', |
|||
])] |
|||
#[UsePolicy(InvoiceReportPolicy::class)] |
|||
class InvoiceReport extends Model |
|||
{ |
|||
public static function boot(): void |
|||
{ |
|||
self::boot(); |
|||
static::creating(fn (InvoiceReport $invoiceReport) => 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, |
|||
]; |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Models; |
|||
|
|||
use App\Domains\Finance\Casts\MoneyCurrency; |
|||
use App\Domains\Finance\Enums\PaymentDirection; |
|||
use App\Domains\Finance\Enums\PaymentStatus; |
|||
use App\Domains\Finance\Policies\PaymentPolicy; |
|||
use App\Domains\System\Traits\Model\HasPublicUlid; |
|||
use Database\Factories\Finance\PaymentFactory; |
|||
use Illuminate\Database\Eloquent\Attributes\Fillable; |
|||
use Illuminate\Database\Eloquent\Attributes\UsePolicy; |
|||
use Illuminate\Database\Eloquent\Factories\Factory; |
|||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
use Illuminate\Database\Eloquent\Relations\BelongsTo; |
|||
|
|||
#[Fillable([ |
|||
'virtual_account', |
|||
'bank_ref_id', |
|||
'amount', |
|||
'valid_until', |
|||
'direction', |
|||
'status', |
|||
'transaction_id', |
|||
])] |
|||
#[UsePolicy(PaymentPolicy::class)] |
|||
class Payment extends Model |
|||
{ |
|||
use HasFactory; |
|||
use HasPublicUlid; |
|||
|
|||
protected $casts = [ |
|||
'amount' => MoneyCurrency::class, |
|||
'valid_until' => 'datetime_immutable', |
|||
'direction' => PaymentDirection::class, |
|||
'status' => PaymentStatus::class, |
|||
]; |
|||
|
|||
protected static function newFactory(): Factory |
|||
{ |
|||
return PaymentFactory::new(); |
|||
} |
|||
|
|||
public function transaction(): BelongsTo |
|||
{ |
|||
return $this->belongsTo(Invoice::class); |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Models; |
|||
|
|||
use App\Domains\Finance\Policies\ProductMappingPolicy; |
|||
use Illuminate\Database\Eloquent\Attributes\Fillable; |
|||
use Illuminate\Database\Eloquent\Attributes\UsePolicy; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
use Illuminate\Database\Eloquent\Relations\BelongsTo; |
|||
|
|||
#[Fillable([ |
|||
'code', |
|||
'name', |
|||
'chart_of_account_id', |
|||
'client_id', |
|||
])] |
|||
#[UsePolicy(ProductMappingPolicy::class)] |
|||
class ProductMapping extends Model |
|||
{ |
|||
public function chartOfAccount(): BelongsTo |
|||
{ |
|||
return $this->belongsTo(ChartOfAccount::class); |
|||
} |
|||
} |
|||
@ -1,47 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Models; |
|||
|
|||
use App\Domains\Finance\Casts\MoneyCurrency; |
|||
use App\Domains\Finance\Enums\InvoiceStatus; |
|||
use App\Domains\Finance\Policies\StudentInvoiceReportPolicy; |
|||
use BadMethodCallException; |
|||
use Illuminate\Database\Eloquent\Attributes\Fillable; |
|||
use Illuminate\Database\Eloquent\Attributes\UsePolicy; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
#[Fillable([ |
|||
'invoice_id', |
|||
'student_id', |
|||
'name', |
|||
'faculty_id', |
|||
'faculty_name', |
|||
'study_program_id', |
|||
'study_program_name', |
|||
'term_id', |
|||
'term_name', |
|||
'amount', |
|||
'paid_amount', |
|||
'issued_at', |
|||
'paid_at', |
|||
'status', |
|||
])] |
|||
#[UsePolicy(StudentInvoiceReportPolicy::class)] |
|||
class StudentInvoiceReport extends Model |
|||
{ |
|||
public static function boot(): void |
|||
{ |
|||
self::boot(); |
|||
static::creating(fn (InvoiceReport $invoiceReport) => 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, |
|||
]; |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Policies; |
|||
|
|||
use App\Domains\Finance\Models\ChartOfAccount; |
|||
use App\Domains\Identity\Models\User; |
|||
|
|||
class ChartOfAccountPolicy |
|||
{ |
|||
/** |
|||
* Determine whether the user can view any models. |
|||
*/ |
|||
public function viewAny(User $user): bool |
|||
{ |
|||
return $user->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'); |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Policies; |
|||
|
|||
use App\Domains\Finance\Models\Invoice; |
|||
use App\Domains\Identity\Models\User; |
|||
|
|||
class InvoicePolicy |
|||
{ |
|||
/** |
|||
* Determine whether the user can view any models. |
|||
*/ |
|||
public function viewAny(User $user): bool |
|||
{ |
|||
return $user->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'); |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Policies; |
|||
|
|||
use App\Domains\Finance\Models\InvoiceReport; |
|||
use App\Domains\Identity\Models\User; |
|||
|
|||
class InvoiceReportPolicy |
|||
{ |
|||
/** |
|||
* Determine whether the user can view any models. |
|||
*/ |
|||
public function viewAny(User $user): bool |
|||
{ |
|||
return $user->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; |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Policies; |
|||
|
|||
use App\Domains\Finance\Models\Payment; |
|||
use App\Domains\Identity\Models\User; |
|||
|
|||
class PaymentPolicy |
|||
{ |
|||
/** |
|||
* Determine whether the user can view any models. |
|||
*/ |
|||
public function viewAny(User $user): bool |
|||
{ |
|||
return $user->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'); |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Policies; |
|||
|
|||
use App\Domains\Finance\Models\ProductMapping; |
|||
use App\Domains\Identity\Models\User; |
|||
|
|||
class ProductMappingPolicy |
|||
{ |
|||
/** |
|||
* Determine whether the user can view any models. |
|||
*/ |
|||
public function viewAny(User $user): bool |
|||
{ |
|||
return $user->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'); |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Policies; |
|||
|
|||
use App\Domains\Finance\Models\StudentInvoiceReport; |
|||
use App\Domains\Identity\Models\User; |
|||
|
|||
class StudentInvoiceReportPolicy |
|||
{ |
|||
/** |
|||
* Determine whether the user can view any models. |
|||
*/ |
|||
public function viewAny(User $user): bool |
|||
{ |
|||
return $user->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; |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Providers; |
|||
|
|||
use App\Domains\System\Traits\Provider\RegistersDomainEvents; |
|||
use Illuminate\Support\ServiceProvider; |
|||
|
|||
class FinanceServiceProvider extends ServiceProvider |
|||
{ |
|||
use RegistersDomainEvents; |
|||
|
|||
protected array $listen = []; |
|||
|
|||
public function register(): void |
|||
{ |
|||
$this->app->register(RelationshipServiceProvider::class); |
|||
} |
|||
|
|||
public function boot(): void |
|||
{ |
|||
$this->registerEvents(); |
|||
} |
|||
} |
|||
@ -1,31 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Providers; |
|||
|
|||
use Illuminate\Support\ServiceProvider; |
|||
|
|||
class RelationshipServiceProvider extends ServiceProvider |
|||
{ |
|||
/** |
|||
* Boot cross-domain runtime relationships. |
|||
* |
|||
* This provider is strictly dedicated to runtime, decoupled cross-domain |
|||
* relationship definitions. Use Model::resolveRelationUsing() here to wire |
|||
* explicit polymorphic or foreign-key macros between models that live in |
|||
* different domains — without introducing a hard compile-time dependency |
|||
* between those domains/ |
|||
* |
|||
* Do NOT add route registrations, bindings, event listeners, or any other |
|||
* bootstrapping logic here. Keep this file focused on relationships only. |
|||
* |
|||
* Example: |
|||
* |
|||
* Order::resolveRelationUsing('customer', fn (Order $order) => |
|||
* $order->belongsTo(Customer::class, 'customer_id') |
|||
* ); |
|||
*/ |
|||
public function boot(): void |
|||
{ |
|||
// Register cross-domain relationships below. |
|||
} |
|||
} |
|||
@ -1,79 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Schemas\Invoice; |
|||
|
|||
use App\Domains\Academic\Queries\Selection\SelectFacultyQuery; |
|||
use App\Domains\Academic\Queries\Selection\SelectStudyProgramQuery; |
|||
use App\Domains\Academic\Queries\Selection\SelectTermQuery; |
|||
use App\Domains\Finance\Support\Schemas\InvoiceSchema; |
|||
use App\UI\Enums\InputType; |
|||
use App\UI\Support\Schemas\InputSchema; |
|||
|
|||
class StudentInvoiceSchema |
|||
{ |
|||
public static function make(): InvoiceSchema |
|||
{ |
|||
return InvoiceSchema::make() |
|||
->addInput( |
|||
InputSchema::make() |
|||
->key('student_id') |
|||
->label(__('domains/finance/field.invoice.detail.student_id')) |
|||
->type(InputType::TEXTLINE) |
|||
->rules(['required']) |
|||
) |
|||
->addInput( |
|||
InputSchema::make() |
|||
->key('student_name') |
|||
->label(__('domains/finance/field.invoice.detail.student_name')) |
|||
->type(InputType::TEXTLINE) |
|||
->rules(['required', 'string']) |
|||
) |
|||
->addInput( |
|||
InputSchema::make() |
|||
->key('faculty') |
|||
->label(__('domains/finance/field.invoice.detail.faculty')) |
|||
->type(InputType::SELECT) |
|||
->rules(['required', 'string']) |
|||
->select2() |
|||
->live() |
|||
->options(fn () => SelectFacultyQuery::fetch()) |
|||
) |
|||
->addInput( |
|||
InputSchema::make() |
|||
->key('study_program') |
|||
->label(__('domains/finance/field.invoice.detail.study_program')) |
|||
->type(InputType::SELECT) |
|||
->rules(['required', 'string']) |
|||
->select2() |
|||
->dependsOn('faculty') |
|||
->setContextKeys(['faculty']) |
|||
->options(fn ($context) => SelectStudyProgramQuery::fetch($context)) |
|||
) |
|||
->addInput( |
|||
InputSchema::make() |
|||
->key('term_entry') |
|||
->label(__('domains/finance/field.invoice.detail.term_entry')) |
|||
->type(InputType::SELECT) |
|||
->rules(['required']) |
|||
->options(fn () => SelectTermQuery::fetch()) |
|||
->select2() |
|||
) |
|||
->addInput( |
|||
InputSchema::make() |
|||
->key('semester') |
|||
->label(__('domains/finance/field.invoice.detail.semester')) |
|||
->type(InputType::SELECT) |
|||
->rules(['required']) |
|||
->options(array_combine(range(1, 13), range(1, 13))) |
|||
->select2() |
|||
) |
|||
->addInput( |
|||
InputSchema::make() |
|||
->key('fee_type') |
|||
->label(__('domains/finance/field.invoice.detail.fee_type')) |
|||
->type(InputType::SELECT) |
|||
->rules(['required']) |
|||
->select2() |
|||
); |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Support\Schemas; |
|||
|
|||
use App\UI\Support\Schemas\AbstractGroupSchema; |
|||
|
|||
class InvoiceSchema extends AbstractGroupSchema |
|||
{ |
|||
protected bool $coaVisibility = false; |
|||
|
|||
/** |
|||
* Fluent setter for COA visibility. |
|||
*/ |
|||
public function withCoaVisibility(bool $visible = true): static |
|||
{ |
|||
$this->coaVisibility = $visible; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function isCoaVisible(): bool |
|||
{ |
|||
return $this->coaVisibility; |
|||
} |
|||
} |
|||
@ -1,8 +1,8 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Casts; |
|||
namespace App\Domains\System\Casts; |
|||
|
|||
use App\Domains\Finance\Support\ValueObjects\Money; |
|||
use App\Domains\System\Support\ValueObjects\Money; |
|||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
use InvalidArgumentException; |
|||
@ -1,6 +1,6 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Support\ValueObjects; |
|||
namespace App\Domains\System\Support\ValueObjects; |
|||
|
|||
use InvalidArgumentException; |
|||
use NumberFormatter; |
|||
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers\Api\V1\Lookup; |
|||
|
|||
use App\Domains\Academic\Queries\Lookup\TermLookup; |
|||
use App\Http\Controllers\Controller; |
|||
use App\Http\Resources\LookupResource; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class TermLookupController extends Controller |
|||
{ |
|||
public function __invoke(Request $request, TermLookup $lookup) |
|||
{ |
|||
$terms = $lookup->fetch()->map(fn ($term) => (object) [ |
|||
'id' => $term, |
|||
'text' => $term, |
|||
]); |
|||
|
|||
return LookupResource::collection($terms); |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers\Web\Finance; |
|||
|
|||
use App\Attributes\LayoutData; |
|||
use App\Attributes\Seo; |
|||
use App\Http\Controllers\Controller; |
|||
use App\Http\DataTables\Finance\ChartOfAccountDataTable; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class ChartOfAccountController extends Controller |
|||
{ |
|||
/** |
|||
* Handle the incoming request. |
|||
*/ |
|||
#[Seo( |
|||
title: 'domains/finance/seo.coa.title', |
|||
description: 'domains/finance/seo.coa.description', |
|||
keywords: 'domains/finance/seo.coa.keywords' |
|||
)] |
|||
#[LayoutData( |
|||
header: 'domains/finance/seo.coa.title', |
|||
breadcrumbs: [ |
|||
'ui/menu.dashboard' => 'dashboard', |
|||
'domains/finance/seo.coa.title' => '', |
|||
], |
|||
)] |
|||
public function __invoke(ChartOfAccountDataTable $dataTable) |
|||
{ |
|||
return $dataTable->render('pages.finance.chart-of-account.index'); |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers\Web\Finance; |
|||
|
|||
use App\Attributes\LayoutData; |
|||
use App\Attributes\Seo; |
|||
use App\Http\Controllers\Controller; |
|||
use App\Http\DataTables\Finance\InvoiceDataTable; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class InvoiceController extends Controller |
|||
{ |
|||
/** |
|||
* Handle the incoming request. |
|||
*/ |
|||
#[Seo( |
|||
title: 'domains/finance/seo.invoice.title', |
|||
description: 'domains/finance/seo.invoice.description', |
|||
keywords: 'domains/finance/seo.invoice.keywords' |
|||
)] |
|||
#[LayoutData( |
|||
header: 'domains/finance/seo.invoice.title', |
|||
breadcrumbs: [ |
|||
'ui/menu.dashboard' => 'dashboard', |
|||
'domains/finance/seo.invoice.title' => '', |
|||
], |
|||
)] |
|||
public function __invoke(InvoiceDataTable $dataTable) |
|||
{ |
|||
return $dataTable->render('pages.finance.invoice.index'); |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers\Web\Finance; |
|||
|
|||
use App\Attributes\LayoutData; |
|||
use App\Attributes\Seo; |
|||
use App\Http\Controllers\Controller; |
|||
use App\Http\DataTables\Finance\ProductMappingDataTable; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class ProductMappingController extends Controller |
|||
{ |
|||
/** |
|||
* Handle the incoming request. |
|||
*/ |
|||
#[Seo( |
|||
title: 'domains/finance/seo.product_mapping.title', |
|||
description: 'domains/finance/seo.product_mapping.description', |
|||
keywords: 'domains/finance/seo.product_mapping.keywords' |
|||
)] |
|||
#[LayoutData( |
|||
header: 'domains/finance/seo.product_mapping.title', |
|||
breadcrumbs: [ |
|||
'ui/menu.dashboard' => 'dashboard', |
|||
'domains/finance/seo.product_mapping.title' => '', |
|||
], |
|||
)] |
|||
public function __invoke(ProductMappingDataTable $dataTable) |
|||
{ |
|||
return $dataTable->render('pages.finance.product-mapping.index'); |
|||
} |
|||
} |
|||
@ -1,143 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\DataTables\Finance; |
|||
|
|||
use App\Domains\Finance\Models\ChartOfAccount; |
|||
use Illuminate\Database\Eloquent\Builder as QueryBuilder; |
|||
use Yajra\DataTables\EloquentDataTable; |
|||
use Yajra\DataTables\Html\Builder as HtmlBuilder; |
|||
use Yajra\DataTables\Html\Button; |
|||
use Yajra\DataTables\Html\Column; |
|||
use Yajra\DataTables\Services\DataTable; |
|||
|
|||
class ChartOfAccountDataTable extends DataTable |
|||
{ |
|||
/** |
|||
* Build the DataTable class. |
|||
* |
|||
* @param QueryBuilder<ChartOfAccount> $query Results from query() method. |
|||
*/ |
|||
public function dataTable(QueryBuilder $query): EloquentDataTable |
|||
{ |
|||
return (new EloquentDataTable($query)) |
|||
->editColumn('status', fn (ChartOfAccount $coa) => view('components.badge', [ |
|||
'label' => $coa->status->label(), |
|||
'variant' => $coa->status->variant(), |
|||
])) |
|||
->editColumn('classification', fn (ChartOfAccount $coa) => $coa->classification->label()) |
|||
->addColumn( |
|||
'action', |
|||
fn ($coa) => view('components.datatables.action-button', [ |
|||
'log' => true, |
|||
'edit' => [ |
|||
'modal' => 'chartofaccount-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<ChartOfAccount> |
|||
*/ |
|||
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('javascript:void(0)') |
|||
->attr(['data-coreui-toggle' => 'modal', 'data-coreui-target' => '#chartofaccount-form-modal']) |
|||
->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'); |
|||
} |
|||
} |
|||
@ -1,115 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\DataTables\Finance; |
|||
|
|||
use App\Domains\Finance\Models\Invoice; |
|||
use Illuminate\Database\Eloquent\Builder as QueryBuilder; |
|||
use Yajra\DataTables\EloquentDataTable; |
|||
use Yajra\DataTables\Html\Builder as HtmlBuilder; |
|||
use Yajra\DataTables\Html\Button; |
|||
use Yajra\DataTables\Html\Column; |
|||
use Yajra\DataTables\Services\DataTable; |
|||
|
|||
class InvoiceDataTable extends DataTable |
|||
{ |
|||
/** |
|||
* Build the DataTable class. |
|||
* |
|||
* @param QueryBuilder<Invoice> $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<Invoice> |
|||
*/ |
|||
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('javascript:void(0)') |
|||
->attr(['data-coreui-toggle' => 'modal', 'data-coreui-target' => '#invoice-form-modal']) |
|||
->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'); |
|||
} |
|||
} |
|||
@ -1,137 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\DataTables\Finance; |
|||
|
|||
use App\Domains\Finance\Models\ProductMapping; |
|||
use Illuminate\Database\Eloquent\Builder as QueryBuilder; |
|||
use Yajra\DataTables\EloquentDataTable; |
|||
use Yajra\DataTables\Html\Builder as HtmlBuilder; |
|||
use Yajra\DataTables\Html\Button; |
|||
use Yajra\DataTables\Html\Column; |
|||
use Yajra\DataTables\Services\DataTable; |
|||
|
|||
class ProductMappingDataTable extends DataTable |
|||
{ |
|||
/** |
|||
* Build the DataTable class. |
|||
* |
|||
* @param QueryBuilder<ProductMapping> $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' => 'product-mapping-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<ProductMapping> |
|||
*/ |
|||
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('javascript:void(0)') |
|||
->attr(['data-coreui-toggle' => 'modal', 'data-coreui-target' => '#product-mapping-form-modal']) |
|||
->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'); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
<?php |
|||
|
|||
|
|||
return [ |
|||
'fee_type' => [ |
|||
'not_defined' => 'Not Set', |
|||
'partially_defined' => 'Partial', |
|||
'completely_defined' => 'Complete', |
|||
], |
|||
]; |
|||
@ -1,38 +0,0 @@ |
|||
<?php |
|||
|
|||
use App\Domains\Finance\Enums\AccountClassification; |
|||
use App\Domains\Finance\Enums\InvoiceStatus; |
|||
use App\Domains\Finance\Enums\InvoiceType; |
|||
use App\Domains\Finance\Enums\PaymentDirection; |
|||
use App\Domains\Finance\Enums\PaymentStatus; |
|||
|
|||
return [ |
|||
'invoice_status' => [ |
|||
InvoiceStatus::PAID->value => 'Paid', |
|||
InvoiceStatus::PENDING->value => 'Pending', |
|||
InvoiceStatus::PARTIALLY_PAID->value => 'Partially Paid', |
|||
InvoiceStatus::OVERDUE->value => 'Overdue', |
|||
], |
|||
'invoice_type' => [ |
|||
InvoiceType::STUDENT_INVOICE->value => 'Student Invoice', |
|||
InvoiceType::VENDOR_BILL->value => 'Vendor Bill', |
|||
InvoiceType::CREDIT_NOTE->value => 'Credit Note', |
|||
], |
|||
'payment_status' => [ |
|||
PaymentStatus::PENDING->value => 'Pending', |
|||
PaymentStatus::SUCCESS->value => 'Success', |
|||
PaymentStatus::FAILED->value => 'Failed', |
|||
PaymentStatus::EXPIRED->value => 'Expired', |
|||
], |
|||
'payment_direction' => [ |
|||
PaymentDirection::RECEIPT->value => 'Receipt', |
|||
PaymentDirection::DISBURSEMENT->value => 'Disbursement', |
|||
], |
|||
'account_classification' => [ |
|||
AccountClassification::ASSET->value => 'Asset', |
|||
AccountClassification::LIABILITY->value => 'Liability', |
|||
AccountClassification::EQUITY->value => 'Equity', |
|||
AccountClassification::REVENUE->value => 'Revenue', |
|||
AccountClassification::EXPENSE->value => 'Expense', |
|||
], |
|||
]; |
|||
@ -1,50 +0,0 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
'coa' => [ |
|||
'code' => 'Account Code', |
|||
'name' => 'Account Name', |
|||
'classification' => 'Classification', |
|||
'status' => 'Status', |
|||
'parent' => 'Derived From', |
|||
], |
|||
'invoice' => [ |
|||
'name' => 'Invoice Name', |
|||
'code' => 'Invoice Code', |
|||
'type' => 'Invoice Type', |
|||
'amount' => 'Invoice Amount', |
|||
'client_name' => 'Client Name', |
|||
'status' => 'Status', |
|||
'coa_name' => 'Account Name (COA)', |
|||
'detail' => [ |
|||
// Detail for Student |
|||
'student_name' => 'Student Name', |
|||
'student_id' => 'Student ID', |
|||
'faculty' => 'Faculty', |
|||
'study_program' => 'Study Program', |
|||
'term_entry' => 'Term Entry', |
|||
'fee_type' => 'Fee Type', |
|||
'semester' => 'Semester', |
|||
|
|||
// Vendor Bill |
|||
'vendor_name' => 'Vendor Name', |
|||
'vendor_id' => 'Vendor ID', |
|||
], |
|||
], |
|||
'payment' => [ |
|||
'virtual_account' => 'Virtual Account', |
|||
'bank_ref_id' => 'Bank Reference Code', |
|||
'bank_name' => 'Bank Name', |
|||
'nominal' => 'Nominal', |
|||
'validity' => 'Validity', |
|||
'payment_date' => 'Payment Date', |
|||
'direction' => 'Direction', |
|||
'status' => 'Status', |
|||
], |
|||
'product_mapping' => [ |
|||
'code' => 'Product Code', |
|||
'name' => 'Product Name', |
|||
'coa_name' => 'Account Name (COA)', |
|||
'client_name' => 'Client Name', |
|||
], |
|||
]; |
|||
@ -1,19 +0,0 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
'coa' => [ |
|||
'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', |
|||
], |
|||
]; |
|||
@ -0,0 +1,7 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
'select' => 'Select :attribute', |
|||
'email' => 'name@example.com', |
|||
'filter' => ':attribute Filter', |
|||
]; |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
'fee_type' => [ |
|||
'not_defined' => 'Not Set', |
|||
'partially_defined' => 'Partial', |
|||
'completely_defined' => 'Complete', |
|||
], |
|||
]; |
|||
@ -0,0 +1,7 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
'select' => 'Pilih :attribute', |
|||
'email' => 'nama@contoh.com', |
|||
'filter' => 'Filter :attribute', |
|||
]; |
|||
@ -1,25 +1,212 @@ |
|||
import '../plugin/apexchart.js' |
|||
|
|||
export default function AlpineChart(Alpine) { |
|||
Alpine.directive('chart', function (el, { expression }, { evaluate, cleanup }) { |
|||
let config = evaluate(expression); |
|||
Alpine.directive( |
|||
"chart", |
|||
function (el, { expression }, { evaluateLater, cleanup }) { |
|||
const getContent = evaluateLater(expression); |
|||
|
|||
const chart = new window.ApexCharts(el, config); |
|||
chart.render(); |
|||
import("apexcharts").then(({ default: ApexCharts }) => { |
|||
getContent((userConfig) => { |
|||
if (!userConfig || typeof userConfig !== "object") return; |
|||
|
|||
// 1. Fetch live computed CSS variables directly from :root (document.documentElement)
|
|||
const getCoreUIColors = () => { |
|||
const style = getComputedStyle(document.body); |
|||
|
|||
// CoreUI v4/v5 & Bootstrap 5 theme variable names
|
|||
const bodyColor = |
|||
style.getPropertyValue("--cui-body-color").trim() || |
|||
style.getPropertyValue("--bs-body-color").trim() || |
|||
"#a6b0cf"; |
|||
|
|||
const secondaryColor = |
|||
style |
|||
.getPropertyValue("--cui-secondary-color") |
|||
.trim() || |
|||
style |
|||
.getPropertyValue("--bs-secondary-color") |
|||
.trim() || |
|||
"#8a93a2"; |
|||
|
|||
const borderColor = |
|||
style |
|||
.getPropertyValue("--cui-border-color") |
|||
.trim() || |
|||
style |
|||
.getPropertyValue( |
|||
"--bs-border-color-translucent", |
|||
) |
|||
.trim() || |
|||
style |
|||
.getPropertyValue("--bs-border-color") |
|||
.trim() || |
|||
"rgba(255, 255, 255, 0.08)"; |
|||
|
|||
const primaryColor = |
|||
style.getPropertyValue("--cui-primary").trim() || |
|||
style.getPropertyValue("--bs-primary").trim() || |
|||
"#6366f1"; |
|||
|
|||
const theme = |
|||
document.documentElement.getAttribute( |
|||
"data-coreui-theme", |
|||
) || |
|||
document.documentElement.getAttribute( |
|||
"data-bs-theme", |
|||
) || |
|||
"dark"; |
|||
|
|||
return { |
|||
bodyColor, |
|||
secondaryColor, |
|||
borderColor, |
|||
primaryColor, |
|||
theme, |
|||
}; |
|||
}; |
|||
|
|||
const colors = getCoreUIColors(); |
|||
|
|||
// 2. Base Configuration explicitly overriding ApexCharts SVG text fills
|
|||
const baseConfig = { |
|||
chart: { |
|||
type: "line", |
|||
height: 360, |
|||
foreColor: colors.secondaryColor, // Sets default text/legend colors
|
|||
background: "transparent", |
|||
}, |
|||
colors: [colors.primaryColor], |
|||
theme: { |
|||
mode: colors.theme, |
|||
}, |
|||
tooltip: { |
|||
theme: colors.theme, |
|||
}, |
|||
grid: { |
|||
borderColor: colors.borderColor, |
|||
}, |
|||
xaxis: { |
|||
labels: { |
|||
style: { |
|||
colors: colors.secondaryColor, |
|||
fontSize: "12px", |
|||
}, |
|||
}, |
|||
axisBorder: { color: colors.borderColor }, |
|||
axisTicks: { color: colors.borderColor }, |
|||
}, |
|||
yaxis: { |
|||
labels: { |
|||
style: { |
|||
colors: colors.secondaryColor, |
|||
fontSize: "12px", |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
|
|||
// Deep merge user options with base configuration
|
|||
const options = Object.assign({}, baseConfig, userConfig); |
|||
options.chart = Object.assign( |
|||
{}, |
|||
baseConfig.chart, |
|||
userConfig.chart, |
|||
); |
|||
options.theme = Object.assign( |
|||
{}, |
|||
baseConfig.theme, |
|||
userConfig.theme, |
|||
); |
|||
options.tooltip = Object.assign( |
|||
{}, |
|||
baseConfig.tooltip, |
|||
userConfig.tooltip, |
|||
); |
|||
|
|||
const event = function(event) { |
|||
const newData = event.detail |
|||
chart.updateSeries(newData.series, true, true); |
|||
chart.updateOptions(newData.options, true, true); |
|||
// Preserve user primary colors if explicitly provided in PHP, otherwise use CoreUI's
|
|||
if (!userConfig.colors) { |
|||
options.colors = baseConfig.colors; |
|||
} |
|||
|
|||
name = el.getAttribute('id'); |
|||
requestAnimationFrame(() => { |
|||
const chart = new ApexCharts(el, options); |
|||
chart.render(); |
|||
|
|||
// 3. Theme switch handler
|
|||
const observer = new MutationObserver(() => { |
|||
const updated = getCoreUIColors(); |
|||
|
|||
chart.updateOptions( |
|||
{ |
|||
chart: { |
|||
foreColor: updated.secondaryColor, |
|||
}, |
|||
theme: { mode: updated.theme }, |
|||
tooltip: { theme: updated.theme }, |
|||
grid: { borderColor: updated.borderColor }, |
|||
xaxis: { |
|||
labels: { |
|||
style: { |
|||
colors: updated.secondaryColor, |
|||
}, |
|||
}, |
|||
axisBorder: { |
|||
color: updated.borderColor, |
|||
}, |
|||
axisTicks: { |
|||
color: updated.borderColor, |
|||
}, |
|||
}, |
|||
yaxis: { |
|||
labels: { |
|||
style: { |
|||
colors: updated.secondaryColor, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
false, |
|||
true, |
|||
); |
|||
}); |
|||
|
|||
observer.observe(document.documentElement, { |
|||
attributes: true, |
|||
attributeFilter: [ |
|||
"data-coreui-theme", |
|||
"data-bs-theme", |
|||
"class", |
|||
], |
|||
}); |
|||
|
|||
window.addEventListener(`chart-${name}-update`, event) |
|||
// 4. Dynamic update event listener
|
|||
const name = el.getAttribute("id"); |
|||
const eventHandler = (event) => { |
|||
const detail = event.detail || event; |
|||
if (detail.series) |
|||
chart.updateSeries(detail.series, true); |
|||
if (detail.options) |
|||
chart.updateOptions(detail.options, true); |
|||
}; |
|||
|
|||
if (name) { |
|||
window.addEventListener( |
|||
`chart-${name}-update`, |
|||
eventHandler, |
|||
); |
|||
} |
|||
|
|||
cleanup(() => { |
|||
observer.disconnect(); |
|||
if (name) |
|||
window.removeEventListener( |
|||
`chart-${name}-update`, |
|||
eventHandler, |
|||
); |
|||
chart.destroy(); |
|||
window.removeEventListener(`chart-${name}-update`, event) |
|||
}); |
|||
}) |
|||
}); |
|||
}); |
|||
}); |
|||
}, |
|||
); |
|||
} |
|||
|
|||
@ -1,46 +1,94 @@ |
|||
import Quill from "quill"; |
|||
import "quill/dist/quill.snow.css"; |
|||
|
|||
export default function alpineQuill(Alpine) { |
|||
Alpine.directive('quill', function (el, { expression }, { evaluate, cleanup }) { |
|||
const options = expression ? evaluate(expression) : {}; |
|||
const defaultOptions = { theme: 'snow', ...options }; |
|||
Alpine.directive( |
|||
"quill", |
|||
(el, { expression }, { evaluateLater, cleanup }) => { |
|||
// 1. Fallback gracefully if x-quill has no parameters
|
|||
const getContent = expression |
|||
? evaluateLater(expression) |
|||
: (cb) => cb({}); |
|||
|
|||
getContent((userOptions) => { |
|||
const options = Object.assign( |
|||
{ |
|||
theme: "snow", |
|||
placeholder: "Write something...", |
|||
}, |
|||
typeof userOptions === "object" ? userOptions : {}, |
|||
); |
|||
|
|||
// 2. Identify or create container DIV (Quill crashes on <textarea>)
|
|||
let editorContainer; |
|||
let targetInput = el; |
|||
|
|||
if (el.tagName === "TEXTAREA" || el.tagName === "INPUT") { |
|||
// Create a container DIV for Quill right before the textarea
|
|||
editorContainer = document.createElement("div"); |
|||
el.parentNode.insertBefore(editorContainer, el); |
|||
|
|||
if (typeof window.Quill === 'undefined') { |
|||
throw new Error('Quill must be loaded'); |
|||
// Hide the textarea without breaking Livewire bindings
|
|||
el.style.display = "none"; |
|||
} else { |
|||
editorContainer = el; |
|||
} |
|||
|
|||
const container = document.createElement('div'); |
|||
el.appendChild(container); |
|||
const quill = new window.Quill(container, defaultOptions); |
|||
// 3. Instantiate Quill on the container DIV
|
|||
const quill = new Quill(editorContainer, options); |
|||
|
|||
// Populate initial content from textarea / Alpine model
|
|||
if (targetInput.value && targetInput.value !== "<p><br></p>") { |
|||
quill.root.innerHTML = targetInput.value; |
|||
} |
|||
|
|||
let isUpdating = false; |
|||
|
|||
// 1. Text changes handler
|
|||
quill.on('text-change', () => { |
|||
// 4. Sync Quill edits -> hidden input -> Livewire
|
|||
quill.on("text-change", () => { |
|||
if (isUpdating) return; |
|||
isUpdating = true; |
|||
const htmlContent = quill.root.innerHTML; |
|||
el.value = htmlContent === '<p><br></p>' ? '' : htmlContent; |
|||
el.dispatchEvent(new Event('input', { bubbles: true })); |
|||
|
|||
const html = quill.root.innerHTML; |
|||
const value = html === "<p><br></p>" ? "" : html; |
|||
|
|||
targetInput.value = value; |
|||
|
|||
// Dispatch input & change events on the target input so Livewire detects it
|
|||
targetInput.dispatchEvent( |
|||
new Event("input", { bubbles: true }), |
|||
); |
|||
targetInput.dispatchEvent( |
|||
new Event("change", { bubbles: true }), |
|||
); |
|||
|
|||
isUpdating = false; |
|||
}); |
|||
|
|||
// 2. Element Value Getter/Setter
|
|||
Object.defineProperty(el, 'value', { |
|||
get() { return quill.root.innerHTML; }, |
|||
set(newValue) { |
|||
if (isUpdating || newValue === quill.root.innerHTML) return; |
|||
// 5. Sync Livewire dynamic resets -> Quill
|
|||
const handleExternalUpdate = () => { |
|||
if (isUpdating) return; |
|||
if (targetInput.value !== quill.root.innerHTML) { |
|||
isUpdating = true; |
|||
quill.root.innerHTML = newValue || ''; |
|||
quill.root.innerHTML = targetInput.value || ""; |
|||
isUpdating = false; |
|||
}, |
|||
configurable: true |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
quill.on('selection-change', (range) => { |
|||
if (range == null) { |
|||
el.dispatchEvent(new Event('blur', { bubbles: true })); |
|||
targetInput.addEventListener("input", handleExternalUpdate); |
|||
|
|||
// 6. Cleanup DOM when Livewire removes the element
|
|||
cleanup(() => { |
|||
targetInput.removeEventListener( |
|||
"input", |
|||
handleExternalUpdate, |
|||
); |
|||
if (editorContainer !== el) { |
|||
editorContainer.remove(); |
|||
el.style.display = ""; |
|||
} |
|||
}); |
|||
|
|||
cleanup(() => el.innerHTML = '') |
|||
}) |
|||
}); |
|||
}, |
|||
); |
|||
} |
|||
|
|||
@ -1,23 +1,23 @@ |
|||
// resources/js/app.js
|
|||
import { Alpine, Livewire } from '../../vendor/livewire/livewire/dist/livewire.esm'; |
|||
import bs from './alpine/bs'; |
|||
import alpineAxios from './alpine/axios'; |
|||
import alpineSelect2 from "./alpine/select2.js"; |
|||
import alpineQuill from "./alpine/quill.js"; |
|||
import alpineAsk from "./alpine/ask.js"; |
|||
import AlpineChart from "./alpine/chart.js"; |
|||
// resources/js/app.js
|
|||
import { Alpine, Livewire } from '../../vendor/livewire/livewire/dist/livewire.esm'; |
|||
import bs from './alpine/bs'; |
|||
import alpineAxios from './alpine/axios'; |
|||
import alpineSelect2 from "./alpine/select2.js"; |
|||
import alpineQuill from "./alpine/quill.js"; |
|||
import alpineAsk from "./alpine/ask.js"; |
|||
import AlpineChart from "./alpine/chart.js"; |
|||
|
|||
// 1. Expose both instances globally so Blade directives can see them
|
|||
window.Alpine = Alpine; |
|||
window.Livewire = Livewire; |
|||
// 1. Expose both instances globally so Blade directives can see them
|
|||
window.Alpine = Alpine; |
|||
window.Livewire = Livewire; |
|||
|
|||
// 2. Wrap plugin registration inside the initialization event listener
|
|||
Alpine.plugin(bs); |
|||
Alpine.plugin(alpineAxios); |
|||
Alpine.plugin(alpineAsk); |
|||
Alpine.plugin(alpineSelect2); |
|||
Alpine.plugin(alpineQuill); |
|||
Alpine.plugin(AlpineChart); |
|||
// 2. Wrap plugin registration inside the initialization event listener
|
|||
Alpine.plugin(bs); |
|||
Alpine.plugin(alpineAxios); |
|||
Alpine.plugin(alpineAsk); |
|||
Alpine.plugin(alpineSelect2); |
|||
Alpine.plugin(alpineQuill); |
|||
Alpine.plugin(AlpineChart); |
|||
|
|||
// 3. Boot Livewire
|
|||
Livewire.start(); |
|||
// 3. Boot Livewire
|
|||
Livewire.start(); |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue