<?php
/**
 * @copyright (c) Kufer Software GmbH, Oderstraße 7, D-84453 Mühldorf
 */


class BelegungAPI
{
  public const BELEGUNG_BEARBEITEN_KEINE_AENDERUNG = 'belegungBearbeitenKeineAenderung';
  public const BELEGUNG_BEARBEITEN_FEHLER = 'belegungBearbeitenFehler';
  public const BELEGUNG_BEARBEITEN_TECHNISCHER_FEHLER = 'belegungBearbeitenTechnischerFehler';
  public const BELEGUNG_BEARBEITEN_ERFOLGREICH = 'belegungBearbeitenErfolgreich';

  public const STATUS_AENDERUNG_TYP_ANMELDUNG = 'statusAenderungTypAnmeldung';
  public const STATUS_AENDERUNG_TYP_STORNO = 'statusAenderungTypStorno';
  public const STATUS_AENDERUNG_TYP_EINFACH = 'statusAenderungTypEinfach';
  public const STATUS_AENDERUNG_TYP_KEINE = 'statusAenderungTypKeine';

  public const STATUS_AENDERUNG_ANMELDUNG_ERFOLGREICH = 'statusAenderungAnmeldungErfolgreich';
  public const STATUS_AENDERUNG_ANMELDUNG_FEHLER = 'statusAenderungAnmeldungFehler';
  public const STATUS_AENDERUNG_STORNO_ERFOLGREICH = 'statusAenderungStornoErfolgreich';
  public const STATUS_AENDERUNG_STORNO_FEHLER = 'statusAenderungStornoFehler';
  public const STATUS_AENDERUNG_EINFACH_ERFOLGREICH = 'statusAenderungEinfachErfolgreich';
  public const STATUS_AENDERUNG_EINFACH_FEHLER = 'statusAenderungEinfachFehler';
  public const STATUS_AENDERUNG_KEINE = 'statusAenderungKeine';
  public const STATUS_AENDERUNG_BELEGUNGSDATEN_NICHT_AKTUELL = 'statusAenderungBelegungsdatenNichtAktuell';
  public const STATUS_AENDERUNG_PER_MAIL_VERSENDET = 'statusAenderungPerMailVersendet';
  public const STATUS_AENDERUNG_PER_MAIL_FEHLER = 'statusAenderungPerMailFehler';

  public const STATUS_AENDERUNG_HINDERUNG_TEIL_EINER_MEHRFACHANMLEDUNG = 'hinderungTeilEinerMehrfachFirmenAnmeldung';
  public const STATUS_AENDERUNG_HINDERUNG_TEIL_EINER_ELTERN_KIND_ANMELDUNG = 'hinderungElternKindAnmeldung';

  
  public static function storniereBelegungDMZ(
    $pBelegId,
    $pStatus,
    $pStornogrund,
    $pAnzahlFreiwerdenderPlaetze = 1,
    $pDebug = false,
    $pFirmenBelegungen = array(),
    $pAustritt = null,
    $pNoMapping = false,
    $pWartelistenHandlingDmz = true
  ) {
    $abgleichAPI = new AbgleichAPI();

    if (!$abgleichAPI->isAktiviert() || !SessionOperations::isReplikat()) {
      throw new Exception('Fehlkonfiguration - soll Belegung per DMZ stornieren, aber die DMZ ist nicht aktiviert oder bin im Direktbetrieb');
    }

    $abmeldung = new AbgleichAbmeldung();
    $abmeldung->id = $pBelegId;
    $abmeldung->status = $pStatus;
    $abmeldung->stornogrund = $pStornogrund;
    
    $abmeldung->anzahlfrei = $pAnzahlFreiwerdenderPlaetze;
    $abmeldung->austritt = $pAustritt !== '' ? $pAustritt : DateTimeOperations::getDateNow();
    if ($pWartelistenHandlingDmz) {
      $abmeldung->wartelistenhandling = 'W';
    } else {
      $abmeldung->wartelistenhandling = 'F';
    }






    
    $message = XMLNode::getXMLNode($abmeldung, 'belegung');

    if (count($pFirmenBelegungen) > 0) {
      $belegungenNode = new XMLNode('firmenBelegungen');
      foreach ($pFirmenBelegungen as $firmenBelegung) {
        $mitarbeiterNode = new XMLNode('id', $firmenBelegung->id);
        $belegungenNode->addChild($mitarbeiterNode);
      }
      $message->addChild($belegungenNode);
    }
    if ($pDebug) {
      $stornoMessage = 'Folgende XML-Nachricht wurde zwecks Storno an die DMZ versendet:' . "\n";
      $stornoMessage .= $message->getXMLstring();
      Logger::Logg($stornoMessage, 100);
    }

    $nutzlast = new Nutzlast($message);
    
    if ($pNoMapping) {
      $nutzlast->addNode(new XMLNode('nomapping', 'W'));
    }


    
    try {
      $result = $abgleichAPI->sendeNachricht(
        AbgleichAPI::ABGLEICH_ABMELDUNG,
        $nutzlast
      );
    } catch (Exception $e) {
      
      throw $e;
    }

    return $result;
  }

  
  public static function storniereBelegungMitAboGebuehrDMZ($pBelegId, $pStornoGrund, $pAustritt)
  {
    if ($pAustritt == '') {
      $pAustritt = DateTimeOperations::getDateNow();
    }
    $abgleichAPI = new AbgleichAPI();

    if (!$abgleichAPI->isAktiviert() || !SessionOperations::isReplikat()) {
      throw new Exception('Fehlkonfiguration - soll Belegung per DMZ stornieren, aber die DMZ ist nicht aktiviert oder bin im Direktbetrieb');
    }

    $message = new XMLNode('belegung');
    $message->addChild(new XMLNode('id', $pBelegId));
    $message->addChild(new XMLNode('stornogrund', $pStornoGrund));
    $message->addChild(new XMLNode('austritt', $pAustritt));
    try {
      $result = $abgleichAPI->sendeNachricht(
        AbgleichAPI::ABGLEICH_ABMELDUNG_KURS_MIT_ABO_GEBUEHR,
        new Nutzlast($message)
      );
    } catch (Exception $e) {
      
      throw $e;
    }

    return $result;
  }

  

