diff --git a/app/Http/Controllers/Admin/Finance/ExpenseController.php b/app/Http/Controllers/Admin/Finance/ExpenseController.php new file mode 100644 index 0000000..5fc6a77 --- /dev/null +++ b/app/Http/Controllers/Admin/Finance/ExpenseController.php @@ -0,0 +1,102 @@ + 'Pengeluaran', + 'expenses' => Expense::latest()->get(), + ]); + } + + public function store(ExpenseRequest $request): RedirectResponse + { + $validatedData = $request->validated(); + $barbershopId = Auth::user()->barbershop_id; + $barbershop = Barbershop::findOrFail($barbershopId); + + if ($validatedData['use_cash'] === '1' && $barbershop->cash < $validatedData['amount']) { + notify()->error('Saldo kas tidak mencukupi', 'Gagal'); + + return back()->withInput(); + } + + DB::transaction(function () use ($validatedData, $barbershop, $barbershopId) { + $newExpense = Expense::create(array_merge($validatedData, [ + 'user_id' => Auth::id(), + 'barbershop_id' => $barbershopId, + ])); + + if ($validatedData['use_cash'] === '1') { + $barbershop->decrement('cash', $validatedData['amount']); + } + + $this->uploadAttachment($validatedData['proof'], 'expenses', Expense::class, $newExpense->id); + }); + + notify()->success('Data berhasil ditambahkan', 'Berhasil'); + + return back(); + } + + public function update(ExpenseRequest $request, Expense $expense): RedirectResponse + { + $validatedData = $request->validated(); + $barbershop = Barbershop::findOrFail($expense->barbershop_id); + + $amountDifference = $validatedData['amount'] - $expense->amount; + + if ($validatedData['use_cash'] === '1') { + $requiredCash = $barbershop->cash - ($amountDifference < 0 ? 0 : $amountDifference); + if ($requiredCash < 0) { + notify()->error('Saldo kas tidak mencukupi', 'Gagal'); + + return back()->withInput(); + } + } + + DB::transaction(function () use ($validatedData, $expense, $barbershop) { + if ($expense->use_cash === '1') { + $barbershop->increment('cash', $expense->amount); + } + + $expense->update($validatedData); + + if ($validatedData['use_cash'] === '1') { + $barbershop->decrement('cash', $validatedData['amount']); + } + + if (isset($validatedData['proof'])) { + $this->uploadAttachment($validatedData['proof'], 'expenses', Expense::class, $expense->id); + } + }); + + notify()->success('Data berhasil diperbarui', 'Berhasil'); + + return back(); + } + + public function delete(Expense $expense): RedirectResponse + { + $expense->delete(); + + notify()->success('Data berhasil dihapus', 'Berhasil'); + + return back(); + } +} diff --git a/app/Http/Controllers/Admin/Master/CustomerController.php b/app/Http/Controllers/Admin/Master/CustomerController.php index e2e0c0d..614396e 100644 --- a/app/Http/Controllers/Admin/Master/CustomerController.php +++ b/app/Http/Controllers/Admin/Master/CustomerController.php @@ -33,7 +33,7 @@ public function store(CustomerRequest $request): RedirectResponse notify()->success('Data berhasil ditambahkan', 'Berhasil'); - return redirect('dashboard/master/customers'); + return back(); } public function update(Customer $customer, CustomerRequest $request): RedirectResponse @@ -44,7 +44,7 @@ public function update(Customer $customer, CustomerRequest $request): RedirectRe notify()->success('Data berhasil diubah', 'Berhasil'); - return redirect('dashboard/master/customers'); + return back(); } public function delete(Customer $customer): RedirectResponse diff --git a/app/Http/Requests/Admin/Finance/ExpenseRequest.php b/app/Http/Requests/Admin/Finance/ExpenseRequest.php new file mode 100644 index 0000000..f97e73a --- /dev/null +++ b/app/Http/Requests/Admin/Finance/ExpenseRequest.php @@ -0,0 +1,45 @@ +merge([ + 'amount' => removeRupiahFormatting($this->amount), + ]); + } + + public function rules(): array + { + $isEdit = $this->isMethod('put'); + + return [ + 'name' => ['required', 'string', 'max:100'], + 'amount' => ['required', 'numeric', 'min:0', 'max:99999999.99'], + 'proof' => $isEdit + ? ['nullable', 'image', 'mimes:jpeg,png,jpg,gif,svg', 'max:2048'] + : ['required', 'image', 'mimes:jpeg,png,jpg,gif,svg', 'max:2048'], + 'use_cash' => ['required', Rule::in(['1', '2'])], + ]; + } + + protected function failedValidation(Validator $validator): void + { + $this->handleValidationFailure($validator); + } +} diff --git a/app/Models/Expense.php b/app/Models/Expense.php index fd66e0a..7c16546 100644 --- a/app/Models/Expense.php +++ b/app/Models/Expense.php @@ -2,12 +2,15 @@ namespace App\Models; +use App\Observers\ExpenseObserver; +use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Database\Eloquent\SoftDeletes; +#[ObservedBy([ExpenseObserver::class])] class Expense extends Model { use HasFactory, SoftDeletes; @@ -17,6 +20,7 @@ class Expense extends Model 'barbershop_id', 'name', 'amount', + 'use_cash', ]; public function user(): BelongsTo diff --git a/app/Observers/ExpenseObserver.php b/app/Observers/ExpenseObserver.php new file mode 100644 index 0000000..dff45be --- /dev/null +++ b/app/Observers/ExpenseObserver.php @@ -0,0 +1,51 @@ +attachment && $expense->attachment->formatted_attachment) { + Storage::delete("expenses/{$expense->attachment->formatted_attachment}"); + } + } + + /** + * Handle the Expense "restored" event. + */ + public function restored(Expense $expense): void + { + // + } + + /** + * Handle the Expense "force deleted" event. + */ + public function forceDeleted(Expense $expense): void + { + // + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 42ba323..ee8793b 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -4,6 +4,7 @@ use App\Models\Barbershop; use App\Models\Customer; +use App\Models\Expense; use App\Models\Service; use App\Models\User; use Illuminate\Support\Facades\Route; @@ -28,5 +29,6 @@ public function boot(): void Route::bind('user', fn (string $user) => User::findOrFail(decryptId($user))); Route::bind('customer', fn (string $customer) => Customer::findOrFail(decryptId($customer))); Route::bind('service', fn (string $service) => Service::findOrFail(decryptId($service))); + Route::bind('expense', fn (string $expense) => Expense::findOrFail(decryptId($expense))); } } diff --git a/database/migrations/2024_11_18_172745_create_expenses_table.php b/database/migrations/2024_11_18_172745_create_expenses_table.php index ea5e231..49a754a 100644 --- a/database/migrations/2024_11_18_172745_create_expenses_table.php +++ b/database/migrations/2024_11_18_172745_create_expenses_table.php @@ -17,6 +17,7 @@ public function up(): void $table->foreignId('barbershop_id')->constrained('barbershop')->cascadeOnDelete(); $table->string('name'); $table->decimal('amount', 10, 2); + $table->enum('use_cash', ['1', '2'])->default('2')->comment('1:Yes 2:No'); $table->timestamps(); $table->softDeletes(); }); diff --git a/public/assets/admin/css/style.css b/public/assets/admin/css/style.css index 3cf3ece..e57a08c 100644 --- a/public/assets/admin/css/style.css +++ b/public/assets/admin/css/style.css @@ -9895,7 +9895,7 @@ .custom-input .invalid-feedback { } .custom-input .form-check .form-check-input:valid { - border-color: #54BA4A; + /* border-color: #54BA4A; */ } .custom-input .form-check .form-check-input:invalid { @@ -9911,7 +9911,7 @@ .custom-input .form-check .form-check-input:valid~.form-check-label { } .custom-input .form-check .form-check-input:checked { - background-color: #54BA4A; + /* background-color: #54BA4A; */ } .custom-input .form-check .invalid-feedback { @@ -10097,7 +10097,7 @@ .checkbox-wrapper { display: flex; gap: calc(8px + 8 * (100vw - 320px) / 1600); flex-wrap: wrap; - justify-content: center; + /* justify-content: center; */ } @media (max-width: 1200px) { diff --git a/public/assets/admin/js/custom/expense.js b/public/assets/admin/js/custom/expense.js new file mode 100644 index 0000000..146a8e7 --- /dev/null +++ b/public/assets/admin/js/custom/expense.js @@ -0,0 +1,33 @@ +$(document).ready(function () { + $("#createModal").on("show.bs.modal", function (event) { + const button = $(event.relatedTarget); + const data = { + modalTitle: button.data("modal-title"), + url: button.data("url"), + method: button.data("method"), + + name: button.data("name"), + amount: button.data("amount"), + useCash: button.data("use-cash"), + }; + + $(".modal-title").text(data.modalTitle); + $("#form").attr('action', data.url); + $("#form-method").val(data.method); + + if (data.method === 'put') { + $("#name").val(data.name); + $("#amount").val(data.amount); + $(`input[name="use_cash"][value="${data.useCash}"]`).prop("checked", true); + } + }); + + $('#createModal').on('hidden.bs.modal', function () { + const method = $("#form-method").val(); + if (method === 'put') { + $("#name").val(''); + $("#amount").val(''); + $('input[name="use_cash"]').prop("checked", false); + } + }); +}); diff --git a/resources/views/pages/admin/finance/expenses.blade.php b/resources/views/pages/admin/finance/expenses.blade.php new file mode 100644 index 0000000..b669259 --- /dev/null +++ b/resources/views/pages/admin/finance/expenses.blade.php @@ -0,0 +1,181 @@ +@extends('layouts.admin') +@section('styles') + +@endsection +@section('app') +
| No | +Nama | +Jumlah | +Bukti | +Tanggal | +Aksi | +
|---|---|---|---|---|---|
| {{ $loop->iteration }} | +{{ $expense->name }} | +{{ formatToRupiah($expense->amount) ?? '-' }} | +
+ @if ($expense->attachment && $expense->attachment->formatted_attachment)
+ attachment->formatted_attachment}") }}">
+
+
+ @endif
+ |
+ {{ formatDateIndo($expense->created_at) }} | +
+
|
+