Files
RelaisSMS/app/Services/Recipients/RecipientResolver.php
T

174 lines
5.6 KiB
PHP

<?php
namespace App\Services\Recipients;
use App\Models\Contact;
use App\Models\ContactGroup;
use App\Models\ContactPhoneNumber;
use Illuminate\Support\Collection;
class RecipientResolver
{
/**
* @param array<int, int|string> $contactIds
* @param array<int, int|string> $groupIds
*/
public function resolve(array $contactIds, array $groupIds): RecipientResolutionResult
{
$directContactIds = $this->cleanIds($contactIds);
$selectedGroupIds = $this->cleanIds($groupIds);
$groups = ContactGroup::query()
->with(['contacts' => fn ($query) => $query->select('contacts.id')])
->whereKey($selectedGroupIds)
->orderBy('name')
->get();
/** @var array<int, array<int, string>> $groupNamesByContactId */
$groupNamesByContactId = [];
foreach ($groups as $group) {
foreach ($group->contacts as $contact) {
$groupNamesByContactId[$contact->id][] = $group->name;
}
}
$selectedContactIds = $directContactIds
->merge($groups->flatMap(fn (ContactGroup $group): Collection => $group->contacts->pluck('id')))
->unique()
->values();
$contacts = Contact::query()
->with([
'groups',
'phoneNumbers' => fn ($query) => $query
->orderByDesc('is_primary')
->orderBy('id'),
])
->whereKey($selectedContactIds)
->orderBy('last_name')
->orderBy('first_name')
->get();
$recipients = [];
$excluded = [];
$seenNumbers = [];
foreach ($contacts as $contact) {
$selection = $this->selectionLabels($contact, $directContactIds, $groupNamesByContactId[$contact->id] ?? []);
if ($contact->sms_opt_out) {
$excluded[] = $this->excludedEntry($contact, 'opt_out', null, $selection);
continue;
}
if ($contact->phoneNumbers->isEmpty()) {
$excluded[] = $this->excludedEntry($contact, 'missing_phone', null, $selection);
continue;
}
foreach ($contact->phoneNumbers as $phoneNumber) {
if (! $phoneNumber->is_valid || $phoneNumber->normalized_number === null) {
$excluded[] = $this->excludedEntry($contact, 'invalid', $phoneNumber, $selection);
continue;
}
if (! $phoneNumber->hasDetectedMobileNumber()) {
$excluded[] = $this->excludedEntry($contact, 'non_mobile', $phoneNumber, $selection);
continue;
}
if (isset($seenNumbers[$phoneNumber->normalized_number])) {
$excluded[] = $this->excludedEntry(
$contact,
'duplicate',
$phoneNumber,
$selection,
['duplicate_of' => $seenNumbers[$phoneNumber->normalized_number]],
);
continue;
}
$entry = [
'contact_id' => $contact->id,
'contact_name' => $contact->displayName(),
'phone_number_id' => $phoneNumber->id,
'number' => $phoneNumber->normalized_number,
'raw_number' => $phoneNumber->raw_number,
'label' => $phoneNumber->label,
'selection' => $selection,
];
$recipients[] = $entry;
$seenNumbers[$phoneNumber->normalized_number] = $entry['contact_name'];
}
}
return new RecipientResolutionResult(
recipients: $recipients,
excluded: $excluded,
selectedContactCount: $contacts->count(),
selectedGroupCount: $groups->count(),
);
}
/**
* @param array<int, int|string> $ids
* @return Collection<int, int>
*/
private function cleanIds(array $ids): Collection
{
return collect($ids)
->map(fn (int|string $id): int => (int) $id)
->filter(fn (int $id): bool => $id > 0)
->unique()
->values();
}
/**
* @param Collection<int, int> $directContactIds
* @param array<int, string> $groupNames
* @return array<int, string>
*/
private function selectionLabels(Contact $contact, Collection $directContactIds, array $groupNames): array
{
$selection = $groupNames;
if ($directContactIds->contains($contact->id)) {
array_unshift($selection, 'Selection directe');
}
return array_values(array_unique($selection));
}
/**
* @param array<int, string> $selection
* @param array<string, mixed> $extra
* @return array<string, mixed>
*/
private function excludedEntry(
Contact $contact,
string $reason,
?ContactPhoneNumber $phoneNumber,
array $selection,
array $extra = [],
): array {
return array_merge([
'contact_id' => $contact->id,
'contact_name' => $contact->displayName(),
'contact_source' => $contact->source,
'phone_number_id' => $phoneNumber?->id,
'reason' => $reason,
'number' => $phoneNumber?->normalized_number,
'raw_number' => $phoneNumber?->raw_number,
'label' => $phoneNumber?->label,
'selection' => $selection,
], $extra);
}
}