groupBy('product_id') ->orderByDesc('total') ->limit(3) ->get(); $productIds = $top->pluck('product_id')->toArray(); $products = Product::whereIn('id', $productIds)->get()->keyBy('id'); $topProducts = collect(); foreach ($productIds as $id) { $product = $products->get($id); if ($product) { $product->total_sold = $top->firstWhere('product_id', $id)->total ?? 0; $topProducts->push($product); } } if ($topProducts->count() < 3) { $needed = 3 - $topProducts->count(); $excluded = $topProducts->pluck('id')->toArray(); $extras = Product::whereNotIn('id', $excluded)->limit($needed)->get(); foreach ($extras as $e) { $e->total_sold = 0; $topProducts->push($e); } } return view('landing', ['topProducts' => $topProducts]); }); // TEST PENGIRIMAN EMAIL ROUTE Route::get('/test-email', [\App\Http\Controllers\SendEmailController::class, 'showForm']); Route::post('/test-email', [\App\Http\Controllers\SendEmailController::class, 'sendEmail']); // AUTH ROUTES Route::get('/login', [AuthController::class, 'showLoginForm'])->name('login'); Route::post('/authenticate', [AuthController::class, 'authenticate']); Route::post('/logout', [AuthController::class, 'logout'])->name('logout'); // PASSWORD RESET ROUTES (Khusus Admin) Route::get('/forgot-password', [App\Http\Controllers\PasswordResetController::class, 'showLinkRequestForm'])->name('password.request'); Route::post('/forgot-password', [App\Http\Controllers\PasswordResetController::class, 'sendResetLinkEmail'])->name('password.email'); Route::get('/reset-password/{token}', [App\Http\Controllers\PasswordResetController::class, 'showResetForm'])->name('password.reset'); Route::post('/reset-password', [App\Http\Controllers\PasswordResetController::class, 'reset'])->name('password.update'); // PROTECTED ROUTES Route::middleware(['auth'])->group(function () { // DASHBOARD REDIRECTION Route::get('/dashboard', [App\Http\Controllers\DashboardController::class, 'index']); Route::get('/admin/dashboard', [App\Http\Controllers\DashboardController::class, 'adminIndex']); Route::get('/landing', function () { $top = Sale::select('product_id', DB::raw('SUM(quantity) as total')) ->groupBy('product_id') ->orderByDesc('total') ->limit(3) ->get(); $productIds = $top->pluck('product_id')->toArray(); $products = Product::whereIn('id', $productIds)->get()->keyBy('id'); $topProducts = collect(); foreach ($productIds as $id) { $product = $products->get($id); if ($product) { $product->total_sold = $top->firstWhere('product_id', $id)->total ?? 0; $topProducts->push($product); } } if ($topProducts->count() < 3) { $needed = 3 - $topProducts->count(); $excluded = $topProducts->pluck('id')->toArray(); $extras = Product::whereNotIn('id', $excluded)->limit($needed)->get(); foreach ($extras as $e) { $e->total_sold = 0; $topProducts->push($e); } } return view('landing', ['topProducts' => $topProducts]); }); Route::get('/transaksi', function () { return view('transaksi'); }); Route::get('/laporan', function () { return view('laporan'); }); // PRODUCT - CRUD for karyawan (restricted to their branch) Route::get('/produk', [ProductController::class, 'index']); Route::post('/produk', [ProductController::class, 'store']); Route::put('/produk/{id}', [ProductController::class, 'update']); Route::delete('/produk/{id}', [ProductController::class, 'destroy']); // BAHAN BAKU (RAW MATERIALS) - View only, all management via purchase Route::get('/bahan-baku', [App\Http\Controllers\RawMaterialController::class, 'index']); // ADMIN ONLY ROUTES Route::middleware(['can:admin-access'])->group(function () { // Manajemen akun Route::get('/manajemen-akun', [App\Http\Controllers\AccountController::class, 'index']); Route::post('/manajemen-akun', [App\Http\Controllers\AccountController::class, 'store']); Route::put('/manajemen-akun/{id}', [App\Http\Controllers\AccountController::class, 'update']); Route::delete('/manajemen-akun/{id}', [App\Http\Controllers\AccountController::class, 'destroy']); // Manajemen cabang Route::get('/branches', [App\Http\Controllers\BranchController::class, 'index']); Route::post('/branches', [App\Http\Controllers\BranchController::class, 'store']); Route::delete('/branches/{id}', [App\Http\Controllers\BranchController::class, 'destroy']); // OPERATIONAL COSTS Route::get('/biaya-operasional', [App\Http\Controllers\OperationalCostController::class, 'index']); Route::post('/biaya-operasional', [App\Http\Controllers\OperationalCostController::class, 'store']); Route::put('/biaya-operasional/{id}', [App\Http\Controllers\OperationalCostController::class, 'update']); Route::delete('/biaya-operasional/{id}', [App\Http\Controllers\OperationalCostController::class, 'destroy']); // BAHAN BAKU (RAW MATERIALS) - Admin view only Route::get('/bahan-baku', [App\Http\Controllers\RawMaterialController::class, 'index']); Route::get('/laporan/neraca-laba-rugi', [App\Http\Controllers\FinancialReportController::class, 'profitAndLoss']); Route::get('/laporan/neraca-laba-rugi/export-pdf', [App\Http\Controllers\FinancialReportController::class, 'exportProfitAndLossPdf']); Route::get('/laporan/buku-besar', [App\Http\Controllers\FinancialReportController::class, 'generalLedger']); Route::get('/laporan/jurnal-umum', [App\Http\Controllers\FinancialReportController::class, 'generalJournal']); Route::get('/laporan/perubahan-modal', [App\Http\Controllers\FinancialReportController::class, 'equityStatement']); Route::get('/laporan/neraca-keuangan', [App\Http\Controllers\FinancialReportController::class, 'balanceSheet']); Route::get('/laporan/arus-kas', [App\Http\Controllers\FinancialReportController::class, 'cashFlow']); }); // SHARED / GENERAL REPORTS Route::get('/laporan/pembelian', [App\Http\Controllers\PurchaseController::class, 'index']); Route::get('/transaksi/pembelian/create', [App\Http\Controllers\PurchaseController::class, 'create']); Route::get('/laporan/pembelian/export-pdf', [App\Http\Controllers\PurchaseController::class, 'exportPdf'])->middleware('can:admin-access'); Route::post('/laporan/pembelian', [App\Http\Controllers\PurchaseController::class, 'store']); Route::put('/laporan/pembelian/{id}', [App\Http\Controllers\PurchaseController::class, 'update']); Route::delete('/laporan/pembelian/{id}', [App\Http\Controllers\PurchaseController::class, 'destroy']); Route::get('/laporan/penjualan', [App\Http\Controllers\SaleController::class, 'index']); Route::get('/transaksi/penjualan/create', [App\Http\Controllers\SaleController::class, 'create']); Route::get('/laporan/penjualan/export-pdf', [App\Http\Controllers\SaleController::class, 'exportPdf'])->middleware('can:admin-access'); Route::post('/laporan/penjualan', [App\Http\Controllers\SaleController::class, 'store']); Route::put('/laporan/penjualan/{id}', [App\Http\Controllers\SaleController::class, 'update']); Route::delete('/laporan/penjualan/{id}', [App\Http\Controllers\SaleController::class, 'destroy']); // DEBUG ROUTE - hapus setelah selesai debug Route::get('/debug-stok', function () { $rawMaterials = \App\Models\RawMaterial::all()->map(fn($rm) => [ 'id' => $rm->id, 'name' => $rm->name, 'unit' => $rm->unit, 'stock_quantity' => $rm->stock_quantity, ]); $recipes = \App\Models\ProductRecipe::with(['product', 'rawMaterial'])->get()->map(fn($r) => [ 'product' => $r->product?->name, 'raw_material' => $r->rawMaterial?->name, 'quantity_needed' => $r->quantity_needed, ]); $purchases = \App\Models\Purchase::orderBy('id', 'desc')->limit(10)->get()->map(fn($p) => [ 'id' => $p->id, 'item' => $p->item, 'quantity' => $p->quantity, 'type' => $p->type, 'raw_material_id'=> $p->raw_material_id, 'branch' => $p->branch, 'date' => $p->date, ]); $stocks = \App\Models\Stock::with('product')->get()->map(fn($s) => [ 'product' => $s->product?->name, 'branch' => $s->branch, 'quantity' => $s->quantity, ]); return response()->json([ 'raw_materials' => $rawMaterials, 'product_recipes' => $recipes, 'recent_purchases' => $purchases, 'stocks' => $stocks, ], 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); }); });