| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 | <?php
namespace App\Filament\Company\Pages\Accounting;
use App\Enums\Accounting\AccountCategory;
use App\Enums\Banking\BankAccountType;
use App\Filament\Forms\Components\CreateCurrencySelect;
use App\Models\Accounting\Account;
use App\Models\Accounting\AccountSubtype;
use App\Utilities\Accounting\AccountCode;
use Filament\Actions\Action;
use Filament\Actions\CreateAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Pages\Page;
use Filament\Support\Enums\MaxWidth;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rules\Unique;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
class AccountChart extends Page
{
    protected static ?string $title = 'Chart of Accounts';
    protected static ?string $slug = 'accounting/chart';
    protected static string $view = 'filament.company.pages.accounting.chart';
    #[Url]
    public ?string $activeTab = AccountCategory::Asset->value;
    protected function configureAction(Action $action): void
    {
        $action
            ->modal()
            ->slideOver()
            ->modalWidth(MaxWidth::TwoExtraLarge);
    }
    #[Computed]
    public function categories(): Collection
    {
        return AccountSubtype::withCount('accounts')
            ->with(['accounts' => function ($query) {
                $query->withLastTransactionDate()->with('adjustment');
            }])
            ->get()
            ->groupBy('category');
    }
    public function editChartAction(): Action
    {
        return EditAction::make()
            ->iconButton()
            ->name('editChart')
            ->label('Edit account')
            ->modalHeading('Edit Account')
            ->icon('heroicon-m-pencil-square')
            ->record(fn (array $arguments) => Account::find($arguments['chart']))
            ->form(fn (Form $form) => $this->getChartForm($form)->operation('edit'));
    }
    public function createChartAction(): Action
    {
        return CreateAction::make()
            ->link()
            ->name('createChart')
            ->model(Account::class)
            ->label('Add a new account')
            ->icon('heroicon-o-plus-circle')
            ->form(fn (Form $form) => $this->getChartForm($form)->operation('create'))
            ->fillForm(fn (array $arguments): array => $this->getChartFormDefaults($arguments['subtype']));
    }
    private function getChartFormDefaults(int $subtypeId): array
    {
        $accountSubtype = AccountSubtype::find($subtypeId);
        $generatedCode = AccountCode::generate($accountSubtype);
        return [
            'subtype_id' => $subtypeId,
            'code' => $generatedCode,
        ];
    }
    private function getChartForm(Form $form, bool $useActiveTab = true): Form
    {
        return $form
            ->schema([
                $this->getTypeFormComponent($useActiveTab),
                $this->getCodeFormComponent(),
                $this->getNameFormComponent(),
                ...$this->getBankAccountFormComponents(),
                $this->getCurrencyFormComponent(),
                $this->getDescriptionFormComponent(),
                $this->getArchiveFormComponent(),
            ]);
    }
    protected function getTypeFormComponent(bool $useActiveTab = true): Component
    {
        return Select::make('subtype_id')
            ->label('Type')
            ->required()
            ->live()
            ->disabledOn('edit')
            ->searchable()
            ->options($this->getChartSubtypeOptions($useActiveTab))
            ->afterStateUpdated(static function (?string $state, Set $set): void {
                if ($state) {
                    $accountSubtype = AccountSubtype::find($state);
                    $generatedCode = AccountCode::generate($accountSubtype);
                    $set('code', $generatedCode);
                    $set('is_bank_account', false);
                    $set('bankAccount.type', null);
                    $set('bankAccount.number', null);
                }
            });
    }
    protected function getCodeFormComponent(): Component
    {
        return TextInput::make('code')
            ->label('Code')
            ->required()
            ->hiddenOn('edit')
            ->validationAttribute('account code')
            ->unique(table: Account::class, column: 'code', ignoreRecord: true)
            ->validateAccountCode(static fn (Get $get) => $get('subtype_id'));
    }
    protected function getBankAccountFormComponents(): array
    {
        return [
            Checkbox::make('is_bank_account')
                ->live()
                ->visible(function (Get $get, string $operation) {
                    if ($operation === 'edit') {
                        return false;
                    }
                    $subtype = $get('subtype_id');
                    if (empty($subtype)) {
                        return false;
                    }
                    $accountSubtype = AccountSubtype::find($subtype);
                    if (! $accountSubtype) {
                        return false;
                    }
                    return in_array($accountSubtype->category, [
                        AccountCategory::Asset,
                        AccountCategory::Liability,
                    ]) && $accountSubtype->multi_currency;
                })
                ->afterStateUpdated(static function ($state, Get $get, Set $set) {
                    if ($state) {
                        $subtypeId = $get('subtype_id');
                        if (empty($subtypeId)) {
                            return;
                        }
                        $subtype = AccountSubtype::find($subtypeId);
                        if (! $subtype) {
                            return;
                        }
                        // Set default bank account type based on account category
                        if ($subtype->category === AccountCategory::Asset) {
                            $set('bankAccount.type', BankAccountType::Depository->value);
                        } elseif ($subtype->category === AccountCategory::Liability) {
                            $set('bankAccount.type', BankAccountType::Credit->value);
                        }
                    } else {
                        // Clear bank account fields
                        $set('bankAccount.type', null);
                        $set('bankAccount.number', null);
                    }
                }),
            Group::make()
                ->relationship('bankAccount')
                ->schema([
                    Select::make('type')
                        ->label('Bank account type')
                        ->options(function (Get $get) {
                            $subtype = $get('../subtype_id');
                            if (empty($subtype)) {
                                return [];
                            }
                            $accountSubtype = AccountSubtype::find($subtype);
                            if (! $accountSubtype) {
                                return [];
                            }
                            if ($accountSubtype->category === AccountCategory::Asset) {
                                return [
                                    BankAccountType::Depository->value => BankAccountType::Depository->getLabel(),
                                    BankAccountType::Investment->value => BankAccountType::Investment->getLabel(),
                                ];
                            } elseif ($accountSubtype->category === AccountCategory::Liability) {
                                return [
                                    BankAccountType::Credit->value => BankAccountType::Credit->getLabel(),
                                    BankAccountType::Loan->value => BankAccountType::Loan->getLabel(),
                                ];
                            }
                            return [];
                        })
                        ->searchable()
                        ->columnSpan(1)
                        ->disabledOn('edit')
                        ->required(),
                    TextInput::make('number')
                        ->label('Bank account number')
                        ->unique(ignoreRecord: true, modifyRuleUsing: static function (Unique $rule, $state) {
                            $companyId = Auth::user()->currentCompany->id;
                            return $rule->where('company_id', $companyId)->where('number', $state);
                        })
                        ->maxLength(20)
                        ->validationAttribute('account number'),
                ])
                ->visible(static function (Get $get, ?Account $record, string $operation) {
                    if ($operation === 'create') {
                        return (bool) $get('is_bank_account');
                    }
                    if ($operation === 'edit' && $record) {
                        return (bool) $record->bankAccount;
                    }
                    return false;
                }),
        ];
    }
    protected function getNameFormComponent(): Component
    {
        return TextInput::make('name')
            ->label('Name')
            ->required();
    }
    protected function getCurrencyFormComponent(): Component
    {
        return CreateCurrencySelect::make('currency_code')
            ->disabledOn('edit')
            ->required(false)
            ->requiredIfAccepted('is_bank_account')
            ->validationMessages([
                'required_if_accepted' => 'The currency is required for bank accounts.',
            ])
            ->visible(function (Get $get): bool {
                return filled($get('subtype_id')) && AccountSubtype::find($get('subtype_id'))->multi_currency;
            });
    }
    protected function getDescriptionFormComponent(): Component
    {
        return Textarea::make('description')
            ->label('Description');
    }
    protected function getArchiveFormComponent(): Component
    {
        return Checkbox::make('archived')
            ->label('Archive account')
            ->helperText('Archived accounts will not be available for selection in transactions, offerings, or other new records.')
            ->hiddenOn('create');
    }
    private function getChartSubtypeOptions($useActiveTab = true): array
    {
        $subtypes = $useActiveTab ?
            AccountSubtype::where('category', $this->activeTab)->get() :
            AccountSubtype::all();
        return $subtypes->groupBy(fn (AccountSubtype $subtype) => $subtype->type->getLabel())
            ->map(fn (Collection $subtypes, string $type) => $subtypes->mapWithKeys(static fn (AccountSubtype $subtype) => [$subtype->id => $subtype->name]))
            ->toArray();
    }
    protected function getHeaderActions(): array
    {
        return [
            CreateAction::make()
                ->button()
                ->label('Add new account')
                ->model(Account::class)
                ->form(fn (Form $form) => $this->getChartForm($form, false)->operation('create')),
        ];
    }
    public function getCategoryLabel($categoryValue): string
    {
        return AccountCategory::from($categoryValue)->getPluralLabel();
    }
}
 |