|
26 | 26 | import lombok.RequiredArgsConstructor; |
27 | 27 |
|
28 | 28 | import java.time.LocalDate; |
| 29 | +import java.util.Set; |
29 | 30 |
|
30 | 31 | @Service |
31 | 32 | @RequiredArgsConstructor |
32 | 33 | public class TicketService { |
33 | 34 |
|
| 35 | + private static final Set<TicketStatus> ACTIVE_TICKET_STATUSES = Set.of(TicketStatus.OPEN, TicketStatus.IN_PROGRESS); |
| 36 | + |
34 | 37 | private final TicketRepository ticketRepository; |
35 | 38 | private final UserRepository userRepository; |
36 | 39 | private final InventoryStockRepository inventoryStockRepository; |
@@ -103,6 +106,10 @@ public TicketResponse createTicket(CreateTicketRequest request) { |
103 | 106 | } |
104 | 107 | } |
105 | 108 |
|
| 109 | + if (ticketType == TicketType.SHIFT_CHANGE) { |
| 110 | + validateNoDuplicateActiveShiftTicket(request, requesterAssignment); |
| 111 | + } |
| 112 | + |
106 | 113 | Ticket ticket = Ticket.builder() |
107 | 114 | .ticketCode(generateTicketCode(ticketType)) |
108 | 115 | .ticketType(ticketType) |
@@ -143,6 +150,85 @@ public TicketResponse createTicket(CreateTicketRequest request) { |
143 | 150 | return mapToResponse(saved); |
144 | 151 | } |
145 | 152 |
|
| 153 | + private void validateNoDuplicateActiveShiftTicket(CreateTicketRequest request, WorkShiftAssignment requesterAssignment) { |
| 154 | + Integer requesterId = request.getRequesterUserId() != null ? request.getRequesterUserId() : request.getCreatedById(); |
| 155 | + if (requesterId == null) { |
| 156 | + return; |
| 157 | + } |
| 158 | + |
| 159 | + List<Ticket> activeTickets = ticketRepository.findByTicketTypeAndCreatedByIdAndStatusIn( |
| 160 | + TicketType.SHIFT_CHANGE, |
| 161 | + requesterId, |
| 162 | + List.copyOf(ACTIVE_TICKET_STATUSES)); |
| 163 | + |
| 164 | + String incomingAction = resolveTicketAction(request.getTitle(), request.getRelatedEntityType()); |
| 165 | + Long incomingRelatedEntityId = resolveRelatedEntityId(request, requesterAssignment); |
| 166 | + Integer incomingRequesterAssignmentId = request.getSwapRequesterAssignmentId() != null |
| 167 | + ? request.getSwapRequesterAssignmentId() |
| 168 | + : requesterAssignment != null ? requesterAssignment.getId() : null; |
| 169 | + Integer incomingTargetUserId = request.getSwapTargetUserId() != null |
| 170 | + ? request.getSwapTargetUserId() |
| 171 | + : request.getAssignedToUserId(); |
| 172 | + |
| 173 | + for (Ticket active : activeTickets) { |
| 174 | + String existingAction = resolveTicketAction(active.getTitle(), active.getRelatedEntityType()); |
| 175 | + if (!incomingAction.equals(existingAction)) { |
| 176 | + continue; |
| 177 | + } |
| 178 | + |
| 179 | + if ("SHIFT_SWAP".equals(incomingAction)) { |
| 180 | + Integer existingRequesterAssignmentId = parseMetaInt(active.getDescription(), "SWAP_REQUESTER_ASSIGNMENT_ID"); |
| 181 | + Integer existingTargetUserId = parseMetaInt(active.getDescription(), "SWAP_TARGET_USER_ID"); |
| 182 | + |
| 183 | + if (incomingRequesterAssignmentId != null |
| 184 | + && incomingRequesterAssignmentId.equals(existingRequesterAssignmentId) |
| 185 | + && incomingTargetUserId != null |
| 186 | + && incomingTargetUserId.equals(existingTargetUserId)) { |
| 187 | + throw new RuntimeException("Đã có ticket đổi ca đang chờ xử lý cho ca này"); |
| 188 | + } |
| 189 | + continue; |
| 190 | + } |
| 191 | + |
| 192 | + if (incomingRelatedEntityId != null && incomingRelatedEntityId.equals(active.getRelatedEntityId())) { |
| 193 | + throw new RuntimeException("Đã có ticket cùng loại đang chờ xử lý cho ca đã chọn"); |
| 194 | + } |
| 195 | + } |
| 196 | + } |
| 197 | + |
| 198 | + private String resolveTicketAction(String title, String relatedEntityType) { |
| 199 | + String normalizedTitle = title == null ? "" : title.trim().toLowerCase(); |
| 200 | + String normalizedEntityType = relatedEntityType == null ? "" : relatedEntityType.trim().toUpperCase(); |
| 201 | + |
| 202 | + if ("SHIFT_SWAP".equals(normalizedEntityType) |
| 203 | + || normalizedTitle.contains("đổi ca") |
| 204 | + || normalizedTitle.contains("nhờ nhận ca")) { |
| 205 | + return "SHIFT_SWAP"; |
| 206 | + } |
| 207 | + if (normalizedTitle.contains("nghỉ ca")) { |
| 208 | + return "SHIFT_CANCEL"; |
| 209 | + } |
| 210 | + if (normalizedTitle.contains("cập nhật ca")) { |
| 211 | + return "SHIFT_UPDATE"; |
| 212 | + } |
| 213 | + return normalizedEntityType; |
| 214 | + } |
| 215 | + |
| 216 | + private Integer parseMetaInt(String description, String key) { |
| 217 | + if (description == null || description.isBlank()) { |
| 218 | + return null; |
| 219 | + } |
| 220 | + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\[" + key + "=(\\d+)\\]"); |
| 221 | + java.util.regex.Matcher matcher = pattern.matcher(description); |
| 222 | + if (!matcher.find()) { |
| 223 | + return null; |
| 224 | + } |
| 225 | + try { |
| 226 | + return Integer.parseInt(matcher.group(1)); |
| 227 | + } catch (NumberFormatException ex) { |
| 228 | + return null; |
| 229 | + } |
| 230 | + } |
| 231 | + |
146 | 232 | private WorkShiftAssignment validateShiftSwapTicketRequest(CreateTicketRequest request) { |
147 | 233 | if (request.getRequesterUserId() == null) { |
148 | 234 | throw new RuntimeException("Thiếu requesterUserId cho ticket đổi ca"); |
|
0 commit comments