Rolf B: Daten eintragen (Lösungsansatz)

Beitrag lesen

Hallo Jörg,

oookay. Du hast die Tabellen also normalisiert, das ist gut so.

Dann ist das Ab-Datum in der Tabelle 1 aber ausschließlich relevant, wenn in der Tabelle 2 noch keine Sätze stehen. Oder wenn Rechnungen ausgesetzt werden sollen, der Kunde also bspw. für 2 Monate beitragsfrei gestellt werden soll.

(Was meine ich: Der Kunde hat bis März 2021 Rechnungen bekommen, aus irgendeinem Grund soll er für April und Mai nicht bezahlen. Man könnte also das "ab" Datum in Tabelle 1 auf Juni 2021 stellen).

Sobald für diesen Kunden in Tabelle 2 bereits Sätze vorhanden sind, solltest Du Dich nicht am Ab-Datum in Tabelle 1 orientieren, sondern an den vorhandenen Einträgen in Tabelle 2. Statt schön, stark und mutig[1] die Monate im laufenden Jahr durchzuprobieren, solltest Du alle relevanten Einträge in Tabelle 2 per SELECT holen, absteigend nach Jahr, Monat sortieren und mit LIMIT 1 dafür sorgen, dass Du nur den neuesten bekommst. Wenn Du keinen Eintrag findest, verwendest Du ab_jahr und ab_monat aus Tabelle 1.

Damit hast Du deinen Startzeitpunkt T1. Speichere ihn in $LfdMonat und $LfdJahr.

Deinen Endezeitpunkt T2 bestimmst Du wie geschrieben. Du speicherst ihn im Moment in $Monat und $Jahr; ich würde die Variablen $EndMonat und $EndJahr nennen, der Klarheit wegen. Du hast für Monat und Jahr jeweils zwei Variablen, dann sollte keine davon nur $Monat heißen.

Und jetzt lässt Du für den aktuellen Tabelle-1 Satz eine Schleife laufen, solange T1 <= T2 ist. Wann ist ein Pärchen aus (monat,jahr) kleiner oder gleich einem anderen Pärchen?

Fall 1: Jahr 1 ist kleiner als Jahr 2. Der Monat ist egal. Fall 2: Jahr 1 ist gleich Jahr 2 UND Monat 1 ist kleiner oder gleich Monat 2.

Ich schlage damit diese PHP / Pseudocode Mischung als Lösung vor:

$ende = ARRAY($EndMonat, $EndJahr);

while($row_alle = mysqli_fetch_row($result_alle)) {
   $letzteRechnung = ... // 
   if (/* $letzteRechnung vorhanden */) {
      $lfd = ARRAY($letzteRechnung[ /* monat-spalte */ ],
                   $letzteRechnung[ /* jahr-spalte */ ]);
      // Beginne mit dem Folgemonat
      NächsterMonat($lfd);
   } else {
      // Erste Rechnung
      $lfd = ARRAY($row_alle[2],  /* monat */
                   $row_alle[3]);  /* jahr */
   }
   
   while (VergleicheZeitpunkt($lfd, $ende <= 0) {
      // Schreibe Satz für LfdMonat, LfdJahr in Tabelle 2

      NächsterMonat($lfd);
   }
}

function NächsterMonat(&$mj) {   // <-- Referenz!!!
   $mj[0]++;
   if ($m[0] > 12) {
      $m[0] = 1;
      $j[0]++;
   }
}

// Liefert -1 wenn $mj1 < $mj2, 0 wenn gleich und 1 wenn >
function VergleicheZeitpunkt($mj1, $mj2) {
   // Spaceship Operator <=> ab PHP 7!
   $ergebnis = $mj1[0] <=> $mj2[0];
   return $ergebnis == 0 ? $ergebnis : $mj1[1] <=> $mj2[1];
}

Ich fasse Monat und Jahr in ein Array zusammen, weil man so einen m/j Zeitpunkt leicht als Parameter übergeben kann. Eine Klasse dafür zu machen wäre noch schicker, aber das ist nicht dein Ding, soweit ich weiß.

Die beiden Helper-Funktionen machen den Code lesbarer und selbstdokumentierender (und wären gerne Methoden einer Klasse 😉). VergleicheZeitpunkt funktioniert so ab PHP 7, vor PHP 7 muss man es mühsamer mit 4 IF Abfragen machen.

Den Zeitpunkt-Vergleich hattest Du bisher falsch. Das ist ein Tupelvergleich, die vergleicht man Wert für Wert.

VergleicheZeitpunkt geht so vor, dass es zuerst die Jahreswerte vergleicht. Sind sie verschieden, ist der Monat nicht mehr relevant. Nur wenn sie gleich sind (<=> liefert für die Jahre 0), dürfen die Monate verglichen werden.

Was hältst Du von diesem Plan?

Rolf

--
sumpsi - posui - obstruxi

  1. schön gegen das Duplikat gerannt, stark zurückgeschleudert worden und mutig neu probiert ↩︎