trucks (with optional last_known_coordinates POINT 4326), collection_teams, team_members. Admin CRUD for both. TeamConflictDetector flags double-assignment of driver/scanner/truck/helper across active teams; override_conflicts: true bypasses for emergencies. Promotes routes.default_team_id to a real FK now that collection_teams exists. 144 feature tests passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
2.8 KiB
PHP
83 lines
2.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Team;
|
|
|
|
use App\Models\CollectionTeam;
|
|
use App\Models\TeamMember;
|
|
use App\Models\Truck;
|
|
|
|
class TeamConflictDetector
|
|
{
|
|
/**
|
|
* Returns an array of human-readable conflict messages for the given
|
|
* team composition. Empty array means no conflicts.
|
|
*/
|
|
public function check(
|
|
?int $driverId,
|
|
?int $scannerId,
|
|
?int $truckId,
|
|
array $helperIds = [],
|
|
?int $excludeTeamId = null,
|
|
): array {
|
|
$conflicts = [];
|
|
|
|
if ($driverId) {
|
|
$existing = CollectionTeam::query()
|
|
->where('status', CollectionTeam::STATUS_ACTIVE)
|
|
->where('driver_id', $driverId)
|
|
->when($excludeTeamId, fn ($q, $id) => $q->where('id', '!=', $id))
|
|
->first();
|
|
if ($existing) {
|
|
$conflicts[] = "Driver already on team {$existing->name}";
|
|
}
|
|
}
|
|
|
|
if ($scannerId) {
|
|
$existing = CollectionTeam::query()
|
|
->where('status', CollectionTeam::STATUS_ACTIVE)
|
|
->where('scanner_id', $scannerId)
|
|
->when($excludeTeamId, fn ($q, $id) => $q->where('id', '!=', $id))
|
|
->first();
|
|
if ($existing) {
|
|
$conflicts[] = "Scanner already on team {$existing->name}";
|
|
}
|
|
}
|
|
|
|
if ($truckId) {
|
|
$existing = CollectionTeam::query()
|
|
->where('status', CollectionTeam::STATUS_ACTIVE)
|
|
->where('truck_id', $truckId)
|
|
->when($excludeTeamId, fn ($q, $id) => $q->where('id', '!=', $id))
|
|
->first();
|
|
if ($existing) {
|
|
$conflicts[] = "Truck already on team {$existing->name}";
|
|
}
|
|
|
|
$truck = Truck::find($truckId);
|
|
if ($truck && $truck->status !== Truck::STATUS_ACTIVE) {
|
|
$conflicts[] = "Truck status is {$truck->status}, not active";
|
|
}
|
|
}
|
|
|
|
if (! empty($helperIds)) {
|
|
$today = now()->toDateString();
|
|
$assigned = TeamMember::query()
|
|
->whereIn('user_id', $helperIds)
|
|
->where('role_in_team', TeamMember::ROLE_HELPER)
|
|
->whereDate('assigned_from', '<=', $today)
|
|
->where(function ($q) use ($today) {
|
|
$q->whereNull('assigned_until')->orWhereDate('assigned_until', '>=', $today);
|
|
})
|
|
->when($excludeTeamId, fn ($q, $id) => $q->where('team_id', '!=', $id))
|
|
->with('user:id,first_name,last_name')
|
|
->get();
|
|
|
|
foreach ($assigned as $tm) {
|
|
$conflicts[] = "Helper {$tm->user?->first_name} {$tm->user?->last_name} already assigned to another team";
|
|
}
|
|
}
|
|
|
|
return $conflicts;
|
|
}
|
|
}
|