Browse Source

feat: implement product mapping modification and invoice processing updates

Co-authored-by: Junie <junie@jetbrains.com>
master
Syarif Ubaidillah 1 month ago
parent
commit
9cc74afb2b
  1. 15
      app/Domains/Academic/Queries/Selection/SelectFacultyQuery.php
  2. 21
      app/Domains/Academic/Queries/Selection/SelectStudyProgramQuery.php
  3. 15
      app/Domains/Academic/Queries/Selection/SelectTermQuery.php
  4. 1
      app/Domains/Admission/Actions/IntakeScheduling/DefineAdmissionSchedule.php
  5. 1
      app/Domains/Admission/DTOs/IntakeScheduling/DefineAdmissionScheduleDTO.php
  6. 13
      app/Domains/Admission/Queries/Selection/SelectFeeTypeQuery.php
  7. 1
      app/Domains/Finance/Actions/LedgerConfiguration/AdjustAccountClassification.php
  8. 2
      app/Domains/Finance/Actions/LedgerConfiguration/SuspendAccountPostings.php
  9. 16
      app/Domains/Finance/Actions/PaymentProcessing/InitializeInvoice.php
  10. 11
      app/Domains/Finance/Actions/ProductConfiguration/CreateProductMapping.php
  11. 13
      app/Domains/Finance/Actions/ProductConfiguration/ModifyProductMapping.php
  12. 11
      app/Domains/Finance/Actions/ProductConfiguration/RegisterProductMapping.php
  13. 1
      app/Domains/Finance/DTOs/LedgerConfiguration/AdjustAccountClassificationDTO.php
  14. 10
      app/Domains/Finance/DTOs/PaymentProcessing/Detail/InvoiceDetailDTO.php
  15. 36
      app/Domains/Finance/DTOs/PaymentProcessing/Detail/StudentInvoiceDetailDTO.php
  16. 12
      app/Domains/Finance/DTOs/PaymentProcessing/IssueInvoiceDTO.php
  17. 10
      app/Domains/Finance/DTOs/ProductConfiguration/ModifyProductMappingDTO.php
  18. 24
      app/Domains/Finance/Enums/InvoiceType.php
  19. 66
      app/Domains/Finance/Schemas/Invoice/StudentInvoiceSchema.php
  20. 67
      app/Domains/Finance/Support/Schema/InvoiceSchema.php
  21. 11
      app/Domains/System/Enums/LifecycleStatus.php
  22. 4
      app/Domains/System/Traits/Enum/HasPredicateMethod.php
  23. 5
      app/Http/DataTables/Finance/ChartOfAccountDataTable.php
  24. 4
      app/Http/DataTables/Finance/ProductMappingDataTable.php
  25. 26
      app/Livewire/Forms/Finance/ChartOfAccountForm.php
  26. 101
      app/Livewire/Forms/Finance/InvoiceForm.php
  27. 11
      app/Livewire/Forms/Finance/PaymentForm.php
  28. 21
      app/Livewire/Forms/Finance/ProductMappingForm.php
  29. 114
      app/UI/Support/Settings/SettingSchema.php
  30. 180
      composer.lock
  31. 2
      lang/en/domains/finance/enum.php
  32. 29
      lang/en/domains/finance/field.php
  33. 2
      lang/id/domains/finance/enum.php
  34. 1
      lang/id/domains/finance/field.php
  35. 145
      resources/js/alpine/select2.js
  36. 6
      resources/views/components/form/checkbox.blade.php
  37. 4
      resources/views/components/form/input.blade.php
  38. 18
      resources/views/components/form/select.blade.php
  39. 4
      resources/views/components/form/textarea.blade.php
  40. 2
      resources/views/components/layouts/nav/topbar.blade.php
  41. 6
      resources/views/components/modal.blade.php
  42. 43
      resources/views/pages/finance/chart-of-account/⚡form-modal/form-modal.blade.php
  43. 79
      resources/views/pages/finance/chart-of-account/⚡form-modal/form-modal.php
  44. 4
      resources/views/pages/finance/invoice/index.blade.php
  45. 66
      resources/views/pages/finance/invoice/⚡form-modal/form-modal.blade.php
  46. 34
      resources/views/pages/finance/invoice/⚡form-modal/form-modal.php
  47. 2
      resources/views/pages/finance/product-mapping/index.blade.php
  48. 24
      resources/views/pages/finance/product-mapping/⚡form-modal/form-modal.blade.php
  49. 36
      resources/views/pages/finance/product-mapping/⚡form-modal/form-modal.php

15
app/Domains/Academic/Queries/Selection/SelectFacultyQuery.php

@ -0,0 +1,15 @@
<?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'));
}
}

21
app/Domains/Academic/Queries/Selection/SelectStudyProgramQuery.php

@ -0,0 +1,21 @@
<?php
namespace App\Domains\Academic\Queries\Selection;
use App\Domains\Academic\Models\StudyProgram;
use Illuminate\Support\Collection;
class SelectStudyProgramQuery
{
public static function fetch(array $context = []): Collection
{
if(!isset($context['faculty'])) {
return new Collection();
}
return once(fn() => StudyProgram::select('id', 'name')
->where('faculty_id', $context['faculty'])
->get()
->pluck('name', 'id'));
}
}

15
app/Domains/Academic/Queries/Selection/SelectTermQuery.php

@ -0,0 +1,15 @@
<?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'));
}
}

1
app/Domains/Admission/Actions/IntakeScheduling/DefineAdmissionSchedule.php

@ -4,6 +4,7 @@ namespace App\Domains\Admission\Actions\IntakeScheduling;
use App\Domains\Admission\DTOs\IntakeScheduling\DefineAdmissionScheduleDTO; use App\Domains\Admission\DTOs\IntakeScheduling\DefineAdmissionScheduleDTO;
use App\Domains\Admission\Enums\AdmissionStatus; use App\Domains\Admission\Enums\AdmissionStatus;
use App\Domains\Admission\Events\IntakeScheduling\AdmissionScheduleDefined;
use App\Domains\Admission\Models\AdmissionSchedule; use App\Domains\Admission\Models\AdmissionSchedule;
class DefineAdmissionSchedule class DefineAdmissionSchedule

1
app/Domains/Admission/DTOs/IntakeScheduling/DefineAdmissionScheduleDTO.php

@ -3,6 +3,7 @@
namespace App\Domains\Admission\DTOs\IntakeScheduling; namespace App\Domains\Admission\DTOs\IntakeScheduling;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Illuminate\Contracts\Support\Arrayable;
readonly class DefineAdmissionScheduleDTO readonly class DefineAdmissionScheduleDTO
{ {

13
app/Domains/Admission/Queries/Selection/SelectFeeTypeQuery.php

@ -0,0 +1,13 @@
<?php
namespace App\Domains\Admission\Queries\Selection;
use Illuminate\Database\Eloquent\Collection;
class SelectFeeTypeQuery
{
public function fetch(): Collection
{
return new Collection();
}
}

1
app/Domains/Finance/Actions/LedgerConfiguration/AdjustAccountClassification.php

@ -11,7 +11,6 @@ class AdjustAccountClassification
{ {
$coa->update([ $coa->update([
'code' => $dto->code, 'code' => $dto->code,
'name' => $dto->name,
'classification' => $dto->classification, 'classification' => $dto->classification,
'parent_id' => $dto->parentId, 'parent_id' => $dto->parentId,
]); ]);

2
app/Domains/Finance/Actions/LedgerConfiguration/SuspendAccountPostings.php

@ -10,7 +10,7 @@ class SuspendAccountPostings
public function execute(ChartOfAccount $coa): void public function execute(ChartOfAccount $coa): void
{ {
$coa->update([ $coa->update([
'status' => LifecycleStatus::ARCHIVED, 'status' => LifecycleStatus::INACTIVE,
]); ]);
} }
} }

16
app/Domains/Finance/Actions/PaymentProcessing/InitializeInvoice.php

@ -2,10 +2,22 @@
namespace App\Domains\Finance\Actions\PaymentProcessing; namespace App\Domains\Finance\Actions\PaymentProcessing;
use App\Domains\Finance\DTOs\PaymentProcessing\IssueInvoiceDTO;
use App\Domains\Finance\Models\Invoice;
class InitializeInvoice class InitializeInvoice
{ {
public function execute(): void 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,
]);
} }
} }

11
app/Domains/Finance/Actions/ProductConfiguration/CreateProductMapping.php

@ -1,11 +0,0 @@
<?php
namespace App\Domains\Finance\Actions\ProductConfiguration;
class CreateProductMapping
{
public function execute(): void
{
//
}
}

13
app/Domains/Finance/Actions/ProductConfiguration/ModifyProductMapping.php

@ -0,0 +1,13 @@
<?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]);
}
}

11
app/Domains/Finance/Actions/ProductConfiguration/RegisterProductMapping.php

@ -2,10 +2,17 @@
namespace App\Domains\Finance\Actions\ProductConfiguration; namespace App\Domains\Finance\Actions\ProductConfiguration;
use App\Domains\Finance\DTOs\ProductConfiguration\RegisterProductMappingDTO;
use App\Domains\Finance\Models\ProductMapping;
class RegisterProductMapping class RegisterProductMapping
{ {
public function execute(): void public function execute(RegisterProductMappingDTO $dto): void
{ {
// ProductMapping::create([
'name' => $dto->name,
'code' => $dto->code,
'chart_of_account_id' => $dto->coaId
]);
} }
} }

1
app/Domains/Finance/DTOs/LedgerConfiguration/AdjustAccountClassificationDTO.php

