$projectId, 'material_id' => $materialId], [ 'on_hand_qty' => \DB::raw("on_hand_qty + {$quantity}"), 'unit_cost' => $unitCost ?? $material->unit_cost, ] ); } /** * Request a deployment (creates deployment record). */ public function requestDeployment( int $projectId, int $materialId, float $quantity, ?int $requestedBy = null, ?string $notes = null, ): MaterialDeployment { $deployment = MaterialDeployment::create([ 'project_id' => $projectId, 'material_id' => $materialId, 'requested_by' => $requestedBy, 'quantity' => $quantity, 'status' => DeploymentStatus::Requested, 'notes' => $notes, ]); MaterialRequested::dispatch($deployment); return $deployment; } /** * Dispatch deployment (guard: check inventory). */ public function dispatch(MaterialDeployment $deployment): MaterialDeployment { $inventory = ProjectInventory::where('project_id', $deployment->project_id) ->where('material_id', $deployment->material_id) ->first(); if (!$inventory || $inventory->available_qty < $deployment->quantity) { throw new \InvalidArgumentException( "Insufficient stock. Available: " . ($inventory?->available_qty ?? 0) . ", Requested: {$deployment->quantity}" ); } // Allocate from inventory $inventory->increment('allocated_qty', $deployment->quantity); $deployment->transitionTo(DeploymentStatus::PendingDispatch); $deployment->transitionTo(DeploymentStatus::InTransit); return $deployment->fresh(); } /** * Mark as delivered. */ public function deliver(MaterialDeployment $deployment): MaterialDeployment { $deployment->transitionTo(DeploymentStatus::Delivered); MaterialDelivered::dispatch($deployment); return $deployment->fresh(); } /** * Mark as consumed (deduct from inventory). */ public function consume(MaterialDeployment $deployment): MaterialDeployment { $deployment->transitionTo(DeploymentStatus::Consumed); $inventory = ProjectInventory::where('project_id', $deployment->project_id) ->where('material_id', $deployment->material_id) ->first(); if ($inventory) { $inventory->decrement('on_hand_qty', $deployment->quantity); $inventory->decrement('allocated_qty', $deployment->quantity); $inventory->increment('consumed_qty', $deployment->quantity); } return $deployment->fresh(); } /** * Cancel a deployment. */ public function cancel(MaterialDeployment $deployment): MaterialDeployment { // Release allocated stock if it was dispatched if (in_array($deployment->status, [DeploymentStatus::PendingDispatch, DeploymentStatus::InTransit])) { $inventory = ProjectInventory::where('project_id', $deployment->project_id) ->where('material_id', $deployment->material_id) ->first(); if ($inventory) { $inventory->decrement('allocated_qty', $deployment->quantity); } } $deployment->transitionTo(DeploymentStatus::Cancelled); return $deployment->fresh(); } }