201 lines
6.3 KiB
PHP
201 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Import\Handlers;
|
|
|
|
use App\Models\Import;
|
|
use App\Models\Person\Person;
|
|
use App\Models\Person\PersonGroup;
|
|
use App\Models\Person\PersonType;
|
|
use App\Services\Import\DateNormalizer;
|
|
use App\Services\Import\BaseEntityHandler;
|
|
use App\Services\Import\EntityResolutionService;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class PersonHandler extends BaseEntityHandler
|
|
{
|
|
protected EntityResolutionService $resolutionService;
|
|
|
|
public function __construct($entityConfig = null)
|
|
{
|
|
parent::__construct($entityConfig);
|
|
$this->resolutionService = new EntityResolutionService();
|
|
}
|
|
|
|
public function getEntityClass(): string
|
|
{
|
|
return Person::class;
|
|
}
|
|
|
|
public function resolve(array $mapped, array $context = []): mixed
|
|
{
|
|
// PHASE 3: Use EntityResolutionService to check chain-based deduplication
|
|
// This prevents creating duplicate Persons when Contract/ClientCase already exists
|
|
$import = $context['import'] ?? null;
|
|
|
|
if ($import) {
|
|
$personId = $this->resolutionService->resolvePersonFromContext($import, $mapped, $context);
|
|
|
|
if ($personId) {
|
|
$person = Person::find($personId);
|
|
|
|
if ($person) {
|
|
Log::info('PersonHandler: Resolved existing Person via chain', [
|
|
'person_id' => $personId,
|
|
'resolution_method' => 'EntityResolutionService',
|
|
]);
|
|
|
|
return $person;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fall back to configured deduplication fields (tax_number, SSN)
|
|
$dedupeBy = $this->getOption('deduplicate_by', ['tax_number', 'social_security_number']);
|
|
|
|
foreach ($dedupeBy as $field) {
|
|
if (! empty($mapped[$field])) {
|
|
$person = Person::where($field, $mapped[$field])->first();
|
|
if ($person) {
|
|
Log::info('PersonHandler: Resolved existing Person by identifier', [
|
|
'person_id' => $person->id,
|
|
'field' => $field,
|
|
]);
|
|
|
|
return $person;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function process(Import $import, array $mapped, array $raw, array $context = []): array
|
|
{
|
|
// Add import to context for EntityResolutionService
|
|
$context['import'] = $import;
|
|
|
|
$existing = $this->resolve($mapped, $context);
|
|
|
|
if ($existing) {
|
|
// Update if configured
|
|
$mode = $this->getOption('update_mode', 'update');
|
|
|
|
if ($mode === 'skip') {
|
|
return [
|
|
'action' => 'skipped',
|
|
'entity' => $existing,
|
|
'message' => 'Person already exists (skip mode)',
|
|
];
|
|
}
|
|
|
|
$payload = $this->buildPayload($mapped, $existing);
|
|
$appliedFields = $this->trackAppliedFields($existing, $payload);
|
|
|
|
if (empty($appliedFields)) {
|
|
return [
|
|
'action' => 'skipped',
|
|
'entity' => $existing,
|
|
'message' => 'No changes detected',
|
|
];
|
|
}
|
|
|
|
$existing->fill($payload);
|
|
$existing->save();
|
|
|
|
return [
|
|
'action' => 'updated',
|
|
'entity' => $existing,
|
|
'applied_fields' => $appliedFields,
|
|
];
|
|
}
|
|
|
|
// Create new person
|
|
Log::info('PersonHandler: Creating new Person (no existing entity found)', [
|
|
'has_tax_number' => !empty($mapped['tax_number']),
|
|
'has_ssn' => !empty($mapped['social_security_number']),
|
|
'has_contract' => isset($context['contract']),
|
|
'has_client_case' => isset($context['client_case']),
|
|
]);
|
|
|
|
$person = new Person;
|
|
$payload = $this->buildPayload($mapped, $person);
|
|
|
|
// Ensure required foreign keys have defaults
|
|
if (!isset($payload['group_id'])) {
|
|
$payload['group_id'] = $this->getDefaultPersonGroupId();
|
|
}
|
|
if (!isset($payload['type_id'])) {
|
|
$payload['type_id'] = $this->getDefaultPersonTypeId();
|
|
}
|
|
|
|
Log::debug('PersonHandler: Payload before fill', [
|
|
'payload' => $payload,
|
|
'has_group_id' => isset($payload['group_id']),
|
|
'group_id_value' => $payload['group_id'] ?? null,
|
|
]);
|
|
|
|
$person->fill($payload);
|
|
|
|
Log::debug('PersonHandler: Person attributes after fill', [
|
|
'attributes' => $person->getAttributes(),
|
|
'has_group_id' => isset($person->group_id),
|
|
'group_id_value' => $person->group_id ?? null,
|
|
]);
|
|
|
|
$person->save();
|
|
|
|
Log::info('PersonHandler: Created new Person', [
|
|
'person_id' => $person->id,
|
|
]);
|
|
|
|
return [
|
|
'action' => 'inserted',
|
|
'entity' => $person,
|
|
'applied_fields' => array_keys($payload),
|
|
];
|
|
}
|
|
|
|
protected function buildPayload(array $mapped, $model): array
|
|
{
|
|
$payload = [];
|
|
|
|
$fieldMap = [
|
|
'first_name' => 'first_name',
|
|
'last_name' => 'last_name',
|
|
'full_name' => 'full_name',
|
|
'gender' => 'gender',
|
|
'birthday' => 'birthday',
|
|
'tax_number' => 'tax_number',
|
|
'social_security_number' => 'social_security_number',
|
|
'description' => 'description',
|
|
'group_id' => 'group_id',
|
|
'type_id' => 'type_id',
|
|
];
|
|
|
|
foreach ($fieldMap as $source => $target) {
|
|
if (array_key_exists($source, $mapped)) {
|
|
$value = $mapped[$source];
|
|
|
|
// Normalize date fields
|
|
if ($source === 'birthday' && $value) {
|
|
$value = DateNormalizer::toDate((string) $value);
|
|
}
|
|
|
|
$payload[$target] = $value;
|
|
}
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
private function getDefaultPersonGroupId(): int
|
|
{
|
|
return (int) (PersonGroup::min('id') ?? 1);
|
|
}
|
|
|
|
private function getDefaultPersonTypeId(): int
|
|
{
|
|
return (int) (PersonType::min('id') ?? 1);
|
|
}
|
|
}
|