211 lines
7.7 KiB
PHP
211 lines
7.7 KiB
PHP
<?php
|
|
|
|
namespace Modules\IndustrialEngineering\Services;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Modules\IndustrialEngineering\Models\BuildGenealogyEvent;
|
|
use Modules\IndustrialEngineering\Models\CustomerInstalledAsset;
|
|
use Modules\IndustrialEngineering\Models\SerializedUnit;
|
|
use Modules\IndustrialEngineering\Models\SerializedUnitComponent;
|
|
use Modules\IndustrialEngineering\Models\SerialRegistry;
|
|
|
|
class TraceabilityService
|
|
{
|
|
public function __construct(
|
|
protected BomService $bomService
|
|
) {
|
|
}
|
|
|
|
public function createSerializedUnit(
|
|
int $businessId,
|
|
string $unitCode,
|
|
string $serialNumber,
|
|
?int $releasePackageId = null,
|
|
?int $workOrderId = null,
|
|
?int $lineRevisionId = null
|
|
): SerializedUnit {
|
|
return DB::transaction(function () use ($businessId, $unitCode, $serialNumber, $releasePackageId, $workOrderId, $lineRevisionId) {
|
|
$registry = SerialRegistry::create([
|
|
'business_id' => $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];
|
|
}
|
|
}
|