Browse Source
- Introduced `ChartOfAccount` model with parent-child relationships, casting, and policy integration. - Defined DTOs and actions for registering, adjusting, and suspending accounts (`RegisterChartOfAccount`, `AdjustAccountClassification`, `SuspendAccountPostings`). - Added enums for account classifications and balances. - Created `AccountingServiceProvider` and `RelationshipServiceProvider` for domain initialization and runtime relationship binding. - Migrated COA-related logic from Billing domain and removed redundant files. - Updated DataTables, Livewire forms, and localization for COA and product management.master
62 changed files with 1222 additions and 116 deletions
@ -0,0 +1,19 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Accounting\Actions\LedgerConfiguration; |
||||
|
|
||||
|
use App\Domains\Accounting\DTOs\LedgerConfiguration\AdjustAccountClassificationDTO; |
||||
|
use App\Domains\Accounting\Models\ChartOfAccount; |
||||
|
|
||||
|
class AdjustAccountClassification |
||||
|
{ |
||||
|
public function execute(ChartOfAccount $coa, AdjustAccountClassificationDTO $dto): void |
||||
|
{ |
||||
|
$coa->update([ |
||||
|
'code' => $dto->code, |
||||
|
'normal_balance' => $dto->normal_balance, |
||||
|
'classification' => $dto->classification, |
||||
|
'parent_id' => $dto->parentId, |
||||
|
]); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Accounting\Actions\LedgerConfiguration; |
||||
|
|
||||
|
use App\Domains\Accounting\DTOs\LedgerConfiguration\RegisterChartOfAccountDTO; |
||||
|
use App\Domains\Accounting\Models\ChartOfAccount; |
||||
|
|
||||
|
class RegisterChartOfAccount |
||||
|
{ |
||||
|
public function execute(RegisterChartOfAccountDTO $dto): void |
||||
|
{ |
||||
|
ChartOfAccount::create([ |
||||
|
'code' => $dto->code, |
||||
|
'name' => $dto->name, |
||||
|
'classification' => $dto->classification, |
||||
|
'normal_balance' => $dto->normal_balance, |
||||
|
'parent_id' => $dto->parentId, |
||||
|
]); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Accounting\Actions\LedgerConfiguration; |
||||
|
|
||||
|
use App\Domains\Accounting\Models\ChartOfAccount; |
||||
|
use App\Domains\System\Enums\LifecycleStatus; |
||||
|
|
||||
|
class SuspendAccountPostings |
||||
|
{ |
||||
|
public function execute(ChartOfAccount $coa): void |
||||
|
{ |
||||
|
$coa->update([ |
||||
|
'status' => LifecycleStatus::INACTIVE, |
||||
|
]); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Accounting\DTOs\LedgerConfiguration; |
||||
|
|
||||
|
use App\Domains\Accounting\Enums\Ledger\AccountBalance; |
||||
|
use App\Domains\Accounting\Enums\Ledger\AccountClassification; |
||||
|
|
||||
|
readonly class AdjustAccountClassificationDTO |
||||
|
{ |
||||
|
public function __construct( |
||||
|
public string $code, |
||||
|
public string $name, |
||||
|
public AccountClassification $classification, |
||||
|
public AccountBalance $normal_balance, |
||||
|
public ?int $parentId, |
||||
|
) {} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Accounting\DTOs\LedgerConfiguration; |
||||
|
|
||||
|
use App\Domains\Accounting\Enums\Ledger\AccountBalance; |
||||
|
use App\Domains\Accounting\Enums\Ledger\AccountClassification; |
||||
|
use App\Domains\System\Enums\LifecycleStatus; |
||||
|
|
||||
|
readonly class RegisterChartOfAccountDTO |
||||
|
{ |
||||
|
public function __construct( |
||||
|
public string $code, |
||||
|
public string $name, |
||||
|
public AccountClassification $classification, |
||||
|
public AccountBalance $normal_balance, |
||||
|
public LifecycleStatus $status, |
||||
|
public ?int $parentId, |
||||
|
) {} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Accounting\Enums\Ledger; |
||||
|
|
||||
|
use App\Domains\System\Traits\Enum\HasPredicateMethod; |
||||
|
use App\UI\Enums\Concerns\InteractsWithLabels; |
||||
|
use App\UI\Enums\Contracts\HasLabel; |
||||
|
use Yajra\DataTables\Html\HasOptions; |
||||
|
|
||||
|
enum AccountBalance: string implements HasLabel |
||||
|
{ |
||||
|
use HasPredicateMethod; |
||||
|
use HasOptions; |
||||
|
use InteractsWithLabels; |
||||
|
|
||||
|
case CREDIT = 'credit'; |
||||
|
case DEBIT = 'debit'; |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Accounting\Enums\Ledger; |
||||
|
|
||||
|
use App\Domains\System\Traits\Enum\HasPredicateMethod; |
||||
|
use App\UI\Enums\Concerns\InteractsWithLabels; |
||||
|
use App\UI\Enums\Contracts\HasLabel; |
||||
|
use Yajra\DataTables\Html\HasOptions; |
||||
|
|
||||
|
enum AccountClassification: string implements HasLabel |
||||
|
{ |
||||
|
use HasPredicateMethod; |
||||
|
use HasOptions; |
||||
|
use InteractsWithLabels; |
||||
|
|
||||
|
case LIABILITY = 'liability'; |
||||
|
case ASSET = 'asset'; |
||||
|
case EQUITY = 'equity'; |
||||
|
case REVENUE = 'revenue'; |
||||
|
case EXPENSE = 'expense'; |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Accounting\Models; |
||||
|
|
||||
|
use App\Domains\Accounting\Enums\Ledger\AccountBalance; |
||||
|
use App\Domains\Accounting\Enums\Ledger\AccountClassification; |
||||
|
use App\Domains\Accounting\Policies\ChartOfAccountPolicy; |
||||
|
use App\Domains\System\Traits\Model\HasPublicUlid; |
||||
|
use Illuminate\Database\Eloquent\Attributes\Fillable; |
||||
|
use Illuminate\Database\Eloquent\Attributes\UsePolicy; |
||||
|
use Illuminate\Database\Eloquent\Model; |
||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo; |
||||
|
use Illuminate\Database\Eloquent\Relations\HasMany; |
||||
|
|
||||
|
#[Fillable([ |
||||
|
'code', |
||||
|
'name', |
||||
|
'classification', |
||||
|
'normal_balance', |
||||
|
'parent_id', |
||||
|
])] |
||||
|
#[UsePolicy(ChartOfAccountPolicy::class)] |
||||
|
class ChartOfAccount extends Model |
||||
|
{ |
||||
|
use HasPublicUlid; |
||||
|
|
||||
|
protected $casts = [ |
||||
|
'classification' => AccountClassification::class, |
||||
|
'normal_balance' => AccountBalance::class, |
||||
|
]; |
||||
|
|
||||
|
public function parent(): BelongsTo |
||||
|
{ |
||||
|
return $this->belongsTo(ChartOfAccount::class, 'parent_id'); |
||||
|
} |
||||
|
|
||||
|
public function children(): HasMany |
||||
|
{ |
||||
|
return $this->hasMany(ChartOfAccount::class, 'parent_id'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,34 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Accounting\Policies; |
||||
|
|
||||
|
use App\Domains\Accounting\Models\ChartOfAccount; |
||||
|
use App\Domains\Identity\Models\User; |
||||
|
|
||||
|
class ChartOfAccountPolicy |
||||
|
{ |
||||
|
public function viewAny(User $user): bool |
||||
|
{ |
||||
|
return $user->hasPermissionTo('coa.viewAny'); |
||||
|
} |
||||
|
|
||||
|
public function view(User $user, ChartOfAccount $model): bool |
||||
|
{ |
||||
|
return $user->hasPermissionTo('coa.view'); |
||||
|
} |
||||
|
|
||||
|
public function create(User $user): bool |
||||
|
{ |
||||
|
return $user->hasPermissionTo('coa.create'); |
||||
|
} |
||||
|
|
||||
|
public function update(User $user, ChartOfAccount $model): bool |
||||
|
{ |
||||
|
return $user->hasPermissionTo('coa.update'); |
||||
|
} |
||||
|
|
||||
|
public function delete(User $user, ChartOfAccount $model): bool |
||||
|
{ |
||||
|
return $user->hasPermissionTo('coa.delete'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Accounting\Providers; |
||||
|
|
||||
|
use App\Domains\System\Traits\Provider\RegistersDomainEvents; |
||||
|
use Illuminate\Support\ServiceProvider; |
||||
|
|
||||
|
class AccountingServiceProvider extends ServiceProvider |
||||
|
{ |
||||
|
use RegistersDomainEvents; |
||||
|
|
||||
|
protected array $listen = []; |
||||
|
|
||||
|
public function register(): void |
||||
|
{ |
||||
|
$this->app->register(RelationshipServiceProvider::class); |
||||
|
} |
||||
|
|
||||
|
public function boot(): void |
||||
|
{ |
||||
|
$this->registerEvents(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Accounting\Providers; |
||||
|
|
||||
|
use Illuminate\Support\ServiceProvider; |
||||
|
|
||||
|
class RelationshipServiceProvider extends ServiceProvider |
||||
|
{ |
||||
|
/** |
||||
|
* Boot cross-domain runtime relationships. |
||||
|
* |
||||
|
* This provider is strictly dedicated to runtime, decoupled cross-domain |
||||
|
* relationship definitions. Use Model::resolveRelationUsing() here to wire |
||||
|
* explicit polymorphic or foreign-key macros between models that live in |
||||
|
* different domains — without introducing a hard compile-time dependency |
||||
|
* between those domains. |
||||
|
* |
||||
|
* Do NOT add route registrations, bindings, event listeners, or any other |
||||
|
* bootstrapping logic here. Keep this file focused on relationships only. |
||||
|
* |
||||
|
* Example: |
||||
|
* |
||||
|
* Order::resolveRelationUsing('customer', fn (Order $order) => |
||||
|
* $order->belongsTo(Customer::class, 'customer_id') |
||||
|
* ); |
||||
|
*/ |
||||
|
public function boot(): void |
||||
|
{ |
||||
|
// Register cross-domain relationships below. |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Billing\Actions\Catalog; |
||||
|
|
||||
|
use App\Domains\Billing\DTOs\Catalog\ModifyProductDTO; |
||||
|
use App\Domains\Billing\Models\Product; |
||||
|
use Illuminate\Support\Facades\DB; |
||||
|
|
||||
|
class ModifyProduct |
||||
|
{ |
||||
|
public function execute(Product $product, ModifyProductDTO $dto): Product |
||||
|
{ |
||||
|
return DB::transaction(function () use ($product, $dto) { |
||||
|
$product->update([ |
||||
|
'name' => $dto->name, |
||||
|
'code' => $dto->code, |
||||
|
'coa_id' => $dto->coaId, |
||||
|
]); |
||||
|
|
||||
|
return $product->fresh(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Billing\Actions\Catalog; |
||||
|
|
||||
|
use App\Domains\Billing\DTOs\Catalog\RegisterProductDTO; |
||||
|
use App\Domains\Billing\Models\Product; |
||||
|
use Illuminate\Support\Facades\DB; |
||||
|
|
||||
|
class RegisterProduct |
||||
|
{ |
||||
|
public function execute(RegisterProductDTO $dto): Product |
||||
|
{ |
||||
|
return DB::transaction(function () use ($dto) { |
||||
|
return Product::create([ |
||||
|
'name' => $dto->name, |
||||
|
'code' => $dto->code, |
||||
|
'coa_id' => $dto->coaId, |
||||
|
]); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Billing\DTOs\Catalog; |
||||
|
|
||||
|
readonly class ModifyProductDTO |
||||
|
{ |
||||
|
public function __construct( |
||||
|
public string $name, |
||||
|
public string $code, |
||||
|
public int $coaId, |
||||
|
) {} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Domains\Billing\DTOs\Catalog; |
||||
|
|
||||
|
readonly class RegisterProductDTO |
||||
|
{ |
||||
|
public function __construct( |
||||
|
public string $name, |
||||
|
public string $code, |
||||
|
public int $coaId, |
||||
|
) {} |
||||
|
} |
||||
@ -1,9 +0,0 @@ |
|||||
<?php |
|
||||
|
|
||||
namespace App\Domains\Billing\Enums\Catalog; |
|
||||
|
|
||||
enum AccountBalance: string |
|
||||
{ |
|
||||
case CREDIT = 'credit'; |
|
||||
case DEBIT = 'debit'; |
|
||||
} |
|
||||
@ -1,12 +0,0 @@ |
|||||
<?php |
|
||||
|
|
||||
namespace App\Domains\Billing\Enums\Catalog; |
|
||||
|
|
||||
enum AccountClassification: string |
|
||||
{ |
|
||||
case LIABILITY = 'liability'; |
|
||||
case ASSET = 'asset'; |
|
||||
case EQUITY = 'equity'; |
|
||||
case REVENUE = 'revenue'; |
|
||||
case EXPENSE = 'expense'; |
|
||||
} |
|
||||
@ -1,21 +0,0 @@ |
|||||
<?php |
|
||||
|
|
||||
namespace App\Domains\Billing\Models; |
|
||||
|
|
||||
use Database\Factories\Billing\ChartOfAccountFactory; |
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable; |
|
||||
use Illuminate\Database\Eloquent\Attributes\UseFactory; |
|
||||
use Illuminate\Database\Eloquent\Model; |
|
||||
|
|
||||
#[Fillable([ |
|
||||
'code', |
|
||||
'name', |
|
||||
'classification', |
|
||||
'normal_balance' |
|
||||
])] |
|
||||
#[UseFactory(ChartOfAccountFactory::class)] |
|
||||
class ChartOfAccount extends Model |
|
||||
{ |
|
||||
use HasFactory; |
|
||||
} |
|
||||
@ -1,11 +0,0 @@ |
|||||
<?php |
|
||||
|
|
||||
namespace App\Domains\Payment\Listeners\VirtualAccount; |
|
||||
|
|
||||
class InitializeVirtualAccountForPublishedInvoice |
|
||||
{ |
|
||||
public function handle(object $event): void |
|
||||
{ |
|
||||
// |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,32 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Http\Controllers\Web\Accounting; |
||||
|
|
||||
|
use App\Attributes\LayoutData; |
||||
|
use App\Attributes\Seo; |
||||
|
use App\Http\Controllers\Controller; |
||||
|
use App\Http\DataTables\Accounting\ChartOfAccountDataTable; |
||||
|
|
||||
|
class ChartOfAccountController extends Controller |
||||
|
{ |
||||
|
/** |
||||
|
* Handle the incoming request. |
||||
|
*/ |
||||
|
#[Seo( |
||||
|
title: 'domains/accounting/seo.coa.title', |
||||
|
description: 'domains/accounting/seo.coa.description', |
||||
|
keywords: 'domains/accounting/seo.coa.keywords' |
||||
|
)] |
||||
|
#[LayoutData( |
||||
|
header: 'domains/accounting/seo.coa.title', |
||||
|
breadcrumbs: [ |
||||
|
'ui/menu.dashboard' => 'dashboard', |
||||
|
'domains/accounting/seo.coa.title' => '', |
||||
|
], |
||||
|
)] |
||||
|
public function __invoke(ChartOfAccountDataTable $dataTable) |
||||
|
{ |
||||
|
return $dataTable->render('pages.accounting.chartofaccounts.index'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,31 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Http\Controllers\Web\Billing; |
||||
|
|
||||
|
use App\Attributes\LayoutData; |
||||
|
use App\Attributes\Seo; |
||||
|
use App\Http\Controllers\Controller; |
||||
|
use App\Http\DataTables\Billing\ProductDataTable; |
||||
|
|
||||
|
class ProductController extends Controller |
||||
|
{ |
||||
|
/** |
||||
|
* Handle the incoming request. |
||||
|
*/ |
||||
|
#[Seo( |
||||
|
title: 'domains/billing/seo.product.title', |
||||
|
description: 'domains/billing/seo.product.description', |
||||
|
keywords: 'domains/billing/seo.product.keywords' |
||||
|
)] |
||||
|
#[LayoutData( |
||||
|
header: 'domains/billing/seo.product.title', |
||||
|
breadcrumbs: [ |
||||
|
'ui/menu.dashboard' => 'dashboard', |
||||
|
'domains/billing/seo.product.title' => '', |
||||
|
], |
||||
|
)] |
||||
|
public function __invoke(ProductDataTable $dataTable) |
||||
|
{ |
||||
|
return $dataTable->render('pages.billing.products.index'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,130 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Http\DataTables\Accounting; |
||||
|
|
||||
|
use App\Domains\Accounting\Models\ChartOfAccount; |
||||
|
use Illuminate\Database\Eloquent\Builder as QueryBuilder; |
||||
|
use Yajra\DataTables\EloquentDataTable; |
||||
|
use Yajra\DataTables\Html\Builder as HtmlBuilder; |
||||
|
use Yajra\DataTables\Html\Button; |
||||
|
use Yajra\DataTables\Html\Column; |
||||
|
use Yajra\DataTables\Services\DataTable; |
||||
|
|
||||
|
class ChartOfAccountDataTable extends DataTable |
||||
|
{ |
||||
|
/** |
||||
|
* Build the DataTable class. |
||||
|
* |
||||
|
* @param QueryBuilder<ChartOfAccount> $query Results from query() method. |
||||
|
*/ |
||||
|
public function dataTable(QueryBuilder $query): EloquentDataTable |
||||
|
{ |
||||
|
return (new EloquentDataTable($query)) |
||||
|
->editColumn('normal_balance', fn (ChartOfAccount $chartOfAccount) => $chartOfAccount->normal_balance->label()) |
||||
|
->editColumn('classification', fn (ChartOfAccount $chartOfAccount) => $chartOfAccount->classification->label()) |
||||
|
->addColumn( |
||||
|
'action', |
||||
|
fn (ChartOfAccount $chartOfAccount) => view('components.datatables.action-button', [ |
||||
|
'edit' => [ |
||||
|
'modal' => 'chartofaccount-form-modal', |
||||
|
'permission' => auth()->user()->can('update', $chartOfAccount), |
||||
|
], |
||||
|
'delete' => [ |
||||
|
'url' => null, |
||||
|
'title' => __('ui/button.delete'), |
||||
|
'permission' => auth()->user()->can('delete', $chartOfAccount), |
||||
|
'message' => __('ui/confirmation.delete', ['resource' => __('resources.chartOfAccount')]), |
||||
|
'success_message' => __('ui/crud.success.deleted', ['resource' => __('resources.chartOfAccount')]), |
||||
|
], |
||||
|
'table_name' => 'chart-of-account-table', |
||||
|
'id' => $chartOfAccount->ulid, |
||||
|
]) |
||||
|
) |
||||
|
->addIndexColumn(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the query source of dataTable. |
||||
|
* |
||||
|
* @return QueryBuilder<ChartOfAccount> |
||||
|
*/ |
||||
|
public function query(ChartOfAccount $model): QueryBuilder |
||||
|
{ |
||||
|
return $model->newQuery(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Optional method if you want to use the html builder. |
||||
|
*/ |
||||
|
public function html(): HtmlBuilder |
||||
|
{ |
||||
|
return $this->builder() |
||||
|
->setTableId('chart-of-account-table') |
||||
|
->columns($this->getColumns()) |
||||
|
->minifiedAjax() |
||||
|
->orderBy(1) |
||||
|
->layout([ |
||||
|
'topStart' => [ |
||||
|
'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', |
||||
|
'features' => ['buttons', 'pageLength'], |
||||
|
], |
||||
|
'topEnd' => [ |
||||
|
'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', |
||||
|
'features' => ['search'], |
||||
|
], |
||||
|
|
||||
|
'bottomStart' => 'info', |
||||
|
'bottomEnd' => 'paging', |
||||
|
]) |
||||
|
->parameters([ |
||||
|
'language' => [ |
||||
|
'search' => '', |
||||
|
'searchPlaceholder' => __('ui/button.lookup'), |
||||
|
], |
||||
|
]) |
||||
|
->buttons([ |
||||
|
Button::make('add') |
||||
|
->action('$("#chartofaccount-form-modal").modal("show");') |
||||
|
->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) |
||||
|
->addClass('btn-sm'), |
||||
|
Button::make('reload') |
||||
|
->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) |
||||
|
->addClass('btn-sm'), |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the dataTable columns definition. |
||||
|
*/ |
||||
|
public function getColumns(): array |
||||
|
{ |
||||
|
return [ |
||||
|
Column::computed('DT_RowIndex') |
||||
|
->title('#') |
||||
|
->searchable(false) |
||||
|
->orderable(false), |
||||
|
Column::make('code') |
||||
|
->title(__('domains/accounting/field.coa.code')), |
||||
|
Column::make('name') |
||||
|
->title(__('domains/accounting/field.coa.name')), |
||||
|
Column::make('normal_balance') |
||||
|
->title(__('domains/accounting/field.coa.normal_balance')), |
||||
|
Column::make('classification') |
||||
|
->title(__('domains/accounting/field.coa.classification')), |
||||
|
Column::computed('action') |
||||
|
->title(__('ui/label.actions')) |
||||
|
->exportable(false) |
||||
|
->printable(false) |
||||
|
->width(60) |
||||
|
->addClass('text-center'), |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the filename for export. |
||||
|
*/ |
||||
|
protected function filename(): string |
||||
|
{ |
||||
|
return 'ChartOfAccountDataTable_' . date('YmdHis'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,131 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Http\DataTables\Billing; |
||||
|
|
||||
|
use App\Domains\Billing\Models\Product; |
||||
|
use Illuminate\Database\Eloquent\Builder as QueryBuilder; |
||||
|
use Yajra\DataTables\EloquentDataTable; |
||||
|
use Yajra\DataTables\Html\Builder as HtmlBuilder; |
||||
|
use Yajra\DataTables\Html\Button; |
||||
|
use Yajra\DataTables\Html\Column; |
||||
|
use Yajra\DataTables\Services\DataTable; |
||||
|
|
||||
|
class ProductDataTable extends DataTable |
||||
|
{ |
||||
|
/** |
||||
|
* Build the DataTable class. |
||||
|
* |
||||
|
* @param QueryBuilder<Product> $query Results from query() method. |
||||
|
*/ |
||||
|
public function dataTable(QueryBuilder $query): EloquentDataTable |
||||
|
{ |
||||
|
return (new EloquentDataTable($query)) |
||||
|
->editColumn('chart_of_account.name', fn (Product $product) => $product->chartOfAccount?->name ?? '-') |
||||
|
->addColumn( |
||||
|
'action', |
||||
|
fn (Product $product) => view('components.datatables.action-button', [ |
||||
|
'edit' => [ |
||||
|
'modal' => 'product-form-modal', |
||||
|
'permission' => auth()->user()->can('update', $product), |
||||
|
], |
||||
|
'delete' => [ |
||||
|
'url' => null, |
||||
|
'title' => __('ui/button.delete'), |
||||
|
'permission' => auth()->user()->can('delete', $product), |
||||
|
'message' => __('ui/confirmation.delete', ['resource' => __('resources.product')]), |
||||
|
'success_message' => __('ui/crud.success.deleted', ['resource' => __('resources.product')]), |
||||
|
], |
||||
|
'table_name' => 'product-table', |
||||
|
'id' => $product->id, |
||||
|
]) |
||||
|
) |
||||
|
->rawColumns(['action']) |
||||
|
->addIndexColumn(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the query source of dataTable. |
||||
|
* |
||||
|
* @return QueryBuilder<Product> |
||||
|
*/ |
||||
|
public function query(Product $model): QueryBuilder |
||||
|
{ |
||||
|
return $model->newQuery()->with(['chartOfAccount', 'pricing']); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Optional method if you want to use the html builder. |
||||
|
*/ |
||||
|
public function html(): HtmlBuilder |
||||
|
{ |
||||
|
return $this->builder() |
||||
|
->setTableId('product-table') |
||||
|
->columns($this->getColumns()) |
||||
|
->minifiedAjax() |
||||
|
->orderBy(1) |
||||
|
->layout([ |
||||
|
'topStart' => [ |
||||
|
'className' => 'col-md-auto me-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-start align-items-center align-items-md-start gap-1', |
||||
|
'features' => ['buttons', 'pageLength'], |
||||
|
], |
||||
|
'topEnd' => [ |
||||
|
'className' => 'col-md-auto ms-auto d-flex flex-sm-row flex-column justify-content-center justify-content-md-end align-items-center align-items-md-start gap-1', |
||||
|
'features' => ['search'], |
||||
|
], |
||||
|
|
||||
|
'bottomStart' => 'info', |
||||
|
'bottomEnd' => 'paging', |
||||
|
]) |
||||
|
->parameters([ |
||||
|
'language' => [ |
||||
|
'search' => '', |
||||
|
'searchPlaceholder' => __('ui/button.lookup'), |
||||
|
], |
||||
|
'scrollX' => true, |
||||
|
'scrollCollapse' => true, |
||||
|
'responsive' => true, |
||||
|
]) |
||||
|
->buttons([ |
||||
|
Button::make('add') |
||||
|
->action('$("#product-form-modal").modal("show");') |
||||
|
->text(svg('tabler-plus', ['width' => 16, 'height' => 16])->toHtml()) |
||||
|
->addClass('btn-sm'), |
||||
|
Button::make('reload') |
||||
|
->text(svg('tabler-reload', ['width' => 16, 'height' => 16])->toHtml()) |
||||
|
->addClass('btn-sm'), |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the dataTable columns definition. |
||||
|
*/ |
||||
|
public function getColumns(): array |
||||
|
{ |
||||
|
return [ |
||||
|
Column::computed('DT_RowIndex') |
||||
|
->title('#') |
||||
|
->searchable(false) |
||||
|
->orderable(false), |
||||
|
Column::make('code') |
||||
|
->title(__('domains/billing/field.product.code')), |
||||
|
Column::make('name') |
||||
|
->title(__('domains/billing/field.product.name')), |
||||
|
Column::make('chart_of_account.name') |
||||
|
->title(__('domains/billing/field.product.coa')), |
||||
|
Column::computed('action') |
||||
|
->title(__('ui/label.actions')) |
||||
|
->exportable(false) |
||||
|
->printable(false) |
||||
|
->width(60) |
||||
|
->addClass('text-center'), |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the filename for export. |
||||
|
*/ |
||||
|
protected function filename(): string |
||||
|
{ |
||||
|
return 'ProductDataTable_' . date('YmdHis'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Livewire\Forms\Accounting; |
||||
|
|
||||
|
use App\Domains\Accounting\Enums\Ledger\AccountBalance; |
||||
|
use App\Domains\Accounting\Enums\Ledger\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/accounting/field.coa.code')] |
||||
|
public string $code = ''; |
||||
|
|
||||
|
#[Validate('required|string|max:255', as: 'domains/accounting/field.coa.name')] |
||||
|
public string $name = ''; |
||||
|
|
||||
|
#[Validate('required', as: 'domains/accounting/field.coa.classification')] |
||||
|
public ?AccountClassification $classification = null; |
||||
|
|
||||
|
#[Validate('required', as: 'domains/accounting/field.coa.normal_balance')] |
||||
|
public ?AccountBalance $normal_balance = null; |
||||
|
|
||||
|
#[Validate(as: 'domains/accounting/field.coa.parent')] |
||||
|
public int|string|null $parent_id = null; |
||||
|
|
||||
|
#[Validate('required', as: 'domains/accounting/field.coa.status')] |
||||
|
public ?LifecycleStatus $status = LifecycleStatus::ACTIVE; |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Livewire\Forms\Billing; |
||||
|
|
||||
|
use Livewire\Attributes\Validate; |
||||
|
use Livewire\Form; |
||||
|
|
||||
|
class ProductForm extends Form |
||||
|
{ |
||||
|
#[Validate('required|string|max:255', as: 'domains/billing/field.product.code')] |
||||
|
public string $code = ''; |
||||
|
|
||||
|
#[Validate('required|string|max:255', as: 'domains/billing/field.product.name')] |
||||
|
public string $name = ''; |
||||
|
|
||||
|
#[Validate('required|integer|exists:chart_of_accounts,id', as: 'domains/billing/field.product.coa')] |
||||
|
public ?int $coa_id = null; |
||||
|
} |
||||
@ -1,26 +0,0 @@ |
|||||
<?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; |
|
||||
} |
|
||||
@ -0,0 +1,21 @@ |
|||||
|
<?php |
||||
|
|
||||
|
use App\Domains\Accounting\Enums\Ledger\AccountClassification; |
||||
|
use App\Domains\Accounting\Enums\Ledger\AccountBalance; |
||||
|
|
||||
|
return [ |
||||
|
|
||||
|
'account_balance' => [ |
||||
|
AccountBalance::CREDIT->value => 'Credit', |
||||
|
AccountBalance::DEBIT->value => 'Debit', |
||||
|
], |
||||
|
|
||||
|
|
||||
|
'account_classification' => [ |
||||
|
AccountClassification::ASSET->value => 'Asset', |
||||
|
AccountClassification::LIABILITY->value => 'Liability', |
||||
|
AccountClassification::EQUITY->value => 'Equity', |
||||
|
AccountClassification::REVENUE->value => 'Revenue', |
||||
|
AccountClassification::EXPENSE->value => 'Expense', |
||||
|
], |
||||
|
]; |
||||
@ -0,0 +1,12 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
'coa' => [ |
||||
|
'code' => 'Account Code', |
||||
|
'name' => 'Account Name', |
||||
|
'normal_balance' => 'Normal Balance', |
||||
|
'classification' => 'Classification', |
||||
|
'status' => 'Status', |
||||
|
'parent' => 'Derived From', |
||||
|
], |
||||
|
]; |
||||
@ -0,0 +1,9 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
'coa' => [ |
||||
|
'title' => 'Chart of Accounts (COA) Data', |
||||
|
'description' => 'Manage chart of accounts for financial records.', |
||||
|
'keywords' => 'chart of accounts, coa, accounting, finance', |
||||
|
], |
||||
|
]; |
||||
@ -0,0 +1,9 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
'product' => [ |
||||
|
'code' => 'Product Code', |
||||
|
'name' => 'Product Name', |
||||
|
'coa' => 'Chart of Account (COA)', |
||||
|
], |
||||
|
]; |
||||
@ -0,0 +1,9 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
'product' => [ |
||||
|
'title' => 'Product Data', |
||||
|
'description' => 'Manage products and their chart of account mappings.', |
||||
|
'keywords' => 'product, products, billing, finance, coa', |
||||
|
], |
||||
|
]; |
||||
@ -0,0 +1,19 @@ |
|||||
|
<?php |
||||
|
|
||||
|
use App\Domains\Accounting\Enums\Ledger\AccountBalance; |
||||
|
use App\Domains\Accounting\Enums\Ledger\AccountClassification; |
||||
|
|
||||
|
return [ |
||||
|
'account_balance' => [ |
||||
|
AccountBalance::CREDIT->value => 'Kredit', |
||||
|
AccountBalance::DEBIT->value => 'Debit', |
||||
|
], |
||||
|
|
||||
|
'account_classification' => [ |
||||
|
AccountClassification::ASSET->value => 'Aset', |
||||
|
AccountClassification::LIABILITY->value => 'Liabilitas', |
||||
|
AccountClassification::EQUITY->value => 'Ekuitas', |
||||
|
AccountClassification::REVENUE->value => 'Pendapatan', |
||||
|
AccountClassification::EXPENSE->value => 'Beban', |
||||
|
], |
||||
|
]; |
||||
@ -0,0 +1,12 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
'coa' => [ |
||||
|
'code' => 'Kode Akun', |
||||
|
'name' => 'Nama Akun', |
||||
|
'normal_balance' => 'Saldo Normal', |
||||
|
'classification' => 'Klasifikasi', |
||||
|
'status' => 'Status', |
||||
|
'parent' => 'Turunan Dari', |
||||
|
], |
||||
|
]; |
||||
@ -0,0 +1,9 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
'coa' => [ |
||||
|
'title' => 'Data Bagan Akun (COA)', |
||||
|
'description' => 'Kelola bagan akun untuk pencatatan keuangan.', |
||||
|
'keywords' => 'bagan akun, coa, akuntansi, keuangan', |
||||
|
], |
||||
|
]; |
||||
@ -0,0 +1,9 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
'product' => [ |
||||
|
'code' => 'Kode Produk', |
||||
|
'name' => 'Nama Produk', |
||||
|
'coa' => 'Bagan Akun (COA)', |
||||
|
], |
||||
|
]; |
||||
@ -0,0 +1,9 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
'product' => [ |
||||
|
'title' => 'Data Produk', |
||||
|
'description' => 'Kelola produk dan pemetaan bagan akun.', |
||||
|
'keywords' => 'produk, billing, tagihan, coa', |
||||
|
], |
||||
|
]; |
||||
@ -0,0 +1,8 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
'clients' => [ |
||||
|
'synced' => 'Klien berhasil disinkronkan.', |
||||
|
'fail_to_sync' => 'Klien gagal disinkronkan: :error.', |
||||
|
], |
||||
|
]; |
||||
@ -0,0 +1,20 @@ |
|||||
|
<x-layouts.app> |
||||
|
<x-card> {{ $dataTable->table() }} </x-card> |
||||
|
|
||||
|
<livewire:pages::system.audit.audit-view-modal |
||||
|
key-name="ulid" |
||||
|
:model="\App\Domains\Accounting\Models\ChartOfAccount::class" |
||||
|
translation="domains/accounting/field.coa." |
||||
|
/> |
||||
|
|
||||
|
<livewire:pages::accounting.chartofaccounts.form-modal /> |
||||
|
|
||||
|
@can('chart-of-account.delete') |
||||
|
<livewire:datatables.delete-action key-name="ulid" :model="\App\Domains\Accounting\Models\ChartOfAccount::class" /> |
||||
|
@endcan |
||||
|
|
||||
|
@push('page-scripts') |
||||
|
@vite('resources/js/plugin/datatables.js') |
||||
|
{{ $dataTable->scripts(attributes: ['type' => 'module']) }} |
||||
|
@endpush |
||||
|
</x-layouts.app> |
||||
@ -0,0 +1,56 @@ |
|||||
|
@use(App\Domains\Accounting\Enums\Ledger\AccountBalance) |
||||
|
@use(App\Domains\Accounting\Enums\Ledger\AccountClassification) |
||||
|
@use(App\Domains\System\Enums\LifecycleStatus) |
||||
|
<x-modal id="chartofaccount-form-modal" :title="$this->title" wire:submit="save" wire:loading form livewire> |
||||
|
<div class="d-flex flex-column gap-3"> |
||||
|
<x-form.select |
||||
|
name="form.parent" |
||||
|
:label="__('domains/accounting/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/accounting/field.coa.code')" |
||||
|
/> |
||||
|
|
||||
|
<x-form.input |
||||
|
name="form.name" |
||||
|
wire:model="form.name" |
||||
|
:disabled="$mode == 'update'" |
||||
|
:label="__('domains/accounting/field.coa.name')" |
||||
|
/> |
||||
|
|
||||
|
<x-form.select |
||||
|
name="form.classification" |
||||
|
:label="__('domains/accounting/field.coa.normal_balance')" |
||||
|
wire:model="form.normal_balance" |
||||
|
:disabled="$form->parent_id != null" |
||||
|
:options="AccountBalance::options()" |
||||
|
/> |
||||
|
|
||||
|
<x-form.select |
||||
|
name="form.classification" |
||||
|
:label="__('domains/accounting/field.coa.classification')" |
||||
|
wire:model="form.classification" |
||||
|
:disabled="$form->parent_id != null" |
||||
|
:options="AccountClassification::options()" |
||||
|
/> |
||||
|
|
||||
|
<x-form.select |
||||
|
name="form.status" |
||||
|
:label="__('domains/accounting/field.coa.status')" |
||||
|
wire:model="form.status" |
||||
|
:disabled="$mode == 'update'" |
||||
|
:options="LifecycleStatus::options()" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<x-slot:footer> |
||||
|
<x-button type="button" theme="secondary" :label="__('ui/button.cancel')" data-coreui-dismiss="modal"/> |
||||
|
<x-button type="submit" theme="primary" :label="__('ui/button.save')"/> |
||||
|
</x-slot:footer> |
||||
|
</x-modal> |
||||
@ -0,0 +1,115 @@ |
|||||
|
<?php |
||||
|
|
||||
|
use App\Domains\Accounting\Actions\LedgerConfiguration\AdjustAccountClassification; |
||||
|
use App\Domains\Accounting\Actions\LedgerConfiguration\RegisterChartOfAccount; |
||||
|
use App\Domains\Accounting\DTOs\LedgerConfiguration\AdjustAccountClassificationDTO; |
||||
|
use App\Domains\Accounting\DTOs\LedgerConfiguration\RegisterChartOfAccountDTO; |
||||
|
use App\Domains\Accounting\Models\ChartOfAccount; |
||||
|
use App\Livewire\Concerns\WithModal; |
||||
|
use App\Livewire\Concerns\WithToast; |
||||
|
use App\Livewire\Forms\Accounting\ChartOfAccountForm; |
||||
|
use Illuminate\Support\Collection; |
||||
|
use Livewire\Attributes\Computed; |
||||
|
use Livewire\Attributes\Locked; |
||||
|
use Livewire\Component; |
||||
|
|
||||
|
new class extends Component |
||||
|
{ |
||||
|
use WithModal; |
||||
|
use WithToast; |
||||
|
|
||||
|
public ChartOfAccountForm $form; |
||||
|
|
||||
|
#[Locked] |
||||
|
public ?string $id = null; |
||||
|
|
||||
|
#[Locked] |
||||
|
public string $mode = 'create'; |
||||
|
|
||||
|
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 |
||||
|
{ |
||||
|
$this->id = $id; |
||||
|
$this->mode = 'update'; |
||||
|
$coa = $this->coa->except(['ulid', 'created_at', 'updated_at']); |
||||
|
$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 = null; |
||||
|
if($this->form->parent_id) { |
||||
|
$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, |
||||
|
normal_balance: $this->form->normal_balance, |
||||
|
status: $this->form->status, |
||||
|
parentId: $parentId, |
||||
|
)); |
||||
|
$this->success(__('ui/crud.success.created', ['resource' => __('resources.chart_of_account')])); |
||||
|
} elseif ($this->mode == 'update') { |
||||
|
$adjust->execute($this->coa, new AdjustAccountClassificationDTO( |
||||
|
code: $this->form->code, |
||||
|
name: $this->form->name, |
||||
|
classification: $this->form->classification, |
||||
|
normal_balance: $this->form->normal_balance, |
||||
|
parentId: $parentId, |
||||
|
)); |
||||
|
$this->success(__('ui/crud.success.updated', ['resource' => __('resources.chart_of_account')])); |
||||
|
} |
||||
|
|
||||
|
$this->dispatch('hide-chartofaccount-form-modal'); |
||||
|
$this->js("LaravelDataTables['chart-of-account-table'].ajax.reload(null, false)"); |
||||
|
} |
||||
|
|
||||
|
public function hide(): void |
||||
|
{ |
||||
|
$this->form->reset(); |
||||
|
$this->reset('id', 'mode'); |
||||
|
$this->form->reset(); |
||||
|
} |
||||
|
}; |
||||
@ -0,0 +1,17 @@ |
|||||
|
<x-layouts.app> |
||||
|
<x-card> |
||||
|
{{ $dataTable->table() }} |
||||
|
</x-card> |
||||
|
|
||||
|
<livewire:pages::system.audit.audit-view-modal key-name="id" model="\App\Domains\Billing\Models\Product::class" |
||||
|
translation="domains/billing/field.product."/> |
||||
|
|
||||
|
<livewire:pages::billing.products.form-modal /> |
||||
|
|
||||
|
<livewire:datatables.delete-action key-name="id" :model="\App\Domains\Billing\Models\Product::class" /> |
||||
|
|
||||
|
@push('page-scripts') |
||||
|
@vite(['resources/js/plugin/datatables.js', 'resources/js/plugin/select2.js']) |
||||
|
{{ $dataTable->scripts(attributes: ['type' => 'module']) }} |
||||
|
@endpush |
||||
|
</x-layouts.app> |
||||
@ -0,0 +1,27 @@ |
|||||
|
<x-modal id="product-form-modal" :title="$this->title" wire:submit="save" wire:loading form livewire> |
||||
|
<div class="d-flex flex-column gap-3"> |
||||
|
<x-form.input |
||||
|
name="form.code" |
||||
|
:label="__('domains/billing/field.product.code')" |
||||
|
wire:model="form.code" |
||||
|
/> |
||||
|
<x-form.input |
||||
|
name="form.name" |
||||
|
:label="__('domains/billing/field.product.name')" |
||||
|
wire:model="form.name" |
||||
|
/> |
||||
|
<x-form.select x-select2="{ |
||||
|
dropdownParent: $('#product-form-modal') |
||||
|
}" |
||||
|
name="form.coa_id" |
||||
|
:label="__('domains/billing/field.product.coa')" |
||||
|
wire:model="form.coa_id" |
||||
|
:options="$this->coaOptions" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<x-slot:footer> |
||||
|
<x-button type="button" theme="secondary" :label="__('ui/button.cancel')" data-bs-dismiss="modal" /> |
||||
|
<x-button type="submit" theme="primary" :label="__('ui/button.save')" /> |
||||
|
</x-slot:footer> |
||||
|
</x-modal> |
||||
@ -0,0 +1,83 @@ |
|||||
|
<?php |
||||
|
|
||||
|
use App\Domains\Accounting\Models\ChartOfAccount; |
||||
|
use App\Domains\Billing\Actions\Catalog\ModifyProduct; |
||||
|
use App\Domains\Billing\Actions\Catalog\RegisterProduct; |
||||
|
use App\Domains\Billing\DTOs\Catalog\ModifyProductDTO; |
||||
|
use App\Domains\Billing\DTOs\Catalog\RegisterProductDTO; |
||||
|
use App\Domains\Billing\Models\Product; |
||||
|
use App\Livewire\Concerns\WithModal; |
||||
|
use App\Livewire\Concerns\WithToast; |
||||
|
use App\Livewire\Forms\Billing\ProductForm; |
||||
|
use Illuminate\Support\Collection; |
||||
|
use Livewire\Attributes\Computed; |
||||
|
use Livewire\Attributes\Locked; |
||||
|
use Livewire\Component; |
||||
|
|
||||
|
new class extends Component |
||||
|
{ |
||||
|
use WithModal; |
||||
|
use WithToast; |
||||
|
|
||||
|
#[Locked] |
||||
|
public ?int $id = null; |
||||
|
|
||||
|
#[Locked] |
||||
|
public string $mode = 'create'; |
||||
|
|
||||
|
public ProductForm $form; |
||||
|
|
||||
|
protected string $resourceName = 'product'; |
||||
|
|
||||
|
public function show(int|string $id): void |
||||
|
{ |
||||
|
$this->id = $id; |
||||
|
$this->mode = 'update'; |
||||
|
|
||||
|
$product = Product::findOrFail($id); |
||||
|
$this->form->fill([ |
||||
|
'code' => $product->code, |
||||
|
'name' => $product->name, |
||||
|
'coa_id' => $product->coa_id, |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
#[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(RegisterProduct $register, ModifyProduct $modify): void |
||||
|
{ |
||||
|
$this->form->validate(); |
||||
|
|
||||
|
if ($this->mode === 'create') { |
||||
|
$register->execute(new RegisterProductDTO( |
||||
|
name: $this->form->name, |
||||
|
code: $this->form->code, |
||||
|
coaId: $this->form->coa_id, |
||||
|
)); |
||||
|
} elseif ($this->mode === 'update') { |
||||
|
$product = Product::findOrFail($this->id); |
||||
|
$modify->execute($product, new ModifyProductDTO( |
||||
|
name: $this->form->name, |
||||
|
code: $this->form->code, |
||||
|
coaId: $this->form->coa_id, |
||||
|
)); |
||||
|
} |
||||
|
|
||||
|
$this->dispatch('hide-product-form-modal'); |
||||
|
$this->js("LaravelDataTables['product-table'].ajax.reload(null, false)"); |
||||
|
} |
||||
|
|
||||
|
public function hide(): void |
||||
|
{ |
||||
|
$this->form->reset(); |
||||
|
$this->reset('id', 'mode'); |
||||
|
} |
||||
|
}; |
||||
Loading…
Reference in new issue