<commit_before>/*                                                                           *\
*    ____                   ____  ___ __                                      *
*   / __ \____  ___  ____  / __ \/ (_) /_____  _____                          *
*  / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/   OpenOlitor             *
* / /_/ / /_/ /  __/ / / / /_/ / / / /_/ /_/ / /       contributed by tegonal *
* \____/ .___/\___/_/ /_/\____/_/_/\__/\____/_/        http://openolitor.ch   *
*     /_/                                                                     *
*                                                                             *
* This program is free software: you can redistribute it and/or modify it     *
* under the terms of the GNU General Public License as published by           *
* the Free Software Foundation, either version 3 of the License,              *
* or (at your option) any later version.                                      *
*                                                                             *
* This program is distributed in the hope that it will be useful, but         *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY  *
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details.                                                               *
*                                                                             *
* You should have received a copy of the GNU General Public License along     *
* with this program. If not, see http://www.gnu.org/licenses/                 *
*                                                                             *
\*                                                                           */
package ch.openolitor.core.data

import ch.openolitor.core.models.
  _
import ch.openolitor.stammdaten.models._
import org.odftoolkit.simple._
import org.odftoolkit.simple.table._
import scala.collection.JavaConversions._
import scala.reflect.runtime.universe.{ Try => UTry, _ }
import java.util.Date
import akka.actor._
import java.io.File
import java.io.FileInputStream
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import ch.openolitor.util.DateTimeUtil
import scala.collection.immutable.TreeMap
import java.io.InputStream
import scala.util._
import org.joda.time.format.DateTimeFormatter
import java.util.Locale

case class ParseException(msg: String) extends Exception(msg)

class DataImportParser extends Actor with ActorLogging {
  import DataImportParser._

  val receive: Receive = {
    case ParseSpreadsheet(file) =>
      val rec = sender
      try {
        importData(file) match {
          case Success(result) =>
            rec ! result
          case Failure(error) =>
            log.warning("Couldn't import data {}", error.getMessage)
            rec ! ParseError(error)
        }
      } catch {
        case t: Throwable =>
          log.warning("Couldn't import data {}", t)
          rec ! ParseError(t)
      }
  }

  val modifyColumns = Seq("erstelldat", "ersteller", "modifidat", "modifikator")

