Merge branch 'production' of https://git.abbauf.com/Magang-2025/Kasir into production

This commit is contained in:
adityaalfarison 2025-09-15 11:00:29 +07:00
commit 1834441d78
19 changed files with 105 additions and 126 deletions

View File

@ -64,12 +64,17 @@ class DetailNampanExport implements FromCollection, WithHeadings, WithTitle, Wit
public function styles(Worksheet $sheet) public function styles(Worksheet $sheet)
{ {
$styles = [ $styles = [
1 => ['font' => ['bold' => true]], // Header row 1 => ['font' => ['bold' => true]],
]; ];
// Style for recap row if exists
if (isset($this->data['rekap_harian'])) { 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], 'font' => ['bold' => true],
'fill' => [ 'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID, 'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,

View File

@ -70,12 +70,17 @@ class DetailProdukExport implements FromCollection, WithHeadings, WithTitle, Wit
public function styles(Worksheet $sheet) public function styles(Worksheet $sheet)
{ {
$styles = [ $styles = [
1 => ['font' => ['bold' => true]], // Header row 1 => ['font' => ['bold' => true]],
]; ];
// Style for recap row if exists
if (isset($this->data['rekap_harian'])) { 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], 'font' => ['bold' => true],
'fill' => [ 'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID, 'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
@ -83,7 +88,7 @@ class DetailProdukExport implements FromCollection, WithHeadings, WithTitle, Wit
], ],
]; ];
} }
return $styles; return $styles;
} }
} }

View File

@ -47,7 +47,6 @@ class RingkasanExport implements FromCollection, WithHeadings, WithStyles
'Pendapatan' => $totalPendapatan, 'Pendapatan' => $totalPendapatan,
]); ]);
// Tambahkan baris kosong biar rapi
$rows->push(['Tanggal' => '', 'Nama Sales' => '', 'Item Terjual' => '', 'Berat' => '', 'Pendapatan' => '']); $rows->push(['Tanggal' => '', 'Nama Sales' => '', 'Item Terjual' => '', 'Berat' => '', 'Pendapatan' => '']);
} }
@ -68,7 +67,7 @@ class RingkasanExport implements FromCollection, WithHeadings, WithStyles
public function styles(Worksheet $sheet) public function styles(Worksheet $sheet)
{ {
return [ return [
1 => ['font' => ['bold' => true]], // Header bold 1 => ['font' => ['bold' => true]],
]; ];
} }
} }

View File

