- 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)
228 lines
8.2 KiB
PHP
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.');
|
|
}
|
|
}
|
|
}
|