383 lines
13 KiB
PHP
383 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Wine;
|
|
use App\Models\GrapeVariety;
|
|
use App\Models\Harvest;
|
|
use App\Models\WineProduction;
|
|
use Illuminate\Http\Request;
|
|
use App\Http\Requests\StoreWineRequest;
|
|
use App\Http\Requests\UpdateWineRequest;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class WineController extends Controller
|
|
{
|
|
/**
|
|
* Display a listing of wines.
|
|
*/
|
|
public function index()
|
|
{
|
|
$wines = Wine::with('grapeVariety')
|
|
->orderBy('vintage', 'desc')
|
|
->paginate(15);
|
|
|
|
return view('wines.index', compact('wines'));
|
|
}
|
|
|
|
/**
|
|
* Display wine catalog for e-shop (with search and filters)
|
|
*/
|
|
public function catalog(Request $request)
|
|
{
|
|
$query = Wine::with('grapeVariety')
|
|
->where('status', 'ready') // Only show wines marked as available for sale
|
|
->where('bottles_in_stock', '>', 0); // Only show wines in stock
|
|
|
|
// Search by wine name
|
|
if ($request->filled('search')) {
|
|
$query->where('wine_name', 'like', '%' . $request->search . '%');
|
|
}
|
|
|
|
// Filter by grape variety
|
|
if ($request->filled('variety') && $request->variety !== 'all') {
|
|
$query->where('grape_variety_id', $request->variety);
|
|
}
|
|
|
|
// Filter by vintage
|
|
if ($request->filled('vintage') && $request->vintage !== 'all') {
|
|
$query->where('vintage', $request->vintage);
|
|
}
|
|
|
|
// Filter by wine type
|
|
if ($request->filled('wine_type') && $request->wine_type !== 'all') {
|
|
$query->where('wine_type', $request->wine_type);
|
|
}
|
|
|
|
// Filter by sweetness
|
|
if ($request->filled('sweetness') && $request->sweetness !== 'all') {
|
|
$query->where('sweetness', $request->sweetness);
|
|
}
|
|
|
|
$wines = $query->orderBy('vintage', 'desc')->paginate(12);
|
|
|
|
// Get all grape varieties for filter dropdown (distinct by name to avoid duplicates)
|
|
$grapeVarieties = GrapeVariety::all()
|
|
->unique('variety_name')
|
|
->sortBy('variety_name')
|
|
->values();
|
|
|
|
// Get all unique vintages for filter dropdown
|
|
$vintages = Wine::where('status', 'ready')
|
|
->where('bottles_in_stock', '>', 0)
|
|
->distinct()
|
|
->pluck('vintage')
|
|
->sort()
|
|
->values();
|
|
|
|
// Get all unique wine types for filter dropdown
|
|
$wineTypes = Wine::where('status', 'ready')
|
|
->where('bottles_in_stock', '>', 0)
|
|
->whereNotNull('wine_type')
|
|
->distinct()
|
|
->pluck('wine_type')
|
|
->values();
|
|
|
|
// Get all unique sweetness levels for filter dropdown
|
|
$sweetnessLevels = Wine::where('status', 'ready')
|
|
->where('bottles_in_stock', '>', 0)
|
|
->whereNotNull('sweetness')
|
|
->distinct()
|
|
->pluck('sweetness')
|
|
->values();
|
|
|
|
return view('catalog.index', compact('wines', 'grapeVarieties', 'vintages', 'wineTypes', 'sweetnessLevels'));
|
|
}
|
|
|
|
/**
|
|
* Show the form for creating a new wine.
|
|
*/
|
|
public function create()
|
|
{
|
|
$grapeVarieties = GrapeVariety::all();
|
|
|
|
return view('wines.create', compact('grapeVarieties'));
|
|
}
|
|
|
|
/**
|
|
* Store a newly created wine in storage.
|
|
*/
|
|
public function store(StoreWineRequest $request)
|
|
{
|
|
$validated = $request->validated();
|
|
|
|
$wine = Wine::create($validated);
|
|
|
|
return redirect()->route('wines.show', $wine)
|
|
->with('success', 'Wine created successfully.');
|
|
}
|
|
|
|
/**
|
|
* Display the specified wine.
|
|
*/
|
|
public function show($id)
|
|
{
|
|
$wine = Wine::with('grapeVariety', 'harvests.plannedTask')->findOrFail($id);
|
|
|
|
// Check if this is a catalog request (from the route)
|
|
if (request()->route()->getName() === 'catalog.show') {
|
|
return view('catalog.show', compact('wine'));
|
|
}
|
|
|
|
return view('wines.show', compact('wine'));
|
|
}
|
|
|
|
/**
|
|
* Show the form for editing the specified wine.
|
|
*/
|
|
public function edit(Wine $wine)
|
|
{
|
|
$grapeVarieties = GrapeVariety::all();
|
|
|
|
return view('wines.edit', compact('wine', 'grapeVarieties'));
|
|
}
|
|
|
|
/**
|
|
* Update the specified wine in storage.
|
|
*/
|
|
public function update(UpdateWineRequest $request, Wine $wine)
|
|
{
|
|
$validated = $request->validated();
|
|
|
|
// Handle image upload
|
|
if ($request->hasFile('image')) {
|
|
// Delete old image if exists
|
|
if ($wine->image_url) {
|
|
\Storage::disk('public')->delete($wine->image_url);
|
|
}
|
|
$validated['image_url'] = $request->file('image')->store('wines', 'public');
|
|
}
|
|
|
|
// Remove the 'image' key from validated data (we only want 'image_url')
|
|
unset($validated['image']);
|
|
|
|
$wine->update($validated);
|
|
|
|
return redirect()->route('wines.show', $wine)
|
|
->with('success', 'Wine updated successfully.');
|
|
}
|
|
|
|
/**
|
|
* Remove the specified wine from storage.
|
|
*/
|
|
public function destroy(Wine $wine)
|
|
{
|
|
$wineName = $wine->wine_name;
|
|
$wine->delete();
|
|
|
|
return redirect()->route('winemaker.cellar.index')
|
|
->with('success', "Wine '{$wineName}' has been permanently removed from the cellar.");
|
|
}
|
|
|
|
/**
|
|
* Display cellar wine inventory (for winemaker)
|
|
*/
|
|
public function cellarIndex()
|
|
{
|
|
$wines = Wine::with('grapeVariety')
|
|
->orderBy('vintage', 'desc')
|
|
->orderBy('wine_name')
|
|
->get();
|
|
|
|
return view('winemaker.cellar.index', compact('wines'));
|
|
}
|
|
|
|
/**
|
|
* Display wines available for sales management (for winemaker)
|
|
*/
|
|
public function salesIndex()
|
|
{
|
|
$wines = Wine::with('grapeVariety')
|
|
->where('bottles_in_stock', '>', 0)
|
|
->orderBy('vintage', 'desc')
|
|
->orderBy('wine_name')
|
|
->paginate(15);
|
|
|
|
return view('winemaker.sales.index', compact('wines'));
|
|
}
|
|
|
|
/**
|
|
* Add wine to sales (mark as available)
|
|
*/
|
|
public function addToSales($id)
|
|
{
|
|
$wine = Wine::findOrFail($id);
|
|
|
|
if ($wine->status === 'in_production') {
|
|
return redirect()->back()->with('error', 'Cannot add wine that is still in production to catalog.');
|
|
}
|
|
|
|
if ($wine->bottles_in_stock <= 0) {
|
|
return redirect()->back()->with('error', 'Cannot add wine without stock to catalog.');
|
|
}
|
|
|
|
if (!$wine->price_per_bottle) {
|
|
return redirect()->back()->with('error', 'Please set a price before adding wine to catalog.');
|
|
}
|
|
|
|
$wine->update(['status' => 'ready']);
|
|
|
|
return redirect()->back()->with('success', 'Wine has been added to the catalog.');
|
|
}
|
|
|
|
/**
|
|
* Remove wine from sales (mark as not available)
|
|
*/
|
|
public function removeFromSales($id)
|
|
{
|
|
$wine = Wine::findOrFail($id);
|
|
|
|
$wine->update(['status' => 'aging']);
|
|
|
|
return redirect()->back()->with('success', 'Wine has been removed from the catalog.');
|
|
}
|
|
|
|
/**
|
|
* Show form to create blended wine from multiple harvests
|
|
*/
|
|
public function createBlendForm()
|
|
{
|
|
$availableHarvests = Harvest::with(['varietyVariation.grapeVariety', 'wineProductions', 'plannedTask'])
|
|
->where(function ($query) {
|
|
$query->whereHas('plannedTask', function ($sub) {
|
|
$sub->whereNotNull('execution_date');
|
|
})->orWhereDoesntHave('plannedTask');
|
|
})
|
|
->get()
|
|
->filter(function ($harvest) {
|
|
return $harvest->remaining_weight > 0.01;
|
|
})
|
|
->sortByDesc(function ($harvest) {
|
|
return $harvest->plannedTask?->execution_date ?? $harvest->date;
|
|
});
|
|
|
|
return view('winemaker.cellar.create-blend', compact('availableHarvests'));
|
|
}
|
|
|
|
/**
|
|
* Store a new blended wine from multiple harvests
|
|
*/
|
|
public function storeBlend(Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'harvests' => 'required|array|min:1',
|
|
'harvests.*.selected' => 'sometimes',
|
|
'harvests.*.weight' => 'required_with:harvests.*.selected|numeric|min:0.01',
|
|
'wine_name' => 'required|string|max:255',
|
|
'vintage' => 'required|integer|min:1900|max:' . (date('Y') + 1),
|
|
'wine_type' => 'required|in:red,white,rose',
|
|
'sweetness' => 'required|in:dry,semi_dry,semi_sweet,sweet',
|
|
'alcohol_percentage' => 'required|numeric|min:0|max:20',
|
|
'bottles_produced' => 'required|integer|min:1',
|
|
'bottle_volume' => 'nullable|numeric|min:0.1|max:10',
|
|
'production_date' => 'required|date',
|
|
'bottling_date' => 'nullable|date',
|
|
'status' => 'required|in:in_production,aging,ready,sold_out',
|
|
'price_per_bottle' => 'nullable|numeric|min:0',
|
|
'description' => 'nullable|string',
|
|
'image' => 'nullable|image|mimes:jpeg,jpg,png,webp|max:2048',
|
|
]);
|
|
|
|
try {
|
|
DB::beginTransaction();
|
|
|
|
// Filter selected harvests and calculate total weight
|
|
$selectedHarvests = [];
|
|
$totalWeight = 0;
|
|
|
|
foreach ($validated['harvests'] as $harvestId => $data) {
|
|
if (isset($data['selected']) && isset($data['weight']) && $data['weight'] > 0) {
|
|
$harvest = Harvest::with('varietyVariation.grapeVariety')->findOrFail($harvestId);
|
|
|
|
// Validate weight
|
|
if ($data['weight'] > $harvest->remaining_weight) {
|
|
return back()
|
|
->withInput()
|
|
->with('error', 'Harvest #' . $harvestId . ': Cannot use ' . $data['weight'] . ' kg. Only ' . number_format($harvest->remaining_weight, 2) . ' kg available.');
|
|
}
|
|
|
|
$selectedHarvests[] = [
|
|
'harvest' => $harvest,
|
|
'weight' => $data['weight']
|
|
];
|
|
$totalWeight += $data['weight'];
|
|
}
|
|
}
|
|
|
|
if (count($selectedHarvests) === 0) {
|
|
return back()
|
|
->withInput()
|
|
->with('error', 'Please select at least one harvest with a weight.');
|
|
}
|
|
|
|
// Determine primary grape variety (from harvest with highest weight)
|
|
$primaryHarvest = collect($selectedHarvests)->sortByDesc('weight')->first();
|
|
$grapeVariety = $primaryHarvest['harvest']->varietyVariation->grapeVariety;
|
|
|
|
// Handle image upload
|
|
$imagePath = null;
|
|
if ($request->hasFile('image')) {
|
|
$imagePath = $request->file('image')->store('wines', 'public');
|
|
}
|
|
|
|
// Create the wine
|
|
$wine = Wine::create([
|
|
'wine_name' => $validated['wine_name'],
|
|
'vintage' => $validated['vintage'],
|
|
'grape_variety_id' => $grapeVariety->id,
|
|
'wine_type' => $validated['wine_type'],
|
|
'sweetness' => $validated['sweetness'],
|
|
'alcohol_percentage' => $validated['alcohol_percentage'],
|
|
'bottles_produced' => $validated['bottles_produced'],
|
|
'bottles_in_stock' => $validated['bottles_produced'],
|
|
'bottle_volume' => $validated['bottle_volume'] ?? 0.75,
|
|
'production_date' => $validated['production_date'],
|
|
'bottling_date' => $validated['bottling_date'],
|
|
'status' => $validated['status'],
|
|
'price_per_bottle' => $validated['price_per_bottle'],
|
|
'description' => $validated['description'],
|
|
'image_url' => $imagePath,
|
|
]);
|
|
|
|
// Create wine production records for each harvest
|
|
$harvestIds = [];
|
|
foreach ($selectedHarvests as $item) {
|
|
$blendPercentage = ($item['weight'] / $totalWeight) * 100;
|
|
|
|
WineProduction::create([
|
|
'wine_id' => $wine->id,
|
|
'harvest_id' => $item['harvest']->id,
|
|
'consumed_weight' => $item['weight'],
|
|
'blend_percentage' => $blendPercentage,
|
|
]);
|
|
|
|
$harvestIds[] = '#' . $item['harvest']->id;
|
|
}
|
|
|
|
DB::commit();
|
|
|
|
$message = 'Blended wine created successfully from ' . count($selectedHarvests) . ' harvest(s): ' . implode(', ', $harvestIds);
|
|
$message .= '. Total blend: ' . number_format($totalWeight, 2) . ' kg';
|
|
|
|
return redirect()->route('winemaker.cellar.index')
|
|
->with('success', $message);
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return back()
|
|
->withInput()
|
|
->with('error', 'Failed to create blended wine: ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|