diff --git a/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php b/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php index 1031240..2c17650 100644 --- a/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php +++ b/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Admin\Finance; +use App\Enums\EmployeeAdvanceStatus; use App\Http\Controllers\Controller; use App\Http\Requests\Admin\Finance\EmployeeAdvanceRequest; use App\Http\Requests\PaginatedRequest; @@ -20,7 +21,14 @@ public function __construct( public function index(PaginatedRequest $request): Response { return Inertia::render('admin/finance/employee-advance/index', [ - 'employeeAdvances' => $this->service->paginated(...$request->validatedWithDefaults()), + 'employeeAdvances' => $this->service->paginated( + ...$request->validatedWithDefaults(), + filters: $request->only(['status']), + ), + 'filters' => $request->only(['status']), + 'filterOptions' => [ + 'statusOptions' => EmployeeAdvanceStatus::toSelect(), + ], ]); } diff --git a/app/Services/Admin/Finance/EmployeeAdvanceService.php b/app/Services/Admin/Finance/EmployeeAdvanceService.php index 81be675..9f4aad1 100644 --- a/app/Services/Admin/Finance/EmployeeAdvanceService.php +++ b/app/Services/Admin/Finance/EmployeeAdvanceService.php @@ -3,23 +3,27 @@ namespace App\Services\Admin\Finance; use App\Enums\EmployeeAdvanceStatus; -use App\Models\CashAccount; use App\Models\EmployeeAdvance; use App\Services\Concerns\HandlesCashTransactions; use App\Services\NotificationService; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; -use Illuminate\Validation\ValidationException; class EmployeeAdvanceService { use HandlesCashTransactions; + private function canViewAll(): bool + { + return auth()->user()->hasAnyRole(['developer', 'owner', 'direktur', 'admin-toko']); + } + public function getAll(array $filters = []): Collection { return EmployeeAdvance::select(['id', 'employee_id', 'amount', 'paid_amount', 'description', 'due_date', 'status', 'created_at']) ->with(['employee.user.userProfile']) + ->when(! $this->canViewAll(), fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))) ->latest() ->get(); } @@ -29,6 +33,8 @@ public function paginated(int $perPage = 25, string $search = '', string $sort = return EmployeeAdvance::query() ->select(['id', 'employee_id', 'amount', 'paid_amount', 'description', 'due_date', 'status', 'created_at']) ->with(['employee.user.userProfile']) + ->when(! $this->canViewAll(), fn ($q) => $q->whereHas('employee', fn ($eq) => $eq->where('user_id', auth()->id()))) + ->when($filters['status'] ?? null, fn ($q) => $q->where('status', $filters['status'])) ->when($search, fn ($q) => $q->where('description', 'like', "%{$search}%")) ->orderBy($sort, $direction) ->paginate($perPage); @@ -36,27 +42,19 @@ public function paginated(int $perPage = 25, string $search = '', string $sort = public function create(array $data): EmployeeAdvance { - $employeeAdvance = DB::transaction(function () use ($data) { - $employee = auth()->user()->employee; + $employee = auth()->user()->employee; - if (! $employee) { - throw new \Exception('Anda tidak terdaftar sebagai karyawan.'); - } + if (! $employee) { + throw new \Exception('Anda tidak terdaftar sebagai karyawan.'); + } - $cashTransaction = $this->debitCash( - amount: $data['amount'], - description: 'Kasbon: '.$data['description'], - ); - - return EmployeeAdvance::create([ - 'cash_transaction_id' => $cashTransaction->id, - 'employee_id' => $employee->id, - 'amount' => $data['amount'], - 'description' => $data['description'], - 'due_date' => $data['due_date'], - 'status' => EmployeeAdvanceStatus::PENDING, - ]); - }); + $employeeAdvance = EmployeeAdvance::create([ + 'employee_id' => $employee->id, + 'amount' => $data['amount'], + 'description' => $data['description'], + 'due_date' => $data['due_date'], + 'status' => EmployeeAdvanceStatus::PENDING, + ]); $employeeAdvance->load('employee.user'); @@ -73,56 +71,36 @@ public function create(array $data): EmployeeAdvance public function update(EmployeeAdvance $employeeAdvance, array $data): EmployeeAdvance { - return DB::transaction(function () use ($employeeAdvance, $data) { - $cashAccount = CashAccount::firstOrFail(); - $cashTransaction = $employeeAdvance->cashTransaction; + $employeeAdvance->update([ + 'amount' => $data['amount'], + 'description' => $data['description'], + 'due_date' => $data['due_date'], + ]); - $oldAmount = $employeeAdvance->amount; - $newAmount = $data['amount']; - $difference = $newAmount - $oldAmount; - - if ($difference > 0 && $cashAccount->balance < $difference) { - throw ValidationException::withMessages([ - 'amount' => 'Saldo tidak mencukupi.', - ]); - } - - $newBalance = $cashAccount->balance - $difference; - $cashAccount->update(['balance' => $newBalance]); - - $cashTransaction->update([ - 'amount' => $newAmount, - 'balance_after' => $newBalance, - 'description' => 'Kasbon: '.$data['description'], - ]); - - $employeeAdvance->update([ - 'amount' => $newAmount, - 'description' => $data['description'], - 'due_date' => $data['due_date'], - ]); - - return $employeeAdvance; - }); + return $employeeAdvance; } public function delete(EmployeeAdvance $employeeAdvance): bool { - return DB::transaction(function () use ($employeeAdvance) { - $cashAccount = CashAccount::firstOrFail(); + if ($employeeAdvance->status === EmployeeAdvanceStatus::APPROVED && $employeeAdvance->cash_transaction_id) { + $cashAccount = $this->getCashAccount(); + $newBalance = $cashAccount->balance + $employeeAdvance->amount; + $cashAccount->update(['balance' => $newBalance]); + $employeeAdvance->cashTransaction()->delete(); + } - if ($employeeAdvance->status === EmployeeAdvanceStatus::PAID) { - $newBalance = $cashAccount->balance + $employeeAdvance->amount; - $cashAccount->update(['balance' => $newBalance]); - } - - return $employeeAdvance->delete(); - }); + return $employeeAdvance->delete(); } public function approve(EmployeeAdvance $employeeAdvance): EmployeeAdvance { + $cashTransaction = $this->debitCash( + amount: $employeeAdvance->amount, + description: 'Kasbon: '.$employeeAdvance->description, + ); + $employeeAdvance->update([ + 'cash_transaction_id' => $cashTransaction->id, 'status' => EmployeeAdvanceStatus::APPROVED, 'verified_by_id' => auth()->id(), 'verified_at' => now(), diff --git a/resources/js/pages/admin/finance/employee-advance/index.tsx b/resources/js/pages/admin/finance/employee-advance/index.tsx index 8c5d820..f1f94c3 100644 --- a/resources/js/pages/admin/finance/employee-advance/index.tsx +++ b/resources/js/pages/admin/finance/employee-advance/index.tsx @@ -1,10 +1,8 @@ -import { Head, router } from '@inertiajs/react'; -import { Plus } from 'lucide-react'; -import { useEffect, useState } from 'react'; import type { PaginationState } from '@/components/data-table'; import { DataTable } from '@/components/data-table'; import { DatePicker } from '@/components/date-picker'; import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog'; +import { FilterPopover } from '@/components/filter-popover'; import { FormDialog } from '@/components/form-dialog'; import InputError from '@/components/input-error'; import { PageHeader } from '@/components/page-header'; @@ -12,18 +10,33 @@ import { RupiahInput } from '@/components/rupiah-input'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; import { useCan } from '@/hooks/use-can'; import { useServerTable } from '@/hooks/use-server-table'; import { + approve, destroy, index as employeeAdvanceIndex, + pay, store, update, - approve, - pay, } from '@/routes/admin/finance/employee-advances'; -import { createEmployeeAdvanceColumns } from './columns'; +import { Head, router, usePage } from '@inertiajs/react'; +import { Plus } from 'lucide-react'; +import { useEffect, useState } from 'react'; import type { EmployeeAdvance } from './columns'; +import { createEmployeeAdvanceColumns } from './columns'; + +type TypeOption = { + value: string; + label: string; +}; type Props = { employeeAdvances: { @@ -33,10 +46,21 @@ type Props = { per_page: number; total: number; }; + filters: { + status?: string; + }; + filterOptions: { + statusOptions: TypeOption[]; + }; }; -export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) { +export default function EmployeeAdvanceIndex({ + employeeAdvances, + filters, + filterOptions, +}: Props) { const { can } = useCan(); + const { auth } = usePage().props as { auth: { user: { id: number } } }; const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); @@ -56,12 +80,17 @@ export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) { const { search, + filterOpen, + setFilterOpen, handlePageChange, handlePerPageChange, handleSearchChange, + applyFilter, + clearFilters, } = useServerTable({ route: () => employeeAdvanceIndex.url(), pagination, + filters, }); useEffect(() => { @@ -116,8 +145,39 @@ export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) { handleApprove: (employeeAdvance) => setApproving(employeeAdvance), handlePay: (employeeAdvance) => setPaying(employeeAdvance), can, + authUserId: auth.user.id, }); + const filterToolbar = ( + +
+ + +
+
+ ); + return ( <> @@ -187,8 +247,8 @@ export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) { value={ dueDate ? dueDate - .toISOString() - .split('T')[0] + .toISOString() + .split('T')[0] : '' } /> @@ -214,6 +274,7 @@ export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) { onPerPageChange={handlePerPageChange} onSearchChange={handleSearchChange} searchValue={search} + toolbar={filterToolbar} /> @@ -331,7 +392,7 @@ export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) { }} title="Bayar Kasbon" description={(advance) => - `Apakah Anda yakin ingin membayar kasbon "${advance.description}" sebesar ${advance.amount}? Saldo kas akan dikembalikan.` + `Apakah Anda yakin ingin membayar kasbon "${advance.description}" sebesar ${advance.formatted_amount}? Saldo kas akan dikembalikan.` } confirmLabel="Bayar" onConfirm={handlePay}