  private static function getBelegungsStatusAendernObject($belegung, $status_neu, $anmeldeDatumNeuSetzen, $bet_offen_alt, $entgelt_alt, $zugehoerigeBelegungId = -1)
  {
    $belStatusAendernObj = new AbgleichBelegungStatusAendern();
    $belStatusAendernObj->id = $belegung->id;
    $belStatusAendernObj->status_alt = $belegung->status;
    $belStatusAendernObj->status_neu = $status_neu;
    $belStatusAendernObj->entgelt = $belegung->entgelt;
    $belStatusAendernObj->bet_offen = $belegung->bet_offen;
    $belStatusAendernObj->anmeldedatumNeuSetzen = $anmeldeDatumNeuSetzen;
    $belStatusAendernObj->bet_offen_alt = $bet_offen_alt;
    $belStatusAendernObj->entgelt_alt = $entgelt_alt;
    $belStatusAendernObj->zugehoerigeBelegungId = $zugehoerigeBelegungId;

    return $belStatusAendernObj;
  }

  public static function getBelegungsinformationen($belId, $noMapping = false)
  {
    $abgleichApi = new \AbgleichAPI();
    $infoNode = new \XMLNode('Belegungstatus');
    $infoNode->addChild(new \XMLNode('belid', $belId));

    return $abgleichApi->sendeNachricht(
      \AbgleichAPI::ABGLEICH_GETBELEGUNGSTATUS,
      new \Nutzlast($infoNode),
      null,
      $noMapping
    );
  }

