Hallo Bernd,
natürlich ist das möglich.
Du hast zwei Eingaben:
- Die verfügbaren Zeiten des Mitarbeiters. Das ist ein Array aus Zeitintervallen (wenn's keine Termine gibt, ist das ein Eintrag, oder vielleicht auch mehr, der MA könnte ja geplant von 9-12 und 18-21 Uhr da sein).
- Verplante Zeiträume des Mitarbeiters, ein Array mit 0-N Zeitintervallen.
Für den Fall "nach Bedarf" brauchst Du einen Sonderfall, wenn jemand bedarfsweise da ist, dann bleibt das wohl bedarfsweise, egal wie viele Termine schon sind, oder?
Wie dein Beispiel 1 zeigt, ist es durchaus möglich, dass ein verplanter Zeitraum übe die reguläre Anwesenheitszeit hinausgeht. Macht aber nichts.
Man sollte erwarten, dass die verplanten Zeiträume des Mitarbeiters sich nicht überschneiden. Aber selbst wenn - egal, das ist das gleiche Problem wie ein Termin, der vor Arbeitsbeginn beginnt.
Du gehst nun die verplanten Zeiträume (Termine) des MA durch. Pro Termin gehst Du das Array mit Verfügbaren Zeiträumen durch und prüfst, ob der Termin den verfügbaren Zeitraum überschneidet:
Erstmal eine Art Pseudo-PHP Code:
foreach ($termine as $termin) {
foreach ($verfuegbareZeiten as $verfuegbar) {
if (/* $termin überschneidet $verfuegbar */) {
/* manipulieren */
}
}
}
Ein Zeitraum ist ein Tupel aus Anfang und Endezeitpunkt (dafür bieten sich Objekte an, willst Du da einsteigen? Die Alternative ist ein Array mit zwei Einträgen. z.B.
[ 'von' => '11:30', 'bis' => '12:30' ]
).
Wann überschneiden sich zwei Zeiträume z1, z2? Die einfachere Frage ist, wann sie sich nicht überschneiden. Nämlich: Wenn das Ende des einen vor dem Anfang des anderen liegt (oder Ende und Anfang gleich sind). Also: Wenn z1.bis <= z2.von ODER z2.bis <= z1.von. Diese Abfrage invertieren wir im if und sind fertig.
Würde man mit Objekten programmieren, sähe die Abfrage so aus:
if ($z1->ueberschneidet($z2)) {
// manipulieren
}
Ja, tatsächlich. Sowas würde man als Methode der Zeitintervall-Klasse bauen und schön sprechend formulieren:
class Zeitintervall
{
public $von;
public $bis;
public function beginnt_vor($z2) {
return $this->von <= $z2->von;
}
public function endet_nach($z2) {
return $this->bis >= $z2->bis;
}
public function endet_vor($z2) {
return $this->bis <= $z2->von;
}
public function ueberschneidet($z2) {
return !($this->endet_vor($z2) || $z2->endet_vor($this));
}
}
Mit Arrays könnte man die Abfrage am Ort und Stelle formulieren:
if (!($z1['bis'] <= $z2['von'] || $z2['bis'] <= $z2['von']))
// manipulieren
}
Oder es mit einer Helper-Funktion lesbarer machen:
if (!(endet_vor($z1, $z2) || liegt_vor($z2, $z1)))
// manipulieren
}
…
// anderswo
function endet_vor($z1, z2) {
return $z1['bis'] <= $z2['von'];
}
Das Thema "Manipulieren" ist schon etwas schwieriger. Ein Termin kann mit einem verfügbaren Zeitraum 5 Dinge tun:
- gar nichts (wenn sie sich nicht überschneiden)
- vom Anfang etwas wegschneiden ('von' wird größer)
- vom Ende etwas wegschneiden ('bis' wird kleiner)
- eliminieren (komplette überlappung)
- teilen (liegt mitten drin)
Wegschneiden ist noch harmlos, weil du dann nur einen bestehenden Eintrag der verfügbaren Zeiten abändern musst. Eliminieren und Teilen würde das iterierte Array verändern, und sowas ist immer sehr tricky. Die sicherste Lösung ist, die verfügbaren Zeiten pro Durchlauf in ein temporäres Array umzukopieren, und danach das temporäre Array als verfügbare Zeiten zu verwenden.
Der Code verwendet die Zeitintervall-Klasse. Wenn Du lieber kleine Arrays für die Intervalle verwenden willst, dann mach es ähnlich wie oben und übersetze die Methoden der Klasse in Helper-Funktionen. Oder baue die jeweilige Logik direkt an Ort und Stelle ein.
foreach ($termine as $termin)
{
$tempVerfuerbar = ARRAY();
foreach ($verfuegbareZeiten as $verfuegbar)
{
if ($termin->ueberschneidet($verfuegbar))
{
if ($termin->beginnt_vor($verfuegbar))
{
if ($termin->endet_nach($verfuegbar))
{
$verfuegbar = null;
}
else
{
// Verfügbare Zeit am Anfang verringern
$verfuegbar->von = $termin->bis;
}
}
else if ($verfuegbar->beginnt_vor($termin)
{
if ($verfuegbar->endet_vor($termin)
{
// Termin liegt mitten drin. Vorderes Fragment ins tempArray legen
array_push($tempVerfuegbar, new Zeitraum($verfuegbar->von, $termin_von));
// $verfuegbar wird zum hinteren Fragment.
$verfuegbar->von = $termin->bis;
}
else {
// Verfügbare Zeit am Ende verringern
$verfuegbar->bis= $termin->von;
}
}
}
if ($verfuegbar)
{
// $verfuegbar-Zeitraum an Temparray anhängen, sofern er nicht gelöscht wurde.
array_push($tempVerfuegbar, $verfuegbar);
}
}
$verfuegbareZeiten = $tempVerfuegbar;
}
Statt mit Objekten kannst Du das auch alles mit Arrays realisieren, es ist dann nur schwerer lesbar.
Das ist so grob die Idee. Anwenden auf deinen Fall kannst Du sie sicher selbst.
Zu deiner fetten if (wochentag[date....])
Logik würde ich Dir folgende Variante empfehlen (funktioniert ab PHP 5.5, vorher brauchst Du eine Extravariable für das Array mit den Kürzeln):
<?php
$w_prefix = ARRAY('so', 'mo', 'di', 'mi', 'do', 'fr', 'sa')[date("w", $zeit)];
$von = $arrayUserAktiv["{$w_prefix}_von"];
$bis = $arrayUserAktiv["{$w_prefix}_bis"];
if ($von != "")
{
echo "$von Uhr bis $bis Uhr";
}
else
{
echo "nach Bedarf";
}
// oder statt if:
echo $von == "" ? "nach Bedarf" : "$von Uhr bis $bis Uhr";
?>
In der ?: Version sind es dann 4 Zeilen, statt 56.
Aus $von und $nach baust Du, für die Verfügbarkeitssuche, deinen initialen Verfügbarkeitszeitraum auf und führst dann die von mir skizzierte Logik durch. Das ist jetzt keine fertige Lösung, und ich bin auch nicht sicher, dass es tippfehlerfrei ist. Es ist wie üblich VHIG[1]-Code und der neigt nun mal dazu, kleinere Korrekturen zu brauchen. Aber das Prinzip sollte jetzt klar sein. Viel Glück 😉
sumpsi - posui - clusi
Vom Hirn Ins Gerät Rolf ↩︎