@ -8,7 +8,6 @@ readonly class AdjustAccountClassificationDTO
{ {
public function __construct( public function __construct(
public string $code, public string $code,
public string $name,
public AccountClassification $classification, public AccountClassification $classification,
public ?int $parentId, public ?int $parentId,
) {} ) {}

10
app/Domains/Finance/DTOs/PaymentProcessing/Detail/InvoiceDetailDTO.php

@ -0,0 +1,10 @@
<?php
namespace App\Domains\Finance\DTOs\PaymentProcessing\Detail;
interface InvoiceDetailDTO
{
public function toArray(): array;
public static function fromArray(array $data): self;
}

36
app/Domains/Finance/DTOs/PaymentProcessing/Detail/StudentInvoiceDetailDTO.php

@ -0,0 +1,36 @@
<?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'],
);
}
}

12
app/Domains/Finance/DTOs/PaymentProcessing/IssueInvoiceDTO.php

@ -2,14 +2,20 @@
namespace App\Domains\Finance\DTOs\PaymentProcessing; 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 readonly class IssueInvoiceDTO
{ {
public function __construct( public function __construct(
public string $name, public string $name,
public string $clientRefId, public string $clientRefId,
public string $detail, public InvoiceDetailDTO $detail,
public string $type, public InvoiceType $type,
public string $status, public InvoiceStatus $status,
public int $amount,
public string $coaId,
public string $clientId public string $clientId
) {} ) {}
} }

10
app/Domains/Finance/DTOs/ProductConfiguration/ModifyProductMappingDTO.php

@ -0,0 +1,10 @@
<?php
namespace App\Domains\Finance\DTOs\ProductConfiguration;
readonly class ModifyProductMappingDTO
{
public function __construct(
//
) {}
}

24
app/Domains/Finance/Enums/InvoiceType.php

@ -2,16 +2,36 @@
namespace App\Domains\Finance\Enums; 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\Schema\InvoiceSchema;
use App\Domains\System\Traits\Enum\HasPredicateMethod; use App\Domains\System\Traits\Enum\HasPredicateMethod;
use App\UI\Enums\Concerns\InteractsWithLabels; use App\UI\Enums\Concerns\InteractsWithLabels;
use App\UI\Enums\Contracts\HasLabel; use App\UI\Enums\Contracts\HasLabel;
use App\UI\Enums\Contracts\HasSchema;
enum InvoiceType: string implements HasLabel enum InvoiceType: string implements HasLabel, HasSchema
{ {
use HasPredicateMethod; use HasPredicateMethod;
use InteractsWithLabels; use InteractsWithLabels;
case CUSTOMER_INVOICE = 'customer_invoice'; case STUDENT_INVOICE = 'student_invoice';
case VENDOR_BILL = 'vendor_bill'; case VENDOR_BILL = 'vendor_bill';
case CREDIT_NOTE = 'credit_note'; case CREDIT_NOTE = 'credit_note';
public function schema(): InvoiceSchema
{
return match ($this) {
self::STUDENT_INVOICE => StudentInvoiceSchema::make(),
default => InvoiceSchema::make(true),
};
}
public function dtoTransform(array $data): InvoiceDetailDTO
{
return match ($this) {
self::STUDENT_INVOICE => StudentInvoiceDetailDTO::fromArray($data),
};
}
} }

66
app/Domains/Finance/Schemas/Invoice/StudentInvoiceSchema.php

@ -0,0 +1,66 @@
<?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\Schema\InvoiceSchema;
use App\UI\Enums\InputType;
use App\UI\Support\Settings\SettingSchema;
class StudentInvoiceSchema
{
public static function make(): InvoiceSchema
{
return InvoiceSchema::make()
->addSchema(
key: 'student_id',
label: __('domains/finance/field.invoice.detail.student_id'),
schema: SettingSchema::make(InputType::TEXTLINE)
->rules(['required'])
)->addSchema(
key: 'student_name',
label: __('domains/finance/field.invoice.detail.student_name'),
schema: SettingSchema::make(InputType::TEXTLINE)
->rules(['required', 'string'])
)->addSchema(
key: 'faculty',
label: __('domains/finance/field.invoice.detail.faculty'),
schema: SettingSchema::make(InputType::SELECT)
->rules(['required', 'string'])
->select2()
->live()
->options(fn() => SelectFacultyQuery::fetch())
)->addSchema(
key: 'study_program',
label: __('domains/finance/field.invoice.detail.study_program'),
schema: SettingSchema::make(InputType::SELECT)
->rules(['required', 'string'])
->select2()
->dependsOn('faculty')
->setContextKey('faculty')
->options(fn ($context) => SelectStudyProgramQuery::fetch($context))
)->addSchema(
key: 'term',
label: __('domains/finance/field.invoice.detail.term'),
schema: SettingSchema::make(InputType::SELECT)
->rules(['required'])
->options(fn() => SelectTermQuery::fetch())
->select2()
)->addSchema(
key: 'semester',
label: __('domains/finance/field.invoice.detail.semester'),
schema: SettingSchema::make(InputType::SELECT)
->rules(['required'])
->options(range(1, 13))
->select2()
)->addSchema(
key: 'fee_type',
label: __('domains/finance/field.invoice.detail.fee_type'),
schema: SettingSchema::make(InputType::SELECT)
->rules(['required'])
->select2()
);
}
}

67
app/Domains/Finance/Support/Schema/InvoiceSchema.php

@ -0,0 +1,67 @@
<?php
namespace App\Domains\Finance\Support\Schema;
use App\UI\Enums\InputType;
use App\UI\Support\Settings\BaseSchema;
use App\UI\Support\Settings\SettingSchema;
class InvoiceSchema implements BaseSchema
{
protected array $context = [];
protected string $form = '';
public function __construct(
protected array $uiSchema = [],
protected bool $coaVisibility = false
){}
public static function make(bool $coaVisible = false): static
{
return new self([], $coaVisible);
}
public function addSchema(string$key, string $label, SettingSchema $schema): static
{
$schema = $schema->attributes(['label' => $label]);
$uiSchema = array_merge($this->uiSchema, [[
'key' => $key,
'schema' => $schema
]]);
return new self($uiSchema, $this->coaVisibility);
}
public function withContext(array $context): static
{
$this->context = $context;
return $this;
}
public function withFormString(string $form): static
{
$this->form = $form;
return $this;
}
public function getSchema(): array
{
$fields = [];
foreach($this->uiSchema as $field) {
/** @var SettingSchema $schema */
$schema = $field['schema'];
$attributes['options'] =$schema->type->isSelect() ? $schema->resolveOptions($this->context) : '';
$fields[$field['key']] = (object) [
'attributes' => array_merge($schema->attributes, $attributes),
'schema' => $schema,
];
}
return $fields;
}
public function isCoaVisible(): bool
{
return $this->coaVisibility;
}
}

11
app/Domains/System/Enums/LifecycleStatus.php

@ -5,12 +5,21 @@ namespace App\Domains\System\Enums;
use App\Domains\System\Traits\Enum\HasPredicateMethod; use App\Domains\System\Traits\Enum\HasPredicateMethod;
use App\UI\Enums\Concerns\InteractsWithLabels; use App\UI\Enums\Concerns\InteractsWithLabels;
use App\UI\Enums\Contracts\HasLabel; use App\UI\Enums\Contracts\HasLabel;
use App\UI\Enums\Contracts\HasUiBadge;
enum LifecycleStatus: string implements HasLabel enum LifecycleStatus: string implements HasLabel, HasUiBadge
{ {
use HasPredicateMethod; use HasPredicateMethod;
use InteractsWithLabels; use InteractsWithLabels;
case ACTIVE = 'active'; case ACTIVE = 'active';
case INACTIVE = 'inactive'; case INACTIVE = 'inactive';
public function variant(): string
{
return match ($this) {
self::ACTIVE => 'success',
self::INACTIVE => 'danger',
};
}
} }

4
app/Domains/System/Traits/Enum/HasPredicateMethod.php

