diff --git a/app/Actions/Balances/CreateBalanceAction.php b/app/Actions/Balances/CreateBalanceAction.php new file mode 100644 index 0000000..eecbcb9 --- /dev/null +++ b/app/Actions/Balances/CreateBalanceAction.php @@ -0,0 +1,22 @@ +createBalanceCommand->execute($payload->balanceDTO->except('amount')->toArray()); + + return $next($payload); + } +} diff --git a/app/Actions/Ledgers/CreateLedgerAction.php b/app/Actions/Ledgers/CreateLedgerAction.php index 0df16dd..f8db4bb 100644 --- a/app/Actions/Ledgers/CreateLedgerAction.php +++ b/app/Actions/Ledgers/CreateLedgerAction.php @@ -4,6 +4,7 @@ namespace App\Actions\Ledgers; use App\Actions\BaseAction; use App\Commands\Ledgers\CreateLedgerCommand; +use App\DataObjects\CreateBalanceDTO; use App\DataObjects\CreateLedgerDTO; use Closure; use Spatie\LaravelData\Data; @@ -16,7 +17,15 @@ class CreateLedgerAction extends BaseAction public function __invoke(CreateLedgerDTO|Data $payload, Closure $next) { - $this->createLedgerCommand->execute($payload->data); + $ledger = $this->createLedgerCommand->execute($payload->data); + + $payload->balanceDTO = new CreateBalanceDTO( + amount: $payload->amount, + is_starting: false, + ledger_id: $ledger->id, + account_id: $ledger->account_id, + branch_id: $ledger->branch_id, + ); return $next($payload); } diff --git a/app/Actions/Transactions/CreateTransactionAction.php b/app/Actions/Transactions/CreateTransactionAction.php index ec3ae0a..0cbaedc 100644 --- a/app/Actions/Transactions/CreateTransactionAction.php +++ b/app/Actions/Transactions/CreateTransactionAction.php @@ -2,6 +2,7 @@ namespace App\Actions\Transactions; +use App\Actions\Balances\CreateBalanceAction; use App\Actions\BaseAction; use App\Actions\Ledgers\CreateLedgerAction; use App\DataObjects\CreateLedgerDTO; @@ -21,6 +22,8 @@ class CreateTransactionAction extends BaseAction $this->withHoldingAccountLedger($payload); + $this->cashAccountLedger($payload); + return $next($payload); } @@ -56,6 +59,7 @@ class CreateTransactionAction extends BaseAction { return Pipeline::send(passable: $ledgerPayload)->through([ CreateLedgerAction::class, + CreateBalanceAction::class, ])->thenReturn(); } @@ -82,10 +86,27 @@ class CreateTransactionAction extends BaseAction $ledgerPayload = new CreateLedgerDTO( branch_id: $payload->transactionable->branch_id, - amount: $payload->transaction->payable_withholding_tax, + amount: $payload->transaction->payable_withholding_tax ?? 0.00, transaction: $payload->transaction, account: $withholdingAccount, ); $this->ledgerPipe($ledgerPayload); } + + public function cashAccountLedger($payload): void + { + $cashAccount = Account::query()->where('account', 'Cash')->whereHas('balances', function ($balance) use ($payload) { + return $balance->where('branch_id', $payload->transactionable->branch_id); + })->first(); + + $amount = $payload->transaction->gross_amount - $payload->transaction->creditable_withholding_tax; + + $ledgerPayload = new CreateLedgerDTO( + branch_id: $payload->transactionable->branch_id, + amount: $amount ?? 0.00, + transaction: $payload->transaction, + account: $cashAccount, + ); + $this->ledgerPipe($ledgerPayload); + } } diff --git a/app/Commands/Balances/CreateBalanceCommand.php b/app/Commands/Balances/CreateBalanceCommand.php new file mode 100644 index 0000000..76255b5 --- /dev/null +++ b/app/Commands/Balances/CreateBalanceCommand.php @@ -0,0 +1,21 @@ + Balance::query()->updateOrCreate([ + 'id' => $data['id'], + 'account_id' => $data['account_id'], + ], $data), + attempts: 2 + ); + } +} diff --git a/app/DataObjects/CreateBalanceDTO.php b/app/DataObjects/CreateBalanceDTO.php new file mode 100644 index 0000000..2b0d7a3 --- /dev/null +++ b/app/DataObjects/CreateBalanceDTO.php @@ -0,0 +1,30 @@ +where('id', $this->account_id)->first(); + + $currentBalance = $account ? $account->current_balance : 0; + + if ($account->normal_balance == 'credit') { + $this->balance = $currentBalance - $this->amount; + } else { + $this->balance = $currentBalance + $this->amount; + } + } +} diff --git a/app/DataObjects/CreateLedgerDTO.php b/app/DataObjects/CreateLedgerDTO.php index 2108b76..93253c4 100644 --- a/app/DataObjects/CreateLedgerDTO.php +++ b/app/DataObjects/CreateLedgerDTO.php @@ -36,6 +36,7 @@ class CreateLedgerDTO extends Data public ?Model $ledger = null, public ?Model $transaction = null, public ?Model $account = null, + public ?CreateBalanceDTO $balanceDTO = null, ) { $this->transaction_id = $this->transaction->id; $this->account_id = $this->account->id; diff --git a/app/Filament/Resources/ExpenseResource.php b/app/Filament/Resources/ExpenseResource.php index 235e4eb..474ddf9 100644 --- a/app/Filament/Resources/ExpenseResource.php +++ b/app/Filament/Resources/ExpenseResource.php @@ -15,15 +15,19 @@ use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; use Filament\Forms\Form; use Filament\Forms\Get; +use Filament\Forms\Set; use Filament\Resources\Resource; use Filament\Tables; use Filament\Tables\Table; use Illuminate\Support\Collection; +use JetBrains\PhpStorm\NoReturn; class ExpenseResource extends Resource { protected static ?string $model = Expense::class; + protected static bool $isVatable; + protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; public static function form(Form $form): Form @@ -36,29 +40,147 @@ class ExpenseResource extends Resource { return [ Select::make('client') - ->options(Client::query()->get()->pluck('company', 'id'))->live(), - Select::make('branch_id')->options(fn ($get) => Branch::query()->where('client_id', $get('client'))->get()->pluck('code', 'id')) - ->afterStateUpdated(fn ($state, $set) => $set('voucher_number', GenerateVoucher::execute(Branch::find($state))))->live(), + ->options(Client::query()->get()->pluck('company', 'id')) + ->afterStateUpdated(function ($set, $get) { + $set('branch_id', ''); + $set('voucher_number', static::getVoucherNumber($get)); + }) + ->live(), + Select::make('branch_id') + ->relationship('branch', 'code') + ->options(fn ($get) => Branch::query()->where('client_id', $get('client'))->get()->pluck('code', 'id')) + ->afterStateUpdated(fn ($set, $get) => $set('voucher_number', static::getVoucherNumber($get))) + ->live(), TextInput::make('supplier')->label('Supplier Name'), TextInput::make('reference_number')->label('Reference Number'), - TextInput::make('voucher_number')->label('Voucher Number'), + TextInput::make('voucher_number')->label('Voucher Number') + ->default(fn ($get) => static::getVoucherNumber($get)) + ->readOnly(), DatePicker::make('happened_on')->label('Date')->native(false), TableRepeater::make('transactions') - ->headers([ - Header::make('Charge Account'), - Header::make('Description'), - Header::make('Gross Amount'), - Header::make('Withholding Tax'), - Header::make('Net Amount'), - ]) - ->schema([ - Select::make('account_id')->options(fn ($get) => static::getAccountOptions($get)), - TextInput::make('description')->label('Description'), - TextInput::make('gross_amount'), - TextInput::make('payable_withholding_tax'), - TextInput::make('net_amount'), - ])->columnSpan('full'), + ->headers(fn (Get $get): array => static::getTransactionTableHeader($get)) + ->relationship('transactions') + ->schema(fn (Get $get): array => static::getTransactionTableFormSchema($get)) + + ->columnSpan('full'), + ]; + } + + public static function getVoucherNumber(Get $get): string + { + $branch = Branch::find($get('branch_id')); + + if ($branch) { + return GenerateVoucher::execute($branch); + } + + return ''; + } + + public static function getTransactionTableHeader(Get $get): array + { + + if (! static::getIsVatable($get)) { + return [ + Header::make('Charge Account'), + Header::make('Description'), + Header::make('Gross Amount'), + Header::make('Withholding Tax'), + Header::make('Net Amount'), + ]; + } + + return [ + Header::make('Charge Account'), + Header::make('Description'), + Header::make('Gross Amount'), + Header::make('Exempt'), + Header::make('Zero Rated'), + Header::make('Vatable Amount'), + Header::make('Input Tax'), + Header::make('Withholding Tax'), + Header::make('Net Amount'), + ]; + } + + public static function getIsVatable(Get $get): bool + { + $client = Client::find($get('client')); + + return $client && $client->vatable; + + } + + public static function isVatable(bool $value): void + { + static::$isVatable = $value; + dump('sett'); + } + + public static function getTransactionTableFormSchema(Get $get): array + { + if (! static::getIsVatable($get)) { + return [ + Select::make('account_id')->options(fn ($get) => static::getAccountOptions($get)), + TextInput::make('description')->label('Description'), + TextInput::make('gross_amount')->numeric() + ->live() + ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) { + static::setDefaultFormValues($get, $set, $old, $state); + })->default(0), + TextInput::make('payable_withholding_tax')->numeric()->live() + ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) { + static::setDefaultFormValues($get, $set, $old, $state); + })->default(0), + TextInput::make('net_amount')->numeric()->default(0), + ]; + } + + return [ + Select::make('account_id')->options(fn ($get) => static::getAccountOptions($get)), + TextInput::make('description')->label('Description'), + TextInput::make('gross_amount') + ->numeric() + ->live() + ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) { + static::setDefaultFormValues($get, $set, $old, $state); + })->default(0), + TextInput::make('exempt') + ->numeric() + ->live() + ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) { + static::setDefaultFormValues($get, $set, $old, $state); + })->default(0), + TextInput::make('zero_rated') + ->numeric() + ->live() + ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) { + + static::setDefaultFormValues($get, $set, $old, $state); + })->default(0), + TextInput::make('vatable_amount') + ->numeric() + ->live() + ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) { + + static::setDefaultFormValues($get, $set, $old, $state); + })->default(0), + TextInput::make('input_tax') + ->numeric() + ->live() + ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) { + + static::setDefaultFormValues($get, $set, $old, $state); + })->default(0), + TextInput::make('payable_withholding_tax') + ->numeric() + ->live() + ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) { + + static::setDefaultFormValues($get, $set, $old, $state); + })->default(0), + TextInput::make('net_amount')->numeric()->default(0), ]; } @@ -83,6 +205,28 @@ class ExpenseResource extends Resource return $query->get()->pluck('account', 'id'); } + #[NoReturn] + public static function setDefaultFormValues(Get $get, Set $set, ?string $old, ?string $state): void + { + + $exempt = (float) $get('exempt'); + $withHoldingTax = (float) $get('payable_withholding_tax'); + $vatableSales = $get('gross_amount'); + $vatableAmount = $vatableSales / 1.12; + + $inputTax = $vatableAmount * 0.12; + + $netAmount = $get('gross_amount') - $get('payable_withholding_tax'); + + if (static::getIsVatable($get)) { + $netAmount = ($vatableAmount + $exempt) - $withHoldingTax; + } + + $set('input_tax', number_format($inputTax, 2)); + $set('vatable_amount', number_format($vatableAmount, 2)); + $set('net_amount', number_format($netAmount, 2)); + } + public static function table(Table $table): Table { return $table diff --git a/app/Filament/Resources/ExpenseResource/Pages/EditExpense.php b/app/Filament/Resources/ExpenseResource/Pages/EditExpense.php index ef925b2..a4611c9 100644 --- a/app/Filament/Resources/ExpenseResource/Pages/EditExpense.php +++ b/app/Filament/Resources/ExpenseResource/Pages/EditExpense.php @@ -16,4 +16,12 @@ class EditExpense extends EditRecord Actions\DeleteAction::make(), ]; } + + protected function mutateFormDataBeforeFill(array $data): array + { + return [ + 'client' => $this->getRecord()->branch->client->id, + ...$this->getRecord()->toArray(), + ]; + } } diff --git a/app/Models/Client.php b/app/Models/Client.php index b1d9549..7607eb2 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -26,7 +26,7 @@ class Client extends Model public function getVatableAttribute(): bool { - return $this->type->type == 'Vatable' ? true : false; + return $this->type->type == 'Vatable'; } /**