- Refactor New Quote page to enterprise data-entry layout (2-column, dense) - Add Payment Terms dropdown (COD, NET 15, NET 30, NET 60) - Fix sidebar menu active states and route names - Fix brand filter badge visibility on brands page - Remove company_name references (use business instead) - Polish Promotions page layout - Fix double-click issue on sidebar menu collapse - Make all searches case-insensitive (like -> ilike for PostgreSQL)
167 lines
5.5 KiB
PHP
167 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Seller\Processing;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Business;
|
|
use App\Models\Processing\ProcMaterialLot;
|
|
use App\Models\Processing\ProcMaterialMovement;
|
|
use App\Models\Product;
|
|
use Illuminate\Http\Request;
|
|
|
|
class MaterialLotController extends Controller
|
|
{
|
|
public function index(Request $request, Business $business)
|
|
{
|
|
$query = ProcMaterialLot::forBusiness($business->id)
|
|
->with('product')
|
|
->orderByDesc('created_at');
|
|
|
|
if ($request->filled('status')) {
|
|
$query->where('status', $request->status);
|
|
}
|
|
|
|
if ($request->filled('material_type')) {
|
|
$query->where('material_type', $request->material_type);
|
|
}
|
|
|
|
if ($request->filled('search')) {
|
|
$query->where('lot_number', 'ilike', '%'.$request->search.'%');
|
|
}
|
|
|
|
$materialLots = $query->paginate(25);
|
|
|
|
return view('seller.processing.materials.index', compact('business', 'materialLots'));
|
|
}
|
|
|
|
public function create(Request $request, Business $business)
|
|
{
|
|
$products = Product::where('business_id', $business->id)->get();
|
|
|
|
return view('seller.processing.materials.create', compact('business', 'products'));
|
|
}
|
|
|
|
public function store(Request $request, Business $business)
|
|
{
|
|
$validated = $request->validate([
|
|
'lot_number' => 'required|string|max:100',
|
|
'product_id' => 'nullable|exists:products,id',
|
|
'material_type' => 'required|in:biomass,intermediate',
|
|
'weight_available' => 'required|numeric|min:0',
|
|
'uom' => 'nullable|string|max:10',
|
|
]);
|
|
|
|
$validated['business_id'] = $business->id;
|
|
$validated['status'] = 'available';
|
|
$validated['manufacture_source'] = 'manual_entry';
|
|
$validated['uom'] = $validated['uom'] ?? 'g';
|
|
|
|
$lot = ProcMaterialLot::create($validated);
|
|
|
|
// Record initial receive movement
|
|
ProcMaterialMovement::create([
|
|
'business_id' => $business->id,
|
|
'material_lot_id' => $lot->id,
|
|
'movement_type' => 'receive',
|
|
'quantity' => $validated['weight_available'],
|
|
'uom' => $validated['uom'],
|
|
'reason' => 'Initial receipt (manual entry)',
|
|
]);
|
|
|
|
return redirect()
|
|
->route('seller.processing.materials.index', $business)
|
|
->with('success', 'Material lot created successfully.');
|
|
}
|
|
|
|
public function show(Request $request, Business $business, ProcMaterialLot $material)
|
|
{
|
|
$this->authorizeForBusiness($material, $business);
|
|
|
|
$material->load(['product', 'movements' => fn ($q) => $q->orderByDesc('created_at')]);
|
|
|
|
return view('seller.processing.materials.show', compact('business', 'material'));
|
|
}
|
|
|
|
public function edit(Request $request, Business $business, ProcMaterialLot $material)
|
|
{
|
|
$this->authorizeForBusiness($material, $business);
|
|
|
|
$products = Product::where('business_id', $business->id)->get();
|
|
|
|
return view('seller.processing.materials.edit', compact('business', 'material', 'products'));
|
|
}
|
|
|
|
public function update(Request $request, Business $business, ProcMaterialLot $material)
|
|
{
|
|
$this->authorizeForBusiness($material, $business);
|
|
|
|
$validated = $request->validate([
|
|
'lot_number' => 'required|string|max:100',
|
|
'product_id' => 'nullable|exists:products,id',
|
|
'material_type' => 'required|in:biomass,intermediate',
|
|
'status' => 'required|in:available,allocated,on_hold,quarantined',
|
|
]);
|
|
|
|
$material->update($validated);
|
|
|
|
return redirect()
|
|
->route('seller.processing.materials.show', [$business, $material])
|
|
->with('success', 'Material lot updated successfully.');
|
|
}
|
|
|
|
public function destroy(Request $request, Business $business, ProcMaterialLot $material)
|
|
{
|
|
$this->authorizeForBusiness($material, $business);
|
|
|
|
$material->delete();
|
|
|
|
return redirect()
|
|
->route('seller.processing.materials.index', $business)
|
|
->with('success', 'Material lot deleted successfully.');
|
|
}
|
|
|
|
/**
|
|
* Adjust material lot quantity.
|
|
*/
|
|
public function adjust(Request $request, Business $business, ProcMaterialLot $material)
|
|
{
|
|
$this->authorizeForBusiness($material, $business);
|
|
|
|
$validated = $request->validate([
|
|
'adjustment_type' => 'required|in:add,subtract',
|
|
'quantity' => 'required|numeric|min:0',
|
|
'reason' => 'required|string|max:500',
|
|
]);
|
|
|
|
$adjustQty = $validated['adjustment_type'] === 'add'
|
|
? $validated['quantity']
|
|
: -$validated['quantity'];
|
|
|
|
$newWeight = $material->weight_available + $adjustQty;
|
|
|
|
if ($newWeight < 0) {
|
|
return back()->with('error', 'Cannot adjust below zero.');
|
|
}
|
|
|
|
$material->update(['weight_available' => $newWeight]);
|
|
|
|
ProcMaterialMovement::create([
|
|
'business_id' => $business->id,
|
|
'material_lot_id' => $material->id,
|
|
'movement_type' => 'adjust',
|
|
'quantity' => $adjustQty,
|
|
'uom' => $material->uom,
|
|
'reason' => $validated['reason'],
|
|
]);
|
|
|
|
return back()->with('success', 'Adjustment recorded successfully.');
|
|
}
|
|
|
|
protected function authorizeForBusiness(ProcMaterialLot $lot, Business $business): void
|
|
{
|
|
if ($lot->business_id !== $business->id) {
|
|
abort(403, 'Unauthorized access to this material lot.');
|
|
}
|
|
}
|
|
}
|