@ -15,9 +15,9 @@ trait HasPredicateMethod
{ {
if (str_starts_with($method, 'is')) { if (str_starts_with($method, 'is')) {
$expectedCase = Str::substr($method, 2); $expectedCase = Str::substr($method, 2);
$expectedCase = Str::upper($expectedCase); $expectedCase = Str::pascal($expectedCase);
foreach ($this::cases() as $case) { foreach ($this::cases() as $case) {
if ($case->name === $expectedCase || $case->value === Str::kebab($expectedCase)) { if ($case->name === $expectedCase || Str::pascal($case->value) === $expectedCase) {
return $this === $case; return $this === $case;
} }
} }

5
app/Http/DataTables/Finance/ChartOfAccountDataTable.php

@ -20,6 +20,11 @@ class ChartOfAccountDataTable extends DataTable
public function dataTable(QueryBuilder $query): EloquentDataTable public function dataTable(QueryBuilder $query): EloquentDataTable
{ {
return (new EloquentDataTable($query)) 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( ->addColumn(
'action', 'action',
fn ($coa) => view('components.datatables.action-button', [ fn ($coa) => view('components.datatables.action-button', [

4
app/Http/DataTables/Finance/ProductMappingDataTable.php

@ -25,7 +25,7 @@ class ProductMappingDataTable extends DataTable
fn ($productMapping) => view('components.datatables.action-button', [ fn ($productMapping) => view('components.datatables.action-button', [
'log' => true, 'log' => true,
'edit' => [ 'edit' => [
'modal' => 'productmapping-form-modal', 'modal' => 'product-mapping-form-modal',
'permission' => auth()->user()->can('update', $productMapping), 'permission' => auth()->user()->can('update', $productMapping),
], ],
'delete' => [ 'delete' => [
@ -91,7 +91,7 @@ class ProductMappingDataTable extends DataTable
]) ])
->buttons([ ->buttons([
Button::make('add') Button::make('add')
->action('$("#productmapping-form-modal").modal("show");') ->action('$("#product-mapping-form-modal").modal("show");')
->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) ->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml())
->addClass('btn-sm'), ->addClass('btn-sm'),
Button::make('reload') Button::make('reload')

26
app/Livewire/Forms/Finance/ChartOfAccountForm.php

@ -0,0 +1,26 @@
<?php
namespace App\Livewire\Forms\Finance;
use App\Domains\Finance\Enums\AccountClassification;
use App\Domains\System\Enums\LifecycleStatus;
use Livewire\Attributes\Validate;
use Livewire\Form;
class ChartOfAccountForm extends Form
{
#[Validate('required|string|max:255', as: 'domains/finance/field.coa.code')]
public string $code = '';
#[Validate('required|string|max:255', as: 'domains/finance/field.coa.name')]
public string $name = '';
#[Validate('required', as: 'domains/finance/field.coa.classification')]
public AccountClassification $classification = AccountClassification::REVENUE;
#[Validate(as: 'domains/finance/field.coa.parent')]
public ?string $parent_id = null;
#[Validate('required', as: 'domains/finance/field.coa.status')]
public LifecycleStatus $status = LifecycleStatus::ACTIVE;
}

101
app/Livewire/Forms/Finance/InvoiceForm.php

@ -0,0 +1,101 @@
<?php
namespace App\Livewire\Forms\Finance;
use App\Domains\Finance\DTOs\PaymentProcessing\IssueInvoiceDTO;
use App\Domains\Finance\Enums\InvoiceStatus;
use App\Domains\Finance\Enums\InvoiceType;
use App\UI\Support\Settings\SettingSchema;
use Livewire\Attributes\Validate;
use Livewire\Form;
class InvoiceForm extends Form
{
#[Validate('required|string|max:255', as: 'domains/finance/field.invoice.name')]
public string $name = '';
#[Validate('required', as: 'domains/finance/field.invoice.type')]
public ?InvoiceType $type = null;
#[Validate]
public array $detail = [];
#[Validate('required', as: 'domains/finance/field.invoice.type')]
public string $client_id = '';
#[Validate('required', as: 'domains/finance/field.invoice.type')]
public string $coa_id = '';
#[Validate('required', as: 'domains/finance/field.invoice.type')]
public int $amount = 0;
public function updatedDetail($val, $prop): void
{
$formSchema = $this->type->schema()->getSchema();
$this->resetValidation();
foreach ($formSchema as $key => $schema) {
/** @var SettingSchema $schema */
$schema = $schema->schema;
if ($schema->depends == $prop) {
$this->detail[$key] = '';
}
}
$this->validateOnly($prop);
}
public function updatedType(): void
{
$this->reset('detail');
$schema = $this->type?->schema()?->getSchema() ?? [];
foreach ($schema as $key => $value) {
$this->detail[$key] = '';
}
$this->resetValidation();
}
public function rules(): array
{
$rules = [];
$detailSchema = $this->type
?->schema()
->getSchema() ?? [];
foreach ($detailSchema as $key => $schema) {
$rules = array_merge($rules, [
'detail.'.$key => $schema->schema->rules,
]);
}
return $rules;
}
public function validationAttributes(): array
{
$attributes = [];
$detailSchema = $this->type
?->schema()
->getSchema() ?? [];
foreach ($detailSchema as $key => $schema) {
$attributes = array_merge($attributes, [
'detail.'.$key => $schema->schema->attributes['label'],
]);
}
return $attributes;
}
public function toDto(): IssueInvoiceDTO
{
return new IssueInvoiceDTO(
name: $this->name,
clientRefId: $this->client_id,
detail: $this->type->dtoTransform($this->detail),
type: $this->type,
status: InvoiceStatus::PENDING,
clientId: $this->client_id
);
}
}

11
app/Livewire/Forms/Finance/PaymentForm.php

@ -0,0 +1,11 @@
<?php
namespace App\Livewire\Forms\Finance;
use Livewire\Attributes\Validate;
use Livewire\Form;
class PaymentForm extends Form
{
//
}

21
app/Livewire/Forms/Finance/ProductMappingForm.php

@ -0,0 +1,21 @@
<?php
namespace App\Livewire\Forms\Finance;
use Livewire\Attributes\Validate;
use Livewire\Form;
class ProductMappingForm extends Form
{
#[Validate('required|string', as: 'domains/finance/field.product-mapping.code')]
public string $code = '';
#[Validate('required|string', as: 'domains/finance/field.product-mapping.name')]
public string $name = '';
#[Validate('required|string', as: 'domains/finance/field.product-mapping.chart_of_account')]
public string $coa_id = '';
// #[Validate('required|string', as: 'domains/finance/field.product-mapping.client')]
// public string $client = '';
}

114
app/UI/Support/Settings/SettingSchema.php

@ -3,42 +3,120 @@
namespace App\UI\Support\Settings; namespace App\UI\Support\Settings;
use App\UI\Enums\InputType; use App\UI\Enums\InputType;
use Closure;
use Illuminate\Support\Collection;
readonly class SettingSchema implements BaseSchema class SettingSchema implements BaseSchema
{ {
public mixed $default = null;
public array|Closure $options = [];
public array $attributes = [];
public array $rules = ['required', 'string'];
public string $depends = '';
protected array $context = [];
protected array $contextKey = [];
protected bool $live = false;
public function __construct( public function __construct(
public InputType $type, public InputType $type,
public array $rules = ['required', 'string'],
public mixed $default = null,
public array $options = [],
public array $attributes = []
) {} ) {}
public static function make(InputType $type, array $rules = ['required', 'string']): self public static function make(InputType $type): self
{
return new static($type);
}
public function default(mixed $default): static
{
$this->default = $default;
return $this;
}
public function rules(array $rules): static
{
$this->rules = $rules;
return $this;
}
public function options(array|Closure $options): static
{
$this->options = $options;
return $this;
}
public function attributes(array $attributes): static
{ {
return new self($type, $rules); $this->attributes = array_merge($this->attributes, $attributes);
return $this;
} }
public function default(mixed $default): self public function setContextKey(array|string $contextKey): static
{ {
return new self($this->type, $this->rules, $default, $this->options, $this->attributes); $this->contextKey = array_merge($this->contextKey, (array) $contextKey);
return $this;
} }
public function rules(array $rules): self public function withContext(array $context): static
{ {
return new self($this->type, $rules, $this->default, $this->options, $this->attributes); $this->context = array_merge($this->context, $context);
return $this;
} }
public function options(array $options): self public function select2(array $config = []): static
{ {
// If the array is simple (non-associative), we should try to localize the values if they are Enums $this->attributes(['x-select2' => json_encode($config)]);
// But for now, we just pass it as is, or expect the caller to handle it. return $this;
// The common case is ['value' => 'Label']
return new self($this->type, $this->rules, $this->default, $options, $this->attributes);
} }
public function attributes(array $attributes): self public function live(): static
{ {
return new self($this->type, $this->rules, $this->default, $this->options, $attributes); $this->live = true;
return $this;
}
public function dependsOn(string $depends): static
{
$this->depends = $depends;
return $this;
}
public function isLive(): bool
{
return $this->live;
}
public function resolveOptions(array $extraContext = []): array|Collection
{
if ($this->options instanceof Closure) {
$mergedContext = array_merge($this->context, $extraContext);
if(!empty($this->contextKey)) {
$temporaryContext = [];
foreach($this->contextKey as $key) {
$temporaryContext[$key] = !empty($mergedContext[$key]) ? $mergedContext[$key] : null;
}
$mergedContext = $temporaryContext;
}
return ($this->options)($mergedContext);
}
return $this->options;
}
public function getAttributes(array $extraContext = []): array
{
$attributes = $this->attributes;
if ($this->type->isSelect()) {
$attributes['options'] = $this->resolveOptions($extraContext);
}
return $attributes;
} }
} }

180
composer.lock

@ -133,16 +133,16 @@
}, },
{ {
"name": "aws/aws-sdk-php", "name": "aws/aws-sdk-php",
"version": "3.390.1", "version": "3.390.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/aws/aws-sdk-php.git", "url": "https://github.com/aws/aws-sdk-php.git",
"reference": "c75d5f489113e3c140d5a37602375e4359b66a75" "reference": "51115bd27065e978c2a4cf297f8280d8c39e83da"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c75d5f489113e3c140d5a37602375e4359b66a75", "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/51115bd27065e978c2a4cf297f8280d8c39e83da",
"reference": "c75d5f489113e3c140d5a37602375e4359b66a75", "reference": "51115bd27065e978c2a4cf297f8280d8c39e83da",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -224,9 +224,9 @@
"support": { "support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions", "forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues", "issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.390.1" "source": "https://github.com/aws/aws-sdk-php/tree/3.390.4"
}, },
"time": "2026-07-31T02:53:14+00:00" "time": "2026-08-04T18:52:58+00:00"
}, },
{ {
"name": "barryvdh/laravel-dompdf", "name": "barryvdh/laravel-dompdf",
@ -1926,16 +1926,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v13.23.0", "version": "v13.24.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "92a707229148e57f08a249211c8a5a194159c619" "reference": "6d481710375d2aa67656922ef760cdd2b18bcfe0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/92a707229148e57f08a249211c8a5a194159c619", "url": "https://api.github.com/repos/laravel/framework/zipball/6d481710375d2aa67656922ef760cdd2b18bcfe0",
"reference": "92a707229148e57f08a249211c8a5a194159c619", "reference": "6d481710375d2aa67656922ef760cdd2b18bcfe0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1955,7 +1955,7 @@
"guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/guzzle": "^7.8.2",
"guzzlehttp/promises": "^2.0.3", "guzzlehttp/promises": "^2.0.3",
"guzzlehttp/uri-template": "^1.0", "guzzlehttp/uri-template": "^1.0",
"laravel/prompts": "^0.3.0", "laravel/prompts": "^0.3.11",
"laravel/serializable-closure": "^2.0.10", "laravel/serializable-closure": "^2.0.10",
"league/commonmark": "^2.8.1", "league/commonmark": "^2.8.1",
"league/flysystem": "^3.25.1", "league/flysystem": "^3.25.1",
@ -2149,20 +2149,20 @@
"issues": "https://github.com/laravel/framework/issues", "issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework" "source": "https://github.com/laravel/framework"
}, },
"time": "2026-07-27T14:48:58+00:00" "time": "2026-08-04T15:54:59+00:00"
}, },
{ {
"name": "laravel/prompts", "name": "laravel/prompts",
"version": "v0.3.21", "version": "v0.3.22",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/prompts.git", "url": "https://github.com/laravel/prompts.git",
"reference": "7753c65c281c2550c7c183f14e18062073b7d821" "reference": "02b89b39e8972a998db4d5d4ad4719239dd4aee4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/prompts/zipball/7753c65c281c2550c7c183f14e18062073b7d821", "url": "https://api.github.com/repos/laravel/prompts/zipball/02b89b39e8972a998db4d5d4ad4719239dd4aee4",
"reference": "7753c65c281c2550c7c183f14e18062073b7d821", "reference": "02b89b39e8972a998db4d5d4ad4719239dd4aee4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2206,9 +2206,9 @@
"description": "Add beautiful and user-friendly forms to your command-line applications.", "description": "Add beautiful and user-friendly forms to your command-line applications.",
"support": { "support": {
"issues": "https://github.com/laravel/prompts/issues", "issues": "https://github.com/laravel/prompts/issues",
"source": "https://github.com/laravel/prompts/tree/v0.3.21" "source": "https://github.com/laravel/prompts/tree/v0.3.22"
}, },
"time": "2026-06-26T00:11:25+00:00" "time": "2026-08-04T14:50:50+00:00"
}, },
{ {
"name": "laravel/sanctum", "name": "laravel/sanctum",
@ -2405,16 +2405,16 @@
}, },
{ {
"name": "league/commonmark", "name": "league/commonmark",
"version": "2.8.3", "version": "2.9.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/commonmark.git", "url": "https://github.com/thephpleague/commonmark.git",
"reference": "1902f60f984235023acbe03db6ad614a37b3c3e7" "reference": "5703d83ba3da3b2e356a5fedc848ed6d8ffb6529"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/1902f60f984235023acbe03db6ad614a37b3c3e7", "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/5703d83ba3da3b2e356a5fedc848ed6d8ffb6529",
"reference": "1902f60f984235023acbe03db6ad614a37b3c3e7", "reference": "5703d83ba3da3b2e356a5fedc848ed6d8ffb6529",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2451,7 +2451,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "2.9-dev" "dev-main": "2.10-dev"
} }
}, },
"autoload": { "autoload": {
@ -2508,7 +2508,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2026-07-12T15:29:16+00:00" "time": "2026-08-03T13:42:31+00:00"
}, },
{ {
"name": "league/config", "name": "league/config",
@ -3034,16 +3034,16 @@
}, },
{ {
"name": "livewire/livewire", "name": "livewire/livewire",
"version": "v4.3.4", "version": "v4.3.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/livewire/livewire.git", "url": "https://github.com/livewire/livewire.git",
"reference": "e6c8d631e9687fbdd5c2bb7be9d7a5d699cce0e2" "reference": "7ef4b2a876c71744e86463079dd506b26eeab624"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/livewire/livewire/zipball/e6c8d631e9687fbdd5c2bb7be9d7a5d699cce0e2", "url": "https://api.github.com/repos/livewire/livewire/zipball/7ef4b2a876c71744e86463079dd506b26eeab624",
"reference": "e6c8d631e9687fbdd5c2bb7be9d7a5d699cce0e2", "reference": "7ef4b2a876c71744e86463079dd506b26eeab624",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3098,7 +3098,7 @@
"description": "A front-end framework for Laravel.", "description": "A front-end framework for Laravel.",
"support": { "support": {
"issues": "https://github.com/livewire/livewire/issues", "issues": "https://github.com/livewire/livewire/issues",
"source": "https://github.com/livewire/livewire/tree/v4.3.4" "source": "https://github.com/livewire/livewire/tree/v4.3.5"
}, },
"funding": [ "funding": [
{ {
@ -3106,7 +3106,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2026-07-31T00:19:18+00:00" "time": "2026-08-03T04:09:44+00:00"
}, },
{ {
"name": "maatwebsite/excel", "name": "maatwebsite/excel",
@ -4027,16 +4027,16 @@
}, },
{ {
"name": "openspout/openspout", "name": "openspout/openspout",
"version": "v5.8.0", "version": "v5.10.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/openspout/openspout.git", "url": "https://github.com/openspout/openspout.git",
"reference": "1e1aad228e3e289c7e11d97b07496f2569121afc" "reference": "5531f2e5bf1f6cfd6e5caa781a79a9282562c783"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/openspout/openspout/zipball/1e1aad228e3e289c7e11d97b07496f2569121afc", "url": "https://api.github.com/repos/openspout/openspout/zipball/5531f2e5bf1f6cfd6e5caa781a79a9282562c783",
"reference": "1e1aad228e3e289c7e11d97b07496f2569121afc", "reference": "5531f2e5bf1f6cfd6e5caa781a79a9282562c783",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4050,13 +4050,13 @@
"require-dev": { "require-dev": {
"ext-fileinfo": "*", "ext-fileinfo": "*",
"ext-zlib": "*", "ext-zlib": "*",
"friendsofphp/php-cs-fixer": "^3.95.14", "friendsofphp/php-cs-fixer": "^3.95.18",
"infection/infection": "^0.34", "infection/infection": "^0.34",
"phpbench/phpbench": "^1.7.0", "phpbench/phpbench": "^1.7.0",
"phpstan/phpstan": "^2.2.5", "phpstan/phpstan": "^2.2.7",
"phpstan/phpstan-phpunit": "^2.0.18", "phpstan/phpstan-phpunit": "^2.0.18",
"phpstan/phpstan-strict-rules": "^2.0.11", "phpstan/phpstan-strict-rules": "^2.0.12",
"phpunit/phpunit": "^13.2.4" "phpunit/phpunit": "^13.2.6"
}, },
"suggest": { "suggest": {
"ext-iconv": "To handle non UTF-8 CSV files (if \"php-mbstring\" is not already installed or is too limited)", "ext-iconv": "To handle non UTF-8 CSV files (if \"php-mbstring\" is not already installed or is too limited)",
@ -4104,7 +4104,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/openspout/openspout/issues", "issues": "https://github.com/openspout/openspout/issues",
"source": "https://github.com/openspout/openspout/tree/v5.8.0" "source": "https://github.com/openspout/openspout/tree/v5.10.1"
}, },
"funding": [ "funding": [
{ {
@ -4116,7 +4116,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2026-07-15T07:34:50+00:00" "time": "2026-08-03T12:12:33+00:00"
}, },
{ {
"name": "owen-it/laravel-auditing", "name": "owen-it/laravel-auditing",
@ -9051,16 +9051,16 @@
}, },
{ {
"name": "yajra/laravel-datatables-oracle", "name": "yajra/laravel-datatables-oracle",
"version": "v13.1.5", "version": "v13.1.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/yajra/laravel-datatables.git", "url": "https://github.com/yajra/laravel-datatables.git",
"reference": "299c07a7dae380e565bd328104965b6e02286c2f" "reference": "662cbca5d7c7cecd1e21b123eafda0485f0f033d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/299c07a7dae380e565bd328104965b6e02286c2f", "url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/662cbca5d7c7cecd1e21b123eafda0485f0f033d",
"reference": "299c07a7dae380e565bd328104965b6e02286c2f", "reference": "662cbca5d7c7cecd1e21b123eafda0485f0f033d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9128,7 +9128,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/yajra/laravel-datatables/issues", "issues": "https://github.com/yajra/laravel-datatables/issues",
"source": "https://github.com/yajra/laravel-datatables/tree/v13.1.5" "source": "https://github.com/yajra/laravel-datatables/tree/v13.1.6"
}, },
"funding": [ "funding": [
{ {
@ -9136,22 +9136,22 @@
"type": "github" "type": "github"
} }
], ],
"time": "2026-07-03T01:53:46+00:00" "time": "2026-07-31T03:18:01+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
{ {
"name": "barryvdh/laravel-debugbar", "name": "barryvdh/laravel-debugbar",
"version": "v4.4.0", "version": "v4.4.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/fruitcake/laravel-debugbar.git", "url": "https://github.com/fruitcake/laravel-debugbar.git",
"reference": "80ef956bda9e1a5824037d6f2cd06e73092e5634" "reference": "389157fb616e5c5d19d16a88fd9bfcf3e3248e9b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/80ef956bda9e1a5824037d6f2cd06e73092e5634", "url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/389157fb616e5c5d19d16a88fd9bfcf3e3248e9b",
"reference": "80ef956bda9e1a5824037d6f2cd06e73092e5634", "reference": "389157fb616e5c5d19d16a88fd9bfcf3e3248e9b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9226,7 +9226,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/fruitcake/laravel-debugbar/issues", "issues": "https://github.com/fruitcake/laravel-debugbar/issues",
"source": "https://github.com/fruitcake/laravel-debugbar/tree/v4.4.0" "source": "https://github.com/fruitcake/laravel-debugbar/tree/v4.4.1"
}, },
"funding": [ "funding": [
{ {
@ -9238,7 +9238,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2026-07-04T08:30:57+00:00" "time": "2026-08-03T06:23:16+00:00"
}, },
{ {
"name": "barryvdh/laravel-ide-helper", "name": "barryvdh/laravel-ide-helper",
@ -9971,16 +9971,16 @@
}, },
{ {
"name": "laravel/boost", "name": "laravel/boost",
"version": "v2.4.13", "version": "v2.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/boost.git", "url": "https://github.com/laravel/boost.git",
"reference": "f55e08f5afa89ac72f23f574175005b67878f466" "reference": "f6b054dcbc0aacf1d187128edf7d917c6d99792a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/boost/zipball/f55e08f5afa89ac72f23f574175005b67878f466", "url": "https://api.github.com/repos/laravel/boost/zipball/f6b054dcbc0aacf1d187128edf7d917c6d99792a",
"reference": "f55e08f5afa89ac72f23f574175005b67878f466", "reference": "f6b054dcbc0aacf1d187128edf7d917c6d99792a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9991,7 +9991,7 @@
"illuminate/support": "^11.45.3|^12.41.1|^13.0", "illuminate/support": "^11.45.3|^12.41.1|^13.0",
"laravel/mcp": "^0.7.1|^0.8.0|^0.9.0", "laravel/mcp": "^0.7.1|^0.8.0|^0.9.0",
"laravel/prompts": "^0.3.10", "laravel/prompts": "^0.3.10",
"laravel/roster": "^0.5.0", "laravel/roster": "^1.0.0",
"php": "^8.2" "php": "^8.2"
}, },
"require-dev": { "require-dev": {
@ -10033,7 +10033,7 @@
"issues": "https://github.com/laravel/boost/issues", "issues": "https://github.com/laravel/boost/issues",
"source": "https://github.com/laravel/boost" "source": "https://github.com/laravel/boost"
}, },
"time": "2026-07-17T14:28:57+00:00" "time": "2026-08-04T21:10:39+00:00"
}, },
{ {
"name": "laravel/mcp", "name": "laravel/mcp",
@ -10191,16 +10191,16 @@
}, },
{ {
"name": "laravel/pint", "name": "laravel/pint",
"version": "v1.30.0", "version": "v1.30.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/pint.git", "url": "https://github.com/laravel/pint.git",
"reference": "72a0540d1aa10b6c146bda2a22f3ae003123c0ea" "reference": "19ca6de4ce07869f61f09863e37e81562ebc0a9b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/72a0540d1aa10b6c146bda2a22f3ae003123c0ea", "url": "https://api.github.com/repos/laravel/pint/zipball/19ca6de4ce07869f61f09863e37e81562ebc0a9b",
"reference": "72a0540d1aa10b6c146bda2a22f3ae003123c0ea", "reference": "19ca6de4ce07869f61f09863e37e81562ebc0a9b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -10212,7 +10212,7 @@
}, },
"require-dev": { "require-dev": {
"composer/semver": "^3.4.4", "composer/semver": "^3.4.4",
"friendsofphp/php-cs-fixer": "^3.95.17", "friendsofphp/php-cs-fixer": "^3.95.18",
"illuminate/view": "^12.64.0", "illuminate/view": "^12.64.0",
"larastan/larastan": "^3.10.0", "larastan/larastan": "^3.10.0",
"laravel-zero/framework": "^12.1.0", "laravel-zero/framework": "^12.1.0",
@ -10257,36 +10257,37 @@
"issues": "https://github.com/laravel/pint/issues", "issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint" "source": "https://github.com/laravel/pint"
}, },
"time": "2026-07-28T20:48:56+00:00" "time": "2026-08-02T01:28:21+00:00"
}, },
{ {
"name": "laravel/roster", "name": "laravel/roster",
"version": "v0.5.1", "version": "v1.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/roster.git", "url": "https://github.com/laravel/roster.git",
"reference": "5089de7615f72f78e831590ff9d0435fed0102bb" "reference": "89e518bd88ae98ff50f6082f6b517c8d8e8245fa"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/roster/zipball/5089de7615f72f78e831590ff9d0435fed0102bb", "url": "https://api.github.com/repos/laravel/roster/zipball/89e518bd88ae98ff50f6082f6b517c8d8e8245fa",
"reference": "5089de7615f72f78e831590ff9d0435fed0102bb", "reference": "89e518bd88ae98ff50f6082f6b517c8d8e8245fa",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer/semver": "^3.0",
"illuminate/console": "^11.0|^12.0|^13.0", "illuminate/console": "^11.0|^12.0|^13.0",
"illuminate/contracts": "^11.0|^12.0|^13.0", "illuminate/contracts": "^11.0|^12.0|^13.0",
"illuminate/routing": "^11.0|^12.0|^13.0",
"illuminate/support": "^11.0|^12.0|^13.0", "illuminate/support": "^11.0|^12.0|^13.0",
"php": "^8.2", "php": "^8.2",
"symfony/yaml": "^7.2|^8.0" "symfony/yaml": "^7.2|^8.0"
}, },
"require-dev": { "require-dev": {
"laravel/pint": "^1.14", "laravel/pint": "^1.29",
"mockery/mockery": "^1.6", "mockery/mockery": "^1.6",
"orchestra/testbench": "^9.0|^10.0|^11.0", "orchestra/testbench": "^9.0|^10.0|^11.0",
"pestphp/pest": "^3.0|^4.1", "pestphp/pest": "^3.0|^4.1",
"phpstan/phpstan": "^2.0" "phpstan/phpstan": "^2.0",
"rector/rector": "^2.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -10318,7 +10319,7 @@
"issues": "https://github.com/laravel/roster/issues", "issues": "https://github.com/laravel/roster/issues",
"source": "https://github.com/laravel/roster" "source": "https://github.com/laravel/roster"
}, },
"time": "2026-03-05T07:58:43+00:00" "time": "2026-07-18T17:53:15+00:00"
}, },
{ {
"name": "mockery/mockery", "name": "mockery/mockery",
@ -10561,39 +10562,39 @@
}, },
{ {
"name": "pestphp/pest", "name": "pestphp/pest",
"version": "v4.7.5", "version": "v4.7.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pestphp/pest.git", "url": "https://github.com/pestphp/pest.git",
"reference": "5dc49a71d63602a9b98fed0f2017c4679ef9f8e0" "reference": "5b2293f67adcf1b2320b33f521b94a692d18f360"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pestphp/pest/zipball/5dc49a71d63602a9b98fed0f2017c4679ef9f8e0", "url": "https://api.github.com/repos/pestphp/pest/zipball/5b2293f67adcf1b2320b33f521b94a692d18f360",
"reference": "5dc49a71d63602a9b98fed0f2017c4679ef9f8e0", "reference": "5b2293f67adcf1b2320b33f521b94a692d18f360",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"brianium/paratest": "^7.20.0", "brianium/paratest": "^7.20.0",
"composer/xdebug-handler": "^3.0.5", "composer/xdebug-handler": "^3.0.5",
"nunomaduro/collision": "^8.9.4", "nunomaduro/collision": "^8.9.5",
"nunomaduro/termwind": "^2.4.0", "nunomaduro/termwind": "^2.4.0",
"pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin": "^4.0.0",
"pestphp/pest-plugin-arch": "^4.0.2", "pestphp/pest-plugin-arch": "^4.0.2",
"pestphp/pest-plugin-mutate": "^4.0.1", "pestphp/pest-plugin-mutate": "^4.0.1",
"pestphp/pest-plugin-profanity": "^4.2.1", "pestphp/pest-plugin-profanity": "^4.2.1",
"php": "^8.3.0", "php": "^8.3.0",
"phpunit/phpunit": "^12.5.30", "phpunit/phpunit": "^12.5.33",
"symfony/process": "^7.4.13|^8.1.0" "symfony/process": "^7.4.13|^8.1.0"
}, },
"conflict": { "conflict": {
"filp/whoops": "<2.18.3", "filp/whoops": "<2.18.3",
"phpunit/phpunit": ">12.5.30", "phpunit/phpunit": ">12.5.33",
"sebastian/exporter": "<7.0.0", "sebastian/exporter": "<7.0.0",
"webmozart/assert": "<1.11.0" "webmozart/assert": "<1.11.0"
}, },
"require-dev": { "require-dev": {
"mrpunyapal/peststan": "^0.2.11", "mrpunyapal/peststan": "^0.2.12",
"pestphp/pest-dev-tools": "^4.1.0", "pestphp/pest-dev-tools": "^4.1.0",
"pestphp/pest-plugin-browser": "^4.3.1", "pestphp/pest-plugin-browser": "^4.3.1",
"pestphp/pest-plugin-type-coverage": "^4.0.4", "pestphp/pest-plugin-type-coverage": "^4.0.4",
@ -10624,7 +10625,6 @@
"Pest\\Plugins\\Verbose", "Pest\\Plugins\\Verbose",
"Pest\\Plugins\\Version", "Pest\\Plugins\\Version",
"Pest\\Plugins\\Shard", "Pest\\Plugins\\Shard",
"Pest\\Plugins\\Tia",
"Pest\\Plugins\\Parallel" "Pest\\Plugins\\Parallel"
] ]
}, },
@ -10664,7 +10664,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/pestphp/pest/issues", "issues": "https://github.com/pestphp/pest/issues",
"source": "https://github.com/pestphp/pest/tree/v4.7.5" "source": "https://github.com/pestphp/pest/tree/v4.7.8"
}, },
"funding": [ "funding": [
{ {
@ -10676,7 +10676,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2026-07-06T17:06:29+00:00" "time": "2026-08-03T20:49:44+00:00"
}, },
{ {
"name": "pestphp/pest-plugin", "name": "pestphp/pest-plugin",
@ -11880,24 +11880,24 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "12.5.30", "version": "12.5.33",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "900400a5b616d6fb306f9549f6da33ba615d3fbb" "reference": "b98e028a26c5c5ba7e4a54be96ccf35f2914d184"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/900400a5b616d6fb306f9549f6da33ba615d3fbb", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b98e028a26c5c5ba7e4a54be96ccf35f2914d184",
"reference": "900400a5b616d6fb306f9549f6da33ba615d3fbb", "reference": "b98e028a26c5c5ba7e4a54be96ccf35f2914d184",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"ext-filter": "*",
"ext-json": "*", "ext-json": "*",
"ext-libxml": "*", "ext-libxml": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.13.4", "myclabs/deep-copy": "^1.13.4",
"phar-io/manifest": "^2.0.4", "phar-io/manifest": "^2.0.4",
@ -11958,7 +11958,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.30" "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.33"
}, },
"funding": [ "funding": [
{ {
@ -11966,7 +11966,7 @@
"type": "other" "type": "other"
} }
], ],
"time": "2026-06-15T13:12:30+00:00" "time": "2026-07-28T13:58:09+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",

2
lang/en/domains/finance/enum.php

@ -14,7 +14,7 @@ return [
InvoiceStatus::OVERDUE->value => 'Overdue', InvoiceStatus::OVERDUE->value => 'Overdue',
], ],
'invoice_type' => [ 'invoice_type' => [
InvoiceType::CUSTOMER_INVOICE->value => 'Customer Invoice', InvoiceType::STUDENT_INVOICE->value => 'Student Invoice',
InvoiceType::VENDOR_BILL->value => 'Vendor Bill', InvoiceType::VENDOR_BILL->value => 'Vendor Bill',
InvoiceType::CREDIT_NOTE->value => 'Credit Note', InvoiceType::CREDIT_NOTE->value => 'Credit Note',
], ],

29
lang/en/domains/finance/field.php

@ -6,13 +6,40 @@ return [
'name' => 'Account Name', 'name' => 'Account Name',
'classification' => 'Classification', 'classification' => 'Classification',
'status' => 'Status', 'status' => 'Status',
'parent' => 'Derived From'
], ],
'invoice' => [ 'invoice' => [
'name' => 'Payer Name', 'name' => 'Invoice Name',
'code' => 'Invoice Code',
'type' => 'Invoice Type', 'type' => 'Invoice Type',
'amount' => 'Invoice Amount', 'amount' => 'Invoice Amount',
'client_name' => 'Client Name', 'client_name' => 'Client Name',
'status' => 'Status', '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' => 'Study Term',
'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' => [ 'product_mapping' => [
'code' => 'Product Code', 'code' => 'Product Code',

2
lang/id/domains/finance/enum.php

@ -14,7 +14,7 @@ return [
InvoiceStatus::OVERDUE->value => 'Jatuh Tempo', InvoiceStatus::OVERDUE->value => 'Jatuh Tempo',
], ],
'invoice_type' => [ 'invoice_type' => [
InvoiceType::CUSTOMER_INVOICE->value => 'Faktur Pelanggan', InvoiceType::STUDENT_INVOICE->value => 'Faktur Pelanggan',
InvoiceType::VENDOR_BILL->value => 'Tagihan Vendor', InvoiceType::VENDOR_BILL->value => 'Tagihan Vendor',
InvoiceType::CREDIT_NOTE->value => 'Nota Kredit', InvoiceType::CREDIT_NOTE->value => 'Nota Kredit',
], ],

1
lang/id/domains/finance/field.php

@ -6,6 +6,7 @@ return [
'name' => 'Nama Akun', 'name' => 'Nama Akun',
'classification' => 'Klasifikasi', 'classification' => 'Klasifikasi',
'status' => 'Status', 'status' => 'Status',
'parent' => 'Turunan Dari'
], ],
'invoice' => [ 'invoice' => [
'name' => 'Nama Pembayar', 'name' => 'Nama Pembayar',

145
resources/js/alpine/select2.js

@ -1,77 +1,140 @@
export default function alpineSelect2(Alpine) { export default function alpineSelect2(Alpine) {
Alpine.directive('select2', function (el, { expression }, { evaluate, cleanup }) { Alpine.directive(
let config = evaluate(expression); "select2",
function (el, { expression }, { evaluate, cleanup }) {
let config = evaluate(expression) || {};
if((window.jQuery||window.jquery||window.$) && (typeof window.$.fn.select2 !== 'undefined')) { if (
(window.jQuery || window.jquery || window.$) &&
typeof window.$.fn.select2 !== "undefined"
) {
let modalContainer = el.closest(".modal");
let select2Default = { let select2Default = {
placeholder: config.placeholder || 'Select an option', placeholder: config.placeholder || "Select an option",
ajax: config.url ? { dropdownParent: config.dropdownParent || modalContainer || el.parentElement,
allowClear: config.allowClear ?? true,
ajax: config.url
? {
url: config.url, url: config.url,
dataType: 'json', dataType: "json",
delay: 250, delay: 250,
xhrFields: { xhrFields: {
withCredentials: true withCredentials: true,
}, },
data: function (params) { data: function (params) {
return { return {
search: params.terms search: params.terms,
} };
}, },
processResults: function (data) { processResults: function (data) {
return { return {
results: data.data || data results: data.data || data,
} };
} },
} : undefined
} }
: undefined,
};
const container = document.createElement("div");
el.parentElement.insertBefore(container, el);
// Keep wire:ignore on container so Livewire DOM diffing does not destroy Select2 UI
container.setAttribute("wire:ignore", "");
container.appendChild(el);
config = Object.assign(config, select2Default) config = Object.assign(select2Default, config);
let $el = $(el).select2(config) let $el = $(el).select2(config);
const modelName = el.getAttribute('wire:model') || const $select2Container = $el.next(".select2-container");
el.getAttribute('wire:model.live') ||
el.getAttribute('wire:model.blur') || 'select2-clear';
let isUpdating = false const modelName =
el.getAttribute("wire:model") ||
el.getAttribute("wire:model.live") ||
el.getAttribute("wire:model.blur") ||
"select2";
$el.on('change', (e) => { let isUpdating = false;
if(isUpdating) return
isUpdating = true $el.on("change", (e) => {
el.dispatchEvent(new Event('change', {bubbles: true})) if (isUpdating) return;
isUpdating = false isUpdating = true;
}) el.dispatchEvent(new Event("change", { bubbles: true }));
isUpdating = false;
});
// Fix for allowClear: prevent dropdown from opening when clicking the 'x' // Fix for allowClear: prevent dropdown from opening when clicking the 'x'
$el.on('select2:unselecting', function (e) { $el.on("select2:unselecting", function (e) {
$(this).data('unselecting', true); $(this).data("unselecting", true);
}).on('select2:opening', function (e) { }).on("select2:opening", function (e) {
if ($(this).data('unselecting')) { if ($(this).data("unselecting")) {
$(this).removeData('unselecting'); $(this).removeData("unselecting");
e.preventDefault(); e.preventDefault();
} }
}); });
const event = (e) => { const event = (e) => {
$el.val(null).trigger('change', {bubbles: true}) $el.val(null).trigger("change", { bubbles: true });
};
window.addEventListener(`${modelName}-clear`, event);
// Sync error state (is-invalid) from native select to Select2 container
const syncErrorState = () => {
if (el.classList.contains("is-invalid")) {
container.classList.add("is-invalid");
$select2Container
.find(".select2-selection")
.addClass("is-invalid border-danger");
} else {
container.classList.remove("is-invalid");
$select2Container
.find(".select2-selection")
.removeClass("is-invalid border-danger");
} }
};
window.addEventListener(`${modelName}-clear`, event) // Run initial check on load
syncErrorState();
const observer = new MutationObserver(() => { // Observe attribute changes on the native <select> element
const observer = new MutationObserver((mutations) => {
if (isUpdating) return; if (isUpdating) return;
mutations.forEach((mutation) => {
// Update Select2 UI if value attribute changes
if (mutation.attributeName === "value") {
isUpdating = true; isUpdating = true;
$el.trigger('change.select2'); // Sync Select2 UI with new value safely $el.trigger("change.select2");
isUpdating = false; isUpdating = false;
}
// Sync error styling if class attribute changes (e.g. is-invalid added/removed)
if (mutation.attributeName === "class") {
syncErrorState();
}
});
});
observer.observe(el, {
attributes: true,
attributeFilter: ["value", "class"],
}); });
observer.observe(el, { attributes: true, attributeFilter: ['value'] });
// Listen to Livewire DOM updates to force error styling inside wire:ignore wrapper
if (window.Livewire) {
Livewire.hook("morph.updated", ({ el: updatedEl }) => {
if (updatedEl === el || updatedEl.contains(el)) {
syncErrorState();
}
});
}
cleanup(() => { cleanup(() => {
$el.select2('destroy') $el.select2("destroy");
window.removeEventListener(`${modelName}-clear`, event) window.removeEventListener(`${modelName}-clear`, event);
observer.disconnect() observer.disconnect();
}) });
} }
}) },
);
} }

6
resources/views/components/form/checkbox.blade.php

@ -1,9 +1,7 @@
@props(['label' => '', 'name' => '', 'feedback' => null, 'id']) @props(['label' => '', 'name' => '', 'feedback' => null, 'id'])
@php @php($name = $attributes->whereStartsWith('wire:model')->first() ?? $name)
$name = $attributes->has('wire:model') ? $attributes->get('wire:model') : $name;
$id = $id ?? $name;
@endphp
<div class="form-check"> <div class="form-check">
<input <input
{{ {{

4
resources/views/components/form/input.blade.php

@ -5,9 +5,7 @@
'disabled' => false, 'disabled' => false,
]) ])
@php @php($name = $attributes->whereStartsWith('wire:model')->first() ?? $name)
$name = $attributes->has('wire:model') ? $attributes->get('wire:model') : $name;
@endphp
<div class="form-group" @if ($attributes->has('x-show')) x-show="{{ $attributes->get('x-show') }}" x-cloak @endif> <div class="form-group" @if ($attributes->has('x-show')) x-show="{{ $attributes->get('x-show') }}" x-cloak @endif>
@isset($label) @isset($label)

18
resources/views/components/form/select.blade.php

@ -6,26 +6,27 @@
'noLabel' => false, 'noLabel' => false,
]) ])
@php @php($name = $attributes->whereStartsWith('wire:model')->first() ?? $name)
$name = $attributes->has('wire:model') ? $attributes->get('wire:model') : $name; <div @if($attributes->has('x-select2')) x-data="{
@endphp error: @js($errors->first($name)),
hasError: @js($errors->has($name))
<div }" x-init="$watch('error', (val) => console.log(val))" @endif
@if ($attributes->has('x-select2')) wire:ignore @endif
class="form-group" class="form-group"
@if ($attributes->has('x-show')) x-show="{{ $attributes->get('x-show') }}" x-cloak @endif @if ($attributes->has('x-show')) x-show="{{ $attributes->get('x-show') }}" x-cloak @endif
> >
@if (! $noLabel) @if (! $noLabel)
<label for="{{ $name }}">{{ $label }}</label> <label for="{{ $name }}">{{ $label }}</label>
@endif @endif
<select {{ <select {{
$attributes->merge([ $attributes->merge([
'class' => 'form-select'.($errors->has($name) ? ' is-invalid' : ''), 'class' => 'form-select'.($errors->has($name) ? ' is-invalid' : ''),
'name' => $name, 'name' => $name,
'id' => $attributes->has('id') ? $attributes->get('id') : $name, 'id' => $attributes->has('id') ? $attributes->get('id') : $name,
'x-bind:class' => $feedback ? "{'is-invalid': feedback?.$name}" : false, 'x-bind:class' => $feedback ? "{'is-invalid': error != null }" : false,
]) ])
}}> }}>
<option value="">{{ $label }}</option> <option value="">{{ $label }}</option>
@if (! empty($options)) @if (! empty($options))
@foreach ($options as $key => $value) @foreach ($options as $key => $value)
@ -35,8 +36,9 @@
{{ $slot ?? '' }} {{ $slot ?? '' }}
@endif @endif
</select> </select>
@if ($feedback) @if ($feedback)
<span x-text="feedback?.{{ $name }}" x-bind:class="{ 'invalid-feedback': feedback?.{{ $name }} }"></span> <span x-text="error" x-bind:class="{ 'invalid-feedback': error }"></span>
@elseif ($errors->has($name)) @elseif ($errors->has($name))
<span class="invalid-feedback">{{ $errors->first($name) }}</span> <span class="invalid-feedback">{{ $errors->first($name) }}</span>
@endif @endif

4
resources/views/components/form/textarea.blade.php

@ -5,9 +5,7 @@
'disabled' => false, 'disabled' => false,
]) ])
@php @php($name = $attributes->whereStartsWith('wire:model')->first() ?? $name)
$name = $attributes->has('wire:model') ? $attributes->get('wire:model') : $name;
@endphp
<div class="form-group" @if ($attributes->has('x-show')) x-show="{{ $attributes->get('x-show') }}" x-cloak @endif> <div class="form-group" @if ($attributes->has('x-show')) x-show="{{ $attributes->get('x-show') }}" x-cloak @endif>
<label for="{{ $name }}">{{ $label }}</label> <label for="{{ $name }}">{{ $label }}</label>

2
resources/views/components/layouts/nav/topbar.blade.php

@ -77,7 +77,7 @@
</li> </li>
</ul> </ul>
</div> </div>
@isset($breadcrumb) @isset($breadcrumbs)
<div class="container-fluid px-4"> <div class="container-fluid px-4">
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb my-0"> <ol class="breadcrumb my-0">

6
resources/views/components/modal.blade.php

@ -36,7 +36,7 @@
x-on:hide-{{ $id }}.window="$bs.modal.instance($el).hide();" x-on:hide-{{ $id }}.window="$bs.modal.instance($el).hide();"
@endif @endif
> >
<div class="modal-dialog {{ $size }}"> <div class="modal-dialog modal-dialog-scrollable modal-fullscreen-md-down {{ $size }}">
@if ($form) @if ($form)
<form <form
@if ($attributes->has('x-on:submit')) x-on:submit="{{ $attributes->get('x-on:submit') }}" @endif @if ($attributes->has('x-on:submit')) x-on:submit="{{ $attributes->get('x-on:submit') }}" @endif
@ -75,7 +75,7 @@
style="width: 3rem; height: 3rem" style="width: 3rem; height: 3rem"
role="status" role="status"
></div> ></div>
<p class="text-body fw-bold mb-0">{{ __('ui/loading') }}...</p> <p class="text-body fw-bold mb-0">{{ __('ui/common.loading') }}...</p>
</div> </div>
</div> </div>
</div> </div>
@ -98,7 +98,7 @@
style="width: 3rem; height: 3rem" style="width: 3rem; height: 3rem"
role="status" role="status"
></div> ></div>
<p class="text-body fw-bold mb-0">{{ __('ui/loading') }}...</p> <p class="text-body fw-bold mb-0">{{ __('ui/common.loading') }}...</p>
</div> </div>
</div> </div>
</div> </div>

43
resources/views/pages/finance/chart-of-account/⚡form-modal/form-modal.blade.php

@ -1,10 +1,47 @@
@use(App\Domains\Finance\Enums\AccountClassification)
@use(App\Domains\System\Enums\LifecycleStatus)
<x-modal id="chartofaccount-form-modal" :title="$this->title" wire:submit="save" wire:loading form livewire> <x-modal id="chartofaccount-form-modal" :title="$this->title" wire:submit="save" wire:loading form livewire>
<div class="d-flex flex-column gap-3"> <div class="d-flex flex-column gap-3">
{{-- Form Fields --}} <x-form.select
name="form.parent"
:label="__('domains/finance/field.coa.parent')"
wire:model.live="form.parent_id"
:options="$this->chartOfAccounts->pluck('name', 'ulid')"
/>
<x-form.input
name="form.code"
wire:model="form.code"
:disabled="$mode == 'update'"
:label="__('domains/finance/field.coa.code')"
/>
<x-form.input
name="form.name"
wire:model="form.name"
:disabled="$mode == 'update'"
:label="__('domains/finance/field.coa.name')"
/>
<x-form.select
name="form.classification"
:label="__('domains/finance/field.coa.classification')"
wire:model="form.classification"
:disabled="$form->parent_id != null"
:options="AccountClassification::options()"
/>
<x-form.select
name="form.status"
:label="__('domains/finance/field.coa.status')"
wire:model="form.status"
:disabled="$mode == 'update'"
:options="LifecycleStatus::options()"
/>
</div> </div>
<x-slot:footer> <x-slot:footer>
<x-button type="button" theme="secondary" :label="__('ui/button.cancel')" data-bs-dismiss="modal" /> <x-button type="button" theme="secondary" :label="__('ui/button.cancel')" data-bs-dismiss="modal"/>
<x-button type="submit" theme="primary" :label="__('ui/button.save')" /> <x-button type="submit" theme="primary" :label="__('ui/button.save')"/>
</x-slot:footer> </x-slot:footer>
</x-modal> </x-modal>

79
resources/views/pages/finance/chart-of-account/⚡form-modal/form-modal.php

@ -1,7 +1,15 @@
<?php <?php
use App\Domains\Finance\Actions\LedgerConfiguration\AdjustAccountClassification;
use App\Domains\Finance\Actions\LedgerConfiguration\RegisterChartOfAccount;
use App\Domains\Finance\DTOs\LedgerConfiguration\AdjustAccountClassificationDTO;
use App\Domains\Finance\DTOs\LedgerConfiguration\RegisterChartOfAccountDTO;
use App\Domains\Finance\Models\ChartOfAccount;
use App\Livewire\Concerns\WithModal; use App\Livewire\Concerns\WithModal;
use App\Livewire\Concerns\WithToast; use App\Livewire\Concerns\WithToast;
use App\Livewire\Forms\Finance\ChartOfAccountForm;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Locked; use Livewire\Attributes\Locked;
use Livewire\Component; use Livewire\Component;
@ -10,24 +18,89 @@ new class extends Component
use WithModal; use WithModal;
use WithToast; use WithToast;
public ChartOfAccountForm $form;
#[Locked] #[Locked]
public ?string $id = null; public ?string $id = null;
#[Locked] #[Locked]
public string $mode = 'create'; public string $mode = 'create';
protected string $resourceName = 'form'; protected string $resourceName = 'chart_of_account';
public function updating($property, $value): void
{
if($property === 'form.parent_id') {
$props = explode('.', $property);
$props = implode('->', $props);
$this->{$props} = $value;
$coa = $this->chartOfAccounts->where('ulid', $value)->first();
if($coa) {
$this->form->code = $coa->code + 1;
$this->form->classification = $coa->classification;
}
}
}
public function show(int|string $id): void public function show(int|string $id): void
{ {
$this->id = $id; $this->id = $id;
$this->mode = 'update'; $this->mode = 'update';
// $this->form->fill($this->model->only(['name'])); $coa = $this->coa->only(['name', 'code', 'classification', 'status', 'parent_id']);
$coa['parent_id'] = $this->chartOfAccounts
->where('id', $coa['parent_id'])
->first()
?->ulid;
$this->form->fill($coa);
}
#[Computed]
public function coa(): ChartOfAccount
{
return $this->id ? ChartOfAccount::where('ulid', $this->id)->first() : new ChartOfAccount;
}
#[Computed]
public function chartOfAccounts(): Collection
{
return ChartOfAccount::query()
->select(['id', 'name', 'code', 'ulid', 'classification'])
->get();
}
public function save(RegisterChartOfAccount $register, AdjustAccountClassification $adjust): void
{
$this->form->validate();
$parentId = $this->chartOfAccounts
->where('ulid', $this->form->parent_id)
->first()
->id;
if ($this->mode === 'create') {
$register->execute(new RegisterChartOfAccountDTO(
code: $this->form->code,
name: $this->form->name,
classification: $this->form->classification,
status: $this->form->status,
parentId: $parentId,
));
} elseif ($this->mode == 'update') {
$adjust->execute($this->coa, new AdjustAccountClassificationDTO(
code: $this->form->code,
classification: $this->form->classification,
parentId: $parentId,
));
}
$this->dispatch('hide-chartofaccount-form-modal');
$this->js("LaravelDataTables['chartofaccount-table'].ajax.reload(null, false)");
} }
public function hide(): void public function hide(): void
{ {
// $this->form->reset(); $this->form->reset();
$this->reset('id', 'mode'); $this->reset('id', 'mode');
} }
}; };

4
resources/views/pages/finance/invoice/index.blade.php

@ -7,6 +7,10 @@
translation="domains/finance/field.invoice." translation="domains/finance/field.invoice."
/> />
@can('invoice.update')
<livewire:pages::finance.invoice.form-modal />
@endcan
@can('invoice.delete') @can('invoice.delete')
<livewire:datatables.delete-action key-name="ulid" :model="\App\Domains\Finance\Models\Invoice::class" /> <livewire:datatables.delete-action key-name="ulid" :model="\App\Domains\Finance\Models\Invoice::class" />
@endcan @endcan

66
resources/views/pages/finance/invoice/⚡form-modal/form-modal.blade.php

@ -1,10 +1,68 @@
<x-modal id="form-modal" :title="$this->title" wire:submit="save" wire:loading form livewire> @use(App\Domains\Finance\Enums\InvoiceType)
<x-modal id="invoice-form-modal" size="modal-lg" :title="$this->title" wire:submit="save" wire:loading form livewire>
<div class="d-flex flex-column gap-3"> <div class="d-flex flex-column gap-3">
{{-- Form Fields --}} <x-form.select
name="form.type"
:label="__('domains/finance/field.invoice.type')"
wire:model.live="form.type"
:options="InvoiceType::options()"
/>
@forelse ($this->invoiceSchema as $key => $value)
@php($model = $value->schema?->isLive() ? 'wire:model.live' : 'wire:model')
<x-dynamic-component :key="$key"
:component="$value->schema?->type->component()"
:attributes="new Illuminate\View\ComponentAttributeBag($value->attributes)->merge([
$model => 'form.detail.'.$key,
])"
/>
@empty
<div class="col-12">
<div class="alert bg-body-secondary d-flex align-items-center border-0 shadow-sm p-3 my-2"
role="alert">
<div class="me-3 fs-3 text-secondary">
@svg('tabler-x', 'w-100 h-100')
</div> </div>
<div>
<h6 class="alert-heading mb-1 fw-bold">Tidak Ada Field Tambahan</h6>
<p class="mb-0 small text-muted">
Tipe transaksi <strong>{{ $form->type?->label() }}</strong>
belum tersedia.
</p>
</div>
</div>
</div>
@endforelse
<x-form.input
name="form.name"
:label="__('domains/finance/field.invoice.name')"
wire:model="form.name"
/>
<x-form.input
name="form.amount"
:label="__('domains/finance/field.invoice.amount')"
wire:model="form.amount"
/>
<x-form.select
name="form.client_name"
:label="__('domains/finance/field.invoice.client_name')"
wire:model="form.client_name"
/>
@if ($this->form->type == null || $form->type?->schema()->isCoaVisible())
<x-form.select
name="form.coa_name"
:label="__('domains/finance/field.invoice.coa_name')"
wire:model="form.coa_id"
/>
@endif
</div>
@push('scripts')
@vite(['resources/js/plugin/select2.js'])
@endpush
<x-slot:footer> <x-slot:footer>
<x-button type="button" theme="secondary" :label="__('ui/button.cancel')" data-bs-dismiss="modal" /> <x-button type="button" theme="secondary" :label="__('ui/button.cancel')" data-bs-dismiss="modal"/>
<x-button type="submit" theme="primary" :label="__('ui/button.save')" /> <x-button type="submit" theme="primary" :label="__('ui/button.save')"/>
</x-slot:footer> </x-slot:footer>
</x-modal> </x-modal>

