Zusammenfassung: Fügt im „Run Inventory Sync“-Modal einen include_dependencies-Toggle hinzu und persistiert die Auswahl in der InventorySyncRun.selection_payload. Tests, Quickstart und Tasks wurden entsprechend aktualisiert. Files: InventoryLanding.php, InventorySyncButtonTest.php, quickstart.md, tasks.md Motivation: Ermöglicht explizites Ein-/Ausschalten der Dependency-Extraktion pro Sync-Run (z. B. Assignments/Scope Tags/Foundations), statt starrer Defaults. Passt zur bestehenden selection_hash-Logik (InventorySelectionHasher) und zur deterministischen Selektionspersistenz. Verhalten: include_dependencies ist im Modal standardmäßig true. Wird die Option gesetzt, landet der Wert als bool im selection_payload und beeinflusst selection_hash über die Normalisierung. Tests: Neuer/angepasster Pest-Test stellt sicher, dass include_dependencies in selection_payload persistiert. Lokaler Testlauf: ./vendor/bin/sail artisan test tests/Feature/Inventory/InventorySyncButtonTest.php → alle Tests für diese Datei bestanden. ./vendor/bin/pint --dirty wurde ausgeführt (Formatting ok). How to test (quick): Start Sail + Queue: Im Admin → Inventory: „Run Inventory Sync“ öffnen, Include dependencies umschalten, ausführen. Prüfen: neu erstellter InventorySyncRun.selection_payload.include_dependencies ist der gesetzten Auswahl entsprechend. Oder laufen lassen: Notes / Next steps: Diese Änderung bereitet den Weg, später die Dependency-Extraction (042-inventory-dependencies-graph) optional tiefer zu integrieren. Working tree ist sauber; es gibt ein nicht eingebundenes Verzeichnis 0800-future-features (unrelated). Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local> Reviewed-on: #47
222 lines
7.6 KiB
PHP
222 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Models\BulkOperationRun;
|
|
use App\Models\InventorySyncRun;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\BulkOperationService;
|
|
use App\Services\Intune\AuditLogger;
|
|
use App\Services\Inventory\InventorySyncService;
|
|
use Filament\Notifications\Notification;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use RuntimeException;
|
|
|
|
class RunInventorySyncJob implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
/**
|
|
* Create a new job instance.
|
|
*/
|
|
public function __construct(
|
|
public int $tenantId,
|
|
public int $userId,
|
|
public int $bulkRunId,
|
|
public int $inventorySyncRunId,
|
|
) {}
|
|
|
|
/**
|
|
* Execute the job.
|
|
*/
|
|
public function handle(BulkOperationService $bulkOperationService, InventorySyncService $inventorySyncService, AuditLogger $auditLogger): void
|
|
{
|
|
$tenant = Tenant::query()->find($this->tenantId);
|
|
if (! $tenant instanceof Tenant) {
|
|
throw new RuntimeException('Tenant not found.');
|
|
}
|
|
|
|
$user = User::query()->find($this->userId);
|
|
if (! $user instanceof User) {
|
|
throw new RuntimeException('User not found.');
|
|
}
|
|
|
|
$bulkRun = BulkOperationRun::query()->find($this->bulkRunId);
|
|
if (! $bulkRun instanceof BulkOperationRun) {
|
|
throw new RuntimeException('BulkOperationRun not found.');
|
|
}
|
|
|
|
$run = InventorySyncRun::query()->find($this->inventorySyncRunId);
|
|
if (! $run instanceof InventorySyncRun) {
|
|
throw new RuntimeException('InventorySyncRun not found.');
|
|
}
|
|
|
|
$bulkOperationService->start($bulkRun);
|
|
|
|
$processedPolicyTypes = [];
|
|
|
|
$run = $inventorySyncService->executePendingRun(
|
|
$run,
|
|
$tenant,
|
|
function (string $policyType, bool $success, ?string $errorCode) use ($bulkOperationService, $bulkRun, &$processedPolicyTypes): void {
|
|
$processedPolicyTypes[] = $policyType;
|
|
|
|
if ($success) {
|
|
$bulkOperationService->recordSuccess($bulkRun);
|
|
|
|
return;
|
|
}
|
|
|
|
$bulkOperationService->recordFailure($bulkRun, $policyType, $errorCode ?? 'failed');
|
|
},
|
|
);
|
|
|
|
$policyTypes = is_array($bulkRun->item_ids ?? null) ? $bulkRun->item_ids : [];
|
|
if ($policyTypes === []) {
|
|
$policyTypes = is_array($run->selection_payload['policy_types'] ?? null) ? $run->selection_payload['policy_types'] : [];
|
|
}
|
|
|
|
if ($run->status === InventorySyncRun::STATUS_SUCCESS) {
|
|
$bulkOperationService->complete($bulkRun);
|
|
|
|
$auditLogger->log(
|
|
tenant: $tenant,
|
|
action: 'inventory.sync.completed',
|
|
context: [
|
|
'metadata' => [
|
|
'inventory_sync_run_id' => $run->id,
|
|
'bulk_run_id' => $bulkRun->id,
|
|
'selection_hash' => $run->selection_hash,
|
|
'observed' => $run->items_observed_count,
|
|
'upserted' => $run->items_upserted_count,
|
|
],
|
|
],
|
|
actorId: $user->id,
|
|
actorEmail: $user->email,
|
|
actorName: $user->name,
|
|
resourceType: 'inventory_sync_run',
|
|
resourceId: (string) $run->id,
|
|
);
|
|
|
|
Notification::make()
|
|
->title('Inventory sync completed')
|
|
->body('Inventory sync finished successfully.')
|
|
->success()
|
|
->sendToDatabase($user)
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
if ($run->status === InventorySyncRun::STATUS_PARTIAL) {
|
|
$bulkOperationService->complete($bulkRun);
|
|
|
|
$auditLogger->log(
|
|
tenant: $tenant,
|
|
action: 'inventory.sync.partial',
|
|
context: [
|
|
'metadata' => [
|
|
'inventory_sync_run_id' => $run->id,
|
|
'bulk_run_id' => $bulkRun->id,
|
|
'selection_hash' => $run->selection_hash,
|
|
'observed' => $run->items_observed_count,
|
|
'upserted' => $run->items_upserted_count,
|
|
'errors' => $run->errors_count,
|
|
],
|
|
],
|
|
actorId: $user->id,
|
|
actorEmail: $user->email,
|
|
actorName: $user->name,
|
|
status: 'failure',
|
|
resourceType: 'inventory_sync_run',
|
|
resourceId: (string) $run->id,
|
|
);
|
|
|
|
Notification::make()
|
|
->title('Inventory sync completed with errors')
|
|
->body('Inventory sync finished with some errors. Review the run details for error codes.')
|
|
->warning()
|
|
->sendToDatabase($user)
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
if ($run->status === InventorySyncRun::STATUS_SKIPPED) {
|
|
$reason = (string) (($run->error_codes ?? [])[0] ?? 'skipped');
|
|
|
|
foreach ($policyTypes as $policyType) {
|
|
$bulkOperationService->recordSkippedWithReason($bulkRun, (string) $policyType, $reason);
|
|
}
|
|
$bulkOperationService->complete($bulkRun);
|
|
|
|
$auditLogger->log(
|
|
tenant: $tenant,
|
|
action: 'inventory.sync.skipped',
|
|
context: [
|
|
'metadata' => [
|
|
'inventory_sync_run_id' => $run->id,
|
|
'bulk_run_id' => $bulkRun->id,
|
|
'selection_hash' => $run->selection_hash,
|
|
'reason' => $reason,
|
|
],
|
|
],
|
|
actorId: $user->id,
|
|
actorEmail: $user->email,
|
|
actorName: $user->name,
|
|
resourceType: 'inventory_sync_run',
|
|
resourceId: (string) $run->id,
|
|
);
|
|
|
|
Notification::make()
|
|
->title('Inventory sync skipped')
|
|
->body('Inventory sync could not start due to locks or concurrency limits.')
|
|
->warning()
|
|
->sendToDatabase($user)
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
$reason = (string) (($run->error_codes ?? [])[0] ?? 'failed');
|
|
|
|
$missingPolicyTypes = array_values(array_diff($policyTypes, array_unique($processedPolicyTypes)));
|
|
foreach ($missingPolicyTypes as $policyType) {
|
|
$bulkOperationService->recordFailure($bulkRun, (string) $policyType, $reason);
|
|
}
|
|
|
|
$bulkOperationService->complete($bulkRun);
|
|
|
|
$auditLogger->log(
|
|
tenant: $tenant,
|
|
action: 'inventory.sync.failed',
|
|
context: [
|
|
'metadata' => [
|
|
'inventory_sync_run_id' => $run->id,
|
|
'bulk_run_id' => $bulkRun->id,
|
|
'selection_hash' => $run->selection_hash,
|
|
'reason' => $reason,
|
|
],
|
|
],
|
|
actorId: $user->id,
|
|
actorEmail: $user->email,
|
|
actorName: $user->name,
|
|
status: 'failure',
|
|
resourceType: 'inventory_sync_run',
|
|
resourceId: (string) $run->id,
|
|
);
|
|
|
|
Notification::make()
|
|
->title('Inventory sync failed')
|
|
->body('Inventory sync finished with errors.')
|
|
->danger()
|
|
->sendToDatabase($user)
|
|
->send();
|
|
}
|
|
}
|