get()` mengeksekusi query dan mengembalikan koleksi (Collection) dari objek RawMaterial. $materials = RawMaterial::orderBy('created_at', 'desc')->get(); // Mengirim data bahan baku ($materials) ke view. // `view('raw-materials.index')` merujuk ke file view Blade di `resources/views/raw-materials/index.blade.php`. // `compact('materials')` adalah cara singkat untuk mengirim variabel `$materials` ke view dengan nama yang sama. return view('raw-materials.index', compact('materials')); } /** * Metode `create()`: Menampilkan form untuk membuat bahan baku baru. * Metode ini bertanggung jawab untuk menampilkan antarmuka pengguna untuk menambahkan bahan baku baru ke sistem. * * @return \Illuminate\View\View Mengembalikan instance view Laravel yang berisi form. */ public function create() { // Mengirim pengguna ke view 'raw-materials.create', yang berisi form input untuk data bahan baku baru. // View ini biasanya ada di `resources/views/raw-materials/create.blade.php`. return view('raw-materials.create'); } /** * Metode `store()`: Menyimpan data bahan baku baru yang dikirim dari form `create()`. * Metode ini menerima data dari permintaan POST dan menyimpannya ke database setelah validasi. * * @param \Illuminate\Http\Request $request Objek Request yang berisi semua data input dari form. * @return \Illuminate\Http\RedirectResponse Mengarahkan pengguna kembali ke halaman lain dengan pesan status. */ public function store(Request $request) { // Melakukan validasi data yang diterima dari request. // Ini memastikan bahwa data yang masuk ke database sesuai dengan aturan yang ditentukan. $request->validate([ 'name' => 'required|string', // Nama bahan baku wajib diisi. 'stock' => 'required|numeric|min:0', // Stok wajib diisi, harus berupa angka, dan tidak boleh kurang dari 0. 'unit' => 'required', // Satuan (misalnya, kg, liter) wajib diisi. 'price' => 'required|numeric|min:0', // Harga wajib diisi, harus berupa angka, dan tidak boleh kurang dari 0. 'description' => 'nullable', // Deskripsi boleh kosong (tidak wajib). 'type' => 'required|in:in,adjustment' // Tipe transaksi awal harus 'in' (masuk) atau 'adjustment' (penyesuaian). ]); if (RawMaterial::where('name', $request['name'])->exists()) { return back()->with('error', 'Nama bahan baku sudah ada. Silakan gunakan nama yang berbeda.')->withInput(); } // Membuat entri bahan baku baru di tabel 'raw_materials' menggunakan data yang tervalidasi. // `RawMaterial::create()` adalah metode Eloquent yang membuat dan menyimpan record baru ke database. $material = RawMaterial::create([ 'name' => $request->name, // Mengambil nilai 'name' dari request. 'description' => $request->description, // Mengambil nilai 'description' dari request. 'stock' => $request->stock, // Mengambil nilai 'stock' dari request. 'unit' => $request->unit, // Mengambil nilai 'unit' dari request. 'price' => $request->price, // Mengambil nilai 'price' dari request. 'minimum_stock' => 10 // Menetapkan nilai default 'minimum_stock' menjadi 10. ]); // Memeriksa apakah stok awal yang dimasukkan lebih dari 0. if ($material->stock > 0) { // Jika stok lebih dari 0, buat log di tabel 'raw_material_logs'. // Ini mencatat entri awal bahan baku ke dalam sistem. RawMaterialLog::create([ 'raw_material_id' => $material->id, // ID bahan baku yang baru saja dibuat. 'user_id' => auth()->id(), // ID pengguna yang sedang login (yang melakukan operasi ini). 'type' => $request->type, // Tipe log, diambil dari input 'type' form (e.g., 'in' atau 'adjustment'). 'quantity' => $material->stock, // Kuantitas stok yang dicatat dalam log. 'price' => $material->price, // Harga per unit bahan baku saat log dibuat. 'subtotal' => $material->stock * $material->price, // Subtotal (kuantitas * harga). // Catatan log disesuaikan berdasarkan tipe: 'Stok awal bahan baku' jika 'in', 'Penyesuaian stok awal' jika 'adjustment'. 'notes' => $request->type === 'in' ? 'Stok awal bahan baku' : 'Penyesuaian stok awal' ]); } // Mengarahkan pengguna kembali ke halaman index bahan baku setelah operasi berhasil. // `with('success', ...)` menambahkan pesan flash 'success' yang bisa ditampilkan di view. return redirect()->route('raw-materials.index') ->with('success', 'Bahan baku berhasil ditambahkan.'); } /** * Metode `edit()`: Menampilkan form untuk mengedit bahan baku yang sudah ada. * Metode ini mengambil data bahan baku berdasarkan ID dan menyediakannya untuk pengeditan. * * @param \App\Models\RawMaterial $rawMaterial Ini adalah fitur Route Model Binding Laravel. Laravel secara otomatis mencari objek `RawMaterial` berdasarkan ID yang ada di URL dan menyuntikkannya ke metode ini. * @return \Illuminate\View\View Mengembalikan instance view Laravel yang berisi form pengeditan. */ public function edit(RawMaterial $rawMaterial) { // Mengirim data bahan baku ($rawMaterial) yang akan diedit ke view. // View ini biasanya ada di `resources/views/raw-materials/edit.blade.php`. return view('raw-materials.edit', compact('rawMaterial')); } /** * Metode `update()`: Memperbarui data bahan baku yang sudah ada. * Metode ini menerima data dari form pengeditan dan memperbarui record di database. * * @param \Illuminate\Http\Request $request Objek Request yang berisi data input yang diperbarui. * @param \App\Models\RawMaterial $rawMaterial Objek RawMaterial yang akan diperbarui (didapat dari Route Model Binding). * @return \Illuminate\Http\RedirectResponse Mengarahkan pengguna kembali dengan pesan status. */ public function update(Request $request, RawMaterial $rawMaterial) { // Melakukan validasi data yang diterima untuk pembaruan. // Aturannya mirip dengan `store()`, dengan tambahan `minimum_stock`. $request->validate([ 'name' => 'required', 'stock' => 'required|numeric|min:0', 'unit' => 'required', 'price' => 'required|numeric|min:0', 'minimum_stock' => 'required|numeric|min:0', 'description' => 'nullable' ]); // Memeriksa apakah ada perubahan pada nilai stok. // Jika stok yang diinputkan berbeda dengan stok yang ada di database saat ini, maka buat log perubahan. if ($request->stock != $rawMaterial->stock) { // Menghitung selisih antara stok baru dan stok lama. $difference = $request->stock - $rawMaterial->stock; // Menentukan tipe log: 'in' jika stok bertambah, 'out' jika stok berkurang. $type = $difference > 0 ? 'in' : 'out'; // Mengambil nilai absolut dari selisih untuk kuantitas log. $quantity = abs($difference); // Membuat entri log di tabel 'raw_material_logs' untuk mencatat penyesuaian stok. RawMaterialLog::create([ 'raw_material_id' => $rawMaterial->id, // ID bahan baku yang stoknya diubah. 'user_id' => auth()->id(), // ID pengguna yang melakukan perubahan. 'type' => $type, // Tipe log ('in' atau 'out'). 'quantity' => $quantity, // Jumlah kuantitas yang berubah. 'price' => $request->price, // Harga per unit saat perubahan (diambil dari input form). 'subtotal' => $quantity * $request->price, // Subtotal perubahan. 'notes' => 'Penyesuaian stok melalui edit bahan baku' // Catatan default untuk log ini. ]); } // Memperbarui data bahan baku di database dengan semua data dari request. // `update($request->all())` akan memperbarui semua kolom yang ada di `$request->all()` yang sesuai dengan fillable di model. $rawMaterial->update($request->all()); // Mengarahkan kembali ke halaman index bahan baku dengan pesan sukses. return redirect()->route('raw-materials.index') ->with('success', 'Data bahan baku berhasil diperbarui.'); } /** * Metode `destroy()`: Menghapus bahan baku dari database. * Metode ini digunakan untuk menghapus record bahan baku secara permanen. * * @param \App\Models\RawMaterial $rawMaterial Objek RawMaterial yang akan dihapus. * @return \Illuminate\Http\RedirectResponse Mengarahkan pengguna kembali dengan pesan status. */ public function destroy(RawMaterial $rawMaterial) { // Menghapus data bahan baku dari database. $rawMaterial->delete(); // Mengarahkan kembali ke halaman index bahan baku dengan pesan sukses. return redirect()->route('raw-materials.index') ->with('success', 'Bahan baku sudah di hapus.'); } /** * Metode `adjustStock()`: Menyesuaikan stok bahan baku (menambah atau mengurangi). * Metode ini dirancang untuk penyesuaian stok manual (misalnya, koreksi inventaris), bukan untuk pembelian. * * @param \Illuminate\Http\Request $request Objek Request yang berisi data penyesuaian stok. * @param \App\Models\RawMaterial $rawMaterial Objek RawMaterial yang stoknya akan disesuaikan. * @return \Illuminate\Http\RedirectResponse Mengarahkan pengguna kembali dengan pesan status. */ public function adjustStock(Request $request, RawMaterial $rawMaterial) { // Melakukan validasi input untuk penyesuaian stok. $validated = $request->validate([ 'type' => 'required|in:in,out', // Tipe penyesuaian: 'in' (masuk) atau 'out' (keluar). 'quantity' => 'required|numeric|min:1', // Kuantitas penyesuaian, harus angka positif. 'notes' => 'nullable', // Catatan penyesuaian (opsional). ]); // Menggunakan transaksi database (`DB::transaction`). // Ini penting untuk menjaga integritas data: jika ada langkah yang gagal, semua perubahan dalam blok ini akan dibatalkan. DB::transaction(function () use ($rawMaterial, $validated) { // Menghitung subtotal berdasarkan harga bahan baku saat ini dan kuantitas yang disesuaikan. $subtotal = $rawMaterial->price * $validated['quantity']; // Logika penyesuaian stok: if ($validated['type'] === 'in') { // Jika tipe adalah 'in' (masuk), tambahkan kuantitas ke stok bahan baku. $rawMaterial->increment('stock', $validated['quantity']); } else { // Jika tipe adalah 'out' (keluar), kurangi kuantitas dari stok. // Penting: Lakukan pemeriksaan stok untuk mencegah stok negatif. if ($rawMaterial->stock < $validated['quantity']) { // Jika stok tidak mencukupi, lemparkan Exception (ini akan memicu rollback transaksi). throw new \Exception('Insufficient stock.'); } // Kurangi stok bahan baku. $rawMaterial->decrement('stock', $validated['quantity']); } // Mencatat log penyesuaian stok di tabel 'raw_material_logs'. RawMaterialLog::create([ 'raw_material_id' => $rawMaterial->id, // ID bahan baku yang disesuaikan. 'user_id' => auth()->id(), // ID pengguna yang melakukan penyesuaian. 'type' => $validated['type'], // Tipe penyesuaian ('in' atau 'out'). 'quantity' => $validated['quantity'], // Kuantitas yang disesuaikan. 'price' => $rawMaterial->price, // Harga bahan baku saat itu (dari database). 'subtotal' => $subtotal, // Subtotal penyesuaian. 'notes' => $validated['notes'] // Catatan dari pengguna. ]); }); // Mengarahkan kembali ke halaman index bahan baku dengan pesan sukses. return redirect()->route('raw-materials.index') ->with('success', 'Stock adjusted successfully.'); } /** * Metode `report()`: Menampilkan laporan log bahan baku (mutasi stok). * Metode ini memungkinkan pengguna melihat riwayat perubahan stok dengan filter tertentu. * * @param \Illuminate\Http\Request $request Objek Request yang berisi filter (tanggal, ID bahan baku). * @return \Illuminate\View\View Mengembalikan view laporan dengan data log. */ public function report(Request $request) { // Membangun query dasar untuk mengambil log bahan baku. // `with(['rawMaterial', 'user.admin', 'user.karyawan'])` memuat relasi terkait (bahan baku, pengguna, admin/karyawan yang terkait dengan pengguna) // untuk menghindari N+1 query problem dan menampilkan detail yang relevan di laporan. // `orderBy('created_at', 'desc')` mengurutkan log berdasarkan waktu pembuatan terbaru. $query = RawMaterialLog::with(['rawMaterial', 'user.admin', 'user.karyawan']) ->orderBy('created_at', 'desc'); // Menerapkan filter berdasarkan tanggal mulai jika `start_date` ada di request. if ($request->filled('start_date')) { $query->whereDate('created_at', '>=', $request->start_date); } // Menerapkan filter berdasarkan tanggal selesai jika `end_date` ada di request. if ($request->filled('end_date')) { $query->whereDate('created_at', '<=', $request->end_date); } // Menerapkan filter berdasarkan ID bahan baku jika `material_id` ada di request. if ($request->filled('material_id')) { $query->where('raw_material_id', $request->material_id); } // Mengeksekusi query dan mendapatkan hasil log. $logs = $query->get(); // Filter agar hanya log yang bahan bakunya masih ada yang ditampilkan $logs = $logs->filter(function($log) { return $log->rawMaterial !== null; }); // Mengambil semua bahan baku untuk digunakan dalam dropdown filter di tampilan laporan. $materials = RawMaterial::orderBy('name')->get(); // Mengirim data log dan daftar bahan baku ke view 'raw-materials.report'. return view('raw-materials.report', compact('logs', 'materials')); } /** * Metode `lowStock()`: Menampilkan daftar bahan baku dengan stok rendah. * Metode ini berguna untuk mengidentifikasi bahan baku yang perlu segera di-restock. * * @return \Illuminate\View\View Mengembalikan view yang menampilkan bahan baku stok rendah. */ public function lowStock() { // minimum stok jika jumlah di field stok lebih kesil atau sama dengan jumlah di field minimum_stock $materials = RawMaterial::whereRaw('stock <= minimum_stock') ->orderBy('name') ->get(); // Mengirim data bahan baku stok rendah ke view 'raw-materials.low-stock'. return view('raw-materials.low-stock', compact('materials')); } /** * Metode `purchase()`: Menampilkan form untuk mencatat pembelian bahan baku. * Metode ini menyediakan antarmuka untuk mencatat pembelian bahan baku baru dari supplier. * * @return \Illuminate\View\View Mengembalikan view form pembelian. */ public function purchase() { // Mengambil semua bahan baku untuk dropdown pilihan di form pembelian. $materials = RawMaterial::orderBy('name')->get(); // Mengambil semua supplier (pemasok) untuk dropdown pilihan di form pembelian. $suppliers = Supplier::orderBy('name')->get(); // Mengirim data bahan baku dan supplier ke view 'raw-materials.purchase'. return view('raw-materials.purchase', compact('materials', 'suppliers')); } /** * Metode `storePurchase()`: Menyimpan data pembelian bahan baku. * Metode ini memproses data dari form pembelian, memperbarui stok, dan mencatat log pembelian. * * @param \Illuminate\Http\Request $request Objek Request yang berisi data pembelian. * @return \Illuminate\Http\RedirectResponse Mengarahkan pengguna kembali dengan pesan sukses. */ public function storePurchase(Request $request) { // Melakukan validasi input untuk data pembelian. // 'items' diharapkan berupa array, dan setiap item dalam array tersebut harus memiliki 'material_id', 'quantity', dan 'subtotal' yang valid. $validated = $request->validate([ 'items' => 'required|array', // 'items' wajib ada dan harus berupa array. 'items.*.material_id' => 'required|exists:raw_materials,id', // Setiap item harus memiliki 'material_id' yang valid (ada di tabel 'raw_materials'). 'items.*.quantity' => 'required|numeric|min:0.01', // Kuantitas wajib, harus angka, minimal 0.01. 'items.*.subtotal' => 'required|numeric|min:1', // Subtotal wajib, harus angka, minimal 1. 'notes' => 'nullable|string' // Catatan pembelian (opsional). ]); // Menggunakan transaksi database (`DB::transaction`). // Ini memastikan bahwa semua pembaruan stok dan log pembelian untuk semua item berjalan secara atomik. // Jika ada masalah saat memproses satu item, semua perubahan akan dibatalkan. DB::transaction(function () use ($validated) { // Melakukan iterasi (loop) untuk setiap item bahan baku yang dibeli. foreach ($validated['items'] as $item) { // Mencari objek RawMaterial berdasarkan 'material_id' dari item yang dibeli. // `findOrFail()` akan melempar error 404 jika bahan baku tidak ditemukan. $material = RawMaterial::findOrFail($item['material_id']); // Menghitung harga per unit baru. Ini penting karena harga beli bisa bervariasi. // `round(..., 2)` membulatkan hasil ke dua tempat desimal. $newPrice = round($item['subtotal'] / $item['quantity'], 2); // Memperbarui stok dan harga di tabel `raw_materials`. $material->update([ 'stock' => $material->stock + $item['quantity'], // Menambahkan kuantitas yang dibeli ke stok yang ada. 'price' => $newPrice // Memperbarui harga bahan baku dengan harga rata-rata baru dari pembelian ini. ]); // Membuat entri log di tabel `raw_material_logs` untuk mencatat transaksi pembelian ini. RawMaterialLog::create([ 'raw_material_id' => $item['material_id'], // ID bahan baku yang dibeli. 'user_id' => auth()->id(), // ID pengguna yang sedang login yang melakukan pembelian. 'type' => 'in', // Tipe log adalah 'in' (masuk), menunjukkan penambahan stok. 'quantity' => $item['quantity'], // Kuantitas yang dibeli. 'price' => $newPrice, // Harga per unit yang baru dihitung. 'subtotal' => $item['subtotal'], // Subtotal total untuk item ini. // Catatan log: jika ada input 'notes', gunakan itu; jika tidak, gunakan 'Pembelian bahan baku'. 'notes' => $validated['notes'] ? $validated['notes'] : 'Pembelian bahan baku' ]); } }); // Mengarahkan kembali ke halaman index bahan baku dengan pesan sukses setelah semua item berhasil disimpan. return redirect()->route('raw-materials.index') ->with('success', 'Pembelian bahan baku berhasil disimpan.'); } /** * Menampilkan form pemakaian bahan baku */ public function usage() { $materials = \App\Models\RawMaterial::orderBy('name')->get(); return view('raw-materials.usage', compact('materials')); } /** * Proses pemakaian bahan baku (multi-item) */ public function storeUsage(\Illuminate\Http\Request $request) { $request->validate([ 'items' => 'required|array', 'items.*.material_id' => 'required|exists:raw_materials,id', 'items.*.quantity' => 'required|numeric|min:0.01', 'notes' => 'nullable|string', ]); $items = $request->items; $notes = $request->notes; DB::transaction(function () use ($items, $notes) { foreach ($items as $item) { $material = \App\Models\RawMaterial::findOrFail($item['material_id']); if ($material->stock < $item['quantity']) { throw new \Exception('Stok bahan baku ' . $material->name . ' tidak mencukupi.'); } $material->decrement('stock', $item['quantity']); \App\Models\RawMaterialLog::create([ 'raw_material_id' => $material->id, 'user_id' => auth()->id(), 'type' => 'pemakaian', 'quantity' => $item['quantity'], 'price' => 0, 'subtotal' => 0, 'notes' => $notes ?? 'Pemakaian bahan baku', ]); } }); return redirect()->route('raw-materials.usage')->with('success', 'Pemakaian bahan baku berhasil diproses.'); } }