  def importData(file: InputStream): Try[ParseResult] = {
    val doc = SpreadsheetDocument.loadDocument(file)

    //parse all sections
    for {
      (projekte, _) <- Try(doc.withSheet("Projekt")(parseProjekte))
      projekt = projekte.head
      (personen, _) <- Try(doc.withSheet("Personen")(parsePersonen))
      (kunden, kundeIdMapping) <- Try(doc.withSheet("Kunden")(parseKunden(personen)))
      (pendenzen, _) <- Try(doc.withSheet("Pendenzen")(parsePendenzen(kunden)))
      (tours, tourIdMapping) <- Try(doc.withSheet("Touren")(parseTours))
      (abotypen, abotypIdMapping) <- Try(doc.withSheet("Abotypen")(parseAbotypen))
      (depots, depotIdMapping) <- Try(doc.withSheet("Depots")(parseDepots))
      (abwesenheiten, _) <- Try(doc.withSheet("Abwesenheiten")(parseAbwesenheit))
      (vertriebsarten, vertriebsartIdMapping) <- Try(doc.withSheet("Vertriebsarten")(parseVertriebsarten))
      (vertriebe, _) <- Try(doc.withSheet("Vertriebe")(parseVertriebe(vertriebsarten)))
      (abos, _) <- Try(doc.withSheet("Abos")(parseAbos(kundeIdMapping, kunden, vertriebsartIdMapping, vertriebsarten, vertriebe, abotypen, depotIdMapping, depots, tourIdMapping, tours, abwesenheiten)))
      (lieferplanungen, _) <- Try(doc.withSheet("Lieferplanungen")(parseLieferplanungen))
      (lieferungen, _) <- Try(doc.withSheet("Lieferungen")(parseLieferungen(abotypen, vertriebe, abwesenheiten, lieferplanungen, depots, tours)))
      (produzenten, _) <- Try(doc.withSheet("Produzenten")(parseProduzenten))
      (produktkategorien, _) <- Try(doc.withSheet("Produktekategorien")(parseProduktekategorien))
      (produktProduzenten, _) <- Try(doc.withSheet("ProduktProduzenten")(parseProdukteProduzenten))
      (produktProduktekategorien, _) <- Try(doc.withSheet("ProduktProduktkategorien")(parseProdukteProduktkategorien))
      (produkte, _) <- Try(doc.withSheet("Produkte")(parseProdukte(produzenten, produktProduzenten, produktkategorien, produktProduktekategorien)))
      (lieferpositionen, _) <- Try(doc.withSheet("Lieferpositionen")(parseLieferpositionen(produkte, produzenten)))
      (bestellungen, _) <- Try(doc.withSheet("Bestellungen")(parseBestellungen(produzenten, lieferplanungen)))
      (bestellpositionen, _) <- Try(doc.withSheet("Bestellpositionen")(parseBestellpositionen(produkte)))
      (customKundentypen, _) <- Try(doc.withSheet("Kundentypen")(parseCustomKundentypen))
      (tourlieferungen, _) <- Try(doc.withSheet("Tourlieferungen")(parseTourlieferungen(abos, kunden)))
    } yield {
      ParseResult(
        projekt,
        customKundentypen,
        kunden,
        personen,
        pendenzen,
        tours,
        depots,
        abotypen,
        vertriebsarten,
        vertriebe,
        lieferungen,
        lieferplanungen,
        lieferpositionen,
        abos,
        abwesenheiten,
        produkte,
        produktkategorien,
        produktProduktekategorien,
        produzenten,
        produktProduzenten,
        bestellungen,
        bestellpositionen,
        tourlieferungen
      )
    }
  }

  def parseProjekte = {
    parse[Projekt, ProjektId]("id", Seq("bezeichnung", "strasse", "haus_nummer", "adress_zusatz", "plz", "ort",
      "preise_sichtbar", "preise_editierbar", "email_erforderlich", "waehrung", "geschaeftsjahr_monat", "geschaeftsjahr_tag", "two_factor_auth", "sprache") ++ modifyColumns) { id => indexes =>
      row =>
        //match column indexes
        val Seq(indexBezeichnung, indexStrasse, indexHausNummer, indexAdressZusatz, indexPlz, indexOrt, indexPreiseSichtbar,
          indexPreiseEditierbar, indexEmailErforderlich, indexWaehrung, indexGeschaeftsjahrMonat, indexGeschaeftsjahrTag, indexTwoFactorAuth, indexSprache) = indexes take (14)
        val Seq(indexErstelldat, indexErsteller, indexModifidat, indexModifikator) = indexes takeRight (4)
        val twoFactorAuth = parseMap(row.value[String](indexTwoFactorAuth))(r => Rolle(r).getOrElse(throw ParseException(s"Unknown Rolle $r while parsing Projekt")), _.toBoolean)

        Projekt(
          id = ProjektId(id),
          bezeichnung = row.value[String](indexBezeichnung),
          strasse = row.value[Option[String]](indexStrasse),
          hausNummer = row.value[Option[String]](indexHausNummer),
          adressZusatz = row.value[Option[String]](indexAdressZusatz),
          plz = row.value[Option[String]](indexPlz),
          ort = row.value[Option[String]](indexOrt),
          preiseSichtbar = row.value[Boolean](indexPreiseSichtbar),
          preiseEditierbar = row.value[Boolean](indexPreiseEditierbar),
          emailErforderlich = row.value[Boolean](indexEmailErforderlich),
          waehrung = Waehrung(row.value[String](indexWaehrung)),
          geschaeftsjahrMonat = row.value[Int](indexGeschaeftsjahrMonat),
          geschaeftsjahrTag = row.value[Int](indexGeschaeftsjahrTag),
          twoFactorAuthentication = twoFactorAuth,
          sprache = new Locale(row.value[String](indexSprache)),
          //modification flags
          erstelldat = row.value[DateTime](indexErstelldat),
          ersteller = PersonId(row.value[Long](indexErsteller)),
          modifidat = row.value[DateTime](indexModifidat),
          modifikator = PersonId(row.value[Long](indexModifikator))
        )
    }
  }

