$businessId, 'serial_number' => $serialNumber, 'serial_type' => 'unit', 'status' => 'allocated', 'manufactured_at' => now()->toDateString(), ]); return SerializedUnit::create([ 'business_id' => $businessId, 'release_package_id' => $releasePackageId, 'work_order_id' => $workOrderId, 'line_revision_id' => $lineRevisionId, 'serial_registry_id' => $registry->id, 'unit_code' => $unitCode, 'serial_number' => $serialNumber, 'build_status' => 'in_progress', 'build_started_at' => now()->toDateString(), ]); }); } /** * Record a component installed on a serialized unit (as-built genealogy). */ public function recordComponentInstallation( SerializedUnit $unit, array $componentData, ?int $performedBy = null ): SerializedUnitComponent { return DB::transaction(function () use ($unit, $componentData, $performedBy) { $serialRegistryId = null; if (! empty($componentData['serial_number'])) { $registry = SerialRegistry::firstOrCreate( [ 'business_id' => $unit->business_id, 'serial_number' => $componentData['serial_number'], ], [ 'item_master_id' => $componentData['item_master_id'] ?? null, 'serial_type' => 'component', 'status' => 'installed', 'manufactured_at' => $componentData['manufactured_at'] ?? now()->toDateString(), 'manufacturer_id' => $componentData['manufacturer_id'] ?? null, ] ); $serialRegistryId = $registry->id; } $component = SerializedUnitComponent::create(array_merge([ 'business_id' => $unit->business_id, 'serialized_unit_id' => $unit->id, 'installed_at' => now()->toDateString(), ], $componentData, ['serial_registry_id' => $serialRegistryId])); BuildGenealogyEvent::create([ 'business_id' => $unit->business_id, 'serialized_unit_id' => $unit->id, 'event_type' => 'component_installed', 'serialized_unit_component_id' => $component->id, 'performed_by' => $performedBy ?? auth()->id(), 'performed_at' => now(), 'event_data' => $componentData, ]); return $component; }); } public function completeBuild(SerializedUnit $unit): SerializedUnit { $unit->update([ 'build_status' => 'completed', 'build_completed_at' => now()->toDateString(), 'as_built_snapshot' => $this->buildAsBuiltSnapshot($unit), ]); if ($unit->serialRegistry) { $unit->serialRegistry->update(['status' => 'available']); } return $unit->fresh(['components.itemMaster', 'components.children']); } public function buildAsBuiltSnapshot(SerializedUnit $unit): array { $unit->load(['components.itemMaster', 'lineRevision', 'releasePackage']); return [ 'unit_id' => $unit->id, 'serial_number' => $unit->serial_number, 'line_revision_id' => $unit->line_revision_id, 'components' => $unit->components->map(fn ($c) => [ 'id' => $c->id, 'position_code' => $c->position_code, 'label' => $c->component_label, 'serial_number' => $c->serial_number, 'brand' => $c->brand, 'manufacturer' => $c->manufacturer, 'model' => $c->model, 'warranty_expires_at' => $c->warranty_expires_at?->toDateString(), ])->values()->all(), 'snapshot_at' => now()->toIso8601String(), ]; } /** * Register customer installed asset from sold serialized unit. */ public function registerInstalledAsset( SerializedUnit $unit, int $customerId, string $assetCode, string $name, ?int $sellTransactionId = null, ?int $sellLineId = null, ?int $projectId = null ): CustomerInstalledAsset { return DB::transaction(function () use ($unit, $customerId, $assetCode, $name, $sellTransactionId, $sellLineId, $projectId) { $asBuilt = $this->buildAsBuiltSnapshot($unit); $asset = CustomerInstalledAsset::create([ 'business_id' => $unit->business_id, 'serialized_unit_id' => $unit->id, 'line_revision_id' => $unit->line_revision_id, 'customer_id' => $customerId, 'project_id' => $projectId, 'sell_transaction_id' => $sellTransactionId, 'sell_line_id' => $sellLineId, 'asset_code' => $assetCode, 'serial_number' => $unit->serial_number, 'name' => $name, 'installation_status' => 'pending', 'sold_at' => now()->toDateString(), 'as_sold_snapshot' => $asBuilt, ]); $unit->update(['build_status' => 'shipped']); return $asset; }); } /** * Lookup by serial, asset code, or component serial. */ public function lookup(int $businessId, string $query): array { $asset = CustomerInstalledAsset::forBusiness($businessId) ->with(['customer', 'serializedUnit.components.itemMaster', 'lineRevision.lineTemplate', 'warranties']) ->where(function ($q) use ($query) { $q->where('serial_number', $query) ->orWhere('asset_code', $query); }) ->first(); if ($asset) { return ['type' => 'installed_asset', 'data' => $asset]; } $component = SerializedUnitComponent::forBusiness($businessId) ->with(['serializedUnit', 'itemMaster', 'supplier']) ->where('serial_number', $query) ->orWhere('position_code', $query) ->first(); if ($component) { return ['type' => 'component', 'data' => $component]; } $unit = SerializedUnit::forBusiness($businessId) ->with(['components', 'lineRevision', 'installedAsset.customer']) ->where('serial_number', $query) ->orWhere('unit_code', $query) ->first(); if ($unit) { return ['type' => 'serialized_unit', 'data' => $unit]; } return ['type' => 'not_found', 'data' => null]; } }