From a39fe4627227fb658f1ead102dc7c4bd506408fa Mon Sep 17 00:00:00 2001 From: Syarif Ubaidillah Date: Thu, 24 Sep 2026 13:30:00 +0700 Subject: [PATCH] feat: add Billing domain with models, DTOs, actions, and runtime relationship support; refactor mappers and event-driven logic - Introduced Billing-specific models (Invoice, Product, Discount) and DTOs for catalog, discount, and invoicing functionalities. - Implemented runtime relationships using RelationshipServiceProvider. - Refactored `DataPayloadMapper` to `DataPayloadMapper` under `Contract` for improved consistency. - Added `AcademicProductGenerator` for dynamic product identifiers. - Streamlined API registry and mapper dependencies across domains. - Enhanced `AdmissionFeeRateWasSetUp` event to dispatch enriched product data. --- .../Integration/Mappers/FacultyDataMapper.php | 2 +- .../Mappers/StudyProgramDataMapper.php | 2 +- .../Integration/Mappers/TermDataMapper.php | 2 +- .../Providers/AcademicServiceProvider.php | 2 +- .../Generator/AcademicProductGenerator.php | 60 +++++++++ .../SetUpAdmissionFeeRates.php | 9 +- .../Pricing/AdmissionFeeRateWasSetUp.php | 2 +- .../Mappers/AcademicProgramDataMapper.php | 2 +- .../Mappers/AdmissionScheduleDataMapper.php | 2 +- .../Mappers/AdmissionTrackDataMapper.php | 2 +- .../Integration/Mappers/FeeRateDataMapper.php | 2 +- .../Integration/Mappers/FeeTypeDataMapper.php | 2 +- .../Providers/AdmissionServiceProvider.php | 2 +- .../Catalog/ProvisionProductFromFeeRate.php | 11 ++ .../Actions/Discounts/DefineDiscount.php | 11 ++ .../Actions/Invoicing/DraftInvoice.php | 11 ++ .../DTOs/Catalog/ProvisionProductDTO.php | 13 ++ .../DTOs/Discounts/DefineDiscountDTO.php | 19 +++ .../DTOs/Invoicing/DraftInvoiceDTO.php | 14 ++ .../DTOs/Products/ProductIdentifierDTO.php | 24 ++++ .../Billing/Enums/Catalog/AccountBalance.php | 9 ++ .../Enums/Catalog/AccountClassification.php | 12 ++ .../Enums/Discounts/DiscountOperator.php | 12 ++ .../Billing/Enums/Discounts/DiscountType.php | 9 ++ .../Billing/Enums/Invoicing/InvoiceStatus.php | 11 ++ .../Billing/Enums/Invoicing/InvoiceType.php | 10 ++ app/Domains/Billing/Models/ChartOfAccount.php | 21 +++ app/Domains/Billing/Models/Discount.php | 29 +++++ .../Billing/Models/DiscountSnapshot.php | 46 +++++++ app/Domains/Billing/Models/Invoice.php | 27 ++++ app/Domains/Billing/Models/InvoiceItem.php | 38 ++++++ app/Domains/Billing/Models/Product.php | 32 +++++ .../Providers/BillingServiceProvider.php | 23 ++++ .../Providers/RelationshipServiceProvider.php | 38 ++++++ .../Contract/ProductIdentifierGenerator.php | 27 ++++ .../Actions/Api/SynchronizeWithApi.php | 2 +- app/Domains/Channel/Enums/Api/ApiType.php | 4 +- .../ApiMapperRegistry.php | 4 +- .../Integration/Mappers/UserDataMapper.php | 2 +- ...alizeVirtualAccountForPublishedInvoice.php | 11 ++ .../Integration/RunGenericImportPipeline.php | 2 +- .../Providers/SystemServiceProvider.php | 12 ++ .../DataPayloadMapper.php | 2 +- app/Providers/AppServiceProvider.php | 3 + bootstrap/providers.php | 3 + .../ChartOfAccountFactory.php | 6 +- .../factories/Billing/DiscountFactory.php | 19 +++ .../Billing/DiscountRulesFactory.php | 19 +++ .../Billing/DiscountSnapshotFactory.php | 19 +++ .../{Finance => Billing}/InvoiceFactory.php | 6 +- .../factories/Billing/InvoiceItemFactory.php | 19 +++ database/factories/Billing/ProductFactory.php | 19 +++ database/factories/Finance/PaymentFactory.php | 19 --- database/factories/System/FileFactory.php | 19 --- ...026_07_06_035952_create_payments_table.php | 35 ----- ...4_create_student_invoice_reports_table.php | 43 ------ ...15_071129_create_invoice_reports_table.php | 38 ------ ...022815_create_chart_of_accounts_table.php} | 8 +- ...026_09_08_022816_create_products_table.php | 31 +++++ ...26_09_08_022817_create_invoices_table.php} | 15 +-- ...9_08_022818_create_invoice_items_table.php | 37 ++++++ ...6_09_08_022819_create_discounts_table.php} | 10 +- ...022821_create_discount_snapshots_table.php | 35 +++++ .../container/accordion-item.blade.php | 7 +- .../components/container/accordion.blade.php | 6 +- .../⚡detail-view/detail-view.blade.php | 122 ++++++++---------- .../⚡detail-view/detail-view.php | 2 +- .../RunGenericImportPipelineTest.php | 2 +- 68 files changed, 847 insertions(+), 272 deletions(-) create mode 100644 app/Domains/Academic/Support/Generator/AcademicProductGenerator.php create mode 100644 app/Domains/Billing/Actions/Catalog/ProvisionProductFromFeeRate.php create mode 100644 app/Domains/Billing/Actions/Discounts/DefineDiscount.php create mode 100644 app/Domains/Billing/Actions/Invoicing/DraftInvoice.php create mode 100644 app/Domains/Billing/DTOs/Catalog/ProvisionProductDTO.php create mode 100644 app/Domains/Billing/DTOs/Discounts/DefineDiscountDTO.php create mode 100644 app/Domains/Billing/DTOs/Invoicing/DraftInvoiceDTO.php create mode 100644 app/Domains/Billing/DTOs/Products/ProductIdentifierDTO.php create mode 100644 app/Domains/Billing/Enums/Catalog/AccountBalance.php create mode 100644 app/Domains/Billing/Enums/Catalog/AccountClassification.php create mode 100644 app/Domains/Billing/Enums/Discounts/DiscountOperator.php create mode 100644 app/Domains/Billing/Enums/Discounts/DiscountType.php create mode 100644 app/Domains/Billing/Enums/Invoicing/InvoiceStatus.php create mode 100644 app/Domains/Billing/Enums/Invoicing/InvoiceType.php create mode 100644 app/Domains/Billing/Models/ChartOfAccount.php create mode 100644 app/Domains/Billing/Models/Discount.php create mode 100644 app/Domains/Billing/Models/DiscountSnapshot.php create mode 100644 app/Domains/Billing/Models/Invoice.php create mode 100644 app/Domains/Billing/Models/InvoiceItem.php create mode 100644 app/Domains/Billing/Models/Product.php create mode 100644 app/Domains/Billing/Providers/BillingServiceProvider.php create mode 100644 app/Domains/Billing/Providers/RelationshipServiceProvider.php create mode 100644 app/Domains/Billing/Support/Contract/ProductIdentifierGenerator.php rename app/Domains/Channel/Support/{Mapper => Registry}/ApiMapperRegistry.php (89%) create mode 100644 app/Domains/Payment/Listeners/VirtualAccount/InitializeVirtualAccountForPublishedInvoice.php rename app/Domains/System/Support/{Integration => Contract}/DataPayloadMapper.php (93%) rename database/factories/{Finance => Billing}/ChartOfAccountFactory.php (75%) create mode 100644 database/factories/Billing/DiscountFactory.php create mode 100644 database/factories/Billing/DiscountRulesFactory.php create mode 100644 database/factories/Billing/DiscountSnapshotFactory.php rename database/factories/{Finance => Billing}/InvoiceFactory.php (75%) create mode 100644 database/factories/Billing/InvoiceItemFactory.php create mode 100644 database/factories/Billing/ProductFactory.php delete mode 100644 database/factories/Finance/PaymentFactory.php delete mode 100644 database/factories/System/FileFactory.php delete mode 100644 database/migrations/2026_07_06_035952_create_payments_table.php delete mode 100644 database/migrations/2026_07_15_071124_create_student_invoice_reports_table.php delete mode 100644 database/migrations/2026_07_15_071129_create_invoice_reports_table.php rename database/migrations/{2026_07_06_034148_create_chart_of_accounts_table.php => 2026_09_08_022815_create_chart_of_accounts_table.php} (78%) create mode 100644 database/migrations/2026_09_08_022816_create_products_table.php rename database/migrations/{2026_07_06_035903_create_invoices_table.php => 2026_09_08_022817_create_invoices_table.php} (71%) create mode 100644 database/migrations/2026_09_08_022818_create_invoice_items_table.php rename database/migrations/{2026_07_13_045042_create_product_mappings_table.php => 2026_09_08_022819_create_discounts_table.php} (65%) create mode 100644 database/migrations/2026_09_08_022821_create_discount_snapshots_table.php diff --git a/app/Domains/Academic/Integration/Mappers/FacultyDataMapper.php b/app/Domains/Academic/Integration/Mappers/FacultyDataMapper.php index 1ec57b3..a52fe94 100644 --- a/app/Domains/Academic/Integration/Mappers/FacultyDataMapper.php +++ b/app/Domains/Academic/Integration/Mappers/FacultyDataMapper.php @@ -7,7 +7,7 @@ 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 App\Domains\System\Support\Contract\DataPayloadMapper; use Illuminate\Database\Eloquent\Model; class FacultyDataMapper implements DataPayloadMapper diff --git a/app/Domains/Academic/Integration/Mappers/StudyProgramDataMapper.php b/app/Domains/Academic/Integration/Mappers/StudyProgramDataMapper.php index 01f98ff..f5d3af5 100644 --- a/app/Domains/Academic/Integration/Mappers/StudyProgramDataMapper.php +++ b/app/Domains/Academic/Integration/Mappers/StudyProgramDataMapper.php @@ -9,7 +9,7 @@ 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 App\Domains\System\Support\Contract\DataPayloadMapper; use Illuminate\Database\Eloquent\Model; class StudyProgramDataMapper implements DataPayloadMapper diff --git a/app/Domains/Academic/Integration/Mappers/TermDataMapper.php b/app/Domains/Academic/Integration/Mappers/TermDataMapper.php index 8999852..90ed671 100644 --- a/app/Domains/Academic/Integration/Mappers/TermDataMapper.php +++ b/app/Domains/Academic/Integration/Mappers/TermDataMapper.php @@ -7,7 +7,7 @@ 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 App\Domains\System\Support\Contract\DataPayloadMapper; use Carbon\CarbonImmutable; use Illuminate\Database\Eloquent\Model; diff --git a/app/Domains/Academic/Providers/AcademicServiceProvider.php b/app/Domains/Academic/Providers/AcademicServiceProvider.php index 9db0e07..0e54b64 100644 --- a/app/Domains/Academic/Providers/AcademicServiceProvider.php +++ b/app/Domains/Academic/Providers/AcademicServiceProvider.php @@ -6,7 +6,7 @@ use App\Domains\Academic\Integration\Mappers\FacultyDataMapper; use App\Domains\Academic\Integration\Mappers\StudyProgramDataMapper; use App\Domains\Academic\Integration\Mappers\TermDataMapper; use App\Domains\Channel\Enums\Api\ApiType; -use App\Domains\Channel\Support\Mapper\ApiMapperRegistry; +use App\Domains\Channel\Support\Registry\ApiMapperRegistry; use App\Domains\System\Traits\Provider\RegistersDomainEvents; use Illuminate\Support\ServiceProvider; diff --git a/app/Domains/Academic/Support/Generator/AcademicProductGenerator.php b/app/Domains/Academic/Support/Generator/AcademicProductGenerator.php new file mode 100644 index 0000000..cb3f2a5 --- /dev/null +++ b/app/Domains/Academic/Support/Generator/AcademicProductGenerator.php @@ -0,0 +1,60 @@ +loadMissing([ + 'admissionSchedule.admissionTrack', + 'admissionSchedule.term', + 'academicProgram', + 'feeType', + ]); + + $code = sprintf( + '%s-%s-%s-%s', + str($rate->admissionSchedule->admissionTrack->name)->initials(), + 'AC'.$rate->academicProgram->code, + 'FT'.$rate->feeType->code, + $rate->admissionSchedule->term->year_study + ); + + $name = sprintf( + '%s %s %s %s', + $rate->admissionSchedule->admissionTrack->name, + $rate->academicProgram->name, + $rate->feeType->name, + $rate->admissionSchedule->term->year_study + ); + + return new ProductIdentifierDTO( + code: $code, + name: $name, + feeRateType: $rate->getMorphClass(), + feeRateId: $rate->id + ); + } +} diff --git a/app/Domains/Admission/Actions/AdmissionPricing/SetUpAdmissionFeeRates.php b/app/Domains/Admission/Actions/AdmissionPricing/SetUpAdmissionFeeRates.php index cf9343c..2aae407 100644 --- a/app/Domains/Admission/Actions/AdmissionPricing/SetUpAdmissionFeeRates.php +++ b/app/Domains/Admission/Actions/AdmissionPricing/SetUpAdmissionFeeRates.php @@ -2,12 +2,17 @@ namespace App\Domains\Admission\Actions\AdmissionPricing; +use App\Domains\Academic\Support\Generator\AcademicProductGenerator; use App\Domains\Admission\DTOs\AdmissionPricing\AdmissionFeeRatesDTO; use App\Domains\Admission\Events\Pricing\AdmissionFeeRateWasSetUp; use App\Domains\Admission\Models\FeeRate; class SetUpAdmissionFeeRates { + public function __construct(protected AcademicProductGenerator $generator) + { + } + public function execute(AdmissionFeeRatesDTO $dto): void { $rate = FeeRate::updateOrCreate([ @@ -19,8 +24,8 @@ class SetUpAdmissionFeeRates 'amount' => $dto->amount, ]); - $name = $rate->generateProductName(); + $product = $this->generator->generate($rate); - AdmissionFeeRateWasSetUp::dispatch(productName: $name); + AdmissionFeeRateWasSetUp::dispatch($product); } } diff --git a/app/Domains/Admission/Events/Pricing/AdmissionFeeRateWasSetUp.php b/app/Domains/Admission/Events/Pricing/AdmissionFeeRateWasSetUp.php index 0256add..b275262 100644 --- a/app/Domains/Admission/Events/Pricing/AdmissionFeeRateWasSetUp.php +++ b/app/Domains/Admission/Events/Pricing/AdmissionFeeRateWasSetUp.php @@ -10,6 +10,6 @@ class AdmissionFeeRateWasSetUp use Dispatchable, SerializesModels; public function __construct( - public string $productName + public object $product ) {} } diff --git a/app/Domains/Admission/Integration/Mappers/AcademicProgramDataMapper.php b/app/Domains/Admission/Integration/Mappers/AcademicProgramDataMapper.php index ba6c638..1ec0bc9 100644 --- a/app/Domains/Admission/Integration/Mappers/AcademicProgramDataMapper.php +++ b/app/Domains/Admission/Integration/Mappers/AcademicProgramDataMapper.php @@ -6,7 +6,7 @@ use App\Domains\Admission\Actions\StudyModeProvisioning\DefineAcademicProgram; use App\Domains\Admission\DTOs\StudyModeProvisioning\DefineAcademicProgramDTO; use App\Domains\Admission\Models\AcademicProgram; use App\Domains\System\Enums\LifecycleStatus; -use App\Domains\System\Support\Integration\DataPayloadMapper; +use App\Domains\System\Support\Contract\DataPayloadMapper; use Illuminate\Database\Eloquent\Model; class AcademicProgramDataMapper implements DataPayloadMapper diff --git a/app/Domains/Admission/Integration/Mappers/AdmissionScheduleDataMapper.php b/app/Domains/Admission/Integration/Mappers/AdmissionScheduleDataMapper.php index 5b1b21b..2543680 100644 --- a/app/Domains/Admission/Integration/Mappers/AdmissionScheduleDataMapper.php +++ b/app/Domains/Admission/Integration/Mappers/AdmissionScheduleDataMapper.php @@ -9,7 +9,7 @@ use App\Domains\Admission\DTOs\IntakeScheduling\AdjustAdmissionScheduleDurationD use App\Domains\Admission\DTOs\IntakeScheduling\DefineAdmissionScheduleDTO; use App\Domains\Admission\Models\AdmissionSchedule; use App\Domains\Admission\Models\AdmissionTrack; -use App\Domains\System\Support\Integration\DataPayloadMapper; +use App\Domains\System\Support\Contract\DataPayloadMapper; use Carbon\CarbonImmutable; use Illuminate\Database\Eloquent\Model; diff --git a/app/Domains/Admission/Integration/Mappers/AdmissionTrackDataMapper.php b/app/Domains/Admission/Integration/Mappers/AdmissionTrackDataMapper.php index fb7d698..920ad5e 100644 --- a/app/Domains/Admission/Integration/Mappers/AdmissionTrackDataMapper.php +++ b/app/Domains/Admission/Integration/Mappers/AdmissionTrackDataMapper.php @@ -6,7 +6,7 @@ use App\Domains\Admission\Actions\IntakeSourcing\InitializeAdmissionTrack; use App\Domains\Admission\DTOs\IntakeSourcing\InitializeAdmissionTrackDTO; use App\Domains\Admission\Models\AdmissionTrack; use App\Domains\System\Enums\LifecycleStatus; -use App\Domains\System\Support\Integration\DataPayloadMapper; +use App\Domains\System\Support\Contract\DataPayloadMapper; use Illuminate\Database\Eloquent\Model; class AdmissionTrackDataMapper implements DataPayloadMapper diff --git a/app/Domains/Admission/Integration/Mappers/FeeRateDataMapper.php b/app/Domains/Admission/Integration/Mappers/FeeRateDataMapper.php index a902cab..cde91c4 100644 --- a/app/Domains/Admission/Integration/Mappers/FeeRateDataMapper.php +++ b/app/Domains/Admission/Integration/Mappers/FeeRateDataMapper.php @@ -10,7 +10,7 @@ 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; +use App\Domains\System\Support\Contract\DataPayloadMapper; class FeeRateDataMapper implements DataPayloadMapper { diff --git a/app/Domains/Admission/Integration/Mappers/FeeTypeDataMapper.php b/app/Domains/Admission/Integration/Mappers/FeeTypeDataMapper.php index 70c892b..f4a697b 100644 --- a/app/Domains/Admission/Integration/Mappers/FeeTypeDataMapper.php +++ b/app/Domains/Admission/Integration/Mappers/FeeTypeDataMapper.php @@ -6,7 +6,7 @@ use App\Domains\Admission\Actions\AdmissionPricing\DefineFeeType; use App\Domains\Admission\DTOs\AdmissionPricing\FeeTypeDTO; use App\Domains\Admission\Enums\BillingCycle; use App\Domains\Admission\Models\FeeType; -use App\Domains\System\Support\Integration\DataPayloadMapper; +use App\Domains\System\Support\Contract\DataPayloadMapper; use Illuminate\Database\Eloquent\Model; class FeeTypeDataMapper implements DataPayloadMapper diff --git a/app/Domains/Admission/Providers/AdmissionServiceProvider.php b/app/Domains/Admission/Providers/AdmissionServiceProvider.php index c915bd9..95a5e5e 100644 --- a/app/Domains/Admission/Providers/AdmissionServiceProvider.php +++ b/app/Domains/Admission/Providers/AdmissionServiceProvider.php @@ -8,7 +8,7 @@ use App\Domains\Admission\Integration\Mappers\AdmissionTrackDataMapper; use App\Domains\Admission\Integration\Mappers\FeeRateDataMapper; use App\Domains\Admission\Integration\Mappers\FeeTypeDataMapper; use App\Domains\Channel\Enums\Api\ApiType; -use App\Domains\Channel\Support\Mapper\ApiMapperRegistry; +use App\Domains\Channel\Support\Registry\ApiMapperRegistry; use App\Domains\System\Traits\Provider\RegistersDomainEvents; use Illuminate\Support\ServiceProvider; diff --git a/app/Domains/Billing/Actions/Catalog/ProvisionProductFromFeeRate.php b/app/Domains/Billing/Actions/Catalog/ProvisionProductFromFeeRate.php new file mode 100644 index 0000000..bfed9c9 --- /dev/null +++ b/app/Domains/Billing/Actions/Catalog/ProvisionProductFromFeeRate.php @@ -0,0 +1,11 @@ + $rules + */ + public function __construct( + public string $name, + public string $type, + public string $value, + public array $rules, + ) {} +} diff --git a/app/Domains/Billing/DTOs/Invoicing/DraftInvoiceDTO.php b/app/Domains/Billing/DTOs/Invoicing/DraftInvoiceDTO.php new file mode 100644 index 0000000..e692606 --- /dev/null +++ b/app/Domains/Billing/DTOs/Invoicing/DraftInvoiceDTO.php @@ -0,0 +1,14 @@ +'; + case GREATER_OR_EQUAL = '>='; + case LESS = '<'; + case LESS_OR_EQUAL = '<='; + case EQUAL = '='; +} diff --git a/app/Domains/Billing/Enums/Discounts/DiscountType.php b/app/Domains/Billing/Enums/Discounts/DiscountType.php new file mode 100644 index 0000000..b03cbef --- /dev/null +++ b/app/Domains/Billing/Enums/Discounts/DiscountType.php @@ -0,0 +1,9 @@ +belongsTo(ChartOfAccount::class); + } +} diff --git a/app/Domains/Billing/Models/DiscountSnapshot.php b/app/Domains/Billing/Models/DiscountSnapshot.php new file mode 100644 index 0000000..073d1c9 --- /dev/null +++ b/app/Domains/Billing/Models/DiscountSnapshot.php @@ -0,0 +1,46 @@ +belongsTo(ChartOfAccount::class); + } + + public function discount(): BelongsTo + { + return $this->belongsTo(Discount::class); + } + + public function invoice(): BelongsTo + { + return $this->belongsTo(Invoice::class); + } + + public function invoiceItem(): BelongsTo + { + return $this->belongsTo(InvoiceItem::class); + } +} diff --git a/app/Domains/Billing/Models/Invoice.php b/app/Domains/Billing/Models/Invoice.php new file mode 100644 index 0000000..7f8ecba --- /dev/null +++ b/app/Domains/Billing/Models/Invoice.php @@ -0,0 +1,27 @@ +belongsTo(Product::class); + } + + public function chartOfAccount(): BelongsTo + { + return $this->belongsTo(ChartOfAccount::class); + } +} diff --git a/app/Domains/Billing/Models/Product.php b/app/Domains/Billing/Models/Product.php new file mode 100644 index 0000000..8938ce9 --- /dev/null +++ b/app/Domains/Billing/Models/Product.php @@ -0,0 +1,32 @@ +belongsTo(ChartOfAccount::class); + } + + public function pricing(): MorphTo + { + return $this->morphTo(); + } +} diff --git a/app/Domains/Billing/Providers/BillingServiceProvider.php b/app/Domains/Billing/Providers/BillingServiceProvider.php new file mode 100644 index 0000000..2966867 --- /dev/null +++ b/app/Domains/Billing/Providers/BillingServiceProvider.php @@ -0,0 +1,23 @@ +app->register(RelationshipServiceProvider::class); + } + + public function boot(): void + { + $this->registerEvents(); + } +} diff --git a/app/Domains/Billing/Providers/RelationshipServiceProvider.php b/app/Domains/Billing/Providers/RelationshipServiceProvider.php new file mode 100644 index 0000000..b27e582 --- /dev/null +++ b/app/Domains/Billing/Providers/RelationshipServiceProvider.php @@ -0,0 +1,38 @@ + + * $order->belongsTo(Customer::class, 'customer_id') + * ); + */ + public function boot(): void + { + Invoice::resolveRelationUsing('client', function (Invoice $invoice) { + return $invoice->belongsTo(Client::class, 'client_id'); + }); + Client::resolveRelationUsing('invoices', function (Client $client) { + return $client->hasMany(Invoice::class, 'client_id'); + }); + } +} diff --git a/app/Domains/Billing/Support/Contract/ProductIdentifierGenerator.php b/app/Domains/Billing/Support/Contract/ProductIdentifierGenerator.php new file mode 100644 index 0000000..98ee6ce --- /dev/null +++ b/app/Domains/Billing/Support/Contract/ProductIdentifierGenerator.php @@ -0,0 +1,27 @@ + $this->copy() ->tz(config('app.display_timezone', 'UTC'))); + + + Str::macro('initials', function (string $name, int $limit = 2) { + $words = explode(' ', trim($name)); + + $initials = collect($words)->map(function ($word) { + return substr($word, 0, 1); + })->take($limit)->implode(''); + + return strtoupper($initials); + }); } } diff --git a/app/Domains/System/Support/Integration/DataPayloadMapper.php b/app/Domains/System/Support/Contract/DataPayloadMapper.php similarity index 93% rename from app/Domains/System/Support/Integration/DataPayloadMapper.php rename to app/Domains/System/Support/Contract/DataPayloadMapper.php index b29c829..f490887 100644 --- a/app/Domains/System/Support/Integration/DataPayloadMapper.php +++ b/app/Domains/System/Support/Contract/DataPayloadMapper.php @@ -1,6 +1,6 @@ + */ +class DiscountFactory extends Factory +{ + protected $model = Discount::class; + + public function definition(): array + { + return []; + } +} \ No newline at end of file diff --git a/database/factories/Billing/DiscountRulesFactory.php b/database/factories/Billing/DiscountRulesFactory.php new file mode 100644 index 0000000..c2acf86 --- /dev/null +++ b/database/factories/Billing/DiscountRulesFactory.php @@ -0,0 +1,19 @@ + + */ +class DiscountRulesFactory extends Factory +{ + protected $model = DiscountRules::class; + + public function definition(): array + { + return []; + } +} \ No newline at end of file diff --git a/database/factories/Billing/DiscountSnapshotFactory.php b/database/factories/Billing/DiscountSnapshotFactory.php new file mode 100644 index 0000000..8bcc64f --- /dev/null +++ b/database/factories/Billing/DiscountSnapshotFactory.php @@ -0,0 +1,19 @@ + + */ +class DiscountSnapshotFactory extends Factory +{ + protected $model = DiscountSnapshot::class; + + public function definition(): array + { + return []; + } +} \ No newline at end of file diff --git a/database/factories/Finance/InvoiceFactory.php b/database/factories/Billing/InvoiceFactory.php similarity index 75% rename from database/factories/Finance/InvoiceFactory.php rename to database/factories/Billing/InvoiceFactory.php index 1906a3d..a4dd910 100644 --- a/database/factories/Finance/InvoiceFactory.php +++ b/database/factories/Billing/InvoiceFactory.php @@ -1,8 +1,8 @@ + */ +class InvoiceItemFactory extends Factory +{ + protected $model = InvoiceItem::class; + + public function definition(): array + { + return []; + } +} \ No newline at end of file diff --git a/database/factories/Billing/ProductFactory.php b/database/factories/Billing/ProductFactory.php new file mode 100644 index 0000000..d45411c --- /dev/null +++ b/database/factories/Billing/ProductFactory.php @@ -0,0 +1,19 @@ + + */ +class ProductFactory extends Factory +{ + protected $model = Product::class; + + public function definition(): array + { + return []; + } +} \ No newline at end of file diff --git a/database/factories/Finance/PaymentFactory.php b/database/factories/Finance/PaymentFactory.php deleted file mode 100644 index d3b562d..0000000 --- a/database/factories/Finance/PaymentFactory.php +++ /dev/null @@ -1,19 +0,0 @@ - - */ -class PaymentFactory extends Factory -{ - protected $model = Payment::class; - - public function definition(): array - { - return []; - } -} diff --git a/database/factories/System/FileFactory.php b/database/factories/System/FileFactory.php deleted file mode 100644 index 4ef537b..0000000 --- a/database/factories/System/FileFactory.php +++ /dev/null @@ -1,19 +0,0 @@ - - */ -class FileFactory extends Factory -{ - protected $model = File::class; - - public function definition(): array - { - return []; - } -} diff --git a/database/migrations/2026_07_06_035952_create_payments_table.php b/database/migrations/2026_07_06_035952_create_payments_table.php deleted file mode 100644 index 0eb4f73..0000000 --- a/database/migrations/2026_07_06_035952_create_payments_table.php +++ /dev/null @@ -1,35 +0,0 @@ -id(); - $table->ulid(); - $table->integer('virtual_account'); - $table->string('bank_ref_id'); - $table->string('bank'); - $table->string('nominal'); - $table->date('valid_until'); - $table->string('status'); - $table->foreignId('invoice_id')->constrained()->restrictOnDelete(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('payments'); - } -}; diff --git a/database/migrations/2026_07_15_071124_create_student_invoice_reports_table.php b/database/migrations/2026_07_15_071124_create_student_invoice_reports_table.php deleted file mode 100644 index 10b9e71..0000000 --- a/database/migrations/2026_07_15_071124_create_student_invoice_reports_table.php +++ /dev/null @@ -1,43 +0,0 @@ -id(); - $table->foreignId('invoice_id') - ->constrained('invoices') - ->restrictOnDelete(); - $table->string('invoice_name'); - $table->string('student_name'); - $table->string('faculty_id'); - $table->string('faculty_name'); - $table->string('study_program_id'); - $table->string('study_program_name'); - $table->string('term_id'); - $table->string('term_name'); - $table->integer('amount'); - $table->integer('paid_amount'); - $table->dateTime('issued_at'); - $table->dateTime('paid_at'); - $table->string('status'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('student_invoice_reports'); - } -}; diff --git a/database/migrations/2026_07_15_071129_create_invoice_reports_table.php b/database/migrations/2026_07_15_071129_create_invoice_reports_table.php deleted file mode 100644 index c4bf66c..0000000 --- a/database/migrations/2026_07_15_071129_create_invoice_reports_table.php +++ /dev/null @@ -1,38 +0,0 @@ -id(); - $table->foreignId('invoice_id') - ->constrained('invoices') - ->restrictOnDelete(); - $table->string('invoice_name'); - $table->string('customer_name'); - $table->string('customer_id'); - $table->integer('amount'); - $table->integer('paid_amount'); - $table->dateTime('issued_at'); - $table->dateTime('paid_at'); - $table->string('status'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('invoice_reports'); - } -}; diff --git a/database/migrations/2026_07_06_034148_create_chart_of_accounts_table.php b/database/migrations/2026_09_08_022815_create_chart_of_accounts_table.php similarity index 78% rename from database/migrations/2026_07_06_034148_create_chart_of_accounts_table.php rename to database/migrations/2026_09_08_022815_create_chart_of_accounts_table.php index d2342a9..56be674 100644 --- a/database/migrations/2026_07_06_034148_create_chart_of_accounts_table.php +++ b/database/migrations/2026_09_08_022815_create_chart_of_accounts_table.php @@ -14,13 +14,11 @@ return new class extends Migration Schema::create('chart_of_accounts', function (Blueprint $table) { $table->id(); $table->ulid(); - $table->string('code'); + $table->string('code')->unique(); $table->string('name'); $table->string('classification'); - $table->string('status'); - $table->unsignedBigInteger('parent_id') - ->nullable() - ->index(); + $table->string('normal_balance'); + $table->foreignId('parent_id')->nullable(); $table->timestamps(); }); } diff --git a/database/migrations/2026_09_08_022816_create_products_table.php b/database/migrations/2026_09_08_022816_create_products_table.php new file mode 100644 index 0000000..68eab07 --- /dev/null +++ b/database/migrations/2026_09_08_022816_create_products_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('code')->unique(); + $table->string('name'); + $table->morphs('pricing'); + $table->foreignId('chart_of_account_id')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('products'); + } +}; diff --git a/database/migrations/2026_07_06_035903_create_invoices_table.php b/database/migrations/2026_09_08_022817_create_invoices_table.php similarity index 71% rename from database/migrations/2026_07_06_035903_create_invoices_table.php rename to database/migrations/2026_09_08_022817_create_invoices_table.php index fe1a2a8..d035f32 100644 --- a/database/migrations/2026_07_06_035903_create_invoices_table.php +++ b/database/migrations/2026_09_08_022817_create_invoices_table.php @@ -13,17 +13,16 @@ return new class extends Migration { Schema::create('invoices', function (Blueprint $table) { $table->id(); - $table->ulid(); + $table->string('code')->unique(); $table->string('name'); - $table->string('detail'); + $table->json('client_detail'); + $table->string('client_ref_id'); $table->string('type'); - $table->string('amount'); + $table->integer('gross_amount'); + $table->integer('discount_amount'); + $table->integer('net_amount'); $table->string('status'); - $table->string('client_ref_id'); - $table->string('client_id')->index(); - $table->foreignId('chart_of_account_id') - ->constrained() - ->restrictOnDelete(); + $table->foreignId('client_id'); $table->timestamps(); }); } diff --git a/database/migrations/2026_09_08_022818_create_invoice_items_table.php b/database/migrations/2026_09_08_022818_create_invoice_items_table.php new file mode 100644 index 0000000..5ebea72 --- /dev/null +++ b/database/migrations/2026_09_08_022818_create_invoice_items_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('code'); + $table->string('name'); + $table->integer('quantity'); + $table->integer('unit_price'); + $table->integer('gross_amount'); + $table->integer('discount_amount'); + $table->integer('net_amount'); + $table->foreignId('invoice_id')->constrained(); + $table->foreignId('product_id')->constrained(); + $table->foreignId('chart_of_account_id')->constrained(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('invoice_items'); + } +}; diff --git a/database/migrations/2026_07_13_045042_create_product_mappings_table.php b/database/migrations/2026_09_08_022819_create_discounts_table.php similarity index 65% rename from database/migrations/2026_07_13_045042_create_product_mappings_table.php rename to database/migrations/2026_09_08_022819_create_discounts_table.php index 9e09670..fe268bc 100644 --- a/database/migrations/2026_07_13_045042_create_product_mappings_table.php +++ b/database/migrations/2026_09_08_022819_create_discounts_table.php @@ -11,12 +11,14 @@ return new class extends Migration */ public function up(): void { - Schema::create('product_mappings', function (Blueprint $table) { + Schema::create('discounts', function (Blueprint $table) { $table->id(); - $table->string('code'); $table->string('name'); + $table->string('type'); + $table->integer('value'); + $table->json('rules')->default('{}'); + $table->boolean('is_active'); $table->foreignId('chart_of_account_id')->constrained(); - $table->unsignedBigInteger('client_id')->index()->nullable(); $table->timestamps(); }); } @@ -26,6 +28,6 @@ return new class extends Migration */ public function down(): void { - Schema::dropIfExists('product_mappings'); + Schema::dropIfExists('discounts'); } }; diff --git a/database/migrations/2026_09_08_022821_create_discount_snapshots_table.php b/database/migrations/2026_09_08_022821_create_discount_snapshots_table.php new file mode 100644 index 0000000..56fc32c --- /dev/null +++ b/database/migrations/2026_09_08_022821_create_discount_snapshots_table.php @@ -0,0 +1,35 @@ +id(); + $table->string('name'); + $table->string('type'); + $table->integer('value'); + $table->integer('amount'); + $table->foreignId('chart_of_account_id')->constrained(); + $table->foreignId('discount_id')->constrained(); + $table->foreignId('invoice_id')->constrained(); + $table->foreignId('invoice_item_id')->nullable()->constrained()->nullOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('discount_snapshots'); + } +}; diff --git a/resources/views/components/container/accordion-item.blade.php b/resources/views/components/container/accordion-item.blade.php index 273b54a..8366e5f 100644 --- a/resources/views/components/container/accordion-item.blade.php +++ b/resources/views/components/container/accordion-item.blade.php @@ -1,5 +1,8 @@ @props(['itemId' => md5(microtime(true)), 'header' => 'Accordion', 'content' => null]) + @aware(['id' => md5(microtime(true))]) +@php($class = 'accordion-button collapsed bg-body-tertiary py-3 px-4 fw-semibold text-body shadow-none d-flex align-items-center gap-3') +