  def parseKunden(personen: List[Person]) = {
    parse[Kunde, KundeId]("id", Seq("bezeichnung", "strasse", "haus_nummer", "adress_zusatz", "plz", "ort", "bemerkungen",
      "abweichende_lieferadresse", "bezeichnung_lieferung", "strasse_lieferung", "haus_nummer_lieferung",
      "adress_zusatz_lieferung", "plz_lieferung", "ort_lieferung", "zusatzinfo_lieferung", "typen",
      "anzahl_abos", "anzahl_pendenzen", "anzahl_personen") ++ modifyColumns) { id => indexes => row =>
      //match column indexes
      val Seq(indexBezeichnung, indexStrasse, indexHausNummer, indexAdressZusatz, indexPlz, indexOrt, indexBemerkungen,
        indexAbweichendeLieferadresse, indexBezeichnungLieferung, indexStrasseLieferung, indexHausNummerLieferung,
        indexAdresseZusatzLieferung, indexPlzLieferung, indexOrtLieferung, indexZusatzinfoLieferung, indexKundentyp,
        indexAnzahlAbos, indexAnzahlPendenzen, indexAnzahlPersonen) =
        indexes take (19)
      val Seq(indexErstelldat, indexErsteller, indexModifidat, indexModifikator) = indexes takeRight (4)

      val kundeId = KundeId(id)
      val personenByKundeId = personen filter (_.kundeId == kundeId)
      if (personenByKundeId.isEmpty) {
        throw ParseException(s"Kunde id $kundeId does not reference any person. At least one person is required")
      }
      Kunde(
        kundeId,
        bezeichnung = row.value[String](indexBezeichnung),
        strasse = row.value[String](indexStrasse),
        hausNummer = row.value[Option[String]](indexHausNummer),
        adressZusatz = row.value[Option[String]](indexAdressZusatz),
        plz = row.value[String](indexPlz),
        ort = row.value[String](indexOrt),
        bemerkungen = row.value[Option[String]](indexBemerkungen),
        abweichendeLieferadresse = row.value[Boolean](indexAbweichendeLieferadresse),
        bezeichnungLieferung = row.value[Option[String]](indexBezeichnungLieferung),
        strasseLieferung = row.value[Option[String]](indexStrasseLieferung),
        hausNummerLieferung = row.value[Option[String]](indexHausNummerLieferung),
        adressZusatzLieferung = row.value[Option[String]](indexAdresseZusatzLieferung),
        plzLieferung = row.value[Option[String]](indexPlzLieferung),
        ortLieferung = row.value[Option[String]](indexOrtLieferung),
        zusatzinfoLieferung = row.value[Option[String]](indexZusatzinfoLieferung),
        typen = (row.value[String](indexKundentyp).split(",") map (KundentypId)).toSet,
        //Zusatzinformationen
        anzahlAbos = row.value[Int](indexAnzahlAbos),
        anzahlPendenzen = row.value[Int](indexAnzahlPendenzen),
        anzahlPersonen = row.value[Int](indexAnzahlPersonen),
        //modification flags
        erstelldat = row.value[DateTime](indexErstelldat),
        ersteller = PersonId(row.value[Long](indexErsteller)),
        modifidat = row.value[DateTime](indexModifidat),
        modifikator = PersonId(row.value[Long](indexModifikator))
      )
    }
  }