  private static function getAnmeldeDatenXmlNodeForStatusAendern($belegung, $zahlungen)
  {
    $belegungNode = XMLNode::getXMLNode($belegung, 'belegung', array(), true);
    $zahlungenNode = new XMLNode('zahlungen');
    foreach ($zahlungen as $zahlung) {
      $zahlungenNode->addChild(XMLNode::getXMLNode($zahlung, 'zahlung', array(), true));
    }
    $anmeldedatenXmlNode = new XMLNode('anmeldedaten');
    $anmeldedatenXmlNode->addChild($belegungNode);
    $anmeldedatenXmlNode->addChild($zahlungenNode);

    return $anmeldedatenXmlNode;
  }

  
  private static function getStatusAendernFirmaAnmeldenXmlNode($firmabelegung, $tn_belegung, $pStatusNeu, $pAnmeldedatumNeuSetzen, $pGetGebuehrenCallback = null)
  {
    $dbwZahlung = new DBWorkZahlung();
    $dbwKursGeb = new DBWorkKursgebuehr();
    $dbwTeilnehmer = new DBWorkTeilnehmer();

    $teilnehmer = $dbwTeilnehmer->getTeilnehm($tn_belegung->tnr);

    $fa_zahlungen = array($dbwZahlung->getNeutralenZahlungssatz(
      $firmabelegung->knr,
      $firmabelegung->tnr,
      $firmabelegung->id,
      null,
      'Mitarbeiter: ' . $teilnehmer->name . ', ' . $teilnehmer->vorname,
      17,
      $tn_belegung->status . '->' . $pStatusNeu,
      $tn_belegung->status . '->' . $pStatusNeu,
      SessionOperations::getCurrentWebToolsUserKennung()
    ));

    $count = 0;
    

    if (is_null($pGetGebuehrenCallback)) {
      $kursgebuehren = $dbwKursGeb->getKursgebuehrenErmaessigtTab($tn_belegung->knr, $tn_belegung->tnr, '', '', '', null, '');
    } else {
      $kursgebuehren = call_user_func($pGetGebuehrenCallback, $tn_belegung);
    }

    foreach ($kursgebuehren as $kursgebuehr) {
      $fa_zahlungen[] = $dbwZahlung->getZahlungssatz4AnmeldungFirma(
        $firmabelegung->id,
        $firmabelegung->knr,
        $firmabelegung->tnr,
        $kursgebuehr,
        SessionOperations::getCurrentWebToolsUserKennung(),
        --$count,
        1,
        0
      );
    }

    $bet_offen_alt = $firmabelegung->bet_offen;
    $entgelt_alt = $firmabelegung->entgelt;
    $kursgeb = \DBWorkKursgebuehr::getSummeKursgebuehren($kursgebuehren);

    
    $firmabelegung->bet_offen = MathOperations::floatToString(MathOperations::stringToFloat($firmabelegung->bet_offen) + $kursgeb);
    $firmabelegung->entgelt = MathOperations::floatToString(MathOperations::stringToFloat($firmabelegung->entgelt) + $kursgeb);

    $fa_belegung = self::getBelegungsStatusAendernObject($firmabelegung, 'F', $pAnmeldedatumNeuSetzen, $bet_offen_alt, $entgelt_alt, $tn_belegung->id);
    
    return self::getAnmeldeDatenXmlNodeForStatusAendern($fa_belegung, $fa_zahlungen);
  }

  
  public static function belegungAnmeldenDMZ($pBelegungen, $pStatusNeu, $pAnmeldedatumNeuSetzen, $pToolSettings, $pGetGebuehrenCallback = null)
  {
    $abgleichAPI = new AbgleichAPI();
    $dbwBelegung = new DBWorkBelegung();

    if (!$abgleichAPI->isAktiviert() || !SessionOperations::isReplikat()) {
      throw new Exception('Fehlkonfiguration - soll Belegung per DMZ anmelden, aber die DMZ ist nicht aktiviert oder bin im Direktbetrieb');
    }

    $firmenBelegungCache = array();

    $alleAnmeldedatenNode = new XMLNode('alleAnmeldedaten');
    foreach ($pBelegungen as $belegung) {
      $firmabelegungen = $dbwBelegung->getFirmaBelegungenByTeilnehmerBelegung($belegung);

      if (count($firmabelegungen) > 0) {
        $firmabelegung = $firmabelegungen[0];

        
        if (isset($firmenBelegungCache[$firmabelegung->id])) {
          $firmabelegung = $firmenBelegungCache[$firmabelegung->id];
        } else {
          $firmenBelegungCache[$firmabelegung->id] = $firmabelegung;
        }

        
        $tn_anmeldedatenNode = self::getStatusAendernEinfachXmlNode($belegung, $pStatusNeu, $pAnmeldedatumNeuSetzen, $firmabelegung->id);
        $alleAnmeldedatenNode->addChild($tn_anmeldedatenNode);

        
        $fa_anmeldedatenNode = self::getStatusAendernFirmaAnmeldenXmlNode($firmabelegung, $belegung, $pStatusNeu, $pAnmeldedatumNeuSetzen, $pGetGebuehrenCallback);
        $alleAnmeldedatenNode->addChild($fa_anmeldedatenNode);
      } else {
        $bet_offen_alt = $belegung->bet_offen;
        $entgelt_alt = $belegung->entgelt;

        $zahlungen = ZahlungAPI::getZahlungen4BelegungAnmelden($belegung, $pStatusNeu, $pToolSettings, $pGetGebuehrenCallback);
        $kursgebuehr = ZahlungAPI::getKursgebuehrFromZahlungen($zahlungen);
        $belegung->bet_offen = $kursgebuehr;
        $belegung->entgelt = $kursgebuehr;

        $belegungAnmeldung = self::getBelegungsStatusAendernObject($belegung, $pStatusNeu, $pAnmeldedatumNeuSetzen, $bet_offen_alt, $entgelt_alt);
        $anmeldedatenNode = self::getAnmeldeDatenXmlNodeForStatusAendern($belegungAnmeldung, $zahlungen);
        $alleAnmeldedatenNode->addChild($anmeldedatenNode);
      }
    }

    try {
      $result = $abgleichAPI->sendeNachricht(
        AbgleichAPI::ABGLEICH_BELEGUNG_STATUS_AENDERN,
        new Nutzlast($alleAnmeldedatenNode)
      );
    } catch (Exception $e) {
      throw $e;
    }

    return $result;
  }

  private static function getBelegungsStatusAendernEinfachObject($belegung, $status_neu, $anmDatumNeuSetzen, $zugehoerigeBelegungId = -1)
  {
    $belStatusAendernObj = new AbgleichBelegungStatusAendern();
    $belStatusAendernObj->id = $belegung->id;
    $belStatusAendernObj->status_alt = $belegung->status;
    $belStatusAendernObj->status_neu = $status_neu;
    $belStatusAendernObj->anmeldedatumNeuSetzen = $anmDatumNeuSetzen;
    $belStatusAendernObj->bet_offen_alt = $belegung->bet_offen;
    $belStatusAendernObj->entgelt_alt = $belegung->entgelt;
    $belStatusAendernObj->zugehoerigeBelegungId = $zugehoerigeBelegungId;

    return $belStatusAendernObj;
  }

  private static function getStatusAendernEinfachXmlNode($belegung, $statusNeu, $anmeldedatumNeuSetzen, $zugehoerigeBelegungId)
  {
    $dbwZahlung = new DBWorkZahlung();

    $belegungAendernObj = self::getBelegungsStatusAendernEinfachObject($belegung, $statusNeu, $anmeldedatumNeuSetzen, $zugehoerigeBelegungId);
    $zahlungen = array($dbwZahlung->getNeutralenZahlungssatz(
      $belegung->knr,
      $belegung->tnr,
      $belegung->id,
      null,
      'Status von ' . $belegung->status . ' auf ' . $statusNeu . ' gesetzt',
      17,
      $belegung->status . '->' . $statusNeu,
      $belegung->status . '->' . $statusNeu,
      SessionOperations::getCurrentWebToolsUserKennung()
    ));

    return self::getAnmeldeDatenXmlNodeForStatusAendern($belegungAendernObj, $zahlungen);
  }

