import 'package:uto_manager/domain/entities/personnel_record.dart'; import 'package:uto_manager/domain/entities/position_slot.dart'; import 'package:uto_manager/domain/services/unit_tree_service.dart'; /// Domain service for assigning and unassigning soldiers to position slots. /// /// Encapsulates all business rules around personnel assignment, including /// MOS compatibility checks and rank validation. This service is pure domain /// logic with no infrastructure dependencies. class AssignmentService { const AssignmentService({required this.treeService}); /// The [UnitTreeService] used for validation. final UnitTreeService treeService; // --------------------------------------------------------------------------- // Assignment Operations // --------------------------------------------------------------------------- /// Attempts to assign [soldier] to [slot]. /// /// Returns a [ValidationResult] indicating success or the reason for failure. /// On success, returns updated copies of both the slot and the soldier record. AssignmentOutcome assign(PositionSlot slot, PersonnelRecord soldier) { final validation = treeService.validateAssignment(slot, soldier); if (!validation.isValid) { return AssignmentOutcome.failure(validation.reason!); } final mosMismatch = soldier.primaryMos != slot.dutyMos; final rankMismatch = !soldier.payGrade.meetsMinimum(slot.authorizedRank); final updatedSlot = slot.copyWith(assignedPersonnelId: soldier.soldierId); final updatedSoldier = soldier.copyWith(assignedSlotId: slot.slotId); return AssignmentOutcome.success( updatedSlot: updatedSlot, updatedSoldier: updatedSoldier, hasMosMismatch: mosMismatch, hasRankMismatch: rankMismatch, ); } /// Unassigns the soldier currently in [slot]. /// /// Returns updated copies of the slot and the soldier record, or null /// for the soldier if [soldier] is null (slot was already vacant). UnassignmentOutcome unassign(PositionSlot slot, PersonnelRecord? soldier) { if (slot.isVacant) { return UnassignmentOutcome( updatedSlot: slot, updatedSoldier: null, ); } final updatedSlot = slot.copyWith(clearAssignment: true); final updatedSoldier = soldier?.copyWith(clearAssignment: true); return UnassignmentOutcome( updatedSlot: updatedSlot, updatedSoldier: updatedSoldier, ); } // --------------------------------------------------------------------------- // Readiness Calculations // --------------------------------------------------------------------------- /// Returns the aggregate readiness color for a list of [personnel]. /// /// - All FMC → [ReadinessColor.green] /// - Any NMC → [ReadinessColor.red] /// - Otherwise → [ReadinessColor.amber] ReadinessColor aggregateReadiness(List personnel) { if (personnel.isEmpty) return ReadinessColor.grey; final hasNmc = personnel.any((p) => p.readinessState == ReadinessState.notMissionCapable); if (hasNmc) return ReadinessColor.red; final allFmc = personnel.every((p) => p.readinessState == ReadinessState.fullyMissionCapable); if (allFmc) return ReadinessColor.green; return ReadinessColor.amber; } } /// Color coding for readiness display. enum ReadinessColor { /// All soldiers fully mission capable. green, /// Mixed readiness states. amber, /// One or more soldiers not mission capable. red, /// No personnel data available. grey, } /// The outcome of an assignment attempt. class AssignmentOutcome { const AssignmentOutcome._({ required this.isSuccess, this.updatedSlot, this.updatedSoldier, this.failureReason, this.hasMosMismatch = false, this.hasRankMismatch = false, }); /// Creates a successful assignment outcome. factory AssignmentOutcome.success({ required PositionSlot updatedSlot, required PersonnelRecord updatedSoldier, bool hasMosMismatch = false, bool hasRankMismatch = false, }) { return AssignmentOutcome._( isSuccess: true, updatedSlot: updatedSlot, updatedSoldier: updatedSoldier, hasMosMismatch: hasMosMismatch, hasRankMismatch: hasRankMismatch, ); } /// Creates a failed assignment outcome with a [reason]. factory AssignmentOutcome.failure(String reason) { return AssignmentOutcome._(isSuccess: false, failureReason: reason); } /// Whether the assignment succeeded. final bool isSuccess; /// The updated slot (non-null on success). final PositionSlot? updatedSlot; /// The updated soldier record (non-null on success). final PersonnelRecord? updatedSoldier; /// The reason for failure (non-null on failure). final String? failureReason; /// Whether the assigned soldier's MOS does not match the slot's duty MOS. final bool hasMosMismatch; /// Whether the assigned soldier's rank does not meet the slot's minimum. final bool hasRankMismatch; } /// The outcome of an unassignment operation. class UnassignmentOutcome { const UnassignmentOutcome({ required this.updatedSlot, required this.updatedSoldier, }); /// The updated slot with the assignment cleared. final PositionSlot updatedSlot; /// The updated soldier record with the assignment cleared, or null if /// no soldier was assigned. final PersonnelRecord? updatedSoldier; }