470 lines
18 KiB
PHP
470 lines
18 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\ItemTransaksi;
|
|
use App\Models\Produk;
|
|
use App\Models\Transaksi;
|
|
use App\Models\Sales;
|
|
use App\Models\Nampan;
|
|
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
|
|
{
|
|
private const CURRENCY_SYMBOL = 'Rp ';
|
|
private const WEIGHT_UNIT = ' g';
|
|
private const DEFAULT_DISPLAY = '-';
|
|
|
|
public function ringkasan(Request $request)
|
|
{
|
|
$filter = $request->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;
|
|
}
|
|
}
|