Overview
The OrderCreationService creates service orders with optional AI-powered diagnostic analysis. It handles customer/equipment validation, AI usage quota checking, diagnostic analysis, and usage tracking.
Namespace: App\Services\OrderCreationService
Dependencies:
AiDiagnosticService - Performs symptom analysis
AiUsageService - Manages AI quota and tracking
AiTokenEstimator - Estimates token consumption
Methods
create
Creates a new service order with optional AI diagnostic analysis.
public function create(User $actor, array $payload): array
Parameters
The user creating the order
Order configuration array with the following structure:[
'customer_id' => int, // Required
'equipment_id' => int, // Required
'symptoms' => string | null, // Equipment issue description
'status' => string, // OrderStatus constant (default: RECEIVED)
'estimated_cost' => float, // Initial cost estimate (default: 0)
'request_ai_diagnosis' => bool, // Request AI analysis (default: false)
'technician' => string, // For developer role
'technician_user_id' => int, // For admin role
]
Returns
Returns an associative array:
[
'order' => Order, // The created/updated order model
'ai_applied' => bool, // Whether AI diagnostic was applied
'ai_warning' => string | null, // Error message if AI was blocked or failed
]
Behavior
Validation:
- Verifies customer and equipment exist
- Ensures customer and equipment belong to same company
- Checks actor permissions (workers can only create in their company)
- Prevents multiple AI diagnostics on same order
Technician Assignment:
- Worker role: Automatically assigned as technician
- Admin role: Must specify
technician_user_id from active company workers/admins
- Developer role: Can specify custom technician name or uses actor name
AI Diagnostic Flow:
If request_ai_diagnosis is true:
- Checks if order already has AI diagnostic
- Validates company plan supports AI
- Checks monthly query quota
- Estimates and validates token usage
- Performs diagnostic analysis
- Validates actual token consumption
- Updates order with AI results
- Records usage in tracking table
AI Usage Blocking:
If AI quota is exceeded or plan doesn’t support AI:
- Order is still created without AI data
- Returns
ai_applied: false with warning message
- Logs blocked attempt in
company_ai_usage table
Example Usage
Basic Order Creation:
use App\Services\OrderCreationService;
use App\Support\OrderStatus;
$orderService = app(OrderCreationService::class);
$result = $orderService->create(
actor: $user,
payload: [
'customer_id' => 123,
'equipment_id' => 456,
'symptoms' => 'La lavadora no enciende y hace ruido al girar',
'status' => OrderStatus::RECEIVED,
'estimated_cost' => 0,
'request_ai_diagnosis' => false,
]
);
$order = $result['order'];
echo "Order created: #{$order->id}";
Order with AI Diagnostic:
$result = $orderService->create(
actor: $user,
payload: [
'customer_id' => 123,
'equipment_id' => 456,
'symptoms' => 'No enciende, hace ruido extraño y tiene fuga de agua',
'request_ai_diagnosis' => true,
'technician_user_id' => $user->id, // For admin
]
);
if ($result['ai_applied']) {
$order = $result['order'];
echo "AI Diagnostic Applied:";
echo "Causes: " . implode(', ', $order->ai_potential_causes);
echo "Parts needed: " . implode(', ', $order->ai_suggested_parts);
echo "Estimated time: {$order->ai_estimated_time}";
echo "Labor cost: \${$order->ai_cost_repair_labor}";
echo "Parts cost: \${$order->ai_cost_replacement_parts}";
echo "Total cost: \${$order->ai_cost_replacement_total}";
echo "Tokens used: {$order->ai_tokens_used}";
} else {
echo "AI Warning: {$result['ai_warning']}";
// Examples:
// "Tu plan actual no incluye Asistente IA."
// "Se alcanzó el límite mensual de consultas IA para tu empresa."
// "Esta orden ya utilizó su diagnóstico IA disponible."
}
Admin Assigning Technician:
// Admin selects technician from dropdown
$result = $orderService->create(
actor: $adminUser,
payload: [
'customer_id' => 123,
'equipment_id' => 456,
'symptoms' => 'Refrigerador no enfría',
'technician_user_id' => $technicianUser->id,
'request_ai_diagnosis' => true,
]
);
Throws
403 Forbidden:
- Worker trying to create order outside their company
422 Unprocessable Entity:
- Customer and equipment don’t belong to same company
- Admin didn’t select valid active technician from company
AI Diagnostic Integration
When AI diagnostic is requested, the order is enriched with the following fields:
$order->update([
'ai_potential_causes' => string[], // Array of identified causes
'ai_estimated_time' => string, // e.g., "3-5 horas"
'ai_suggested_parts' => string[], // Array of part names
'ai_technical_advice' => string, // Technical recommendations
'ai_diagnosed_at' => timestamp, // When diagnosis was performed
'ai_tokens_used' => int, // Total tokens consumed
'ai_provider' => 'local_stub', // AI provider identifier
'ai_model' => 'heuristic-v1', // Model version used
'ai_requires_parts_replacement' => bool, // Whether parts needed
'ai_cost_repair_labor' => float, // Labor cost estimate
'ai_cost_replacement_parts' => float, // Parts cost estimate
'ai_cost_replacement_total' => float, // Total cost estimate
]);
Token Calculation
Prompt tokens are estimated from equipment and symptom text:
// From OrderCreationService.php:141
private function promptChars(Equipment $equipment, string $symptoms): int
{
$prompt = sprintf(
'Equipo: %s %s %s. Síntomas: %s',
$equipment->type,
$equipment->brand,
$equipment->model ?? '',
$symptoms
);
return mb_strlen($prompt);
}
Response tokens are estimated from AI analysis JSON:
$responseChars = mb_strlen(json_encode($analysis, JSON_UNESCAPED_UNICODE));
$totalTokens = $tokenEstimator->estimateFromChars($promptChars)
+ $tokenEstimator->estimateFromChars($responseChars);
AI Usage Tracking
Every AI diagnostic attempt is tracked:
Successful Usage:
$aiUsageService->registerSuccess(
company: $company,
order: $order,
plan: 'professional',
promptChars: 250,
responseChars: 1200
);
Blocked Usage:
$aiUsageService->registerBlocked(
company: $company,
order: $order,
plan: 'starter',
status: 'blocked_plan',
errorMessage: 'Tu plan actual no incluye Asistente IA.',
promptChars: 250
);
Plan-Based AI Limits
AI diagnostic availability depends on the company’s subscription plan:
// Default plan if no subscription
private const DEFAULT_PLAN = 'starter';
// Plan is retrieved from company subscription
$plan = $company->subscription?->plan ?? self::DEFAULT_PLAN;
The AiPlanPolicyService defines limits for each plan:
- Query limit per month
- Token limit per month
- AI feature enabled/disabled
Error Handling
AI Usage Exceptions:
use App\Services\Exceptions\AiUsageException;
try {
$result = $orderService->create($actor, $payload);
} catch (AiUsageException $e) {
// This won't be thrown - exceptions are caught internally
// Instead check: $result['ai_warning']
}
// Correct approach:
if (!$result['ai_applied'] && $result['ai_warning']) {
// Handle AI limitation
if (str_contains($result['ai_warning'], 'plan')) {
// Suggest plan upgrade
} elseif (str_contains($result['ai_warning'], 'límite')) {
// Show quota exceeded message
}
}
One-Time AI Diagnostic
Each order can only receive AI diagnostic once:
if ($order->ai_diagnosed_at) {
return [
'order' => $order,
'ai_applied' => false,
'ai_warning' => 'Esta orden ya utilizó su diagnóstico IA disponible.',
];
}
This prevents:
- Double-counting quota usage
- Token waste from repeated diagnostics
- Accidental re-analysis
Cost Estimation Update
The AI diagnostic provides cost estimates that can be used to update estimated_cost:
$result = $orderService->create($actor, $payload);
if ($result['ai_applied']) {
$order = $result['order'];
// Optionally update estimated_cost with AI suggestion
$order->update([
'estimated_cost' => $order->ai_cost_replacement_total,
]);
}