query('filter', 'bulan'); $page = $request->query('page', 1); $allSalesNames = Transaksi::select('nama_sales')->distinct()->pluck('nama_sales'); if ($filter === 'hari') { return $this->laporanHarian($page, $allSalesNames); } return $this->laporanBulanan($page, $allSalesNames); } public function detailPerProduk(Request $request) { $request->validate([ 'tanggal' => 'required|date_format:Y-m-d', 'page' => 'nullable|integer|min:1', 'per_page' => 'nullable|integer|min:1|max:100', ]); $tanggal = $request->query('tanggal'); $salesId = $request->query('sales_id'); $nampanId = $request->query('nampan_id'); $namaPembeli = $request->query('nama_pembeli'); $page = $request->query('page', 1); $perPage = $request->query('per_page', 15); $carbonDate = Carbon::parse($tanggal); $produkTerjualQuery = $this->buildBaseItemQuery($carbonDate); $this->applyFilters($produkTerjualQuery, $salesId, $nampanId, $namaPembeli); $produkTerjual = $produkTerjualQuery ->select( 'produks.id as id_produk', 'produks.nama as nama_produk', DB::raw('COUNT(item_transaksis.id) as jumlah_item_terjual'), DB::raw('SUM(produks.berat) as berat_terjual'), DB::raw('SUM(item_transaksis.harga_deal) as pendapatan') ) ->groupBy('produks.id', 'produks.nama') ->get() ->keyBy('id_produk'); $totals = $this->calculateTotals($produkTerjual); $semuaProdukPaginated = Produk::select('id', 'nama')->orderBy('nama')->paginate($perPage, ['*'], 'page', $page); $detailItem = $this->mapProductsWithSalesData($semuaProdukPaginated, $produkTerjual); $filterInfo = $this->buildFilterInfo($carbonDate, $salesId, $nampanId, $namaPembeli); return response()->json([ 'filter' => $filterInfo, 'rekap_harian' => $totals, 'produk' => $detailItem->values(), 'pagination' => $this->buildPaginationInfo($semuaProdukPaginated), ]); } public function detailPerNampan(Request $request) { $request->validate([ 'tanggal' => 'required|date_format:Y-m-d', 'page' => 'nullable|integer|min:1', 'per_page' => 'nullable|integer|min:1|max:100', ]); $tanggal = $request->query('tanggal'); $salesId = $request->query('sales_id'); $produkId = $request->query('produk_id'); $namaPembeli = $request->query('nama_pembeli'); $page = $request->query('page', 1); $perPage = $request->query('per_page', 15); $carbonDate = Carbon::parse($tanggal); // Query untuk mendapatkan data penjualan per nampan $nampanTerjualQuery = $this->buildBaseItemQuery($carbonDate); $this->applyNampanFilters($nampanTerjualQuery, $salesId, $produkId, $namaPembeli); // Menggunakan COALESCE untuk menggabungkan nampan dan brankas $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('COUNT(item_transaksis.id) as jumlah_item_terjual'), DB::raw('SUM(produks.berat) as berat_terjual'), DB::raw('SUM(item_transaksis.harga_deal) as pendapatan') ) ->groupBy('id_nampan', 'nama_nampan') ->get() ->keyBy('id_nampan'); $totals = $this->calculateTotals($nampanTerjual); // Mendapatkan semua nampan + entry untuk brankas $semuaNampan = Nampan::select('id', 'nama')->orderBy('nama')->get(); // Tambahkan entry brankas (id = 0) $brankasEntry = (object) ['id' => 0, 'nama' => 'Brankas']; $semuaNampanCollection = $semuaNampan->prepend($brankasEntry); // Pagination manual $currentPage = $page; $offset = ($currentPage - 1) * $perPage; $itemsForCurrentPage = $semuaNampanCollection->slice($offset, $perPage); $semuaNampanPaginated = new LengthAwarePaginator( $itemsForCurrentPage, $semuaNampanCollection->count(), $perPage, $currentPage, ['path' => request()->url(), 'query' => request()->query()] ); $detailItem = $this->mapNampanWithSalesData($semuaNampanPaginated, $nampanTerjual); $filterInfo = $this->buildNampanFilterInfo($carbonDate, $salesId, $produkId, $namaPembeli); return response()->json([ 'filter' => $filterInfo, 'rekap_harian' => $totals, 'nampan' => $detailItem->values(), 'pagination' => $this->buildPaginationInfo($semuaNampanPaginated), ]); } private function laporanHarian(int $page, Collection $allSalesNames) { $perPage = 7; $endDate = Carbon::today()->subDays(($page - 1) * $perPage); $startDate = $endDate->copy()->subDays($perPage - 1); $transaksis = Transaksi::with('itemTransaksi.item.produk') ->whereBetween('created_at', [$startDate->startOfDay(), $endDate->endOfDay()]) ->orderBy('created_at', 'desc') ->get(); $transaksisByDay = $transaksis->groupBy(function ($transaksi) { return Carbon::parse($transaksi->created_at)->format('Y-m-d'); }); $period = CarbonPeriod::create($startDate, $endDate); $laporan = []; foreach ($period as $date) { $dateString = $date->format('Y-m-d'); $tanggalFormatted = $date->isoFormat('dddd, D MMMM Y'); if (isset($transaksisByDay[$dateString])) { $transaksisPerTanggal = $transaksisByDay[$dateString]; $salesDataTransaksi = $transaksisPerTanggal->groupBy('nama_sales') ->map(fn($transaksisPerSales) => $this->hitungDataSales($transaksisPerSales)); $fullSalesData = $allSalesNames->map(function ($namaSales) use ($salesDataTransaksi) { return $salesDataTransaksi->get($namaSales) ?? $this->defaultSalesData($namaSales); }); $totalItem = $fullSalesData->sum('item_terjual'); $totalBerat = $fullSalesData->sum('berat_terjual_raw'); $totalPendapatan = $fullSalesData->sum('pendapatan_raw'); $laporan[$dateString] = [ 'tanggal' => $tanggalFormatted, 'total_item_terjual' => $totalItem > 0 ? $totalItem : self::DEFAULT_DISPLAY, 'total_berat' => $totalBerat > 0 ? $this->formatWeight($totalBerat) : self::DEFAULT_DISPLAY, 'total_pendapatan' => $totalPendapatan > 0 ? $this->formatCurrency($totalPendapatan) : self::DEFAULT_DISPLAY, 'sales' => $this->formatSalesDataValues($fullSalesData)->values(), ]; } else { $laporan[$dateString] = [ 'tanggal' => $tanggalFormatted, 'total_item_terjual' => self::DEFAULT_DISPLAY, 'total_berat' => self::DEFAULT_DISPLAY, 'total_pendapatan' => self::DEFAULT_DISPLAY, 'sales' => [], ]; } } $totalHariUntukPaginasi = 365; $paginatedData = new LengthAwarePaginator( array_reverse(array_values($laporan)), $totalHariUntukPaginasi, $perPage, $page, ['path' => request()->url(), 'query' => request()->query()] ); return response()->json($paginatedData); } private function laporanBulanan(int $page, Collection $allSalesNames) { $perPage = 12; $transaksis = Transaksi::with('itemTransaksi.item.produk') ->orderBy('created_at', 'desc') ->get(); $laporan = $transaksis->groupBy(function ($transaksi) { return Carbon::parse($transaksi->created_at)->format('F Y'); })->map(function ($transaksisPerTanggal, $tanggal) use ($allSalesNames) { $salesDataTransaksi = $transaksisPerTanggal ->groupBy('nama_sales') ->map(fn($transaksisPerSales) => $this->hitungDataSales($transaksisPerSales)); $fullSalesData = $allSalesNames->map(function ($namaSales) use ($salesDataTransaksi) { return $salesDataTransaksi->get($namaSales) ?? $this->defaultSalesData($namaSales); }); $totalItem = $fullSalesData->sum('item_terjual'); $totalBerat = $fullSalesData->sum('berat_terjual_raw'); $totalPendapatan = $fullSalesData->sum('pendapatan_raw'); return [ 'tanggal' => $tanggal, 'total_item_terjual' => $totalItem > 0 ? $totalItem : self::DEFAULT_DISPLAY, 'total_berat' => $totalBerat > 0 ? $this->formatWeight($totalBerat) : self::DEFAULT_DISPLAY, 'total_pendapatan' => $totalPendapatan > 0 ? $this->formatCurrency($totalPendapatan) : self::DEFAULT_DISPLAY, 'sales' => $this->formatSalesDataValues($fullSalesData)->values(), ]; }); $paginatedData = new LengthAwarePaginator( $laporan->forPage($page, $perPage)->values(), $laporan->count(), $perPage, $page, ['path' => request()->url(), 'query' => request()->query()] ); return response()->json($paginatedData); } private function buildBaseItemQuery(Carbon $carbonDate) { return ItemTransaksi::query() ->join('items', 'item_transaksis.id_item', '=', 'items.id') ->join('produks', 'items.id_produk', '=', 'produks.id') ->join('transaksis', 'item_transaksis.id_transaksi', '=', 'transaksis.id') ->whereDate('transaksis.created_at', $carbonDate); } private function applyFilters($query, $salesId, $nampanId, $namaPembeli) { if ($salesId) { $query->join('sales', 'transaksis.id_sales', '=', 'sales.id') ->where('sales.id', $salesId); } if ($nampanId) { if ($nampanId == 0) { // Filter untuk brankas (id_nampan = null) $query->whereNull('items.id_nampan'); } else { // Filter untuk nampan tertentu $query->where('items.id_nampan', $nampanId); } } if ($namaPembeli) { $query->where('transaksis.nama_pembeli', 'like', "%{$namaPembeli}%"); } } private function applyNampanFilters($query, $salesId, $produkId, $namaPembeli) { if ($salesId) { $query->join('sales', 'transaksis.id_sales', '=', 'sales.id') ->where('sales.id', $salesId); } if ($produkId) { $query->where('produks.id', $produkId); } if ($namaPembeli) { $query->where('transaksis.nama_pembeli', 'like', "%{$namaPembeli}%"); } } private function calculateTotals(Collection $data): array { $totalPendapatan = $data->sum('pendapatan'); $totalItemTerjual = $data->sum('jumlah_item_terjual'); $totalBeratTerjual = $data->sum('berat_terjual'); return [ 'total_item_terjual' => $totalItemTerjual, 'total_berat_terjual' => $this->formatWeight($totalBeratTerjual), 'total_pendapatan' => $this->formatCurrency($totalPendapatan), ]; } private function mapProductsWithSalesData($paginatedData, Collection $salesData): Collection { return $paginatedData->getCollection()->map(function ($item) use ($salesData) { if ($salesData->has($item->id)) { $dataTerjual = $salesData->get($item->id); return [ 'nama_produk' => $item->nama, 'jumlah_item_terjual' => (int) $dataTerjual->jumlah_item_terjual, 'berat_terjual' => $this->formatWeight($dataTerjual->berat_terjual), 'pendapatan' => $this->formatCurrency($dataTerjual->pendapatan), ]; } return [ 'nama_produk' => $item->nama, 'jumlah_item_terjual' => self::DEFAULT_DISPLAY, 'berat_terjual' => self::DEFAULT_DISPLAY, 'pendapatan' => self::DEFAULT_DISPLAY, ]; }); } private function mapNampanWithSalesData($paginatedData, Collection $salesData): Collection { return $paginatedData->getCollection()->map(function ($item) use ($salesData) { if ($salesData->has($item->id)) { $dataTerjual = $salesData->get($item->id); return [ 'nama_nampan' => $item->nama, 'jumlah_item_terjual' => (int) $dataTerjual->jumlah_item_terjual, 'berat_terjual' => $this->formatWeight($dataTerjual->berat_terjual), 'pendapatan' => $this->formatCurrency($dataTerjual->pendapatan), ]; } return [ 'nama_nampan' => $item->nama, 'jumlah_item_terjual' => self::DEFAULT_DISPLAY, 'berat_terjual' => self::DEFAULT_DISPLAY, 'pendapatan' => self::DEFAULT_DISPLAY, ]; }); } private function buildFilterInfo(Carbon $carbonDate, $salesId, $nampanId, $namaPembeli): array { $filterInfo = [ 'tanggal' => $carbonDate->isoFormat('dddd, D MMMM Y'), 'nama_sales' => null, 'nampan' => null, 'nama_pembeli' => $namaPembeli, ]; if ($salesId) { $sales = Sales::find($salesId); $filterInfo['nama_sales'] = $sales?->nama; } if ($nampanId) { if ($nampanId == 0) { $filterInfo['nampan'] = 'Brankas'; } else { $nampan = Nampan::find($nampanId); $filterInfo['nampan'] = $nampan?->nama; } } return $filterInfo; } private function buildNampanFilterInfo(Carbon $carbonDate, $salesId, $produkId, $namaPembeli): array { $filterInfo = [ 'tanggal' => $carbonDate->isoFormat('dddd, D MMMM Y'), 'nama_sales' => null, 'produk' => null, 'nama_pembeli' => $namaPembeli, ]; if ($salesId) { $sales = Sales::find($salesId); $filterInfo['nama_sales'] = $sales?->nama; } if ($produkId) { $produk = Produk::find($produkId); $filterInfo['produk'] = $produk?->nama; } return $filterInfo; } private function buildPaginationInfo($paginatedData): array { return [ 'current_page' => $paginatedData->currentPage(), 'last_page' => $paginatedData->lastPage(), 'per_page' => $paginatedData->perPage(), 'total' => $paginatedData->total(), 'from' => $paginatedData->firstItem(), 'to' => $paginatedData->lastItem(), ]; } private function hitungDataSales(Collection $transaksisPerSales): array { $itemTerjual = $transaksisPerSales->sum(fn($t) => $t->itemTransaksi->count()); $beratTerjual = $transaksisPerSales->sum( fn($t) => $t->itemTransaksi->sum(fn($it) => $it->item->produk->berat ?? 0) ); $pendapatan = $transaksisPerSales->sum('total_harga'); return [ 'nama' => $transaksisPerSales->first()->nama_sales, 'item_terjual' => $itemTerjual, 'berat_terjual_raw' => $beratTerjual, 'pendapatan_raw' => $pendapatan, ]; } private function defaultSalesData(string $namaSales): array { return [ 'nama' => $namaSales, 'item_terjual' => 0, 'berat_terjual_raw' => 0, 'pendapatan_raw' => 0, ]; } private function formatSalesDataValues(Collection $salesData): Collection { 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 ? $this->formatWeight($sale['berat_terjual_raw']) : self::DEFAULT_DISPLAY; $sale['pendapatan'] = $sale['pendapatan_raw'] > 0 ? $this->formatCurrency($sale['pendapatan_raw']) : self::DEFAULT_DISPLAY; unset($sale['berat_terjual_raw'], $sale['pendapatan_raw']); return $sale; }); } private function formatCurrency(float $amount): string { return self::CURRENCY_SYMBOL . number_format($amount, 2, ',', '.'); } private function formatWeight(float $weight): string { return number_format($weight, 2, ',', '.') . self::WEIGHT_UNIT; } }