34
resources/views/pages/finance/invoice/⚡form-modal/form-modal.php

@ -1,8 +1,12 @@
<?php <?php
use App\Domains\Finance\Actions\PaymentProcessing\InitializeInvoice;
use App\Livewire\Concerns\WithModal; use App\Livewire\Concerns\WithModal;
use App\Livewire\Concerns\WithToast; use App\Livewire\Concerns\WithToast;
use App\Livewire\Forms\Finance\InvoiceForm;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Locked; use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component; use Livewire\Component;
new class extends Component new class extends Component
@ -16,7 +20,9 @@ new class extends Component
#[Locked] #[Locked]
public string $mode = 'create'; public string $mode = 'create';
protected string $resourceName = 'form'; public InvoiceForm $form;
protected string $resourceName = 'invoice';
public function show(int|string $id): void public function show(int|string $id): void
{ {
@ -25,9 +31,33 @@ new class extends Component
// $this->form->fill($this->model->only(['name'])); // $this->form->fill($this->model->only(['name']));
} }
#[Computed]
public function invoiceSchema(): array
{
return $this->form
->type
?->schema()
->withContext([
'faculty' => $this->form->detail['faculty'] ?? null,
])
->withFormString('form.detail.')
->getSchema() ?? [];
}
public function save(InitializeInvoice $initializeInvoice): void
{
$this->form->validate();
$initializeInvoice->execute($this->form->toDto());
$this->dispatch('hide-invoice-form-modal');
$this->js("LaravelDataTables['invoice-table'].ajax.reload(null, false)");
}
public function hide(): void public function hide(): void
{ {
// $this->form->reset(); $this->form->reset();
$this->reset('id', 'mode'); $this->reset('id', 'mode');
$this->resetErrorBag();
} }
}; };