  private static function getStatusAendernFirmaXmlNode($firmabelegung, $tn_belegung, $pStatusNeu, $pAnmeldedatumNeuSetzen)
  {
    $dbwZahlung = new DBWorkZahlung();
    $dbwTeilnehmer = new DBWorkTeilnehmer();

    $teilnehmer = $dbwTeilnehmer->getTeilnehm($tn_belegung->tnr);

    $fa_belegung = self::getBelegungsStatusAendernEinfachObject($firmabelegung, $firmabelegung->status, $pAnmeldedatumNeuSetzen, $tn_belegung->id);
    $fa_zahlungen = array($dbwZahlung->getNeutralenZahlungssatz(
      $firmabelegung->knr,
      $firmabelegung->tnr,
      $firmabelegung->id,
      null,
      'Mitarbeiter: ' . $teilnehmer->name . ', ' . $teilnehmer->vorname,
      17,
      $tn_belegung->status . '->' . $pStatusNeu,
      $tn_belegung->status . '->' . $pStatusNeu,
      SessionOperations::getCurrentWebToolsUserKennung()
    ));

    return self::getAnmeldeDatenXmlNodeForStatusAendern($fa_belegung, $fa_zahlungen);
  }

  
  public static function belegungStatusAendernEinfachDMZ($pBelegungen, $pStatusNeu, $pAnmeldedatumNeuSetzen)
  {
    $abgleichAPI = new AbgleichAPI();
    $dbwBelegung = new DBWorkBelegung();

    if (!$abgleichAPI->isAktiviert() || !SessionOperations::isReplikat()) {
      throw new Exception('Fehlkonfiguration - soll Belegung per DMZ anmelden, aber die DMZ ist nicht aktiviert oder bin im Direktbetrieb');
    }

    $alleAnmeldedatenNode = new XMLNode('alleAnmeldedaten');

    foreach ($pBelegungen as $belegung) {
      
      $firmabelegungen = $dbwBelegung->getFirmaBelegungenByTeilnehmerBelegung($belegung);
      if (count($firmabelegungen) > 0) {
        $firmabelegung = $firmabelegungen[0];
        
        
        $tn_anmeldedatenNode = self::getStatusAendernEinfachXmlNode($belegung, $pStatusNeu, $pAnmeldedatumNeuSetzen, $firmabelegung->id);
        $alleAnmeldedatenNode->addChild($tn_anmeldedatenNode);

        $fa_anmeldedatenNode = self::getStatusAendernFirmaXmlNode($firmabelegung, $belegung, $pStatusNeu, $pAnmeldedatumNeuSetzen);
        $alleAnmeldedatenNode->addChild($fa_anmeldedatenNode);
      } else {
        $belegungStatusAendern = self::getBelegungsStatusAendernEinfachObject($belegung, $pStatusNeu, $pAnmeldedatumNeuSetzen);
        $zahlungen = ZahlungAPI::getZahlungen4BelegungStatusAendernEinfach($belegung, $pStatusNeu);
        
        $anmeldedatenNode = self::getAnmeldeDatenXmlNodeForStatusAendern($belegungStatusAendern, $zahlungen);
        $alleAnmeldedatenNode->addChild($anmeldedatenNode);
      }
    }
    try {
      $result = $abgleichAPI->sendeNachricht(
        AbgleichAPI::ABGLEICH_BELEGUNG_STATUS_AENDERN,
        new Nutzlast($alleAnmeldedatenNode)
      );
    } catch (Exception $e) {
      throw $e;
    }

    return $result;
  }

  
  public static function codeStatus($pCode)
  {
    $abgleichAPI = new AbgleichAPI();

    if (!$abgleichAPI->isAktiviert() || !SessionOperations::isReplikat()) {
      throw new Exception('Fehlkonfiguration - soll CodeAnfrage an die DMZ stellen, aber die DMZ ist nicht aktiviert oder bin im Direktbetrieb');
    }
    $anfrage = new CodeAnfrage();
    $anfrage->code = $pCode;

    $message = XMLNode::getXMLNode($anfrage, 'check', array(), true);

    return $abgleichAPI->sendeNachricht(
      AbgleichAPI::ABGLEICH_PUNKTE_BESTAETIGEN,
      new Nutzlast($message)
    );
  }

  
  public function codeFuerBelegungEintragen($pCode, $pBelegungsId)
  {
    $abgleichAPI = new AbgleichAPI();

    if (!$abgleichAPI->isAktiviert() || !SessionOperations::isReplikat()) {
      throw new Exception('Fehlkonfiguration - soll Code in der DMZ eintragen, aber die DMZ ist nicht aktiviert oder bin im Direktbetrieb');
    }
    $anfrage = new CodeAnfrage();
    $anfrage->code = $pCode;
    $anfrage->belegId = $pBelegungsId;
    $message = XMLNode::getXMLNode($anfrage, 'eintragen', array(), true);

    return $abgleichAPI->sendeNachricht(
      AbgleichAPI::ABGLEICH_PUNKTE_BESTAETIGEN,
      new Nutzlast($message)
    );
  }

  
  public function codeFuerNeueBelegung($pCode, $pTnr)
  {
    $abgleichAPI = new AbgleichAPI();

    if (!$abgleichAPI->isAktiviert() || !SessionOperations::isReplikat()) {
      throw new Exception('Fehlkonfiguration - soll TN anmelden und Code in der DMZ eintragen, aber die DMZ ist nicht aktiviert oder bin im Direktbetrieb');
    }
    $anfrage = new CodeAnfrage();
    $anfrage->code = $pCode;
    $anfrage->tnr = $pTnr;
    $message = XMLNode::getXMLNode($anfrage, 'eintragen', array(), true);

    return $abgleichAPI->sendeNachricht(
      AbgleichAPI::ABGLEICH_PUNKTE_BESTAETIGEN,
      new Nutzlast($message)
    );
  }

