Files
votianng/backend/src/main/java/de/stadtbote/votian/OrderEntrySeries.java
T
Sven 363bc27c26 Erweitere VoTiAn NG um Auftrags-, Stamm- und Verwaltungsfunktionen
Ergänzt Auftragserfassung und -bearbeitung, Unternehmerbelege, Stornos, Lagerkorrekturen, Stammdaten, Abrechnung, CRM, Formulare, Berichte und Preisverwaltung auf Basis der aktuellen PHP-Anwendung.

Enthält Integrations- und Oberflächentests sowie dokumentierte Abgleiche und Prüfstände.
2026-10-13 20:31:31 +02:00

57 lines
3.5 KiB
Java

package de.stadtbote.votian;
import de.stadtbote.votian.database.DatabaseService;
import java.time.*;
import java.util.*;
import static de.stadtbote.votian.OrderEntryModels.*;
import static de.stadtbote.votian.OrderEntryCatalog.invalid;
import static de.stadtbote.votian.LegacyMapping.id;
/** getNextOrderDate / isPublicHoliday: holidays are read from the maintained HQ calendar. */
final class OrderEntrySeries {
static List<LocalDateTime> dates(DatabaseService db,LegacyUser user,Request request) {
if(!request.allOccurrences())return Collections.singletonList(request.pickupAt());
if(request.mode()!=Mode.RECURRING || request.recurrence()==null || request.recurrence().endDate()==null)
throw invalid("Für die sofortige Serienanlage sind Dauerauftrag und Serienende erforderlich.");
var start=request.pickupAt();var end=request.recurrence().endDate();
if(end.isAfter(start.toLocalDate().plusYears(2)))throw invalid("Die sofortige Serienanlage ist auf zwei Jahre begrenzt.");
var holidays=new HashSet<LocalDate>();
for(var row:db.queryForList("SELECT ph_year,ph_month,ph_day FROM publicholiday WHERE hq_id=? AND ph_year BETWEEN ? AND ?",user.headquartersId(),start.getYear(),end.getYear()))
holidays.add(LocalDate.of((int)id(row,"ph_year"),(int)id(row,"ph_month"),(int)id(row,"ph_day")));
var dates=new ArrayList<LocalDateTime>();dates.add(start);
var date=start.toLocalDate();
while(true) {
date=next(date,request.recurrence(),holidays,end);
if(date.isAfter(end))break;
var at=date.atTime(start.toLocalTime());
if(ZoneId.of("Europe/Berlin").getRules().getValidOffsets(at).size()!=1)throw invalid("Ein Serientermin fällt in eine ungültige oder mehrdeutige Uhrzeit bei der Zeitumstellung: "+at);
dates.add(at);
if(dates.size()*request.copies()>500 || dates.size()*request.copies()*request.stops().size()>10000)
throw invalid("Die sofortige Serie ist zu groß (maximal 500 Aufträge bzw. 10.000 Stationen). Bitte das Serienende eingrenzen.");
}
return dates;
}
static LocalDate next(LocalDate previous,Recurrence recurrence,Set<LocalDate> holidays,LocalDate end) {
int rhythm=recurrence.rhythm();LocalDate next;
if(rhythm<=3 || rhythm==9) {
for(next=previous.plusDays(1);!next.isAfter(end);next=next.plusDays(1)) {
boolean holiday=holidays.contains(next),weekday=recurrence.weekdays().charAt(next.getDayOfWeek().getValue()-1)=='1';
if((rhythm==9 && holiday) || (!holiday && weekday))return next;
}
return next;
}
boolean backwards=rhythm==7 || rhythm==8;
next=switch(rhythm) {
case 4->previous.plusWeeks(1);case 5->previous.plusWeeks(2);case 11->previous.plusWeeks(4);
case 6->previous.plusMonths(1);
case 7->previous.plusMonths(1).withDayOfMonth(previous.plusMonths(1).lengthOfMonth());
case 8->previous.getDayOfMonth()<20?previous.withDayOfMonth(previous.lengthOfMonth()):previous.plusMonths(1).withDayOfMonth(15);
case 10->previous.getDayOfMonth()<16?previous.withDayOfMonth(16):previous.plusMonths(1).withDayOfMonth(1);
default->throw invalid("Ungültiger Serienrhythmus.");
};
while(holidays.contains(next))next=next.plusDays(backwards?-1:1);
if(!next.isAfter(previous))throw invalid("Der Feiertagskalender lässt keinen folgenden Serientermin zu.");
return next;
}
}