2
resources/views/pages/finance/product-mapping/index.blade.php

@ -7,6 +7,8 @@
translation="domains/finance/field.product-mapping." translation="domains/finance/field.product-mapping."
/> />
<livewire:pages::finance.product-mapping.form-modal />
@can('product-mapping.delete') @can('product-mapping.delete')
<livewire:datatables.delete-action key-name="id" :model="\App\Domains\Finance\Models\ProductMapping::class" /> <livewire:datatables.delete-action key-name="id" :model="\App\Domains\Finance\Models\ProductMapping::class" />
@endcan @endcan

24
resources/views/pages/finance/product-mapping/⚡form-modal/form-modal.blade.php

@ -1,8 +1,28 @@
<x-modal id="form-modal" :title="$this->title" wire:submit="save" wire:loading form livewire> <x-modal id="product-mapping-form-modal" :title="$this->title" wire:submit="save" wire:loading form livewire>
<div class="d-flex flex-column gap-3"> <div class="d-flex flex-column gap-3">
{{-- Form Fields --}} <x-form.input
name="form.name"
:label="__('domains/finance/field.product_mapping.code')"
wire:model="form.code"
/>
<x-form.input
name="form.name"
:label="__('domains/finance/field.product_mapping.name')"
wire:model="form.name"
/>
<x-form.select x-select2="{
dropdownParent: $('#product-mapping-form-modal')
}"
name="form.coa_id"
:label="__('domains/finance/field.product_mapping.coa_name')"
wire:model="form.coa_id"
:options="$this->coaOptions"
/>
</div> </div>
@push('scripts')
@vite('resources/js/plugin/select2.js')
@endpush
<x-slot:footer> <x-slot:footer>
<x-button type="button" theme="secondary" :label="__('ui/button.cancel')" data-bs-dismiss="modal" /> <x-button type="button" theme="secondary" :label="__('ui/button.cancel')" data-bs-dismiss="modal" />
<x-button type="submit" theme="primary" :label="__('ui/button.save')" /> <x-button type="submit" theme="primary" :label="__('ui/button.save')" />