  public const USER_ALLOWED_TO_SEE_BELEGUNGEN_NO_ERROR = 0;
  public const USER_ALLOWED_TO_SEE_BELEGUNGEN_WENIGER_ALS_MINIMALANZAHL = 1;
  public const USER_ALLOWED_TO_SEE_BELEGUNGEN_KURSENDE_ERREICHT = 2;
  public const USER_ALLOWED_TO_SEE_BELEGUNGEN_ZEITBEREICH = 3;
  public const USER_ALLOWED_TO_SEE_BELEGUNGEN_KURSAUSFALL = 4;

  
  public static function getUserAllowedToTnReason($pKurs, $pToolId, bool $allowKinder): int
  {
    
    
    $toolSettings = ToolSettingsAPI::getToolSettings($pToolId);
    
    if (self::shouldNotShowLessThanMinTn($pKurs, $toolSettings)) {
      $dbwBelegung = new DBWorkBelegung(1, true);
      
      $anzahlAnmeldungen = $dbwBelegung->countAngemeldeteTnByKnr($pKurs->id, false, false, $allowKinder);
      $anzahlAnmeldungen += $pKurs->getVarValue('teinzel');
      if ($anzahlAnmeldungen < $pKurs->getVarValue('teil_min')) {
        return self::USER_ALLOWED_TO_SEE_BELEGUNGEN_WENIGER_ALS_MINIMALANZAHL;
      }
    }
    
    if (isset($toolSettings->tnDatenAusblendenNachKursEnde) && (trim($toolSettings->tnDatenAusblendenNachKursEnde) !== '')) {
      $anzTagebisKursEnde = DateTimeOperations::getAnzTage(DateTimeOperations::getDateNow(), $pKurs->ende_dat);
      if ($anzTagebisKursEnde + (int)trim($toolSettings->tnDatenAusblendenNachKursEnde) < 0) {
        return self::USER_ALLOWED_TO_SEE_BELEGUNGEN_KURSENDE_ERREICHT;
      }
    }

    
    if (isset($toolSettings->tnDatenAusblendenVorKursBeginn) && (trim($toolSettings->tnDatenAusblendenVorKursBeginn) !== '')) {
      
      
      $anzTagebisKursBeginn = DateTimeOperations::getAnzTage(DateTimeOperations::getDateNow(), $pKurs->beginn_dat) - 1;
      if (trim($toolSettings->tnDatenAusblendenVorKursBeginn) - $anzTagebisKursBeginn < 0) {
        return self::USER_ALLOWED_TO_SEE_BELEGUNGEN_ZEITBEREICH;
      }
    }
    
    if (($toolSettings->tnDatenAusblendenKursausfall ?? false) && $pKurs->isAusfall()) {
      return self::USER_ALLOWED_TO_SEE_BELEGUNGEN_KURSAUSFALL;
    }

    return self::USER_ALLOWED_TO_SEE_BELEGUNGEN_NO_ERROR;
  }

  
  public static function isUserAllowedToSeeTn($pKurs, $pToolId, bool $allowKinder)
  {
    return self::getUserAllowedToTnReason($pKurs, $pToolId, $allowKinder) === self::USER_ALLOWED_TO_SEE_BELEGUNGEN_NO_ERROR;
  }

  
  private static function shouldNotShowLessThanMinTn($pKurs, $pToolSettings)
  {
    $beruecksichtigeMinTn = $pToolSettings->showTnNurWennMinTn ?? false;
    if ($beruecksichtigeMinTn) {
      $beruecksichtigeKursStatus = $pToolSettings->showTnTrotzMinTnWennKursstatusLaeuft ?? false;

      return !($beruecksichtigeKursStatus && StringOperations::strtoupper($pKurs->abschluss) == 'L');
    }

    return false;
  }

  
  public static function updateBelegung($pBelegung, $pOrigBelegung)
  {
    return static::updateBelegungen(array($pBelegung), array($pOrigBelegung));
  }

  
  public static function updateBelegungen($pBelegungen, $pOrigBelegungen, $pRefreshCache = false, $pNoMapping = false)
  {
    $allgsettings = AllgSettingsAPI::getAllgSettings();
    if (SessionOperations::isReplikat()) {
      if ($allgsettings->dmzEnabled) {
        
        return BelegungAPI::_updateBelegungDmz($pBelegungen, $pOrigBelegungen, $pRefreshCache, $pNoMapping);
      } else {
        
        
        return BelegungAPI::_updateBelegungReplikat($pBelegungen);
      }
    } else {
      return BelegungAPI::_updateBelegungDirekt($pBelegungen, $pRefreshCache);
    }
  }

  
  private static function _updateBelegungDirekt($pBelegungen, $pRefreshCache = false)
  {
    $dbwBelegung = new DBWorkBelegung(1, true);
    
    foreach ($pBelegungen as $belegung) {
      $dbwBelegung->updateBelegung($belegung, true, $pRefreshCache);
    }

    return true;
  }

  
  private static function _updateBelegungReplikat($pBelegungen)
  {
    $dbwBelegung = new DBWorkBelegung(1, true);
    
    foreach ($pBelegungen as $belegung) {
      $dbwBelegung->updateBelegung($belegung, false);
    }

    return true;
  }

  
  private static function _updateBelegungDmz($pBelegungen, $pOrigBelegungen, $pRefreshCache = false, $pNoMapping = false)
  {
    try {
      $abgleichAPI = new AbgleichAPI();
      $message = new XMLNode('belegungen');

      foreach ($pBelegungen as $key => $belegung) {
        
        $diffFields = $belegung->getDiffFields($pOrigBelegungen[$key]);
        $diffFields[] = 'id';
        $belegungNode = XMLNode::getXmlNodeOnlySpecificFields($belegung, 'belegung', $diffFields, true);
        $origBelegungNode = XMLNode::getXmlNodeOnlySpecificFields($pOrigBelegungen[$key], 'oldobject', $diffFields, true);

        $belegungNode->addChild($origBelegungNode);
        $message->addChild($belegungNode);
      }

      $abgleichAPI->sendeNachricht(
        AbgleichAPI::ABGLEICH_BELEGUNGKORREKTUR,
        new Nutzlast($message),
        null,
        $pNoMapping
      );
      BelegungAPI::_updateBelegungDirekt($pBelegungen, $pRefreshCache);

      return true;
    } catch (AbgleichApiException $aae) { 
      Logger::logg('Belegung bearbeiten - folgende Exception ist aufgetreten: ' . $aae->getMessage() . "\n" . ' [' . $aae->getTraceAsString() . ']', 200);

      return false;
    }
  }

  
  public static function saveBelegung($builder)
  {
    $statusMessage = '';

    $belegung = $builder->getData();
    $dbwBelegung = new DBWorkBelegung(1, true);
    
    $origBelegung = $dbwBelegung->getBelegung($belegung->id);
    $allgsettings = AllgSettingsAPI::getAllgSettings();
    
    $changedFields = $builder->getFieldNamesOfChangedFields($origBelegung);

    
    
    
    if (count($changedFields) == 0) {
      return HTMLOperations::getInfoBox('<div class="infobox_text">' . CleanOperations::cleanStringForHtml($allgsettings->failNoChangesMsgBelegung) . '</div>', 'Hinweis', 'in_background');
    } 

    $success = self::updateBelegung($belegung, $origBelegung);

    if ($success) {
      $statusMessage .= HTMLOperations::getSuccessBox('<div class="successbox_text">' . CleanOperations::cleanStringForHtml($allgsettings->successMsgChangeBelegung) . '</div>' . "\n", 'Übermittlung erfolgreich', 'in_background');
    } else {
      $statusMessage .= HTMLOperations::getAlertBox('<div class="alertbox_text">' . CleanOperations::cleanStringForHtml($allgsettings->failSubmitMsgBelegung) . '</div>', 'Fehler', 'in_background');
    }

    return $statusMessage;
  }

  
  public static function createNachlassDialog($pIsReadOnly, $pShowNachlassArt = true)
  {
    $disabledString = $pIsReadOnly ? 'disabled' : '';
    $hlAnmBemerkung = new HinterlegungAnmBemerkung();
    $hlAnmBemerkung->fields = array('grund');
    $result = '<div id="nachlassDialog">' . "\n";
    if ($pShowNachlassArt) {
      $result .= '<div class="nachlassArt">' . "\n";
      $result .= '<div>';
      $result .= HTMLOperations::CreateInputTag('nachlassArt', 'radio', 'nachlass', 'nachlassRadio', 'Nachlass', $disabledString);
      $result .= '<label for="nachlass">Nachlass</label>';
      $result .= '</div>' . "\n";
      $result .= '<div>';
      $result .= HTMLOperations::CreateInputTag('nachlassArt', 'radio', 'rabatt', 'nachlassRadio', 'Rabatt', $disabledString);
      $result .= '<label for="rabatt">Rabatt</label>';
      $result .= '</div>' . "\n";
      $result .= '<div>';
      $result .= HTMLOperations::CreateInputTag('nachlassArt', 'radio', 'abschlag', 'nachlassRadio', 'Abschlag', $disabledString);
      $result .= '<label for="abschlag">Abschlag</label>';
      $result .= '</div>' . "\n";
      $result .= '</div>' . "\n";
    }
    $result .= '<div class="nachlassFelder">' . "\n";
    $result .= '<div class="nachlassHinweis">';
    $result .= '<span>Nachlass wird für alle Gebührenanteile berechnet.</span>';
    $result .= '</div>';
    $result .= '<div class="nachlassBetrag">';
    $result .= '<label for="prozent">in % </label>' . HTMLOperations::CreateInputTag('nachlassProzent', 'text', 'prozent', 'val_float', '', $disabledString);
    $result .= '<label for="betrag">als Betrag in EUR: </label>' . HTMLOperations::CreateInputTag('nachlassBetrag', 'text', 'betrag', 'val_float', '', $disabledString);
    $result .= '</div>' . "\n";
    $result .= '<div class="nachlassGrund">' . "\n";
    $result .= '<label for="grund">Grund: </label>' . HTMLOperations::CreateInputTag('nachlassGrund', 'text', 'grund', '', '', $disabledString) . "\n";
    if (!$pIsReadOnly) {
      $result .= '<img class="hlNachlassGrund" src="layout/button_hinterlegung.gif" alt="Hinterlegung Nachlassgrund" title="Hinterlegung Nachlassgrund">' . "\n";
      $result .= $hlAnmBemerkung->CreateDialog('hlNachlassGrund', '.hlNachlassGrund', 'Nachlassgrund', '250', '250');
    }
    $result .= '</div>';
    $result .= '</div>' . "\n";
    $result .= '<div class="nachlassBerechnung">' . "\n";
    $result .= '<div class="nachlassBeschriftung">';
    $result .= '<div>aktuelle Geb&uuml;hr: </div>';
    $result .= '<div>&Auml;nderungsbetrag: </div>';
    $result .= '<div>neue Geb&uuml;hr: </div>';
    $result .= '</div>' . "\n";
    $result .= '<div class="nachlassAusgabe">' . "\n";
    $result .= '<div><span class="ausgabeAktGebuehr"></span></div>';
    $result .= '<div><span class="ausgabeRabatt"></span></div>';
    $result .= '<div><span class="ausgabeNeueGebuehr"></span></div>';
    $result .= '</div>' . "\n";
    $result .= '</div>' . "\n";
    $result .= '</div>' . "\n";

    return $result;
  }

  
  public static function getBelegungInfosPerDMZ($pTnr, $pKnr)
  {
    $abgleichAPI = new AbgleichAPI();
    $message = new XMLNode('belegung');
    $message->addChild(new XMLNode('tnr', $pTnr, '', '', true));
    $message->addChild(new XMLNode('knr', $pKnr, '', '', true));
    $result = $abgleichAPI->sendeNachricht(
      AbgleichAPI::ABGLEICH_GET_BELEGUNG,
      new Nutzlast($message)
    );

    return json_decode($result->reason);
  }