@ -8,6 +8,7 @@ use App\Models\Produk;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
class LaporanHelper class LaporanHelper
{ {
@ -30,16 +31,19 @@ class LaporanHelper
public function getAllNampanWithPagination(int $page, int $perPage): LengthAwarePaginator public function getAllNampanWithPagination(int $page, int $perPage): LengthAwarePaginator
{ {
$semuaNampan = Nampan::select('id', 'nama')->orderBy('nama')->get(); $semuaPosisi = DB::table('item_transaksis')
$brankasEntry = (object) ['id' => 0, 'nama' => 'Brankas']; ->select('posisi_asal')
$semuaNampanCollection = $semuaNampan->prepend($brankasEntry); ->distinct()
->pluck('posisi_asal')
->sort()
->values();
$offset = ($page - 1) * $perPage; $offset = ($page - 1) * $perPage;
$itemsForCurrentPage = $semuaNampanCollection->slice($offset, $perPage); $itemsForCurrentPage = $semuaPosisi->slice($offset, $perPage);
return new LengthAwarePaginator( return new LengthAwarePaginator(
$itemsForCurrentPage, $itemsForCurrentPage,
$semuaNampanCollection->count(), $semuaPosisi->count(),
$perPage, $perPage,
$page, $page,
['path' => request()->url(), 'query' => request()->query()] ['path' => request()->url(), 'query' => request()->query()]
@ -71,10 +75,10 @@ class LaporanHelper
public function mapNampanWithSalesData($paginatedData, Collection $salesData): Collection public function mapNampanWithSalesData($paginatedData, Collection $salesData): Collection
{ {
return $paginatedData->getCollection()->map(function ($item) use ($salesData) { return $paginatedData->getCollection()->map(function ($item) use ($salesData) {
if ($salesData->has($item->id)) { if ($salesData->has($item)) {
$dataTerjual = $salesData->get($item->id); $dataTerjual = $salesData->get($item);
return [ return [
'nama_nampan' => $item->nama, 'nama_nampan' => $item, // sekarang langsung string posisi
'jumlah_item_terjual' => (int) $dataTerjual->jumlah_item_terjual, 'jumlah_item_terjual' => (int) $dataTerjual->jumlah_item_terjual,
'berat_terjual' => $this->formatWeight($dataTerjual->berat_terjual), 'berat_terjual' => $this->formatWeight($dataTerjual->berat_terjual),
'pendapatan' => $this->formatCurrency($dataTerjual->pendapatan), 'pendapatan' => $this->formatCurrency($dataTerjual->pendapatan),
@ -82,7 +86,7 @@ class LaporanHelper
} }
return [ return [
'nama_nampan' => $item->nama, 'nama_nampan' => $item,
'jumlah_item_terjual' => self::DEFAULT_DISPLAY, 'jumlah_item_terjual' => self::DEFAULT_DISPLAY,
'berat_terjual' => self::DEFAULT_DISPLAY, 'berat_terjual' => self::DEFAULT_DISPLAY,
'pendapatan' => self::DEFAULT_DISPLAY, 'pendapatan' => self::DEFAULT_DISPLAY,
@ -153,11 +157,12 @@ class LaporanHelper
public function hitungDataSales(Collection $transaksisPerSales): array public function hitungDataSales(Collection $transaksisPerSales): array
{ {
$itemTerjual = $transaksisPerSales->sum(fn($t) => $t->itemTransaksi->count()); $itemTerjual = $transaksisPerSales->sum(fn($t) => $t->itemTransaksi->count());
// UBAH BAGIAN INI: Hapus ->item dari path relasi
$beratTerjual = $transaksisPerSales->sum( $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'); $pendapatan = $transaksisPerSales->sum('total_harga');
return [ return [
@ -182,9 +187,9 @@ class LaporanHelper
{ {
return $salesData->map(function ($sale) { return $salesData->map(function ($sale) {
$sale['item_terjual'] = $sale['item_terjual'] > 0 ? $sale['item_terjual'] : self::DEFAULT_DISPLAY; $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; $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; $this->formatCurrency($sale['pendapatan_raw']) : self::DEFAULT_DISPLAY;
unset($sale['berat_terjual_raw'], $sale['pendapatan_raw']); unset($sale['berat_terjual_raw'], $sale['pendapatan_raw']);
@ -201,4 +206,4 @@ class LaporanHelper
{ {
return number_format($weight, 2, ',', '.') . self::WEIGHT_UNIT; return number_format($weight, 2, ',', '.') . self::WEIGHT_UNIT;
} }
} }

View File

@ -44,7 +44,7 @@ class ItemController extends Controller
*/ */
public function show(int $id) public function show(int $id)
{ {
$item = Item::with('produk.foto','nampan','itemTransaksi.transaksi')->findOrFail($id); $item = Item::with('produk.foto','nampan')->findOrFail($id);
return response()->json($item); return response()->json($item);
} }
@ -83,7 +83,7 @@ class ItemController extends Controller
// custom methods // custom methods
public function brankasItem(){ public function brankasItem(){
$items = Item::with('produk.foto','nampan')->whereNull('id_nampan')->belumTerjual()->get(); $items = Item::with('produk.foto','nampan')->whereNull('id_nampan')->get();
return response()->json($items); return response()->json($items);
} }

View File

@ -67,7 +67,7 @@ class LaporanController extends Controller
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Error in detailPerNampan method: ' . $e->getMessage()); 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);
} }
} }

View File

@ -15,7 +15,7 @@ class TransaksiController extends Controller
public function index() public function index()
{ {
$limit = request()->query('limit', null); $limit = request()->query('limit', null);
$query = Transaksi::with(['kasir', 'sales', 'itemTransaksi.item.produk'])->latest(); $query = Transaksi::with(['kasir', 'sales', 'itemTransaksi.produk'])->latest();
if ($limit) { if ($limit) {
$query->limit((int)$limit); $query->limit((int)$limit);
} }
@ -33,7 +33,7 @@ class TransaksiController extends Controller
// Detail transaksi by ID // Detail transaksi by ID
public function show($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); return response()->json($transaksi);
} }
@ -53,7 +53,7 @@ class TransaksiController extends Controller
'ongkos_bikin' => 'nullable|numeric|min:0', 'ongkos_bikin' => 'nullable|numeric|min:0',
'total_harga' => 'required|numeric', 'total_harga' => 'required|numeric',
'items' => 'required|array', 'items' => 'required|array',
'items.*.kode_item' => 'required|exists:items,id', 'items.*.kode_item' => 'required|exists:items,id|numeric',
'items.*.harga_deal' => 'required|numeric', 'items.*.harga_deal' => 'required|numeric',
]); ]);
@ -80,24 +80,21 @@ class TransaksiController extends Controller
// if (!$item) { // if (!$item) {
// throw new \Exception("Item dengan kode_item {$it['kode_item']} tidak ditemukan."); // throw new \Exception("Item dengan kode_item {$it['kode_item']} tidak ditemukan.");
// } // }
$item = Item::find($it['kode_item']); $item = Item::where('id',$it['kode_item'])->with('produk')->first();
ItemTransaksi::create([ ItemTransaksi::create([
'id_transaksi' => $transaksi->id, 'id_transaksi' => $transaksi->id,
'id_item' => $item->id, 'id_produk' => $item->produk->id,
'harga_deal' => $it['harga_deal'], 'harga_deal' => $it['harga_deal'],
'posisi_asal' => $item->nampan ? 'Nampan ' . $item->nampan->nama : 'Brankas', 'posisi_asal' => $it['posisi'],
]); ]);
$item->update([ $item->forceDelete();
'is_sold' => true,
'id_nampan' => null,
]);
} }
DB::commit(); DB::commit();
return response()->json( return response()->json(
$transaksi->load(['itemTransaksi.item.produk.foto', 'kasir', 'sales']), $transaksi->load(['itemTransaksi.produk.foto', 'kasir', 'sales']),
201 201
); );
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@ -13,8 +13,7 @@ class Item extends Model
protected $fillable = [ protected $fillable = [
'id_produk', 'id_produk',
'id_nampan', 'id_nampan',
'is_sold', 'kode_item',
'kode_item', // ✅ ditambahkan agar bisa diisi otomatis
]; ];
protected $hidden = ['created_at', 'updated_at', 'deleted_at']; protected $hidden = ['created_at', 'updated_at', 'deleted_at'];
@ -50,18 +49,8 @@ class Item extends Model
return $this->belongsTo(Produk::class, 'id_produk'); return $this->belongsTo(Produk::class, 'id_produk');
} }
public function scopeBelumTerjual($query)
{
return $query->where('is_sold', false);
}
public function nampan() public function nampan()
{ {
return $this->belongsTo(Nampan::class, 'id_nampan'); return $this->belongsTo(Nampan::class, 'id_nampan');
} }
public function itemTransaksi()
{
return $this->hasOne(ItemTransaksi::class, 'id_item');
}
} }

View File

@ -12,7 +12,7 @@ class ItemTransaksi extends Model
protected $fillable = [ protected $fillable = [
'id_transaksi', 'id_transaksi',
'id_item', 'id_produk',
'harga_deal', 'harga_deal',
'posisi_asal' 'posisi_asal'
]; ];
@ -24,8 +24,8 @@ class ItemTransaksi extends Model
return $this->belongsTo(Transaksi::class, 'id_transaksi'); return $this->belongsTo(Transaksi::class, 'id_transaksi');
} }
public function item() public function produk()
{ {
return $this->belongsTo(Item::class, 'id_item'); return $this->belongsTo(Produk::class, 'id_produk');
} }
} }

View File

@ -36,7 +36,7 @@ class TransaksiRepository
$totalHariUntukPaginasi = $endDate->diffInDays($startDate) + 1; $totalHariUntukPaginasi = $endDate->diffInDays($startDate) + 1;
} }
$transaksis = Transaksi::with(['itemTransaksi.item.produk']) $transaksis = Transaksi::with(['itemTransaksi.produk'])
->whereBetween('created_at', [$startDate->startOfDay(), $endDate->endOfDay()]) ->whereBetween('created_at', [$startDate->startOfDay(), $endDate->endOfDay()])
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')
->get(); ->get();
@ -100,7 +100,7 @@ class TransaksiRepository
{ {
$perPage = self::MONTHLY_PER_PAGE; $perPage = self::MONTHLY_PER_PAGE;
$transaksis = Transaksi::with(['itemTransaksi.item.produk']) $transaksis = Transaksi::with(['itemTransaksi.produk'])
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')
->get(); ->get();

View File

@ -67,7 +67,6 @@ class LaporanService
$perPage = $params['per_page'] ?? self::DEFAULT_PER_PAGE; $perPage = $params['per_page'] ?? self::DEFAULT_PER_PAGE;
// --- Step 1: Calculate overall totals for all filtered items --- // --- 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); $totalsQuery = $this->buildBaseItemQuery($tanggal);
$this->applyFilters($totalsQuery, $params); $this->applyFilters($totalsQuery, $params);
@ -79,8 +78,8 @@ class LaporanService
$rekapHarian = [ $rekapHarian = [
'total_item_terjual' => (int) $totalsResult->total_item_terjual, 'total_item_terjual' => (int) $totalsResult->total_item_terjual,
'total_berat_terjual' => $this->helper->formatWeight($totalsResult->total_berat_terjual), // Assuming formatting helper 'total_berat_terjual' => $this->helper->formatWeight($totalsResult->total_berat_terjual),
'total_pendapatan' => $this->helper->formatCurrency($totalsResult->total_pendapatan), // Assuming formatting helper 'total_pendapatan' => $this->helper->formatCurrency($totalsResult->total_pendapatan),
]; ];
// --- Step 2: Build the filtered sales data subquery --- // --- 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') DB::raw('COALESCE(SUM(item_transaksis.harga_deal), 0) as pendapatan')
) )
->groupBy('produks.id'); ->groupBy('produks.id');
// Apply filters to the subquery
$this->applyFilters($salesSubQuery, $params); $this->applyFilters($salesSubQuery, $params);
// --- Step 3: Fetch paginated products and LEFT JOIN the sales data subquery --- // --- Step 3: Fetch paginated products and LEFT JOIN the sales data subquery ---
@ -113,7 +112,7 @@ class LaporanService
$detailItem = $semuaProdukPaginated->map(function ($item) { $detailItem = $semuaProdukPaginated->map(function ($item) {
return [ return [
'nama_produk' => $item->nama_produk, '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) : '-', 'berat_terjual' => $item->berat_terjual ? $this->helper->formatWeight($item->berat_terjual) : '-',
'pendapatan' => $item->pendapatan ? $this->helper->formatCurrency($item->pendapatan) : '-', 'pendapatan' => $item->pendapatan ? $this->helper->formatCurrency($item->pendapatan) : '-',
]; ];
@ -140,17 +139,15 @@ class LaporanService
$this->applyNampanFilters($nampanTerjualQuery, $params); $this->applyNampanFilters($nampanTerjualQuery, $params);
$nampanTerjual = $nampanTerjualQuery $nampanTerjual = $nampanTerjualQuery
->leftJoin('nampans', 'items.id_nampan', '=', 'nampans.id')
->select( ->select(
DB::raw('COALESCE(items.id_nampan, 0) as id_nampan'), DB::raw('COALESCE(item_transaksis.posisi_asal, "Brankas") as nama_nampan'),
DB::raw('COALESCE(nampans.nama, "Brankas") as nama_nampan'),
DB::raw('COUNT(item_transaksis.id) as jumlah_item_terjual'), DB::raw('COUNT(item_transaksis.id) as jumlah_item_terjual'),
DB::raw('COALESCE(SUM(produks.berat), 0) as berat_terjual'), DB::raw('COALESCE(SUM(produks.berat), 0) as berat_terjual'),
DB::raw('COALESCE(SUM(item_transaksis.harga_deal), 0) as pendapatan') DB::raw('COALESCE(SUM(item_transaksis.harga_deal), 0) as pendapatan')
) )
->groupBy('id_nampan', 'nama_nampan') ->groupBy('nama_nampan')
->get() ->get()
->keyBy('id_nampan'); ->keyBy('nama_nampan');
$totals = $this->helper->calculateTotals($nampanTerjual); $totals = $this->helper->calculateTotals($nampanTerjual);
$semuaNampanPaginated = $this->helper->getAllNampanWithPagination($page, $perPage); $semuaNampanPaginated = $this->helper->getAllNampanWithPagination($page, $perPage);
@ -174,7 +171,6 @@ class LaporanService
$allSalesNames = $this->getAllSalesNames(); $allSalesNames = $this->getAllSalesNames();
if ($filter === 'hari') { if ($filter === 'hari') {
// Tar kalau mau ubah eksport laporan setiap hari, param ke-3 jadiin false #Bagas
$data = $this->processLaporanHarian($allSalesNames, $page, true); $data = $this->processLaporanHarian($allSalesNames, $page, true);
} else { } else {
$data = $this->processLaporanBulanan($allSalesNames, $page, true); $data = $this->processLaporanBulanan($allSalesNames, $page, true);
@ -196,17 +192,14 @@ class LaporanService
return Excel::download(new RingkasanExport($data, $page), $fileName); return Excel::download(new RingkasanExport($data, $page), $fileName);
} }
// Method baru untuk export per produk
public function exportPerProduk(array $params) public function exportPerProduk(array $params)
{ {
$tanggal = $params['tanggal']; $tanggal = $params['tanggal'];
$format = $params['format']; $format = $params['format'];
// Get all data tanpa pagination karena untuk export
$allParams = $params; $allParams = $params;
unset($allParams['page'], $allParams['per_page']); unset($allParams['page'], $allParams['per_page']);
// Get data dengan semua produk (tanpa pagination)
$data = $this->getDetailPerProdukForExport($allParams); $data = $this->getDetailPerProdukForExport($allParams);
$fileName = "laporan_per_produk_{$tanggal}_" . Carbon::now()->format('Ymd') . ".{$format}"; $fileName = "laporan_per_produk_{$tanggal}_" . Carbon::now()->format('Ymd') . ".{$format}";
@ -227,12 +220,10 @@ class LaporanService
{ {
$tanggal = $params['tanggal']; $tanggal = $params['tanggal'];
$format = $params['format']; $format = $params['format'];
// Get all data tanpa pagination karena untuk export
$allParams = $params; $allParams = $params;
unset($allParams['page'], $allParams['per_page']); unset($allParams['page'], $allParams['per_page']);
// Get data dengan semua nampan (tanpa pagination)
$data = $this->getDetailPerNampanForExport($allParams); $data = $this->getDetailPerNampanForExport($allParams);
$fileName = "laporan_per_nampan_{$tanggal}_" . Carbon::now()->format('Ymd') . ".{$format}"; $fileName = "laporan_per_nampan_{$tanggal}_" . Carbon::now()->format('Ymd') . ".{$format}";
@ -249,7 +240,6 @@ class LaporanService
return Excel::download(new DetailNampanExport($data), $fileName); return Excel::download(new DetailNampanExport($data), $fileName);
} }
// Helper method untuk get data produk tanpa pagination (untuk export)
private function getDetailPerProdukForExport(array $params) private function getDetailPerProdukForExport(array $params)
{ {
$tanggal = Carbon::parse($params['tanggal']); $tanggal = Carbon::parse($params['tanggal']);
@ -270,8 +260,7 @@ class LaporanService
->keyBy('id_produk'); ->keyBy('id_produk');
$totals = $this->helper->calculateTotals($produkTerjual); $totals = $this->helper->calculateTotals($produkTerjual);
// Get all products without pagination
$semuaProduk = Produk::select('id', 'nama')->orderBy('nama')->get(); $semuaProduk = Produk::select('id', 'nama')->orderBy('nama')->get();
$detailItem = collect($semuaProduk)->map(function ($item) use ($produkTerjual) { $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) private function getDetailPerNampanForExport(array $params)
{ {
$tanggal = Carbon::parse($params['tanggal']); $tanggal = Carbon::parse($params['tanggal']);
@ -311,30 +299,30 @@ class LaporanService
$this->applyNampanFilters($nampanTerjualQuery, $params); $this->applyNampanFilters($nampanTerjualQuery, $params);
$nampanTerjual = $nampanTerjualQuery $nampanTerjual = $nampanTerjualQuery
->leftJoin('nampans', 'items.id_nampan', '=', 'nampans.id')
->select( ->select(
DB::raw('COALESCE(items.id_nampan, 0) as id_nampan'), 'item_transaksis.posisi_asal as posisi_asal',
DB::raw('COALESCE(nampans.nama, "Brankas") as nama_nampan'),
DB::raw('COUNT(item_transaksis.id) as jumlah_item_terjual'), DB::raw('COUNT(item_transaksis.id) as jumlah_item_terjual'),
DB::raw('COALESCE(SUM(produks.berat), 0) as berat_terjual'), DB::raw('COALESCE(SUM(produks.berat), 0) as berat_terjual'),
DB::raw('COALESCE(SUM(item_transaksis.harga_deal), 0) as pendapatan') DB::raw('COALESCE(SUM(item_transaksis.harga_deal), 0) as pendapatan')
) )
->groupBy('id_nampan', 'nama_nampan') ->groupBy('item_transaksis.posisi_asal')
->get() ->get()
->keyBy('id_nampan'); ->keyBy('posisi_asal');
$totals = $this->helper->calculateTotals($nampanTerjual); $totals = $this->helper->calculateTotals($nampanTerjual);
// Get all nampan without pagination $semuaPosisi = DB::table('item_transaksis')
$semuaNampan = Nampan::select('id', 'nama')->orderBy('nama')->get(); ->select('posisi_asal')
$brankasEntry = (object) ['id' => 0, 'nama' => 'Brankas']; ->distinct()
$semuaNampanCollection = $semuaNampan->prepend($brankasEntry); ->pluck('posisi_asal')
->sort()
->values();
$detailItem = $semuaNampanCollection->map(function ($item) use ($nampanTerjual) { $detailItem = $semuaPosisi->map(function ($posisi) use ($nampanTerjual) {
if ($nampanTerjual->has($item->id)) { if ($nampanTerjual->has($posisi)) {
$dataTerjual = $nampanTerjual->get($item->id); $dataTerjual = $nampanTerjual->get($posisi);
return [ return [
'nama_nampan' => $item->nama, 'nama_nampan' => $posisi,
'jumlah_item_terjual' => (int) $dataTerjual->jumlah_item_terjual, 'jumlah_item_terjual' => (int) $dataTerjual->jumlah_item_terjual,
'berat_terjual' => $this->helper->formatWeight($dataTerjual->berat_terjual), 'berat_terjual' => $this->helper->formatWeight($dataTerjual->berat_terjual),
'pendapatan' => $this->helper->formatCurrency($dataTerjual->pendapatan), 'pendapatan' => $this->helper->formatCurrency($dataTerjual->pendapatan),
@ -342,7 +330,7 @@ class LaporanService
} }
return [ return [
'nama_nampan' => $item->nama, 'nama_nampan' => $posisi,
'jumlah_item_terjual' => LaporanHelper::DEFAULT_DISPLAY, 'jumlah_item_terjual' => LaporanHelper::DEFAULT_DISPLAY,
'berat_terjual' => LaporanHelper::DEFAULT_DISPLAY, 'berat_terjual' => LaporanHelper::DEFAULT_DISPLAY,
'pendapatan' => LaporanHelper::DEFAULT_DISPLAY, 'pendapatan' => LaporanHelper::DEFAULT_DISPLAY,
@ -358,6 +346,7 @@ class LaporanService
]; ];
} }
private function getAllSalesNames(): Collection private function getAllSalesNames(): Collection
{ {
return Cache::remember('all_sales_names', self::CACHE_TTL, function () { return Cache::remember('all_sales_names', self::CACHE_TTL, function () {
@ -377,9 +366,9 @@ class LaporanService
private function buildBaseItemQuery(Carbon $carbonDate) private function buildBaseItemQuery(Carbon $carbonDate)
{ {
// UBAH: Menghapus join ke tabel 'items' dan join 'produks' langsung dari 'item_transaksis'
return ItemTransaksi::query() return ItemTransaksi::query()
->join('items', 'item_transaksis.id_item', '=', 'items.id') ->join('produks', 'item_transaksis.id_produk', '=', 'produks.id')
->join('produks', 'items.id_produk', '=', 'produks.id')
->join('transaksis', 'item_transaksis.id_transaksi', '=', 'transaksis.id') ->join('transaksis', 'item_transaksis.id_transaksi', '=', 'transaksis.id')
->whereDate('transaksis.created_at', $carbonDate); ->whereDate('transaksis.created_at', $carbonDate);
} }
@ -388,14 +377,15 @@ class LaporanService
{ {
if (!empty($params['sales_id'])) { if (!empty($params['sales_id'])) {
$query->join('sales', 'transaksis.id_sales', '=', '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'])) { if (isset($params['nampan_id'])) {
// UBAH: Filter berdasarkan 'item_transaksis.id_nampan'
if ($params['nampan_id'] == 0) { if ($params['nampan_id'] == 0) {
$query->whereNull('items.id_nampan'); $query->whereNull('item_transaksis.id_nampan');
} else { } 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'])) { if (!empty($params['sales_id'])) {
$query->join('sales', 'transaksis.id_sales', '=', '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'])) { if (!empty($params['produk_id'])) {
@ -419,4 +409,4 @@ class LaporanService
$query->where('transaksis.nama_pembeli', 'like', "%{$params['nama_pembeli']}%"); $query->where('transaksis.nama_pembeli', 'like', "%{$params['nama_pembeli']}%");
} }
} }
} }

View File

@ -19,7 +19,6 @@ class ItemFactory extends Factory
return [ return [
'id_produk' => \App\Models\Produk::factory(), 'id_produk' => \App\Models\Produk::factory(),
'id_nampan' => null, 'id_nampan' => null,
'is_sold' => false,
]; ];
} }
} }

View File

@ -3,6 +3,7 @@
namespace Database\Factories; namespace Database\Factories;
use App\Models\Item; use App\Models\Item;
use App\Models\Produk;
use App\Models\Transaksi; use App\Models\Transaksi;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
@ -20,7 +21,7 @@ class ItemTransaksiFactory extends Factory
{ {
return [ return [
'id_transaksi' => Transaksi::factory(), 'id_transaksi' => Transaksi::factory(),
'id_item' => Item::factory(), 'id_produk' => Produk::factory(),
'harga_deal' => $this->faker->randomFloat(2, 100000, 5000000), 'harga_deal' => $this->faker->randomFloat(2, 100000, 5000000),
'created_at' => now(), 'created_at' => now(),
]; ];

View File

@ -15,7 +15,6 @@ return new class extends Migration
$table->id(); $table->id();
$table->foreignId('id_produk')->constrained('produks')->cascadeOnDelete(); $table->foreignId('id_produk')->constrained('produks')->cascadeOnDelete();
$table->foreignId('id_nampan')->nullable()->constrained('nampans'); $table->foreignId('id_nampan')->nullable()->constrained('nampans');
$table->boolean('is_sold')->default(false);
$table->timestamps(); $table->timestamps();
}); });
} }

View File

@ -14,7 +14,7 @@ return new class extends Migration
Schema::create('item_transaksis', function (Blueprint $table) { Schema::create('item_transaksis', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignId('id_transaksi')->constrained('transaksis')->onDelete('cascade'); $table->foreignId('id_transaksi')->constrained('transaksis')->onDelete('cascade');
$table->foreignId('id_item')->constrained('items'); $table->foreignId('id_produk')->constrained('produks');
$table->double('harga_deal'); $table->double('harga_deal');
$table->string('posisi_asal', 100); $table->string('posisi_asal', 100);
$table->timestamps(); $table->timestamps();

View File

@ -64,7 +64,6 @@ class DatabaseSeeder extends Seeder
$jumlah_item = rand(1, 20); $jumlah_item = rand(1, 20);
Item::factory($jumlah_item)->create([ Item::factory($jumlah_item)->create([
'id_produk' => $produk->id, 'id_produk' => $produk->id,
'is_sold' => false,
]); ]);
}); });
@ -84,19 +83,16 @@ class DatabaseSeeder extends Seeder
Transaksi::factory(40)->create()->each(function ($transaksi) { Transaksi::factory(40)->create()->each(function ($transaksi) {
$jumlah_item = rand(1, 2); $jumlah_item = rand(1, 2);
$items = Item::where('is_sold', false)->inRandomOrder()->limit($jumlah_item)->get(); $items = Item::with('produk')->inRandomOrder()->limit($jumlah_item)->get();
if ($items->isEmpty()) return; if ($items->isEmpty()) return;
$total_harga = $transaksi->total_harga; $total_harga = $transaksi->total_harga;
foreach ($items as $item) { foreach ($items as $item) {
$transaksi->itemTransaksi()->create([ $transaksi->itemTransaksi()->create([
'id_item' => $item->id, 'id_produk' => $item->produk->id,
'harga_deal' => $item->produk->harga_jual, 'harga_deal' => $item->produk->harga_jual,
'posisi_asal' => $item->id_nampan ? 'Nampan ' . $item->nampan->nama : 'Brankas', 'posisi_asal' => $item->id_nampan ? 'Nampan ' . $item->nampan->nama : 'Brankas',
]); ]);
$item->update([ $item->delete();
'id_nampan' => null,
'is_sold' => true,
]);
$total_harga += $item->produk->harga_jual; $total_harga += $item->produk->harga_jual;
} }
$transaksi->update(['total_harga' => $total_harga]); $transaksi->update(['total_harga' => $total_harga]);

View File

@ -371,6 +371,7 @@ const fetchData = async (page = 1) => {
total: response.data.nampan ? response.data.nampan.length : 0, total: response.data.nampan ? response.data.nampan.length : 0,
}; };
} }
console.log('Data laporan nampan berhasil diambil:', data.value);
} catch (error) { } catch (error) {
console.error('Gagal mengambil data laporan nampan:', error); console.error('Gagal mengambil data laporan nampan:', error);
data.value = null; data.value = null;

View File

@ -3,7 +3,7 @@
message="Yakin ingin menghapus item ini?" @confirm="hapusPesanan" @cancel="closeDeleteModal" /> message="Yakin ingin menghapus item ini?" @confirm="hapusPesanan" @cancel="closeDeleteModal" />
<!-- ==== TAMBAHAN: Struk Overlay ==== --> <!-- ==== TAMBAHAN: Struk Overlay ==== -->
<StrukOverlay v-if="showStruk" :isOpen="showStruk" :pesanan="pesanan" :total="total" @close="closeStruk"/> <StrukOverlay v-if="showStruk" :isOpen="showStruk" :pesanan="pesanan" :total="total" @close="closeStruk" />
<!-- ==== END TAMBAHAN ==== --> <!-- ==== END TAMBAHAN ==== -->
<div class="p-2 sm:p-4"> <div class="p-2 sm:p-4">
@ -90,7 +90,7 @@
{{ item.produk.nama }} {{ item.produk.nama }}
</td> </td>
<td class="border border-B p-2 truncate max-w-[80px]"> <td class="border border-B p-2 truncate max-w-[80px]">
{{ item.posisi ? item.posisi : "Brankas" }} {{ item.nampan ? item.nampan.nama : "Brankas" }}
</td> </td>
<td class="border border-B p-2 whitespace-nowrap"> <td class="border border-B p-2 whitespace-nowrap">
Rp{{ item.harga_deal.toLocaleString() }} Rp{{ item.harga_deal.toLocaleString() }}
@ -150,14 +150,15 @@ const inputItem = async () => {
item.value = response.data; item.value = response.data;
hargaJual.value = item.value.produk.harga_jual; hargaJual.value = item.value.produk.harga_jual;
console.log(item.value);
if (item.value.is_sold) { if (item.value.is_sold) {
throw new Error("Item sudah terjual"); throw new Error("Item sudah terjual");
} }
if (pesanan.value.some((p) => p.id === item.value.id)) { if (pesanan.value.some((p) => p.id === item.value.id)) {
throw new Error("Item sedang dipesan"); throw new Error("Item sedang dipesan");
} }
info.value = `Item dipilih: ${item.value.produk.nama} dari ${item.value.posisi ? item.value.posisi : "Brankas" info.value = `Item dipilih: ${item.value.produk.nama} dari ${item.value.nampan ? 'Nampan ' + item.value.nampan.nama : "Brankas"}`;
}`;
infoTimeout = setTimeout(() => { infoTimeout = setTimeout(() => {
info.value = ""; info.value = "";
@ -194,18 +195,10 @@ const tambahItem = () => {
return; return;
} }
if (pesanan.value.length >= 2) {
error.value = "Maksimal hanya bisa memesan 2 item.";
clearTimeout(errorTimeout);
errorTimeout = setTimeout(() => {
error.value = "";
}, 3000);
return;
}
// harga deal // harga deal
item.value.kode_item = kodeItem.value; item.value.kode_item = Number(kodeItem.value);
item.value.harga_deal = Number(hargaJual.value); item.value.harga_deal = Number(hargaJual.value);
item.value.posisi = item.value.nampan ? item.value.nampan.nama : "Brankas";
pesanan.value.push(item.value); pesanan.value.push(item.value);

View File

@ -71,7 +71,7 @@
</template> </template>
{{ item.produk?.nama || '' }} {{ item.produk?.nama || '' }}
</td> </td>
<td class="border-r border-D">{{ item.posisi || '' }}</td> <td class="border-r border-D">{{ item.nampan?.nama || 'Brankas' }}</td>
<td class="border-r border-D"> <td class="border-r border-D">
<span v-if="item.produk?.berat">{{ item.produk.berat }}g</span> <span v-if="item.produk?.berat">{{ item.produk.berat }}g</span>
</td> </td>