656 lines
24 KiB
PHP
656 lines
24 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use Illuminate\Http\Request;
|
|
use App\Models\Kamera;
|
|
use App\Models\Lensa;
|
|
use App\Models\Penyewaan;
|
|
use App\Models\User;
|
|
use App\Services\CatalogCodeService;
|
|
use App\Services\InventoryStockService;
|
|
use App\Services\KameraScoringService;
|
|
use App\Services\TopsisService;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\View\View;
|
|
|
|
class RekomendasiController extends Controller
|
|
{
|
|
private function getFilterGuide(): array
|
|
{
|
|
$kameras = Kamera::query()
|
|
->select(['merek', 'price', 'iso', 'af_point'])
|
|
->whereNotNull('price')
|
|
->whereNotNull('iso')
|
|
->whereNotNull('af_point')
|
|
->get();
|
|
|
|
$allGuide = [
|
|
'label' => 'Semua Merek',
|
|
'min_price' => (int) $kameras->min('price'),
|
|
'max_price' => (int) $kameras->max('price'),
|
|
'min_iso' => (int) $kameras->min('iso'),
|
|
'max_iso' => (int) $kameras->max('iso'),
|
|
'min_af_point' => (int) $kameras->min('af_point'),
|
|
'max_af_point' => (int) $kameras->max('af_point'),
|
|
];
|
|
|
|
$brandGuides = $kameras
|
|
->groupBy('merek')
|
|
->sortKeys()
|
|
->mapWithKeys(function (Collection $items, string $merek): array {
|
|
return [
|
|
$merek => [
|
|
'label' => $merek,
|
|
'min_price' => (int) $items->min('price'),
|
|
'max_price' => (int) $items->max('price'),
|
|
'min_iso' => (int) $items->min('iso'),
|
|
'max_iso' => (int) $items->max('iso'),
|
|
'min_af_point' => (int) $items->min('af_point'),
|
|
'max_af_point' => (int) $items->max('af_point'),
|
|
],
|
|
];
|
|
})
|
|
->all();
|
|
|
|
return [
|
|
'all' => $allGuide,
|
|
'brands' => $brandGuides,
|
|
];
|
|
}
|
|
|
|
private function isAllBrands(string $merekInput): bool
|
|
{
|
|
return $merekInput === '' || in_array(Str::lower($merekInput), ['semua', 'semua merek', 'all'], true);
|
|
}
|
|
|
|
private function buildNoResultMessage(Request $request, Collection $availableForBrand, string $merekLabel): string
|
|
{
|
|
if ($availableForBrand->isEmpty()) {
|
|
return 'Merek kamera yang dipilih belum tersedia di dataset.';
|
|
}
|
|
|
|
$parts = [];
|
|
|
|
if ($request->filled('budget')) {
|
|
$parts[] = 'budget tersedia Rp ' . number_format((int) $availableForBrand->min('price'), 0, ',', '.')
|
|
. ' sampai Rp ' . number_format((int) $availableForBrand->max('price'), 0, ',', '.');
|
|
}
|
|
|
|
if ($request->filled('iso_min') || $request->filled('iso_max')) {
|
|
$parts[] = 'ISO tersedia ' . number_format((int) $availableForBrand->min('iso'), 0, ',', '.')
|
|
. ' sampai ' . number_format((int) $availableForBrand->max('iso'), 0, ',', '.');
|
|
}
|
|
|
|
if ($parts === []) {
|
|
$parts[] = 'harga tersedia Rp ' . number_format((int) $availableForBrand->min('price'), 0, ',', '.')
|
|
. ' sampai Rp ' . number_format((int) $availableForBrand->max('price'), 0, ',', '.');
|
|
$parts[] = 'ISO tersedia ' . number_format((int) $availableForBrand->min('iso'), 0, ',', '.')
|
|
. ' sampai ' . number_format((int) $availableForBrand->max('iso'), 0, ',', '.');
|
|
}
|
|
|
|
return 'Tidak ada kamera yang cocok dengan filter yang dipilih untuk ' . $merekLabel . '. '
|
|
. 'Data saat ini memiliki ' . implode(' dan ', $parts) . '. '
|
|
. 'Coba longgarkan salah satu filter atau kosongkan budget/ISO terlebih dahulu.';
|
|
}
|
|
|
|
// Halaman landing
|
|
public function index()
|
|
{
|
|
$merekList = Kamera::select('merek')->distinct()->orderBy('merek')->pluck('merek');
|
|
$filterGuide = $this->getFilterGuide();
|
|
|
|
return view('landing', compact('merekList', 'filterGuide'));
|
|
}
|
|
|
|
// Form rekomendasi
|
|
public function form()
|
|
{
|
|
$merekList = Kamera::select('merek')->distinct()->orderBy('merek')->pluck('merek');
|
|
$filterGuide = $this->getFilterGuide();
|
|
|
|
return view('form_rekomendasi', compact('merekList', 'filterGuide'));
|
|
}
|
|
|
|
/**
|
|
* Menghitung skor kriteria kamera sesuai notebook Jupyter:
|
|
* Price (cost), ISO (benefit), AF Point (benefit), Sensor (benefit).
|
|
*/
|
|
private function hitungSkorKriteria(Collection $kameras): array
|
|
{
|
|
$minPrice = (float) $kameras->min('price');
|
|
$maxPrice = (float) $kameras->max('price');
|
|
$maxIso = max((float) $kameras->max('iso'), 1);
|
|
$maxAfPoint = max((float) $kameras->max('af_point'), 1);
|
|
|
|
$skorKriteria = [];
|
|
foreach ($kameras as $k) {
|
|
$priceScore = ($maxPrice === $minPrice)
|
|
? 1.0
|
|
: (($maxPrice - (float) $k->price) / ($maxPrice - $minPrice)) * 1.0;
|
|
$isoScore = ((float) $k->iso / $maxIso) * 1.0;
|
|
$afScore = ((float) $k->af_point / $maxAfPoint) * 1.0;
|
|
|
|
$skorKriteria[] = [
|
|
'price_score' => round($priceScore, 2),
|
|
'iso_score' => round($isoScore, 2),
|
|
'af_score' => round($afScore, 2),
|
|
'sensor_score' => round((float) $k->sensor_score / 10.0, 2),
|
|
];
|
|
}
|
|
|
|
return $skorKriteria;
|
|
}
|
|
|
|
/**
|
|
* Bobot mengikuti notebook Jupyter.
|
|
* Urutan bobot: [Price, ISO, AF Point, Sensor]
|
|
*/
|
|
private function getBobotByPenggunaan(string $penggunaan): array
|
|
{
|
|
$bobotMap = [
|
|
'outdoor_day' => [0.30, 0.15, 0.25, 0.30],
|
|
'outdoor_night' => [0.15, 0.50, 0.15, 0.20],
|
|
'indoor_cerah' => [0.35, 0.20, 0.20, 0.25],
|
|
'indoor_gelap' => [0.15, 0.55, 0.15, 0.15],
|
|
'all' => [0.25, 0.30, 0.20, 0.25],
|
|
];
|
|
|
|
return $bobotMap[$penggunaan] ?? [0.25, 0.25, 0.25, 0.25];
|
|
}
|
|
|
|
// Proses rekomendasi (TOPSIS)
|
|
public function proses(Request $request)
|
|
{
|
|
// Validasi input
|
|
$request->validate([
|
|
'penggunaan' => 'required|in:outdoor_day,outdoor_night,indoor_cerah,indoor_gelap,all',
|
|
'merek' => 'nullable|string',
|
|
'budget' => 'nullable|integer|min:0',
|
|
'iso_min' => 'nullable|integer',
|
|
'iso_max' => 'nullable|integer|gte:iso_min',
|
|
]);
|
|
|
|
// Cek apakah data kamera ada
|
|
if (Kamera::all()->isEmpty()) {
|
|
return back()->withInput()->with('error', 'Data kamera masih kosong. Silakan isi data kamera terlebih dahulu.');
|
|
}
|
|
|
|
$merekInput = trim((string) $request->input('merek', ''));
|
|
$query = Kamera::query();
|
|
|
|
if (! $this->isAllBrands($merekInput)) {
|
|
$query->whereRaw('LOWER(merek) = ?', [Str::lower($merekInput)], 'and');
|
|
}
|
|
|
|
$availableForBrand = (clone $query)->get();
|
|
$merekLabel = $this->isAllBrands($merekInput) ? 'semua merek' : $merekInput;
|
|
|
|
if ($availableForBrand->isEmpty()) {
|
|
return back()->withInput()->with('error', 'Merek kamera yang dipilih belum tersedia di dataset.');
|
|
}
|
|
|
|
if ($request->budget && $request->budget > 0) {
|
|
$query->where('price', '<=', $request->budget);
|
|
}
|
|
|
|
if ($request->filled('iso_min')) {
|
|
$query->where('iso', '>=', $request->integer('iso_min'));
|
|
}
|
|
|
|
if ($request->filled('iso_max')) {
|
|
$query->where('iso', '<=', $request->integer('iso_max'));
|
|
}
|
|
|
|
$kameras = $query->get();
|
|
|
|
if ($kameras->isEmpty()) {
|
|
return back()
|
|
->withInput()
|
|
->with('error', $this->buildNoResultMessage($request, $availableForBrand, $merekLabel));
|
|
}
|
|
|
|
$skorKriteria = $this->hitungSkorKriteria($kameras);
|
|
|
|
$dataMatrix = [];
|
|
foreach ($skorKriteria as $skor) {
|
|
$dataMatrix[] = [
|
|
$skor['price_score'],
|
|
$skor['iso_score'],
|
|
$skor['af_score'],
|
|
$skor['sensor_score'],
|
|
];
|
|
}
|
|
|
|
$bobot = $this->getBobotByPenggunaan($request->penggunaan);
|
|
|
|
$jenis = ['cost', 'benefit', 'benefit', 'benefit'];
|
|
|
|
$penggunaanLabels = [
|
|
'outdoor_day' => 'Outdoor - Day (Siang)',
|
|
'outdoor_night' => 'Outdoor - Night (Malam)',
|
|
'indoor_cerah' => 'Indoor - Cerah',
|
|
'indoor_gelap' => 'Indoor - Gelap',
|
|
'all' => 'Semua Kondisi',
|
|
];
|
|
$penggunaanLabel = $penggunaanLabels[$request->penggunaan] ?? $request->penggunaan;
|
|
|
|
try {
|
|
$scores = TopsisService::hitung($dataMatrix, $bobot, $jenis);
|
|
|
|
$hasil = [];
|
|
foreach ($kameras as $i => $k) {
|
|
$hasil[] = (object)[
|
|
'id' => $k->id,
|
|
'nama' => $k->nama,
|
|
'merek' => $k->merek,
|
|
'image_path' => $k->image_path,
|
|
'harga' => $k->price,
|
|
'iso' => $k->iso,
|
|
'af_point' => $k->af_point,
|
|
'sensor' => $k->sensor,
|
|
'price_score' => $skorKriteria[$i]['price_score'],
|
|
'iso_score' => $skorKriteria[$i]['iso_score'],
|
|
'af_score' => $skorKriteria[$i]['af_score'],
|
|
'sensor_score' => $skorKriteria[$i]['sensor_score'],
|
|
'skor' => round($scores[$i], 4),
|
|
];
|
|
}
|
|
|
|
usort($hasil, fn($a, $b) => $b->skor <=> $a->skor);
|
|
|
|
$top3 = array_slice($hasil, 0, 3);
|
|
foreach ($top3 as $k) {
|
|
$lensa = Lensa::query()
|
|
->where('merek', $k->merek)
|
|
->orderByDesc('aperture_score')
|
|
->first();
|
|
$k->lensa_rekomendasi = $lensa ? $lensa->nama : null;
|
|
$k->harga_lensa = $lensa ? $lensa->harga_sewa : 0;
|
|
$k->total_harga = $k->harga + ($lensa ? $lensa->harga_sewa : 0);
|
|
}
|
|
|
|
} catch (\Throwable $e) {
|
|
report($e);
|
|
return back()
|
|
->withInput()
|
|
->with('error', 'Proses rekomendasi gagal: ' . $e->getMessage());
|
|
}
|
|
|
|
return view('hasil_rekomendasi', [
|
|
'hasil' => $top3,
|
|
'total_kamera_diproses' => count($kameras),
|
|
'penggunaan_terpilih' => $penggunaanLabel,
|
|
'bobot_yang_digunakan' => $bobot,
|
|
]);
|
|
}
|
|
|
|
// Dashboard admin
|
|
public function dashboard()
|
|
{
|
|
$kameras = Kamera::all();
|
|
$lensas = Lensa::all();
|
|
$penyewaans = Penyewaan::with(['kamera', 'lensa'])
|
|
->latest()
|
|
->take(15)
|
|
->get();
|
|
$users = User::query()
|
|
->where('role', 'user')
|
|
->withCount('penyewaans')
|
|
->latest()
|
|
->get();
|
|
|
|
return view('dashboard', compact('kameras', 'lensas', 'penyewaans', 'users'));
|
|
}
|
|
|
|
public function createKameraAdmin(): View
|
|
{
|
|
return view('admin.kamera_form', [
|
|
'kamera' => new Kamera([]),
|
|
'formMode' => 'create',
|
|
'sensorOptions' => KameraScoringService::sensorOptions(),
|
|
]);
|
|
}
|
|
|
|
public function storeKameraAdmin(Request $request): RedirectResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'nama' => ['required', 'string', 'max:255'],
|
|
'merek' => ['required', 'string', 'max:255'],
|
|
'harga_sewa' => ['required', 'integer', 'min:0'],
|
|
'iso' => ['required', 'integer', 'min:0'],
|
|
'af_point' => ['required', 'integer', 'min:0'],
|
|
'sensor' => ['required', 'string', 'max:255'],
|
|
]);
|
|
|
|
$kamera = Kamera::create(array_merge(
|
|
$validated,
|
|
KameraScoringService::deriveFromSpecs($validated)
|
|
));
|
|
|
|
$kamera->update([
|
|
'kode_kamera' => CatalogCodeService::forKameraId((int) $kamera->id),
|
|
]);
|
|
|
|
return redirect()->route('dashboard')->with('status', 'Data kamera baru berhasil ditambahkan.');
|
|
}
|
|
|
|
public function editKameraAdmin(Kamera $kamera): View
|
|
{
|
|
return view('admin.kamera_form', [
|
|
'kamera' => $kamera,
|
|
'formMode' => 'edit',
|
|
'sensorOptions' => KameraScoringService::sensorOptions(),
|
|
]);
|
|
}
|
|
|
|
public function updateKameraAdmin(Request $request, Kamera $kamera): RedirectResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'nama' => ['required', 'string', 'max:255'],
|
|
'merek' => ['required', 'string', 'max:255'],
|
|
'harga_sewa' => ['required', 'integer', 'min:0'],
|
|
'iso' => ['required', 'integer', 'min:0'],
|
|
'af_point' => ['required', 'integer', 'min:0'],
|
|
'sensor' => ['required', 'string', 'max:255'],
|
|
]);
|
|
|
|
$kamera->update(array_merge(
|
|
$validated,
|
|
KameraScoringService::deriveFromSpecs($validated)
|
|
));
|
|
|
|
return redirect()->route('dashboard')->with('status', 'Data kamera berhasil diperbarui.');
|
|
}
|
|
|
|
public function destroyKameraAdmin(Kamera $kamera): RedirectResponse
|
|
{
|
|
$nama = $kamera->nama;
|
|
Kamera::destroy($kamera->getKey());
|
|
|
|
return redirect()->route('dashboard')->with('status', 'Data kamera ' . $nama . ' berhasil dihapus.');
|
|
}
|
|
|
|
public function createLensaAdmin(): View
|
|
{
|
|
return view('admin.lensa_form', [
|
|
'lensa' => new Lensa([]),
|
|
'formMode' => 'create',
|
|
]);
|
|
}
|
|
|
|
public function storeLensaAdmin(Request $request): RedirectResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'nama' => ['required', 'string', 'max:255'],
|
|
'merek' => ['required', 'string', 'max:255'],
|
|
'harga_sewa' => ['required', 'integer', 'min:0'],
|
|
'aperture_score' => ['required', 'numeric', 'min:0', 'max:10'],
|
|
'stabilisasi' => ['required', 'numeric', 'min:0', 'max:10'],
|
|
'fleksibilitas' => ['required', 'numeric', 'min:0', 'max:10'],
|
|
]);
|
|
|
|
$lensa = Lensa::create($validated);
|
|
|
|
$lensa->update([
|
|
'kode_lensa' => CatalogCodeService::forLensaId((int) $lensa->id),
|
|
]);
|
|
|
|
return redirect()->route('dashboard')->with('status', 'Data lensa baru berhasil ditambahkan.');
|
|
}
|
|
|
|
public function editLensaAdmin(Lensa $lensa): View
|
|
{
|
|
return view('admin.lensa_form', [
|
|
'lensa' => $lensa,
|
|
'formMode' => 'edit',
|
|
]);
|
|
}
|
|
|
|
public function updateLensaAdmin(Request $request, Lensa $lensa): RedirectResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'nama' => ['required', 'string', 'max:255'],
|
|
'merek' => ['required', 'string', 'max:255'],
|
|
'harga_sewa' => ['required', 'integer', 'min:0'],
|
|
'aperture_score' => ['required', 'numeric', 'min:0', 'max:10'],
|
|
'stabilisasi' => ['required', 'numeric', 'min:0', 'max:10'],
|
|
'fleksibilitas' => ['required', 'numeric', 'min:0', 'max:10'],
|
|
]);
|
|
|
|
$lensa->update($validated);
|
|
|
|
return redirect()->route('dashboard')->with('status', 'Data lensa berhasil diperbarui.');
|
|
}
|
|
|
|
public function destroyLensaAdmin(Lensa $lensa): RedirectResponse
|
|
{
|
|
$nama = $lensa->nama;
|
|
Lensa::destroy($lensa->getKey());
|
|
|
|
return redirect()->route('dashboard')->with('status', 'Data lensa ' . $nama . ' berhasil dihapus.');
|
|
}
|
|
|
|
public function verifyUserMember(Request $request, User $user)
|
|
{
|
|
if (! $user->isUser()) {
|
|
return back()->with('error', 'Verifikasi hanya bisa dilakukan untuk akun user.');
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'member_level' => ['required', 'in:tanpa_member,bronze,silver,gold'],
|
|
]);
|
|
|
|
$user->update([
|
|
'member_level' => $validated['member_level'],
|
|
]);
|
|
|
|
return back()->with(
|
|
'status',
|
|
'Level member untuk ' . $user->name . ' berhasil diperbarui menjadi '
|
|
. Str::of($validated['member_level'])->replace('_', ' ')->title() . '.'
|
|
);
|
|
}
|
|
|
|
public function updatePenyewaanStatus(Request $request, Penyewaan $penyewaan)
|
|
{
|
|
$validated = $request->validate([
|
|
'status' => ['required', 'in:disetujui,ditolak,dikembalikan'],
|
|
]);
|
|
|
|
if ($penyewaan->status === $validated['status']) {
|
|
return back()->with('status', 'Status penyewaan sudah sesuai dan tidak ada perubahan.');
|
|
}
|
|
|
|
InventoryStockService::transaction(function () use ($penyewaan, $validated): void {
|
|
InventoryStockService::syncPenyewaanStock(
|
|
$penyewaan,
|
|
(int) $penyewaan->qty,
|
|
$validated['status']
|
|
);
|
|
|
|
$penyewaan->update([
|
|
'status' => $validated['status'],
|
|
]);
|
|
});
|
|
|
|
return back()->with(
|
|
'status',
|
|
'Pengajuan sewa atas nama ' . $penyewaan->nama_penyewa . ' berhasil '
|
|
. match ($validated['status']) {
|
|
'disetujui' => 'disetujui',
|
|
'dikembalikan' => 'ditandai sudah dikembalikan',
|
|
default => 'ditolak',
|
|
} . '.'
|
|
);
|
|
}
|
|
|
|
public function updatePenyewaanAdminNote(Request $request, Penyewaan $penyewaan): RedirectResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'admin_catatan' => ['nullable', 'string', 'max:2000'],
|
|
]);
|
|
|
|
$catatanBaru = filled($validated['admin_catatan'] ?? null)
|
|
? trim((string) $validated['admin_catatan'])
|
|
: null;
|
|
|
|
$updateData = [
|
|
'admin_catatan' => $catatanBaru,
|
|
];
|
|
|
|
if ($catatanBaru !== null && $catatanBaru !== $penyewaan->admin_catatan) {
|
|
$updateData['admin_catatan_read_at'] = null;
|
|
}
|
|
|
|
if ($catatanBaru === null) {
|
|
$updateData['admin_catatan_read_at'] = null;
|
|
}
|
|
|
|
$penyewaan->update($updateData);
|
|
|
|
return back()->with('status', 'Catatan admin untuk pengajuan sewa berhasil diperbarui.');
|
|
}
|
|
|
|
public function editPenyewaan(Penyewaan $penyewaan): View
|
|
{
|
|
return view('admin.penyewaan_edit', [
|
|
'penyewaan' => $penyewaan->load(['kamera', 'lensa']),
|
|
]);
|
|
}
|
|
|
|
public function updatePenyewaan(Request $request, Penyewaan $penyewaan): RedirectResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'nama_penyewa' => ['required', 'string', 'max:100'],
|
|
'email' => ['required', 'email'],
|
|
'telepon' => ['required', 'string', 'max:30'],
|
|
'no_wa' => ['required', 'string', 'max:30'],
|
|
'alamat_ktp' => ['required', 'string', 'max:500'],
|
|
'alamat_tinggal' => ['required', 'string', 'max:500'],
|
|
'instagram' => ['nullable', 'string', 'max:100'],
|
|
'jaminan' => ['required', 'string', 'max:255'],
|
|
'member_level' => ['required', 'in:tanpa_member,bronze,silver,gold'],
|
|
'qty' => ['required', 'integer', 'min:1', 'max:'.max(Kamera::DEFAULT_STOCK, Lensa::DEFAULT_STOCK)],
|
|
'tanggal_mulai' => ['required', 'date'],
|
|
'tanggal_selesai' => ['required', 'date', 'after_or_equal:tanggal_mulai'],
|
|
'jam_sewa' => ['required', 'date_format:H:i'],
|
|
'jam_kembali' => ['required', 'date_format:H:i'],
|
|
'metode_pembayaran' => ['required', 'in:qris,transfer_bca'],
|
|
'status' => ['required', 'in:menunggu,disetujui,ditolak,dikembalikan'],
|
|
'catatan' => ['nullable', 'string', 'max:500'],
|
|
'admin_catatan' => ['nullable', 'string', 'max:2000'],
|
|
'foto_jaminan' => ['nullable', 'image', 'mimes:jpg,jpeg,png', 'max:5120'],
|
|
'foto_diri' => ['nullable', 'image', 'mimes:jpg,jpeg,png', 'max:5120'],
|
|
'bukti_pembayaran' => ['nullable', 'file', 'mimes:jpg,jpeg,png,pdf', 'max:5120'],
|
|
]);
|
|
|
|
$durasiHari = Carbon::parse($validated['tanggal_mulai'])
|
|
->diffInDays(Carbon::parse($validated['tanggal_selesai'])) + 1;
|
|
|
|
$hargaHarian = match ($penyewaan->jenis_sewa) {
|
|
'kamera' => (int) ($penyewaan->kamera->harga_sewa ?? 0),
|
|
'lensa' => (int) ($penyewaan->lensa->harga_sewa ?? 0),
|
|
default => (int) (($penyewaan->kamera->harga_sewa ?? 0) + ($penyewaan->lensa->harga_sewa ?? 0)),
|
|
};
|
|
|
|
$memberDiskon = [
|
|
'tanpa_member' => 0.00,
|
|
'bronze' => 0.05,
|
|
'silver' => 0.10,
|
|
'gold' => 0.15,
|
|
];
|
|
$subTotal = $hargaHarian * $validated['qty'] * $durasiHari;
|
|
$diskon = (int) round($subTotal * ($memberDiskon[$validated['member_level']] ?? 0));
|
|
$totalHarga = max($subTotal - $diskon, 0);
|
|
|
|
$updateData = [
|
|
'nama_penyewa' => $validated['nama_penyewa'],
|
|
'email' => $validated['email'],
|
|
'telepon' => $validated['telepon'],
|
|
'no_wa' => $validated['no_wa'],
|
|
'alamat_ktp' => $validated['alamat_ktp'],
|
|
'alamat_tinggal' => $validated['alamat_tinggal'],
|
|
'instagram' => $validated['instagram'] ?? null,
|
|
'jaminan' => $validated['jaminan'],
|
|
'member_level' => $validated['member_level'],
|
|
'qty' => $validated['qty'],
|
|
'tanggal_mulai' => $validated['tanggal_mulai'],
|
|
'jam_sewa' => $validated['jam_sewa'],
|
|
'tanggal_selesai' => $validated['tanggal_selesai'],
|
|
'jam_kembali' => $validated['jam_kembali'],
|
|
'durasi_hari' => $durasiHari,
|
|
'total_harga' => $totalHarga,
|
|
'metode_pembayaran' => $validated['metode_pembayaran'],
|
|
'catatan' => $validated['catatan'] ?? null,
|
|
'admin_catatan' => filled($validated['admin_catatan'] ?? null) ? trim((string) $validated['admin_catatan']) : null,
|
|
'status' => $validated['status'],
|
|
];
|
|
|
|
if (($updateData['admin_catatan'] ?? null) !== $penyewaan->admin_catatan) {
|
|
$updateData['admin_catatan_read_at'] = $updateData['admin_catatan'] ? null : null;
|
|
}
|
|
|
|
if ($request->hasFile('foto_jaminan')) {
|
|
if ($penyewaan->foto_jaminan_path) {
|
|
Storage::disk('public')->delete($penyewaan->foto_jaminan_path);
|
|
}
|
|
$updateData['foto_jaminan_path'] = $request->file('foto_jaminan')->store('foto-jaminan', 'public');
|
|
}
|
|
|
|
if ($request->hasFile('foto_diri')) {
|
|
if ($penyewaan->foto_diri_path) {
|
|
Storage::disk('public')->delete($penyewaan->foto_diri_path);
|
|
}
|
|
$updateData['foto_diri_path'] = $request->file('foto_diri')->store('foto-diri', 'public');
|
|
}
|
|
|
|
if ($request->hasFile('bukti_pembayaran')) {
|
|
if ($penyewaan->bukti_pembayaran_path) {
|
|
Storage::disk('public')->delete($penyewaan->bukti_pembayaran_path);
|
|
}
|
|
$updateData['bukti_pembayaran_path'] = $request->file('bukti_pembayaran')->store('bukti-pembayaran', 'public');
|
|
}
|
|
|
|
InventoryStockService::transaction(function () use ($penyewaan, $validated, $updateData): void {
|
|
InventoryStockService::syncPenyewaanStock(
|
|
$penyewaan,
|
|
(int) $validated['qty'],
|
|
$validated['status']
|
|
);
|
|
|
|
$penyewaan->update($updateData);
|
|
});
|
|
|
|
return redirect()->route('dashboard')->with('status', 'Pengajuan sewa berhasil diperbarui.');
|
|
}
|
|
|
|
public function destroyPenyewaan(Penyewaan $penyewaan): RedirectResponse
|
|
{
|
|
if ($penyewaan->foto_jaminan_path) {
|
|
Storage::disk('public')->delete($penyewaan->foto_jaminan_path);
|
|
}
|
|
|
|
if ($penyewaan->foto_diri_path) {
|
|
Storage::disk('public')->delete($penyewaan->foto_diri_path);
|
|
}
|
|
|
|
if ($penyewaan->bukti_pembayaran_path) {
|
|
Storage::disk('public')->delete($penyewaan->bukti_pembayaran_path);
|
|
}
|
|
|
|
$namaPenyewa = $penyewaan->nama_penyewa;
|
|
|
|
InventoryStockService::transaction(function () use ($penyewaan): void {
|
|
InventoryStockService::restoreForDeletion($penyewaan);
|
|
Penyewaan::destroy($penyewaan->getKey());
|
|
});
|
|
|
|
return redirect()->route('dashboard')->with('status', 'Pengajuan sewa atas nama ' . $namaPenyewa . ' berhasil dihapus.');
|
|
}
|
|
}
|