  def parsePersonen = {<commit_msg>OO-246: import kunde bezeichnung adjusted<commit_after>/*                                                                           *\
*    ____                   ____  ___ __                                      *
*   / __ \____  ___  ____  / __ \/ (_) /_____  _____                          *
*  / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/   OpenOlitor             *
* / /_/ / /_/ /  __/ / / / /_/ / / / /_/ /_/ / /       contributed by tegonal *
* \____/ .___/\___/_/ /_/\____/_/_/\__/\____/_/        http://openolitor.ch   *
*     /_/                                                                     *
*                                                                             *
* This program is free software: you can redistribute it and/or modify it     *
* under the terms of the GNU General Public License as published by           *
* the Free Software Foundation, either version 3 of the License,              *
* or (at your option) any later version.                                      *
*                                                                             *
* This program is distributed in the hope that it will be useful, but         *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY  *
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details.                                                               *
*                                                                             *
* You should have received a copy of the GNU General Public License along     *
* with this program. If not, see http://www.gnu.org/licenses/                 *
*                                                                             *
\*                                                                           */
package ch.openolitor.core.data

import ch.openolitor.core.models.
  _
import ch.openolitor.stammdaten.models._
import org.odftoolkit.simple._
import org.odftoolkit.simple.table._
import scala.collection.JavaConversions._
import scala.reflect.runtime.universe.{ Try => UTry, _ }
import java.util.Date
import akka.actor._
import java.io.File
import java.io.FileInputStream
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import ch.openolitor.util.DateTimeUtil
import scala.collection.immutable.TreeMap
import java.io.InputStream
import scala.util._
import org.joda.time.format.DateTimeFormatter
import java.util.Locale

case class ParseException(msg: String) extends Exception(msg)

class DataImportParser extends Actor with ActorLogging {
  import DataImportParser._

  val receive: Receive = {
    case ParseSpreadsheet(file) =>
      val rec = sender
      try {
        importData(file) match {
          case Success(result) =>
            rec ! result
          case Failure(error) =>
            log.warning("Couldn't import data {}", error.getMessage)
            rec ! ParseError(error)
        }
      } catch {
        case t: Throwable =>
          log.warning("Couldn't import data {}", t)
          rec ! ParseError(t)
      }
  }

  val modifyColumns = Seq("erstelldat", "ersteller", "modifidat", "modifikator")

  def importData(file: InputStream): Try[ParseResult] = {
    val doc = SpreadsheetDocument.loadDocument(file)

    //parse all sections
    for {
      (projekte, _) <- Try(doc.withSheet("Projekt")(parseProjekte))
      projekt = projekte.head
      (personen, _) <- Try(doc.withSheet("Personen")(parsePersonen))
      (kunden, kundeIdMapping) <- Try(doc.withSheet("Kunden")(parseKunden(personen)))
      (pendenzen, _) <- Try(doc.withSheet("Pendenzen")(parsePendenzen(kunden)))
      (tours, tourIdMapping) <- Try(doc.withSheet("Touren")(parseTours))
      (abotypen, abotypIdMapping) <- Try(doc.withSheet("Abotypen")(parseAbotypen))
      (depots, depotIdMapping) <- Try(doc.withSheet("Depots")(parseDepots))
      (abwesenheiten, _) <- Try(doc.withSheet("Abwesenheiten")(parseAbwesenheit))
      (vertriebsarten, vertriebsartIdMapping) <- Try(doc.withSheet("Vertriebsarten")(parseVertriebsarten))
      (vertriebe, _) <- Try(doc.withSheet("Vertriebe")(parseVertriebe(vertriebsarten)))
      (abos, _) <- Try(doc.withSheet("Abos")(parseAbos(kundeIdMapping, kunden, vertriebsartIdMapping, vertriebsarten, vertriebe, abotypen, depotIdMapping, depots, tourIdMapping, tours, abwesenheiten)))
      (lieferplanungen, _) <- Try(doc.withSheet("Lieferplanungen")(parseLieferplanungen))
      (lieferungen, _) <- Try(doc.withSheet("Lieferungen")(parseLieferungen(abotypen, vertriebe, abwesenheiten, lieferplanungen, depots, tours)))
      (produzenten, _) <- Try(doc.withSheet("Produzenten")(parseProduzenten))
      (produktkategorien, _) <- Try(doc.withSheet("Produktekategorien")(parseProduktekategorien))
      (produktProduzenten, _) <- Try(doc.withSheet("ProduktProduzenten")(parseProdukteProduzenten))
      (produktProduktekategorien, _) <- Try(doc.withSheet("ProduktProduktkategorien")(parseProdukteProduktkategorien))
      (produkte, _) <- Try(doc.withSheet("Produkte")(parseProdukte(produzenten, produktProduzenten, produktkategorien, produktProduktekategorien)))
      (lieferpositionen, _) <- Try(doc.withSheet("Lieferpositionen")(parseLieferpositionen(produkte, produzenten)))
      (bestellungen, _) <- Try(doc.withSheet("Bestellungen")(parseBestellungen(produzenten, lieferplanungen)))
      (bestellpositionen, _) <- Try(doc.withSheet("Bestellpositionen")(parseBestellpositionen(produkte)))
      (customKundentypen, _) <- Try(doc.withSheet("Kundentypen")(parseCustomKundentypen))
      (tourlieferungen, _) <- Try(doc.withSheet("Tourlieferungen")(parseTourlieferungen(abos, kunden)))
    } yield {
      ParseResult(
        projekt,
        customKundentypen,
        kunden,
        personen,
        pendenzen,
        tours,
        depots,
        abotypen,
        vertriebsarten,
        vertriebe,
        lieferungen,
        lieferplanungen,
        lieferpositionen,
        abos,
        abwesenheiten,
        produkte,
        produktkategorien,
        produktProduktekategorien,
        produzenten,
        produktProduzenten,
        bestellungen,
        bestellpositionen,
        tourlieferungen
      )
    }
  }

  def parseProjekte = {
    parse[Projekt, ProjektId]("id", Seq("bezeichnung", "strasse", "haus_nummer", "adress_zusatz", "plz", "ort",
      "preise_sichtbar", "preise_editierbar", "email_erforderlich", "waehrung", "geschaeftsjahr_monat", "geschaeftsjahr_tag", "two_factor_auth", "sprache") ++ modifyColumns) { id => indexes =>
      row =>
        //match column indexes
        val Seq(indexBezeichnung, indexStrasse, indexHausNummer, indexAdressZusatz, indexPlz, indexOrt, indexPreiseSichtbar,
          indexPreiseEditierbar, indexEmailErforderlich, indexWaehrung, indexGeschaeftsjahrMonat, indexGeschaeftsjahrTag, indexTwoFactorAuth, indexSprache) = indexes take (14)
        val Seq(indexErstelldat, indexErsteller, indexModifidat, indexModifikator) = indexes takeRight (4)
        val twoFactorAuth = parseMap(row.value[String](indexTwoFactorAuth))(r => Rolle(r).getOrElse(throw ParseException(s"Unknown Rolle $r while parsing Projekt")), _.toBoolean)

        Projekt(
          id = ProjektId(id),
          bezeichnung = row.value[String](indexBezeichnung),
          strasse = row.value[Option[String]](indexStrasse),
          hausNummer = row.value[Option[String]](indexHausNummer),
          adressZusatz = row.value[Option[String]](indexAdressZusatz),
          plz = row.value[Option[String]](indexPlz),
          ort = row.value[Option[String]](indexOrt),
          preiseSichtbar = row.value[Boolean](indexPreiseSichtbar),
          preiseEditierbar = row.value[Boolean](indexPreiseEditierbar),
          emailErforderlich = row.value[Boolean](indexEmailErforderlich),
          waehrung = Waehrung(row.value[String](indexWaehrung)),
          geschaeftsjahrMonat = row.value[Int](indexGeschaeftsjahrMonat),
          geschaeftsjahrTag = row.value[Int](indexGeschaeftsjahrTag),
          twoFactorAuthentication = twoFactorAuth,
          sprache = new Locale(row.value[String](indexSprache)),
          //modification flags
          erstelldat = row.value[DateTime](indexErstelldat),
          ersteller = PersonId(row.value[Long](indexErsteller)),
          modifidat = row.value[DateTime](indexModifidat),
          modifikator = PersonId(row.value[Long](indexModifikator))
        )
    }
  }

  def parseKunden(personen: List[Person]) = {
    parse[Kunde, KundeId]("id", Seq("bezeichnung", "strasse", "haus_nummer", "adress_zusatz", "plz", "ort", "bemerkungen",
      "abweichende_lieferadresse", "bezeichnung_lieferung", "strasse_lieferung", "haus_nummer_lieferung",
      "adress_zusatz_lieferung", "plz_lieferung", "ort_lieferung", "zusatzinfo_lieferung", "typen",
      "anzahl_abos", "anzahl_pendenzen", "anzahl_personen") ++ modifyColumns) { id => indexes => row =>
      //match column indexes
      val Seq(indexBezeichnung, indexStrasse, indexHausNummer, indexAdressZusatz, indexPlz, indexOrt, indexBemerkungen,
        indexAbweichendeLieferadresse, indexBezeichnungLieferung, indexStrasseLieferung, indexHausNummerLieferung,
        indexAdresseZusatzLieferung, indexPlzLieferung, indexOrtLieferung, indexZusatzinfoLieferung, indexKundentyp,
        indexAnzahlAbos, indexAnzahlPendenzen, indexAnzahlPersonen) =
        indexes take (19)
      val Seq(indexErstelldat, indexErsteller, indexModifidat, indexModifikator) = indexes takeRight (4)

      val kundeId = KundeId(id)
      val personenByKundeId = personen filter (_.kundeId == kundeId)
      if (personenByKundeId.isEmpty) {
        throw ParseException(s"Kunde id $kundeId does not reference any person. At least one person is required")
      }
      val bez = row.value[Option[String]](indexBezeichnung) getOrElse (s"${personenByKundeId.head.vorname}  ${personenByKundeId.head.name}")
      Kunde(
        kundeId,
        bezeichnung = bez,
        strasse = row.value[String](indexStrasse),
        hausNummer = row.value[Option[String]](indexHausNummer),
        adressZusatz = row.value[Option[String]](indexAdressZusatz),
        plz = row.value[String](indexPlz),
        ort = row.value[String](indexOrt),
        bemerkungen = row.value[Option[String]](indexBemerkungen),
        abweichendeLieferadresse = row.value[Boolean](indexAbweichendeLieferadresse),
        bezeichnungLieferung = row.value[Option[String]](indexBezeichnungLieferung),
        strasseLieferung = row.value[Option[String]](indexStrasseLieferung),
        hausNummerLieferung = row.value[Option[String]](indexHausNummerLieferung),
        adressZusatzLieferung = row.value[Option[String]](indexAdresseZusatzLieferung),
        plzLieferung = row.value[Option[String]](indexPlzLieferung),
        ortLieferung = row.value[Option[String]](indexOrtLieferung),
        zusatzinfoLieferung = row.value[Option[String]](indexZusatzinfoLieferung),
        typen = (row.value[String](indexKundentyp).split(",") map (KundentypId)).toSet,
        //Zusatzinformationen
        anzahlAbos = row.value[Int](indexAnzahlAbos),
        anzahlPendenzen = row.value[Int](indexAnzahlPendenzen),
        anzahlPersonen = row.value[Int](indexAnzahlPersonen),
        //modification flags
        erstelldat = row.value[DateTime](indexErstelldat),
        ersteller = PersonId(row.value[Long](indexErsteller)),
        modifidat = row.value[DateTime](indexModifidat),
        modifikator = PersonId(row.value[Long](indexModifikator))
      )
    }
  }

  def parsePersonen = {
{'fraction_non_alphanumeric': 0.11328890631125049, 'fraction_numerical': 0.0007404503680473888, 'mean_word_length': 2.8292194796531023, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 52, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6344648', 'n_tokens_mistral': 7205, 'n_tokens_neox': 7220, 'n_words': 1120}