Files
Verde-Web/app/Services/Team/TeamConflictDetector.php
admin 15f9295d34 feat(backend): complete Module 9 (teams + trucks)
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>
2026-04-30 02:48:40 +08:00

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;
}
}