36
resources/views/pages/finance/product-mapping/⚡form-modal/form-modal.php

@ -1,7 +1,14 @@
<?php <?php
use App\Domains\Finance\Actions\ProductConfiguration\ModifyProductMapping;
use App\Domains\Finance\Actions\ProductConfiguration\RegisterProductMapping;
use App\Domains\Finance\DTOs\ProductConfiguration\RegisterProductMappingDTO;
use App\Domains\Finance\Models\ChartOfAccount;
use App\Livewire\Concerns\WithModal; use App\Livewire\Concerns\WithModal;
use App\Livewire\Concerns\WithToast; use App\Livewire\Concerns\WithToast;
use App\Livewire\Forms\Finance\ProductMappingForm;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Locked; use Livewire\Attributes\Locked;
use Livewire\Component; use Livewire\Component;
@ -16,7 +23,9 @@ new class extends Component
#[Locked] #[Locked]
public string $mode = 'create'; public string $mode = 'create';
protected string $resourceName = 'form'; public ProductMappingForm $form;
protected string $resourceName = 'product-mapping';
public function show(int|string $id): void public function show(int|string $id): void
{ {
@ -25,9 +34,34 @@ new class extends Component
// $this->form->fill($this->model->only(['name'])); // $this->form->fill($this->model->only(['name']));
} }
#[Computed]
public function coaOptions(): Collection
{
return ChartOfAccount::query()
->select(['id', 'name', 'code'])
->orderBy('code')
->get()
->mapWithKeys(fn ($coa) => [$coa->id => "{$coa->code} - {$coa->name}"]);
}
public function save(RegisterProductMapping $register, ModifyProductMapping $modify): void
{
$this->form->validate();
if($this->mode == 'create') {
$register->execute(new RegisterProductMappingDTO(
name: $this->form->name,
code: $this->form->code,
coaId: $this->form->coa_id,
));
} elseif($this->mode == 'update') {
$modify->execute($this->id, $this->form->coa_id);
}
}
public function hide(): void public function hide(): void
{ {
// $this->form->reset(); // $this->form->reset();
$this->dispatch('form.coa_id-clear');
$this->reset('id', 'mode'); $this->reset('id', 'mode');
} }
}; };

Loading…
Cancel
Save