-

+ + + + {{ $study->name }} + - -
-
- -
- - - - -
- - {{ __('domains/admission/field.academic_program.name') }} - + + +
+ + + + + - + @endforeach + + + + @foreach ($this->academicPrograms as $program) + + + @foreach ($this->feeTypes as $fee) - + @php($lookupKey = "{$study->id}_{$program->id}_{$fee->id}") + @php($feeRate = isset($this->feeRates[$lookupKey]) ? $this->feeRates[$lookupKey] : new Money(0)) + @endforeach - - - @foreach ($this->academicPrograms as $program) - - - - @foreach ($this->feeTypes as $fee) - @php($lookupKey = "{$study->id}_{$program->id}_{$fee->id}") - @php($feeRate = isset($this->feeRates[$lookupKey]) ? $this->feeRates[$lookupKey] : new Money(0)) - - @endforeach - - @endforeach - -
+ + {{ __('domains/admission/field.academic_program.name') }} + + + @foreach ($this->feeTypes as $fee) + + {{ $fee->name }}
+ {{ $program->name }} + - {{ $fee->name }} - + +
- {{ $program->name }} - - -
-
+ @endforeach + +
-
-
+ +
@endforeach -
+ diff --git a/resources/views/pages/admission/admission-schedule/⚡detail-view/detail-view.php b/resources/views/pages/admission/admission-schedule/⚡detail-view/detail-view.php index a6df5b5..fe15a71 100644 --- a/resources/views/pages/admission/admission-schedule/⚡detail-view/detail-view.php +++ b/resources/views/pages/admission/admission-schedule/⚡detail-view/detail-view.php @@ -20,7 +20,7 @@ use Livewire\Attributes\Locked; use Livewire\Component; new #[Layout('components.layouts.app')] -#[Seo(title: 'domains/admission/seo.admission_schedule-detail.title', description: 'domains/admission/seo.admission_schedule-detail.description', keywords: 'domains/admission/seo.admission_schedule-detail.keywords')] +#[Seo(title: 'domains/admission/seo.admission_schedule_detail.title', description: 'domains/admission/seo.admission_schedule_detail.description', keywords: 'domains/admission/seo.admission_schedule-detail.keywords')] #[LayoutData(header: 'domains/admission/seo.admission_schedule.title', breadcrumbs: ['ui/menu.dashboard' => 'dashboard', 'domains/admission/seo.admission_schedule.title' => 'admission-schedules.index', '{admissionTrack.name} {term.name}' => ''], context: 'admissionSchedule')] class extends Component { diff --git a/tests/Unit/Domains/System/Actions/Integration/RunGenericImportPipelineTest.php b/tests/Unit/Domains/System/Actions/Integration/RunGenericImportPipelineTest.php index cd6e7ff..e4726f6 100644 --- a/tests/Unit/Domains/System/Actions/Integration/RunGenericImportPipelineTest.php +++ b/tests/Unit/Domains/System/Actions/Integration/RunGenericImportPipelineTest.php @@ -3,7 +3,7 @@ namespace Tests\Unit\Domains\System\Actions\Integration; use App\Domains\System\Actions\Integration\RunGenericImportPipeline; -use App\Domains\System\Support\Integration\DataPayloadMapper; +use App\Domains\System\Support\Contract\DataPayloadMapper; use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Schema;