From 644d6fb222ba917e5f1993cbae355c6c1ee96e41 Mon Sep 17 00:00:00 2001 From: Baghaztra Date: Wed, 3 Sep 2025 17:04:37 +0700 Subject: [PATCH] [Feat] detail laporan --- app/Http/Controllers/LaporanController.php | 162 ++++++++++++++---- app/Models/ItemTransaksi.php | 3 +- app/Models/Transaksi.php | 10 -- database/factories/ProdukFactory.php | 10 +- database/factories/TransaksiFactory.php | 9 +- ...26_031033_create_item_transaksis_table.php | 1 + database/seeders/DatabaseSeeder.php | 13 +- resources/js/components/DetailLaporan.vue | 79 +++++++++ resources/js/pages/Laporan.vue | 3 + resources/views/app.blade.php | 44 +++-- routes/web.php | 1 + 11 files changed, 272 insertions(+), 63 deletions(-) create mode 100644 resources/js/components/DetailLaporan.vue diff --git a/app/Http/Controllers/LaporanController.php b/app/Http/Controllers/LaporanController.php index e7e09b7..32199ec 100644 --- a/app/Http/Controllers/LaporanController.php +++ b/app/Http/Controllers/LaporanController.php @@ -2,12 +2,15 @@ namespace App\Http\Controllers; +use App\Models\ItemTransaksi; +use App\Models\Produk; use App\Models\Transaksi; use Carbon\Carbon; use Carbon\CarbonPeriod; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; class LaporanController extends Controller { @@ -15,7 +18,7 @@ class LaporanController extends Controller { $filter = $request->query('filter', 'bulan'); $page = $request->query('page', 1); - + $allSalesNames = Transaksi::select('nama_sales')->distinct()->pluck('nama_sales'); if ($filter === 'hari') { @@ -28,7 +31,7 @@ class LaporanController extends Controller private function laporanHarian(int $page, Collection $allSalesNames) { $perPage = 7; - + $endDate = Carbon::today()->subDays(($page - 1) * $perPage); $startDate = $endDate->copy()->subDays($perPage - 1); @@ -50,9 +53,9 @@ class LaporanController extends Controller if (isset($transaksisByDay[$dateString])) { $transaksisPerTanggal = $transaksisByDay[$dateString]; - + $salesDataTransaksi = $transaksisPerTanggal->groupBy('nama_sales') - ->map(fn ($transaksisPerSales) => $this->hitungDataSales($transaksisPerSales)); + ->map(fn($transaksisPerSales) => $this->hitungDataSales($transaksisPerSales)); $fullSalesData = $allSalesNames->map(function ($namaSales) use ($salesDataTransaksi) { return $salesDataTransaksi->get($namaSales) ?? $this->defaultSalesData($namaSales); @@ -102,29 +105,29 @@ class LaporanController extends Controller $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)); + ->map(function ($transaksisPerTanggal, $tanggal) use ($allSalesNames) { - $fullSalesData = $allSalesNames->map(function ($namaSales) use ($salesDataTransaksi) { - return $salesDataTransaksi->get($namaSales) ?? $this->defaultSalesData($namaSales); + $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 : '-', + 'total_berat' => $totalBerat > 0 ? number_format($totalBerat, 2, ',', '.') . 'g' : '-', + 'total_pendapatan' => $totalPendapatan > 0 ? 'Rp' . number_format($totalPendapatan, 2, ',', '.') : '-', + 'sales' => $this->formatSalesDataValues($fullSalesData)->values(), + ]; }); - $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 : '-', - 'total_berat' => $totalBerat > 0 ? number_format($totalBerat, 2, ',', '.') . 'g' : '-', - 'total_pendapatan' => $totalPendapatan > 0 ? 'Rp' . number_format($totalPendapatan, 2, ',', '.') : '-', - 'sales' => $this->formatSalesDataValues($fullSalesData)->values(), - ]; - }); - $paginatedData = new LengthAwarePaginator( $laporan->forPage($page, $perPage)->values(), $laporan->count(), @@ -132,15 +135,16 @@ class LaporanController extends Controller $page, ['path' => request()->url(), 'query' => request()->query()] ); - + return response()->json($paginatedData); } 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) + $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'); @@ -151,7 +155,7 @@ class LaporanController extends Controller 'pendapatan_raw' => $pendapatan, ]; } - + private function defaultSalesData(string $namaSales): array { return [ @@ -168,9 +172,105 @@ class LaporanController extends Controller $sale['item_terjual'] = $sale['item_terjual'] > 0 ? $sale['item_terjual'] : '-'; $sale['berat_terjual'] = $sale['berat_terjual_raw'] > 0 ? number_format($sale['berat_terjual_raw'], 2, ',', '.') . 'g' : '-'; $sale['pendapatan'] = $sale['pendapatan_raw'] > 0 ? 'Rp' . number_format($sale['pendapatan_raw'], 2, ',', '.') : '-'; - + unset($sale['berat_terjual_raw'], $sale['pendapatan_raw']); return $sale; }); } -} \ No newline at end of file + +public function detail(Request $request) + { + // 1. VALIDASI DAN PENGAMBILAN PARAMETER FILTER + $request->validate([ + 'tanggal' => 'required|date_format:Y-m-d', + ]); + + $tanggal = $request->query('tanggal'); + $namaSales = $request->query('nama_sales'); + $posisi = $request->query('posisi'); + $namaPembeli = $request->query('nama_pembeli'); // Untuk pencarian + + $carbonDate = Carbon::parse($tanggal); + + // 2. QUERY UTAMA UNTUK MENGAMBIL DATA PRODUK YANG TERJUAL BERDASARKAN FILTER + // Query ini hanya akan mengambil produk yang memiliki transaksi sesuai filter. + $produkTerjualQuery = 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') + // Filter Wajib: Tanggal + ->whereDate('transaksis.created_at', $carbonDate) + // Filter Opsional: Nama Sales + ->when($namaSales, function ($query, $namaSales) { + return $query->where('transaksis.nama_sales', $namaSales); + }) + // Filter Opsional: Posisi Asal Item + ->when($posisi, function ($query, $posisi) { + return $query->where('item_transaksis.posisi_asal', $posisi); + }) + // Filter Opsional: Nama Pembeli (menggunakan LIKE untuk pencarian) + ->when($namaPembeli, function ($query, $namaPembeli) { + return $query->where('transaksis.nama_pembeli', 'like', "%{$namaPembeli}%"); + }) + ->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() + // Mengubah collection menjadi array asosiatif dengan key id_produk agar mudah dicari + ->keyBy('id_produk'); + + + // 3. MENGAMBIL SEMUA PRODUK DARI DATABASE + $semuaProduk = Produk::query()->select('id', 'nama')->get(); + + // 4. MENGGABUNGKAN DATA SEMUA PRODUK DENGAN PRODUK YANG TERJUAL + $detailItem = $semuaProduk->map(function ($produk) use ($produkTerjualQuery) { + // Cek apakah produk ini ada di dalam daftar produk yang terjual + if ($produkTerjualQuery->has($produk->id)) { + $dataTerjual = $produkTerjualQuery->get($produk->id); + return [ + 'nama_produk' => $produk->nama, + 'jumlah_item_terjual' => (int) $dataTerjual->jumlah_item_terjual, + 'berat_terjual' => (float) $dataTerjual->berat_terjual, + 'pendapatan' => (float) $dataTerjual->pendapatan, + ]; + } else { + // Jika produk tidak terjual, berikan nilai default "-" + return [ + 'nama_produk' => $produk->nama, + 'jumlah_item_terjual' => '-', + 'berat_terjual' => '-', + 'pendapatan' => '-', + ]; + } + }); + + // 5. MENGHITUNG TOTAL REKAP HARIAN DARI DATA YANG SUDAH DIFILTER + $totalPendapatan = $produkTerjualQuery->sum('pendapatan'); + $totalItemTerjual = $produkTerjualQuery->sum('jumlah_item_terjual'); + $totalBeratTerjual = $produkTerjualQuery->sum('berat_terjual'); + + // 6. MENYUSUN STRUKTUR RESPONSE FINAL + $response = [ + 'filter' => [ + 'tanggal' => $carbonDate->isoFormat('dddd, D MMMM Y'), + 'nama_sales' => $namaSales, + 'posisi' => $posisi, + 'nama_pembeli' => $namaPembeli, + ], + 'rekap_harian' => [ + 'total_item_terjual' => $totalItemTerjual, + 'total_berat_terjual' => $totalBeratTerjual, + 'total_pendapatan' => $totalPendapatan, + ], + 'produk' => $detailItem, + ]; + + return response()->json($response); + } +} diff --git a/app/Models/ItemTransaksi.php b/app/Models/ItemTransaksi.php index eb9f4ac..994e6e2 100644 --- a/app/Models/ItemTransaksi.php +++ b/app/Models/ItemTransaksi.php @@ -13,7 +13,8 @@ class ItemTransaksi extends Model protected $fillable = [ 'id_transaksi', 'id_item', - 'harga_deal' + 'harga_deal', + 'posisi_asal' ]; protected $hidden = ['created_at', 'updated_at', 'deleted_at']; diff --git a/app/Models/Transaksi.php b/app/Models/Transaksi.php index 31af4ef..3c49d24 100644 --- a/app/Models/Transaksi.php +++ b/app/Models/Transaksi.php @@ -37,14 +37,4 @@ class Transaksi extends Model { return $this->hasMany(ItemTransaksi::class, 'id_transaksi'); } - - public function items() - { - return $this->hasMany(ItemTransaksi::class, 'id_transaksi'); - } - - public function foto () - { - return $this->hasMany(Foto::class, 'id_produk'); - } } diff --git a/database/factories/ProdukFactory.php b/database/factories/ProdukFactory.php index 59c6fde..1d6901b 100644 --- a/database/factories/ProdukFactory.php +++ b/database/factories/ProdukFactory.php @@ -17,12 +17,16 @@ class ProdukFactory extends Factory */ public function definition(): array { + $kategori = Kategori::inRandomOrder()->first(); + $harga_per_gram = $this->faker->numberBetween(80, 120) * 10000; $berat = $this->faker->randomFloat(2, 1, 10); - $kategoriList = Kategori::all()->pluck('id')->toArray(); + return [ - 'nama' => $this->faker->words(3, true), - 'id_kategori' => $this->faker->randomElement($kategoriList), + 'nama' => $kategori->nama . ' ' . $this->faker->words(mt_rand(1, 2), true), + + 'id_kategori' => $kategori->id, + 'berat' => $berat, 'kadar' => $this->faker->numberBetween(10, 24), 'harga_per_gram' => $harga_per_gram, diff --git a/database/factories/TransaksiFactory.php b/database/factories/TransaksiFactory.php index 999c8bb..fee977d 100644 --- a/database/factories/TransaksiFactory.php +++ b/database/factories/TransaksiFactory.php @@ -23,15 +23,16 @@ class TransaksiFactory extends Factory $kasir = User::inRandomOrder()->first(); $date = $this->faker->dateTimeBetween('-3 months'); + $ongkos_bikin = $this->faker->numberBetween(8, 12) * 10000; return [ 'id_kasir' => $kasir?->id, 'id_sales' => $sales?->id, - 'nama_sales' => $sales?->nama ?? $this->faker->name(), - 'nama_pembeli' => $sales?->nama ?? $this->faker->name(), + 'nama_sales' => $sales?->nama, + 'nama_pembeli' => $this->faker->name(), 'no_hp' => $this->faker->phoneNumber(), 'alamat' => $this->faker->address(), - 'ongkos_bikin' => $this->faker->randomFloat(2, 0, 1000000), - 'total_harga' => $this->faker->randomFloat(2, 100000, 5000000), + 'ongkos_bikin' => $ongkos_bikin, + 'total_harga' => $ongkos_bikin, 'created_at' => $date, 'updated_at' => $date, ]; diff --git a/database/migrations/2025_08_26_031033_create_item_transaksis_table.php b/database/migrations/2025_08_26_031033_create_item_transaksis_table.php index 25d4629..929e80b 100644 --- a/database/migrations/2025_08_26_031033_create_item_transaksis_table.php +++ b/database/migrations/2025_08_26_031033_create_item_transaksis_table.php @@ -16,6 +16,7 @@ return new class extends Migration $table->foreignId('id_transaksi')->constrained('transaksis')->onDelete('cascade'); $table->foreignId('id_item')->constrained('items'); $table->double('harga_deal'); + $table->string('posisi_asal', 100); $table->timestamps(); }); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 3f3816c..33bbc37 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -77,17 +77,24 @@ class DatabaseSeeder extends Seeder } } - Transaksi::factory(20)->create()->each(function ($transaksi) { - $jumlah_item = rand(1, 5); + Transaksi::factory(40)->create()->each(function ($transaksi) { + $jumlah_item = rand(1, 2); $items = Item::where('is_sold', false)->inRandomOrder()->limit($jumlah_item)->get(); if ($items->isEmpty()) return; + $total_harga = $transaksi->total_harga; foreach ($items as $item) { $transaksi->itemTransaksi()->create([ 'id_item' => $item->id, 'harga_deal' => $item->produk->harga_jual, + 'posisi_asal' => $item->id_nampan ? 'Nampan ' . $item->nampan->nama : 'Brankas', ]); - $item->update(['is_sold' => true]); + $item->update([ + 'id_nampan' => null, + 'is_sold' => true, + ]); + $total_harga += $item->produk->harga_jual; } + $transaksi->update(['total_harga' => $total_harga]); }); } } diff --git a/resources/js/components/DetailLaporan.vue b/resources/js/components/DetailLaporan.vue new file mode 100644 index 0000000..9be9c5c --- /dev/null +++ b/resources/js/components/DetailLaporan.vue @@ -0,0 +1,79 @@ + + + \ No newline at end of file diff --git a/resources/js/pages/Laporan.vue b/resources/js/pages/Laporan.vue index 2e94f6a..0c626b6 100644 --- a/resources/js/pages/Laporan.vue +++ b/resources/js/pages/Laporan.vue @@ -4,11 +4,14 @@

Laporan

+ +