Files
hub/app/Http/Controllers/Seller/Processing/ProcessingSalesOrderController.php
kelly c7d6ee5e21 feat: multiple UI/UX improvements and case-insensitive search
- 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)
2025-12-14 15:36:00 -07:00

228 lines
8.2 KiB
PHP

<?php
namespace App\Http\Controllers\Seller\Processing;
use App\Http\Controllers\Controller;
use App\Models\Business;
use App\Models\Processing\ProcCustomer;
use App\Models\Processing\ProcMaterialLot;
use App\Models\Processing\ProcSalesOrder;
use App\Models\Processing\ProcSalesOrderLine;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ProcessingSalesOrderController extends Controller
{
public function index(Request $request, Business $business)
{
$query = ProcSalesOrder::forBusiness($business->id)
->with('customer')
->orderByDesc('order_date');
if ($request->filled('status')) {
$query->where('status', $request->status);
}
if ($request->filled('customer_id')) {
$query->where('customer_id', $request->customer_id);
}
if ($request->filled('search')) {
$query->where('order_number', 'ilike', '%'.$request->search.'%');
}
$salesOrders = $query->paginate(25);
$customers = ProcCustomer::forBusiness($business->id)->active()->get();
return view('seller.processing.sales-orders.index', compact('business', 'salesOrders', 'customers'));
}
public function create(Request $request, Business $business)
{
$customers = ProcCustomer::forBusiness($business->id)->active()->get();
$materialLots = ProcMaterialLot::forBusiness($business->id)->available()->get();
return view('seller.processing.sales-orders.create', compact('business', 'customers', 'materialLots'));
}
public function store(Request $request, Business $business)
{
$validated = $request->validate([
'order_number' => 'required|string|max:100',
'customer_id' => 'required|exists:proc_customers,id',
'order_date' => 'required|date',
'due_date' => 'nullable|date',
'notes' => 'nullable|string',
'lines' => 'required|array|min:1',
'lines.*.material_lot_id' => 'required|exists:proc_material_lots,id',
'lines.*.quantity' => 'required|numeric|min:0',
'lines.*.unit_price' => 'required|numeric|min:0',
]);
// Verify customer belongs to business
ProcCustomer::where('business_id', $business->id)
->findOrFail($validated['customer_id']);
DB::transaction(function () use ($validated, $business) {
$subtotal = 0;
foreach ($validated['lines'] as $line) {
$subtotal += $line['quantity'] * $line['unit_price'];
}
$order = ProcSalesOrder::create([
'business_id' => $business->id,
'customer_id' => $validated['customer_id'],
'order_number' => $validated['order_number'],
'order_date' => $validated['order_date'],
'due_date' => $validated['due_date'],
'status' => 'pending',
'subtotal' => $subtotal,
'total' => $subtotal,
'notes' => $validated['notes'],
]);
foreach ($validated['lines'] as $line) {
// Verify material lot belongs to business
$materialLot = ProcMaterialLot::where('business_id', $business->id)
->findOrFail($line['material_lot_id']);
ProcSalesOrderLine::create([
'sales_order_id' => $order->id,
'material_lot_id' => $line['material_lot_id'],
'quantity' => $line['quantity'],
'uom' => $materialLot->uom ?? 'g',
'unit_price' => $line['unit_price'],
'line_total' => $line['quantity'] * $line['unit_price'],
]);
}
});
return redirect()
->route('seller.processing.sales-orders.index', $business)
->with('success', 'Sales order created successfully.');
}
public function show(Request $request, Business $business, ProcSalesOrder $salesOrder)
{
$this->authorizeForBusiness($salesOrder, $business);
$salesOrder->load(['customer', 'lines.materialLot', 'shipments']);
return view('seller.processing.sales-orders.show', compact('business', 'salesOrder'));
}
public function edit(Request $request, Business $business, ProcSalesOrder $salesOrder)
{
$this->authorizeForBusiness($salesOrder, $business);
if ($salesOrder->status === 'shipped' || $salesOrder->status === 'completed') {
return back()->with('error', 'Cannot edit shipped or completed orders.');
}
$customers = ProcCustomer::forBusiness($business->id)->active()->get();
$salesOrder->load('lines.materialLot');
return view('seller.processing.sales-orders.edit', compact('business', 'salesOrder', 'customers'));
}
public function update(Request $request, Business $business, ProcSalesOrder $salesOrder)
{
$this->authorizeForBusiness($salesOrder, $business);
if ($salesOrder->status === 'shipped' || $salesOrder->status === 'completed') {
return back()->with('error', 'Cannot edit shipped or completed orders.');
}
$validated = $request->validate([
'order_number' => 'required|string|max:100',
'customer_id' => 'required|exists:proc_customers,id',
'order_date' => 'required|date',
'due_date' => 'nullable|date',
'status' => 'required|in:pending,confirmed,processing,shipped,completed,cancelled',
'notes' => 'nullable|string',
]);
// Verify customer belongs to business
ProcCustomer::where('business_id', $business->id)
->findOrFail($validated['customer_id']);
$salesOrder->update($validated);
return redirect()
->route('seller.processing.sales-orders.show', [$business, $salesOrder])
->with('success', 'Sales order updated successfully.');
}
public function destroy(Request $request, Business $business, ProcSalesOrder $salesOrder)
{
$this->authorizeForBusiness($salesOrder, $business);
if ($salesOrder->status === 'shipped' || $salesOrder->status === 'completed') {
return back()->with('error', 'Cannot delete shipped or completed orders.');
}
$salesOrder->lines()->delete();
$salesOrder->delete();
return redirect()
->route('seller.processing.sales-orders.index', $business)
->with('success', 'Sales order deleted successfully.');
}
/**
* Confirm a pending order.
*/
public function confirm(Request $request, Business $business, ProcSalesOrder $salesOrder)
{
$this->authorizeForBusiness($salesOrder, $business);
if ($salesOrder->status !== 'pending') {
return back()->with('error', 'Only pending orders can be confirmed.');
}
$salesOrder->update(['status' => 'confirmed']);
return back()->with('success', 'Order confirmed.');
}
/**
* Mark order as processing.
*/
public function startProcessing(Request $request, Business $business, ProcSalesOrder $salesOrder)
{
$this->authorizeForBusiness($salesOrder, $business);
if ($salesOrder->status !== 'confirmed') {
return back()->with('error', 'Only confirmed orders can be marked as processing.');
}
$salesOrder->update(['status' => 'processing']);
return back()->with('success', 'Order marked as processing.');
}
/**
* Cancel an order.
*/
public function cancel(Request $request, Business $business, ProcSalesOrder $salesOrder)
{
$this->authorizeForBusiness($salesOrder, $business);
if (in_array($salesOrder->status, ['shipped', 'completed'])) {
return back()->with('error', 'Cannot cancel shipped or completed orders.');
}
$salesOrder->update(['status' => 'cancelled']);
return back()->with('success', 'Order cancelled.');
}
protected function authorizeForBusiness(ProcSalesOrder $order, Business $business): void
{
if ($order->business_id !== $business->id) {
abort(403, 'Unauthorized access to this sales order.');
}
}
}