  public static function checkMahnstufenPerDMZ(int $tnr, array $mahnstufen): bool
  {
    $abgleichAPI = new AbgleichAPI();
    $message = new XMLNode('data');
    $message->addChild(new XMLNode('tnr', $tnr));
    $mahnstufenNode = new XMLNode('mahnstufen');
    foreach ($mahnstufen as $mahnstufe) {
      $mahnstufenNode->addChild(new XMLNode('mahnstufe', $mahnstufe));
    }
    $message->addChild($mahnstufenNode);
    $result = $abgleichAPI->sendeNachricht(AbgleichAPI::ABGLEICH_CHECK_MAHNSTUFEN, new Nutzlast($message));

    return $result->reason === 'true';
  }

  
  public static function updateBelegungenWithStatus(\Kufer\Web\Classes\Api\Belegung\BelegungStatusAendernHandler $handle, array $belegungen, array $origBelegungen, bool $refreshCache = false): array
  {
    $belegungen = \ArrayOperations::setAttributAsKey($belegungen, 'id');
    $origBelegungen = \ArrayOperations::setAttributAsKey($origBelegungen, 'id');

    $origBelegungenInOrder = array();
    $statiNeu = array();
    foreach ($belegungen as $key => $belegung) {
      $origBelegung = $origBelegungen[$key];
      $origBelegungenInOrder[$key] = $origBelegung;
      $statiNeu[$key] = $belegung->status;

      
      
      $belegung->status = $origBelegung->status;
    }
    unset($origBelegungen);

    if (!self::updateBelegungen($belegungen, $origBelegungenInOrder, $refreshCache)) {
      $result = array();
      foreach ($belegungen as $belegung) {
        $result[$belegung->id] = self::BELEGUNG_BEARBEITEN_FEHLER;
      }

      return $result;
    }

    $result = array();
    foreach ($belegungen as $key => $belegung) {
      if ($belegung->status !== $statiNeu[$key]) {
        if (!\APIGebuehren::hasKursFortlaufendeGebuehren($belegung->knr)) {
          $result[$belegung->id] = self::handleStatusAenderung($belegung, $statiNeu[$key], $handle);
        } else {
          $result[$belegung->id] = self::STATUS_AENDERUNG_EINFACH_FEHLER;
        }
      } else {
        $result[$belegung->id] = self::BELEGUNG_BEARBEITEN_ERFOLGREICH;
      }
    }

    return $result;
  }

  
  public static function handleStatusAenderung(\Belegung $pBelegung, string $pStatusNeu, \Kufer\Web\Classes\Api\Belegung\BelegungStatusAendernHandler $handler): string
  {
    
    

    

    $statusAenderungArt = self::getStatusAenderungArt($pBelegung->status, $pStatusNeu);
    if ($statusAenderungArt === self::STATUS_AENDERUNG_TYP_KEINE) {
      return self::STATUS_AENDERUNG_KEINE;
    }

    $gruende = self::getHinderungsGruende4StatusAenderungOhneStorno($pBelegung);
    if (!empty($gruende) || (\SessionOperations::isReplikat() && !\SessionOperations::isDmzBetrieb())) {
      $success = $handler->sendBelegungStatusAendernMail($pBelegung, $gruende, $pStatusNeu);

      return ($success ? self::STATUS_AENDERUNG_PER_MAIL_VERSENDET : self::STATUS_AENDERUNG_PER_MAIL_FEHLER);
    }

    if ($statusAenderungArt === self::STATUS_AENDERUNG_TYP_ANMELDUNG) {
      $result = $handler->handleAnmeldungStatusAenderung($pBelegung, $pStatusNeu);
    } elseif ($statusAenderungArt === self::STATUS_AENDERUNG_TYP_STORNO) {
      $result = $handler->handleStornoStatusAenderung($pBelegung, $pStatusNeu);
    } elseif ($statusAenderungArt === self::STATUS_AENDERUNG_TYP_EINFACH) {
      $result = $handler->handleEinfacheStatusAenderung($pBelegung, $pStatusNeu);
    }

    assert(isset($result), "Unbekannter Fall $statusAenderungArt ist nicht implementiert");

    $successSofort = array(
      self::STATUS_AENDERUNG_EINFACH_ERFOLGREICH,
      self::STATUS_AENDERUNG_STORNO_ERFOLGREICH,
      self::STATUS_AENDERUNG_ANMELDUNG_ERFOLGREICH
    );

    if (in_array($result, $successSofort)) {
      $handler->postStatusSofortAenderungSuccessHook($pBelegung, $result);
    }

    return $result;
  }

  
  public static function getStatusAenderungArt(string $statusAlt, string $statusNeu, array $erlaubteStatusAenderungen = array('A', 'I', 'P', 'S', 'U', 'W'), array $statusMitGebuehr = array('A', 'T', 'Z')): string
  {
    

    $statusAltTypMitGebuehr = in_array($statusAlt, $statusMitGebuehr);
    $statusNeuTypMitGebuehr = in_array($statusNeu, $statusMitGebuehr);

    $statusCode = self::STATUS_AENDERUNG_TYP_KEINE;
    if ($statusAlt !== $statusNeu && in_array($statusAlt, $erlaubteStatusAenderungen) && in_array($statusNeu, $erlaubteStatusAenderungen)) {
      
      if (!($statusAltTypMitGebuehr xor $statusNeuTypMitGebuehr)) {
        $statusCode = self::STATUS_AENDERUNG_TYP_EINFACH; 
      } elseif ($statusAltTypMitGebuehr) {
        $statusCode = self::STATUS_AENDERUNG_TYP_STORNO;
      } else {
        $statusCode = self::STATUS_AENDERUNG_TYP_ANMELDUNG;
      }
    }

    return $statusCode;
  }

  
  public static function getHinderungsGruende4StatusAenderungOhneStorno(\Belegung $pBelegung): array
  {
    $gruende = array();
    if (\DBWorkBelegung::getDefaultInstance()->isTeilEinerMehrfachAnmeldung($pBelegung->id)) {
      $gruende[] = self::STATUS_AENDERUNG_HINDERUNG_TEIL_EINER_MEHRFACHANMLEDUNG;
    }
    if (\DBWorkBelegung::getDefaultInstance()->IsElternKindanmeldung($pBelegung)) {
      $gruende[] = self::STATUS_AENDERUNG_HINDERUNG_TEIL_EINER_ELTERN_KIND_ANMELDUNG;
    }

    return $gruende;
  }

  
  public static function findBelegungsIdsInDmz(array $ids): array
  {
    if (empty($ids)) {
      return array();
    }

    $root = new \XMLNode('belegungen');
    foreach ($ids as $id) {
      $child = new \XMLNode('belegung');
      $child->addChild(new \XMLNode('id', $id->getId()));
      if (!$id->isGuidEmpty()) {
        $child->addChild(new \XMLNode('guid', $id->getGuid()));
      }

      $root->addChild($child);
    }

    $nutzlast = new \Nutzlast($root);

    $abgleichApi = new \AbgleichAPI();
    try {
      $result = $abgleichApi->sendeNachricht(\AbgleichAPI::ABGLEICH_BELEGUNGEN_IN_KUFERSQL, $nutzlast);
      if ($result->code == \AbgleichApiResult::CODE_SUCCESS) {
        $belegungsIdsExist = \JsonOperations::decode($result->reason);

        return array_map(static function($obj) {
          return \Kufer\Web\Classes\Identifier\Identifier::fromId($obj->id);
        }, array_filter($belegungsIdsExist, static function($obj) {
          return $obj->exists;
        }));
      } else {
        return array();
      }
    } catch (\Exception $ex) {
      \Logger::logg('Fehler in ' . __FUNCTION__ . ': ' . $ex->getMessage(), 40);

      return array();
    }
  }

