From bfed0fbd2b72175f5e75d65f83a1ccfae2011e45 Mon Sep 17 00:00:00 2001 From: Baghaztra Date: Mon, 15 Sep 2025 10:52:40 +0700 Subject: [PATCH] [fix] laporan mencatat asal nampan dengan benar --- app/Exports/DetailNampanExport.php | 11 ++- app/Exports/DetailProdukExport.php | 15 ++-- app/Exports/RingkasanExport.php | 3 +- app/Helpers/LaporanHelper.php | 35 +++++---- app/Http/Controllers/LaporanController.php | 2 +- app/Http/Controllers/TransaksiController.php | 14 ++-- app/Repositories/TransaksiRepository.php | 4 +- app/Services/LaporanService.php | 78 +++++++++----------- resources/js/components/DetailPerNampan.vue | 1 + resources/js/components/KasirForm.vue | 12 +-- resources/js/components/StrukOverlay.vue | 2 +- 11 files changed, 87 insertions(+), 90 deletions(-) diff --git a/app/Exports/DetailNampanExport.php b/app/Exports/DetailNampanExport.php index fad8581..7015979 100644 --- a/app/Exports/DetailNampanExport.php +++ b/app/Exports/DetailNampanExport.php @@ -64,12 +64,17 @@ class DetailNampanExport implements FromCollection, WithHeadings, WithTitle, Wit public function styles(Worksheet $sheet) { $styles = [ - 1 => ['font' => ['bold' => true]], // Header row + 1 => ['font' => ['bold' => true]], ]; - // Style for recap row if exists if (isset($this->data['rekap_harian'])) { - $styles[2] = [ + $lastRow = 1; + if (isset($this->data['nampan'])) { + $lastRow += count($this->data['nampan']); + } + $lastRow++; + + $styles[$lastRow] = [ 'font' => ['bold' => true], 'fill' => [ 'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID, diff --git a/app/Exports/DetailProdukExport.php b/app/Exports/DetailProdukExport.php index 56cbcdf..c984ad3 100644 --- a/app/Exports/DetailProdukExport.php +++ b/app/Exports/DetailProdukExport.php @@ -70,12 +70,17 @@ class DetailProdukExport implements FromCollection, WithHeadings, WithTitle, Wit public function styles(Worksheet $sheet) { $styles = [ - 1 => ['font' => ['bold' => true]], // Header row + 1 => ['font' => ['bold' => true]], ]; - - // Style for recap row if exists + if (isset($this->data['rekap_harian'])) { - $styles[2] = [ + $lastRow = 1; + if (isset($this->data['nampan'])) { + $lastRow += count($this->data['nampan']); + } + $lastRow++; + + $styles[$lastRow] = [ 'font' => ['bold' => true], 'fill' => [ 'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID, @@ -83,7 +88,7 @@ class DetailProdukExport implements FromCollection, WithHeadings, WithTitle, Wit ], ]; } - + return $styles; } } \ No newline at end of file diff --git a/app/Exports/RingkasanExport.php b/app/Exports/RingkasanExport.php index 64da7b5..6781e8b 100644 --- a/app/Exports/RingkasanExport.php +++ b/app/Exports/RingkasanExport.php @@ -47,7 +47,6 @@ class RingkasanExport implements FromCollection, WithHeadings, WithStyles 'Pendapatan' => $totalPendapatan, ]); - // Tambahkan baris kosong biar rapi $rows->push(['Tanggal' => '', 'Nama Sales' => '', 'Item Terjual' => '', 'Berat' => '', 'Pendapatan' => '']); } @@ -68,7 +67,7 @@ class RingkasanExport implements FromCollection, WithHeadings, WithStyles public function styles(Worksheet $sheet) { return [ - 1 => ['font' => ['bold' => true]], // Header bold + 1 => ['font' => ['bold' => true]], ]; } } diff --git a/app/Helpers/LaporanHelper.php b/app/Helpers/LaporanHelper.php index 7ab5cf7..700c9b1 100644 --- a/app/Helpers/LaporanHelper.php +++ b/app/Helpers/LaporanHelper.php @@ -8,6 +8,7 @@ use App\Models\Produk; use Carbon\Carbon; use Illuminate\Support\Collection; use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Facades\DB; class LaporanHelper { @@ -30,16 +31,19 @@ class LaporanHelper public function getAllNampanWithPagination(int $page, int $perPage): LengthAwarePaginator { - $semuaNampan = Nampan::select('id', 'nama')->orderBy('nama')->get(); - $brankasEntry = (object) ['id' => 0, 'nama' => 'Brankas']; - $semuaNampanCollection = $semuaNampan->prepend($brankasEntry); + $semuaPosisi = DB::table('item_transaksis') + ->select('posisi_asal') + ->distinct() + ->pluck('posisi_asal') + ->sort() + ->values(); $offset = ($page - 1) * $perPage; - $itemsForCurrentPage = $semuaNampanCollection->slice($offset, $perPage); + $itemsForCurrentPage = $semuaPosisi->slice($offset, $perPage); return new LengthAwarePaginator( $itemsForCurrentPage, - $semuaNampanCollection->count(), + $semuaPosisi->count(), $perPage, $page, ['path' => request()->url(), 'query' => request()->query()] @@ -71,10 +75,10 @@ class LaporanHelper public function mapNampanWithSalesData($paginatedData, Collection $salesData): Collection { return $paginatedData->getCollection()->map(function ($item) use ($salesData) { - if ($salesData->has($item->id)) { - $dataTerjual = $salesData->get($item->id); + if ($salesData->has($item)) { + $dataTerjual = $salesData->get($item); return [ - 'nama_nampan' => $item->nama, + 'nama_nampan' => $item, // sekarang langsung string posisi 'jumlah_item_terjual' => (int) $dataTerjual->jumlah_item_terjual, 'berat_terjual' => $this->formatWeight($dataTerjual->berat_terjual), 'pendapatan' => $this->formatCurrency($dataTerjual->pendapatan), @@ -82,7 +86,7 @@ class LaporanHelper } return [ - 'nama_nampan' => $item->nama, + 'nama_nampan' => $item, 'jumlah_item_terjual' => self::DEFAULT_DISPLAY, 'berat_terjual' => self::DEFAULT_DISPLAY, 'pendapatan' => self::DEFAULT_DISPLAY, @@ -153,11 +157,12 @@ class LaporanHelper public function hitungDataSales(Collection $transaksisPerSales): array { $itemTerjual = $transaksisPerSales->sum(fn($t) => $t->itemTransaksi->count()); - + + // UBAH BAGIAN INI: Hapus ->item dari path relasi $beratTerjual = $transaksisPerSales->sum( - fn($t) => $t->itemTransaksi->sum(fn($it) => $it->item?->produk?->berat ?? 0) + fn($t) => $t->itemTransaksi->sum(fn($it) => $it->produk?->berat ?? 0) ); - + $pendapatan = $transaksisPerSales->sum('total_harga'); return [ @@ -182,9 +187,9 @@ class LaporanHelper { return $salesData->map(function ($sale) { $sale['item_terjual'] = $sale['item_terjual'] > 0 ? $sale['item_terjual'] : self::DEFAULT_DISPLAY; - $sale['berat_terjual'] = $sale['berat_terjual_raw'] > 0 ? + $sale['berat_terjual'] = $sale['berat_terjual_raw'] > 0 ? $this->formatWeight($sale['berat_terjual_raw']) : self::DEFAULT_DISPLAY; - $sale['pendapatan'] = $sale['pendapatan_raw'] > 0 ? + $sale['pendapatan'] = $sale['pendapatan_raw'] > 0 ? $this->formatCurrency($sale['pendapatan_raw']) : self::DEFAULT_DISPLAY; unset($sale['berat_terjual_raw'], $sale['pendapatan_raw']); @@ -201,4 +206,4 @@ class LaporanHelper { return number_format($weight, 2, ',', '.') . self::WEIGHT_UNIT; } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/LaporanController.php b/app/Http/Controllers/LaporanController.php index f4de54d..0f0a4da 100644 --- a/app/Http/Controllers/LaporanController.php +++ b/app/Http/Controllers/LaporanController.php @@ -67,7 +67,7 @@ class LaporanController extends Controller } catch (\Exception $e) { Log::error('Error in detailPerNampan method: ' . $e->getMessage()); - return response()->json(['error' => 'Terjadi kesalahan saat mengambil data nampan'], 500); + return response()->json(['error' => 'Terjadi kesalahan saat mengambil data nampan' . $e->getMessage()], 500); } } diff --git a/app/Http/Controllers/TransaksiController.php b/app/Http/Controllers/TransaksiController.php index 0b58655..c975503 100644 --- a/app/Http/Controllers/TransaksiController.php +++ b/app/Http/Controllers/TransaksiController.php @@ -15,7 +15,7 @@ class TransaksiController extends Controller public function index() { $limit = request()->query('limit', null); - $query = Transaksi::with(['kasir', 'sales', 'itemTransaksi.item.produk'])->latest(); + $query = Transaksi::with(['kasir', 'sales', 'itemTransaksi.produk'])->latest(); if ($limit) { $query->limit((int)$limit); } @@ -33,7 +33,7 @@ class TransaksiController extends Controller // Detail transaksi by ID public function show($id) { - $transaksi = Transaksi::with(['kasir', 'sales', 'items.item.produk.foto'])->findOrFail($id); + $transaksi = Transaksi::with(['kasir', 'sales', 'items.produk.foto'])->findOrFail($id); return response()->json($transaksi); } @@ -53,7 +53,7 @@ class TransaksiController extends Controller 'ongkos_bikin' => 'nullable|numeric|min:0', 'total_harga' => 'required|numeric', 'items' => 'required|array', - 'items.*.kode_item' => 'required|exists:items,id', + 'items.*.kode_item' => 'required|exists:items,id|numeric', 'items.*.harga_deal' => 'required|numeric', ]); @@ -80,21 +80,21 @@ class TransaksiController extends Controller // if (!$item) { // throw new \Exception("Item dengan kode_item {$it['kode_item']} tidak ditemukan."); // } - $item = Item::find($it['kode_item'])->with('produk')->first(); + $item = Item::where('id',$it['kode_item'])->with('produk')->first(); ItemTransaksi::create([ 'id_transaksi' => $transaksi->id, 'id_produk' => $item->produk->id, 'harga_deal' => $it['harga_deal'], - 'posisi_asal' => $item->nampan ? 'Nampan ' . $item->nampan->nama : 'Brankas', + 'posisi_asal' => $it['posisi'], ]); - $item->delete(); + $item->forceDelete(); } DB::commit(); return response()->json( - $transaksi->load(['itemTransaksi.item.produk.foto', 'kasir', 'sales']), + $transaksi->load(['itemTransaksi.produk.foto', 'kasir', 'sales']), 201 ); } catch (\Exception $e) { diff --git a/app/Repositories/TransaksiRepository.php b/app/Repositories/TransaksiRepository.php index edc13fe..7a06978 100644 --- a/app/Repositories/TransaksiRepository.php +++ b/app/Repositories/TransaksiRepository.php @@ -36,7 +36,7 @@ class TransaksiRepository $totalHariUntukPaginasi = $endDate->diffInDays($startDate) + 1; } - $transaksis = Transaksi::with(['itemTransaksi.item.produk']) + $transaksis = Transaksi::with(['itemTransaksi.produk']) ->whereBetween('created_at', [$startDate->startOfDay(), $endDate->endOfDay()]) ->orderBy('created_at', 'desc') ->get(); @@ -100,7 +100,7 @@ class TransaksiRepository { $perPage = self::MONTHLY_PER_PAGE; - $transaksis = Transaksi::with(['itemTransaksi.item.produk']) + $transaksis = Transaksi::with(['itemTransaksi.produk']) ->orderBy('created_at', 'desc') ->get(); diff --git a/app/Services/LaporanService.php b/app/Services/LaporanService.php index ff9f812..1e280db 100644 --- a/app/Services/LaporanService.php +++ b/app/Services/LaporanService.php @@ -67,7 +67,6 @@ class LaporanService $perPage = $params['per_page'] ?? self::DEFAULT_PER_PAGE; // --- Step 1: Calculate overall totals for all filtered items --- - // We need a separate query for totals that is not affected by pagination. $totalsQuery = $this->buildBaseItemQuery($tanggal); $this->applyFilters($totalsQuery, $params); @@ -79,8 +78,8 @@ class LaporanService $rekapHarian = [ 'total_item_terjual' => (int) $totalsResult->total_item_terjual, - 'total_berat_terjual' => $this->helper->formatWeight($totalsResult->total_berat_terjual), // Assuming formatting helper - 'total_pendapatan' => $this->helper->formatCurrency($totalsResult->total_pendapatan), // Assuming formatting helper + 'total_berat_terjual' => $this->helper->formatWeight($totalsResult->total_berat_terjual), + 'total_pendapatan' => $this->helper->formatCurrency($totalsResult->total_pendapatan), ]; // --- Step 2: Build the filtered sales data subquery --- @@ -92,7 +91,7 @@ class LaporanService DB::raw('COALESCE(SUM(item_transaksis.harga_deal), 0) as pendapatan') ) ->groupBy('produks.id'); - // Apply filters to the subquery + $this->applyFilters($salesSubQuery, $params); // --- Step 3: Fetch paginated products and LEFT JOIN the sales data subquery --- @@ -113,7 +112,7 @@ class LaporanService $detailItem = $semuaProdukPaginated->map(function ($item) { return [ 'nama_produk' => $item->nama_produk, - 'jumlah_item_terjual' => $item->jumlah_item_terjual ? (int) $item->jumlah_item_terjual : 0, // Use 0 or default display value + 'jumlah_item_terjual' => $item->jumlah_item_terjual ? (int) $item->jumlah_item_terjual : 0, 'berat_terjual' => $item->berat_terjual ? $this->helper->formatWeight($item->berat_terjual) : '-', 'pendapatan' => $item->pendapatan ? $this->helper->formatCurrency($item->pendapatan) : '-', ]; @@ -140,17 +139,15 @@ class LaporanService $this->applyNampanFilters($nampanTerjualQuery, $params); $nampanTerjual = $nampanTerjualQuery - ->leftJoin('nampans', 'items.id_nampan', '=', 'nampans.id') ->select( - DB::raw('COALESCE(items.id_nampan, 0) as id_nampan'), - DB::raw('COALESCE(nampans.nama, "Brankas") as nama_nampan'), + DB::raw('COALESCE(item_transaksis.posisi_asal, "Brankas") as nama_nampan'), DB::raw('COUNT(item_transaksis.id) as jumlah_item_terjual'), DB::raw('COALESCE(SUM(produks.berat), 0) as berat_terjual'), DB::raw('COALESCE(SUM(item_transaksis.harga_deal), 0) as pendapatan') ) - ->groupBy('id_nampan', 'nama_nampan') + ->groupBy('nama_nampan') ->get() - ->keyBy('id_nampan'); + ->keyBy('nama_nampan'); $totals = $this->helper->calculateTotals($nampanTerjual); $semuaNampanPaginated = $this->helper->getAllNampanWithPagination($page, $perPage); @@ -174,7 +171,6 @@ class LaporanService $allSalesNames = $this->getAllSalesNames(); if ($filter === 'hari') { - // Tar kalau mau ubah eksport laporan setiap hari, param ke-3 jadiin false #Bagas $data = $this->processLaporanHarian($allSalesNames, $page, true); } else { $data = $this->processLaporanBulanan($allSalesNames, $page, true); @@ -196,17 +192,14 @@ class LaporanService return Excel::download(new RingkasanExport($data, $page), $fileName); } - // Method baru untuk export per produk public function exportPerProduk(array $params) { $tanggal = $params['tanggal']; $format = $params['format']; - // Get all data tanpa pagination karena untuk export $allParams = $params; unset($allParams['page'], $allParams['per_page']); - - // Get data dengan semua produk (tanpa pagination) + $data = $this->getDetailPerProdukForExport($allParams); $fileName = "laporan_per_produk_{$tanggal}_" . Carbon::now()->format('Ymd') . ".{$format}"; @@ -227,12 +220,10 @@ class LaporanService { $tanggal = $params['tanggal']; $format = $params['format']; - - // Get all data tanpa pagination karena untuk export + $allParams = $params; unset($allParams['page'], $allParams['per_page']); - - // Get data dengan semua nampan (tanpa pagination) + $data = $this->getDetailPerNampanForExport($allParams); $fileName = "laporan_per_nampan_{$tanggal}_" . Carbon::now()->format('Ymd') . ".{$format}"; @@ -249,7 +240,6 @@ class LaporanService return Excel::download(new DetailNampanExport($data), $fileName); } - // Helper method untuk get data produk tanpa pagination (untuk export) private function getDetailPerProdukForExport(array $params) { $tanggal = Carbon::parse($params['tanggal']); @@ -270,8 +260,7 @@ class LaporanService ->keyBy('id_produk'); $totals = $this->helper->calculateTotals($produkTerjual); - - // Get all products without pagination + $semuaProduk = Produk::select('id', 'nama')->orderBy('nama')->get(); $detailItem = collect($semuaProduk)->map(function ($item) use ($produkTerjual) { @@ -302,7 +291,6 @@ class LaporanService ]; } - // Helper method untuk get data nampan tanpa pagination (untuk export) private function getDetailPerNampanForExport(array $params) { $tanggal = Carbon::parse($params['tanggal']); @@ -311,30 +299,30 @@ class LaporanService $this->applyNampanFilters($nampanTerjualQuery, $params); $nampanTerjual = $nampanTerjualQuery - ->leftJoin('nampans', 'items.id_nampan', '=', 'nampans.id') ->select( - DB::raw('COALESCE(items.id_nampan, 0) as id_nampan'), - DB::raw('COALESCE(nampans.nama, "Brankas") as nama_nampan'), + 'item_transaksis.posisi_asal as posisi_asal', DB::raw('COUNT(item_transaksis.id) as jumlah_item_terjual'), DB::raw('COALESCE(SUM(produks.berat), 0) as berat_terjual'), DB::raw('COALESCE(SUM(item_transaksis.harga_deal), 0) as pendapatan') ) - ->groupBy('id_nampan', 'nama_nampan') + ->groupBy('item_transaksis.posisi_asal') ->get() - ->keyBy('id_nampan'); + ->keyBy('posisi_asal'); $totals = $this->helper->calculateTotals($nampanTerjual); - // Get all nampan without pagination - $semuaNampan = Nampan::select('id', 'nama')->orderBy('nama')->get(); - $brankasEntry = (object) ['id' => 0, 'nama' => 'Brankas']; - $semuaNampanCollection = $semuaNampan->prepend($brankasEntry); + $semuaPosisi = DB::table('item_transaksis') + ->select('posisi_asal') + ->distinct() + ->pluck('posisi_asal') + ->sort() + ->values(); - $detailItem = $semuaNampanCollection->map(function ($item) use ($nampanTerjual) { - if ($nampanTerjual->has($item->id)) { - $dataTerjual = $nampanTerjual->get($item->id); + $detailItem = $semuaPosisi->map(function ($posisi) use ($nampanTerjual) { + if ($nampanTerjual->has($posisi)) { + $dataTerjual = $nampanTerjual->get($posisi); return [ - 'nama_nampan' => $item->nama, + 'nama_nampan' => $posisi, 'jumlah_item_terjual' => (int) $dataTerjual->jumlah_item_terjual, 'berat_terjual' => $this->helper->formatWeight($dataTerjual->berat_terjual), 'pendapatan' => $this->helper->formatCurrency($dataTerjual->pendapatan), @@ -342,7 +330,7 @@ class LaporanService } return [ - 'nama_nampan' => $item->nama, + 'nama_nampan' => $posisi, 'jumlah_item_terjual' => LaporanHelper::DEFAULT_DISPLAY, 'berat_terjual' => LaporanHelper::DEFAULT_DISPLAY, 'pendapatan' => LaporanHelper::DEFAULT_DISPLAY, @@ -358,6 +346,7 @@ class LaporanService ]; } + private function getAllSalesNames(): Collection { return Cache::remember('all_sales_names', self::CACHE_TTL, function () { @@ -377,9 +366,9 @@ class LaporanService private function buildBaseItemQuery(Carbon $carbonDate) { + // UBAH: Menghapus join ke tabel 'items' dan join 'produks' langsung dari 'item_transaksis' return ItemTransaksi::query() - ->join('items', 'item_transaksis.id_item', '=', 'items.id') - ->join('produks', 'items.id_produk', '=', 'produks.id') + ->join('produks', 'item_transaksis.id_produk', '=', 'produks.id') ->join('transaksis', 'item_transaksis.id_transaksi', '=', 'transaksis.id') ->whereDate('transaksis.created_at', $carbonDate); } @@ -388,14 +377,15 @@ class LaporanService { if (!empty($params['sales_id'])) { $query->join('sales', 'transaksis.id_sales', '=', 'sales.id') - ->where('sales.id', $params['sales_id']); + ->where('sales.id', $params['sales_id']); } if (isset($params['nampan_id'])) { + // UBAH: Filter berdasarkan 'item_transaksis.id_nampan' if ($params['nampan_id'] == 0) { - $query->whereNull('items.id_nampan'); + $query->whereNull('item_transaksis.id_nampan'); } else { - $query->where('items.id_nampan', $params['nampan_id']); + $query->where('item_transaksis.id_nampan', $params['nampan_id']); } } @@ -408,7 +398,7 @@ class LaporanService { if (!empty($params['sales_id'])) { $query->join('sales', 'transaksis.id_sales', '=', 'sales.id') - ->where('sales.id', $params['sales_id']); + ->where('sales.id', $params['sales_id']); } if (!empty($params['produk_id'])) { @@ -419,4 +409,4 @@ class LaporanService $query->where('transaksis.nama_pembeli', 'like', "%{$params['nama_pembeli']}%"); } } -} +} \ No newline at end of file diff --git a/resources/js/components/DetailPerNampan.vue b/resources/js/components/DetailPerNampan.vue index 2c2000d..c7834e1 100644 --- a/resources/js/components/DetailPerNampan.vue +++ b/resources/js/components/DetailPerNampan.vue @@ -371,6 +371,7 @@ const fetchData = async (page = 1) => { total: response.data.nampan ? response.data.nampan.length : 0, }; } + console.log('Data laporan nampan berhasil diambil:', data.value); } catch (error) { console.error('Gagal mengambil data laporan nampan:', error); data.value = null; diff --git a/resources/js/components/KasirForm.vue b/resources/js/components/KasirForm.vue index b845686..dcb3bb5 100644 --- a/resources/js/components/KasirForm.vue +++ b/resources/js/components/KasirForm.vue @@ -195,18 +195,10 @@ const tambahItem = () => { return; } - if (pesanan.value.length >= 2) { - error.value = "Maksimal hanya bisa memesan 2 item."; - clearTimeout(errorTimeout); - errorTimeout = setTimeout(() => { - error.value = ""; - }, 3000); - return; - } - // harga deal - item.value.kode_item = kodeItem.value; + item.value.kode_item = Number(kodeItem.value); item.value.harga_deal = Number(hargaJual.value); + item.value.posisi = item.value.nampan ? item.value.nampan.nama : "Brankas"; pesanan.value.push(item.value); diff --git a/resources/js/components/StrukOverlay.vue b/resources/js/components/StrukOverlay.vue index b85e6a7..75cf1da 100644 --- a/resources/js/components/StrukOverlay.vue +++ b/resources/js/components/StrukOverlay.vue @@ -71,7 +71,7 @@ {{ item.produk?.nama || '' }} - {{ item.posisi || '' }} + {{ item.nampan?.nama || 'Brankas' }} {{ item.produk.berat }}g