| Line 2476... |
Line 2476... |
| 2476 |
otherBeat = ub;
|
2476 |
otherBeat = ub;
|
| 2477 |
break;
|
2477 |
break;
|
| 2478 |
}
|
2478 |
}
|
| 2479 |
}
|
2479 |
}
|
| 2480 |
|
2480 |
|
| - |
|
2481 |
// Multi-day beats can't be drag-swapped — moving one of their day_numbers
|
| - |
|
2482 |
// in isolation would leave the other days behind and break the
|
| - |
|
2483 |
// Day 1 → Day 2 → … chronological contiguity of the plan. Block both
|
| - |
|
2484 |
// sides of the swap, point the BM at the Reschedule flow which moves
|
| - |
|
2485 |
// every day together.
|
| - |
|
2486 |
if (beat.getTotalDays() > 1) {
|
| - |
|
2487 |
return responseSender.badRequest(
|
| - |
|
2488 |
"\"" + (beat.getName() != null ? beat.getName() : "Beat #" + beat.getId())
|
| - |
|
2489 |
+ "\" is a multi-day beat (" + beat.getTotalDays()
|
| - |
|
2490 |
+ " days). Drag-swap is only allowed for single-day beats. Use Reschedule to move the whole plan together.");
|
| - |
|
2491 |
}
|
| - |
|
2492 |
if (otherBeat != null && otherBeat.getTotalDays() > 1) {
|
| - |
|
2493 |
return responseSender.badRequest(
|
| - |
|
2494 |
"Target date already has \"" + (otherBeat.getName() != null ? otherBeat.getName() : "Beat #" + otherBeat.getId())
|
| - |
|
2495 |
+ "\", a multi-day beat (" + otherBeat.getTotalDays()
|
| - |
|
2496 |
+ " days). Drag-swap is only allowed when both beats are single-day. Use Reschedule on one of them first.");
|
| - |
|
2497 |
}
|
| - |
|
2498 |
|
| 2481 |
// Move the dragged beat onto the target date.
|
2499 |
// Move the dragged beat onto the target date. Per-row invariant:
|
| - |
|
2500 |
// each schedule row's end_date == its own start_date (single-day shape).
|
| - |
|
2501 |
// We touch ONLY the two rows being swapped — other schedule rows for the
|
| - |
|
2502 |
// same beat (e.g. older dates) are left exactly as they were.
|
| 2482 |
match.setStartDate(to);
|
2503 |
match.setStartDate(to);
|
| 2483 |
// If another beat occupied the target, slide it onto the source date (swap).
|
2504 |
match.setEndDate(to);
|
| 2484 |
if (otherSchedule != null) {
|
2505 |
if (otherSchedule != null) {
|
| 2485 |
otherSchedule.setStartDate(from);
|
2506 |
otherSchedule.setStartDate(from);
|
| 2486 |
}
|
- |
|
| 2487 |
|
- |
|
| 2488 |
// Recompute endDate as the max across each affected beat's schedules so
|
- |
|
| 2489 |
// the [startDate, endDate] envelope stays consistent.
|
- |
|
| 2490 |
recomputeEndDate(schedules);
|
- |
|
| 2491 |
if (otherSchedules != null) {
|
2507 |
otherSchedule.setEndDate(from);
|
| 2492 |
recomputeEndDate(otherSchedules);
|
- |
|
| 2493 |
}
|
2508 |
}
|
| 2494 |
|
2509 |
|
| 2495 |
Map<String, Object> response = new HashMap<>();
|
2510 |
Map<String, Object> response = new HashMap<>();
|
| 2496 |
response.put("status", true);
|
2511 |
response.put("status", true);
|
| 2497 |
response.put("message", otherBeat != null
|
2512 |
response.put("message", otherBeat != null
|
| Line 2599... |
Line 2614... |
| 2599 |
response.put("status", true);
|
2614 |
response.put("status", true);
|
| 2600 |
response.put("message", "Unscheduled from " + date);
|
2615 |
response.put("message", "Unscheduled from " + date);
|
| 2601 |
return responseSender.ok(response);
|
2616 |
return responseSender.ok(response);
|
| 2602 |
}
|
2617 |
}
|
| 2603 |
|
2618 |
|
| 2604 |
private void recomputeEndDate(List<BeatSchedule> schedules) {
|
- |
|
| 2605 |
LocalDate newEnd = schedules.stream()
|
- |
|
| 2606 |
.map(BeatSchedule::getStartDate)
|
- |
|
| 2607 |
.filter(d -> d != null && d.getYear() != 9999)
|
- |
|
| 2608 |
.max(LocalDate::compareTo).orElse(null);
|
- |
|
| 2609 |
if (newEnd == null) return;
|
- |
|
| 2610 |
for (BeatSchedule s : schedules) {
|
- |
|
| 2611 |
if (s.getStartDate() != null && s.getStartDate().getYear() != 9999) {
|
- |
|
| 2612 |
s.setEndDate(newEnd);
|
- |
|
| 2613 |
}
|
- |
|
| 2614 |
}
|
- |
|
| 2615 |
}
|
- |
|
| 2616 |
|
- |
|
| 2617 |
/**
|
2619 |
/**
|
| 2618 |
* Per-user one-beat-per-day guard. Walks every active beat the sales user
|
2620 |
* Per-user one-beat-per-day guard. Walks every active beat the sales user
|
| 2619 |
* already has and looks for a schedule row on any of the candidate dates.
|
2621 |
* already has and looks for a schedule row on any of the candidate dates.
|
| 2620 |
* Returns null if the candidate dates are clear, otherwise a {date,beatName}
|
2622 |
* Returns null if the candidate dates are clear, otherwise a {date,beatName}
|
| 2621 |
* map describing the first collision so the caller can surface a clean error.
|
2623 |
* map describing the first collision so the caller can surface a clean error.
|