  public static function storniereBelegungPerHashDmz($belegidMssql, $hash)
  {
    $abgleichAPI = new AbgleichAPI();
    $message = new XMLNode('belegung');
    $message->addChild(new XMLNode('belegid', $belegidMssql, '', '', true));
    $message->addChild(new XMLNode('stornohash', $hash, '', '', true));

    $nutzlast = new Nutzlast($message);
    $nutzlast->addNode(new XMLNode('nomapping', 'W'));

    $result = $abgleichAPI->sendeNachricht(
      AbgleichAPI::ABGLEICH_STORNO_MIT_HASH,
      $nutzlast
    );

    
    
    
    
    
    
    
    
    
    
    
    
    

    $resultObj = json_decode($result->reason);

    return array('statuscode' => $resultObj->Item1, 'logmeldung' => $resultObj->Item2);
  }
}

class StornierePerHashStatus
{
  public const FEHLER_ALLGEMEIN = 0;
  public const FEHLER_KEINE_BELEGUNG_GEFUNDEN = 1;
  public const FEHLER_ZU_VIELE_BELEGUNGEN_GEFUNDEN = 2;
  public const FEHLER_BELEGUNG_BEREITS_STORNIERT = 3;
  public const BELEGUNG_STORNIERT_WEBPLATTFORMEN_NICHT = 4;
  public const BELEGUNG_STORNIERT_WEBPLATTFORMEN_TEILWEISE = 5;
  public const KOMPLETT_ERFOLGREICH = 6;
}


class CodeAnfrage extends Datamodul
{
  public $code = '';
  public $tnr = '';
  public $belegId = '';
}