diff --git a/.github/workflows/dependency-graph.yml b/.github/workflows/dependency-graph.yml index 5d5acc5d0..a1e26e79f 100644 --- a/.github/workflows/dependency-graph.yml +++ b/.github/workflows/dependency-graph.yml @@ -8,5 +8,5 @@ jobs: name: Update Dependency Graph runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: scalacenter/sbt-dependency-submission@v2 diff --git a/.github/workflows/sbt-docker-prepublish.yml b/.github/workflows/sbt-docker-prepublish.yml index 120a6993c..dc767235d 100644 --- a/.github/workflows/sbt-docker-prepublish.yml +++ b/.github/workflows/sbt-docker-prepublish.yml @@ -10,15 +10,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install locales run: sudo apt-get -y install locales - name: Fix up git URLs run: echo -e '[url "https://github.com/"]\n insteadOf = "git://github.com/"' >> ~/.gitconfig - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: temurin - name: Docker Login run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin diff --git a/.github/workflows/sbt-docker-publish.yml b/.github/workflows/sbt-docker-publish.yml index a3e7f9736..a90b87c2b 100644 --- a/.github/workflows/sbt-docker-publish.yml +++ b/.github/workflows/sbt-docker-publish.yml @@ -10,15 +10,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install locales run: sudo apt-get -y install locales - name: Fix up git URLs run: echo -e '[url "https://github.com/"]\n insteadOf = "git://github.com/"' >> ~/.gitconfig - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: temurin - name: Docker Login run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin diff --git a/.github/workflows/sbt-test.yml b/.github/workflows/sbt-test.yml index 2fc9014fc..d1eb01eee 100644 --- a/.github/workflows/sbt-test.yml +++ b/.github/workflows/sbt-test.yml @@ -1,7 +1,6 @@ name: sbt test -on: - push: +on: [push, pull_request] jobs: test: @@ -9,15 +8,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install locales run: sudo apt-get -y install locales - name: Fix up git URLs run: echo -e '[url "https://github.com/"]\n insteadOf = "git://github.com/"' >> ~/.gitconfig - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: temurin - name: Run Tests run: sbt "coverage; test; coverageReport; coverageAggregate;" diff --git a/build.sbt b/build.sbt index a88917a70..b969a697d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.13.10" +scalaVersion := "2.13.13" enablePlugins(JavaServerAppPackaging) enablePlugins(DockerPlugin) @@ -27,13 +27,14 @@ import java.text.SimpleDateFormat import java.util.Calendar -val specs2V = "4.19.0" // based on spray 1.3.x built in support +val specs2V = "4.20.5" // based on spray 1.3.x built in support val akkaV = "2.7.+" + val sprayV = "1.3.+" -val scalalikeV = "4.0.0" -val akkaHttpVersion = "10.2.10" -val akkaVersion = "2.7.0" -val testContainersVersion = "1.17.6" +val scalalikeV = "4.2.1" +val akkaHttpVersion = "10.5.3" +val akkaVersion = "2.8.5" +val testContainersVersion = "1.19.7" resolvers += Resolver.typesafeRepo("releases") @@ -41,10 +42,9 @@ val buildSettings = Seq( scalariformPreferences := scalariformPreferences.value .setPreference(DanglingCloseParenthesis, Force) .setPreference(AlignSingleLineCaseStatements, true), - organization := "ch.openolitor.scalamacros", - version := "2.6.28", - scalaVersion := "2.13.10", - crossScalaVersions := Seq("2.13.8", "2.13.10"), + version := "2.6.29", + scalaVersion := "2.13.13", + crossScalaVersions := Seq("2.13.8", "2.13.13"), resolvers ++= Resolver.sonatypeOssRepos("snapshots"), resolvers ++= Resolver.sonatypeOssRepos("releases"), resolvers += "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/", @@ -53,7 +53,7 @@ val buildSettings = Seq( libraryDependencies ++= { Seq( - "org.scala-lang.modules" %% "scala-xml" % "2.1.0", + "org.scala-lang.modules" %% "scala-xml" % "2.2.0", "javax.xml.bind" % "jaxb-api" % "2.3.1", "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, "com.typesafe.akka" %% "akka-http-caching" % akkaHttpVersion, @@ -73,21 +73,21 @@ val buildSettings = Seq( "org.specs2" %% "specs2-junit" % specs2V % "test", "org.specs2" %% "specs2-scalacheck" % specs2V % "test", "org.mockito" %% "mockito-scala" % "1.17.7" % "test", - "org.scalaz" %% "scalaz-core" % "7.3.6", // ### Scala 3 + "org.scalaz" %% "scalaz-core" % "7.3.8", // ### Scala 3 //use scala logging to log outside of the actor system "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5", // ### Scala 3 - "org.scalikejdbc" %% "scalikejdbc-async" % "0.15.0", + "org.scalikejdbc" %% "scalikejdbc-async" % "0.19.0", "org.scalikejdbc" %% "scalikejdbc-config" % scalalikeV, // ### Scala 3 "org.scalikejdbc" %% "scalikejdbc-test" % scalalikeV % "test", // ### Scala 3 "org.scalikejdbc" %% "scalikejdbc-syntax-support-macro" % scalalikeV, // ### Scala 3 "org.scalikejdbc" %% "scalikejdbc-joda-time" % scalalikeV, // ### Scala 3 - "com.github.jasync-sql" % "jasync-mysql" % "2.1.+", + "com.github.jasync-sql" % "jasync-mysql" % "2.2.4", "com.h2database" % "h2" % "2.1.214" % "test", "org.testcontainers" % "mariadb" % testContainersVersion % "test", "io.findify" %% "s3mock" % "0.2.6" % "test", - "ch.qos.logback" % "logback-classic" % "1.4.5", - "org.mariadb.jdbc" % "mariadb-java-client" % "3.1.0", - "mysql" % "mysql-connector-java" % "8.0.31", + "ch.qos.logback" % "logback-classic" % "1.5.3", + "org.mariadb.jdbc" % "mariadb-java-client" % "3.1.4", + "mysql" % "mysql-connector-java" % "8.0.33", // Libreoffice document API "org.odftoolkit" % "simple-odf" % "0.9.0" withSources(), "com.scalapenos" %% "stamina-json" % "0.1.6", // ### NO Scala 3 @@ -100,27 +100,27 @@ val buildSettings = Seq( "com.github.blemale" %% "scaffeine" % "5.2.1", // ### Scala 3 "de.zalando" %% "beard" % "0.3.3" exclude("ch.qos.logback", "logback-classic") from "https://github.com/OpenOlitor/beard/releases/download/0.3.3/beard_2.13-0.3.3.jar", // ### NO Scala 3, NO Scala 2.13 // transitive dependencies of legacy de.zalando.beard - "org.antlr" % "antlr4" % "4.8-1", - "io.monix" %% "monix" % "3.4.0", // ### Scala 3 - "net.codecrete.qrbill" % "qrbill-generator" % "2.4.3", - "io.nayuki" % "qrcodegen" % "1.6.0", - "org.apache.pdfbox" % "pdfbox" % "2.0.26", - "org.apache.pdfbox" % "pdfbox-parent" % "2.0.26" pomOnly(), - "org.apache.xmlgraphics" % "batik-transcoder" % "1.16", - "org.apache.xmlgraphics" % "batik-codec" % "1.16", + "org.antlr" % "antlr4" % "4.13.1", + "io.monix" %% "monix" % "3.4.1", // ### Scala 3 + "net.codecrete.qrbill" % "qrbill-generator" % "3.2.0", + "io.nayuki" % "qrcodegen" % "1.8.0", + "org.apache.pdfbox" % "pdfbox" % "2.0.30", + "org.apache.pdfbox" % "pdfbox-parent" % "2.0.30" pomOnly(), + "org.apache.xmlgraphics" % "batik-transcoder" % "1.17", + "org.apache.xmlgraphics" % "batik-codec" % "1.17", "com.tegonal" %% "cf-env-config-loader" % "1.1.2", // ### NO Scala 3, NO Scala 2.13 "com.eatthepath" % "java-otp" % "0.4.0", - "org.apache.pdfbox" % "pdfbox-tools" % "2.0.27" + "org.apache.pdfbox" % "pdfbox-tools" % "2.0.30" ) }, dependencyOverrides ++= Seq( "org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.1", "xerces" % "xercesImpl" % "2.12.2", - "org.apache.commons" % "commons-compress" % "1.22", - "io.netty" % "netty-handler" % "4.1.85.Final", + "org.apache.commons" % "commons-compress" % "1.26.0", + "io.netty" % "netty-handler" % "4.1.107.Final", "org.apache.jena" % "jena-core" % "4.6.1", "com.google.protobuf" % "protobuf-java" % "3.21.10", - "com.google.guava" % "guava" % "31.1-jre" + "com.google.guava" % "guava" % "33.0.0-jre" ) ) @@ -189,7 +189,7 @@ val updateLatest = sys.env.get("DOCKER_UPDATE_LATEST") match { } dockerUpdateLatest := updateLatest -dockerBaseImage := "eclipse-temurin:17-alpine" +dockerBaseImage := "eclipse-temurin:21-alpine" dockerExposedPorts ++= Seq(9003) // the directories created, e.g. /var/log/openolitor-server, are created using user id 1000, diff --git a/project/build.properties b/project/build.properties index 40f4d4b6b..b089b60c7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1,2 @@ -sbt.version=1.8.0 \ No newline at end of file +sbt.version=1.9.9 + diff --git a/project/plugins.sbt b/project/plugins.sbt index c874537a3..b5eee33fb 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,13 +1,13 @@ -addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.11") +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16") addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.3") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") -addSbtPlugin("org.scalaxb" % "sbt-scalaxb" % "1.9.0") +addSbtPlugin("org.scalaxb" % "sbt-scalaxb" % "1.12.0") -addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.5") //addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.5.10") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.6") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11") -dependencyOverrides += "org.scala-lang.modules" %% "scala-xml" % "2.1.0" +dependencyOverrides += "org.scala-lang.modules" %% "scala-xml" % "2.2.0" diff --git a/scalastyle-config.xml b/scalastyle-config.xml index 7e3596f12..c05a9b669 100644 --- a/scalastyle-config.xml +++ b/scalastyle-config.xml @@ -8,21 +8,28 @@ - + diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 34e2d2880..4961ccf8a 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -306,9 +306,9 @@ openolitor { slick { profile = "slick.jdbc.MySQLProfile$" db { - url = "jdbc:mysql://localhost:3306/csa1?cachePrepStmts=true&cacheCallableStmts=true&cacheServerConfiguration=true&useLocalSessionState=true&elideSetAutoCommits=true&alwaysSendSetIsolation=false&enableQueryTimeouts=false&connectionAttributes=none&verifyServerCertificate=false&useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=UTC&rewriteBatchedStatements=true" - user = "user_csa1" - password = "changeThisPasswrod" + url = "jdbc:mysql://localhost:3307/try?cachePrepStmts=true&cacheCallableStmts=true&cacheServerConfiguration=true&useLocalSessionState=true&elideSetAutoCommits=true&alwaysSendSetIsolation=false&enableQueryTimeouts=false&connectionAttributes=none&verifyServerCertificate=false&useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=UTC&rewriteBatchedStatements=true" + user = "super" + password = "thedefaultdbpasswordneedstobechanged" driver = "com.mysql.cj.jdbc.Driver" connectionPool = HikariCP numThreads = 5 @@ -320,10 +320,10 @@ openolitor { # Mandant specific db settings db: { default: { - url = "jdbc:mysql://localhost:3306/csa1" + url = "jdbc:mysql://localhost:3307/try" driver = "com.mysql.cj.jdbc.Driver" - user = "user_csa1" - password = "changeThisPasswrod" + user = "super" + password = "thedefaultdbpasswordneedstobechanged" } } diff --git a/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzAktionenService.scala b/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzAktionenService.scala index dac797021..9e5e42b3c 100644 --- a/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzAktionenService.scala +++ b/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzAktionenService.scala @@ -28,7 +28,7 @@ import ch.openolitor.arbeitseinsatz.ArbeitseinsatzCommandHandler._ import ch.openolitor.core._ import ch.openolitor.core.db._ import ch.openolitor.core.domain._ -import ch.openolitor.stammdaten.EmailHandler +import ch.openolitor.stammdaten.MailCommandForwarder import akka.util.Timeout import scala.concurrent.duration._ @@ -42,39 +42,22 @@ import ch.openolitor.core.repositories.EventPublishingImplicits._ import scala.concurrent.ExecutionContext.Implicits._ object ArbeitseinsatzAktionenService { - def apply(implicit sysConfig: SystemConfig, system: ActorSystem, mailService: ActorRef): ArbeitseinsatzAktionenService = new DefaultArbeitseinsatzAktionenService(sysConfig, system, mailService) + def apply(implicit sysConfig: SystemConfig, system: ActorSystem): ArbeitseinsatzAktionenService = new DefaultArbeitseinsatzAktionenService(sysConfig, system) } -class DefaultArbeitseinsatzAktionenService(sysConfig: SystemConfig, override val system: ActorSystem, override val mailService: ActorRef) - extends ArbeitseinsatzAktionenService(sysConfig, mailService) with DefaultArbeitseinsatzWriteRepositoryComponent { +class DefaultArbeitseinsatzAktionenService(sysConfig: SystemConfig, override val system: ActorSystem) + extends ArbeitseinsatzAktionenService(sysConfig) with DefaultArbeitseinsatzWriteRepositoryComponent { } /** * Actor zum Verarbeiten der Aktionen für das Arbeitseinsatz Modul */ -class ArbeitseinsatzAktionenService(override val sysConfig: SystemConfig, override val mailService: ActorRef) extends EventService[PersistentEvent] with LazyLogging with AsyncConnectionPoolContextAware with EmailHandler - with ArbeitseinsatzDBMappings with MailServiceReference with ArbeitseinsatzEventStoreSerializer { +class ArbeitseinsatzAktionenService(override val sysConfig: SystemConfig) extends EventService[PersistentEvent] with LazyLogging with AsyncConnectionPoolContextAware + with ArbeitseinsatzDBMappings with ArbeitseinsatzEventStoreSerializer { self: ArbeitseinsatzWriteRepositoryComponent => - implicit val timeout = Timeout(15.seconds) //sending mails might take a little longer - val handle: Handle = { - case SendEmailToArbeitsangebotPersonenEvent(meta, subject, body, replyTo, context) => - checkBccAndSend(meta, subject, body, replyTo, context.person, context, mailService) case e => logger.warn(s"Unknown event:$e") } - - protected def checkBccAndSend(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], person: PersonEmailData, context: Product, mailService: ActorRef)(implicit originator: PersonId = meta.originator): Unit = { - DB localTxPostPublish { implicit session => implicit publisher => - lazy val bccAddress = config.getString("smtp.bcc") - arbeitseinsatzWriteRepository.getProjekt map { projekt: Projekt => - projekt.sendEmailToBcc match { - case true => sendEmail(meta, subject, body, replyTo, Some(bccAddress), person, None, context, mailService) - case false => sendEmail(meta, subject, body, replyTo, None, person, None, context, mailService) - } - } - } - } - } diff --git a/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzCommandHandler.scala b/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzCommandHandler.scala index 21acf0fa2..8957e714e 100644 --- a/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzCommandHandler.scala +++ b/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzCommandHandler.scala @@ -32,8 +32,9 @@ import ch.openolitor.core.domain._ import ch.openolitor.core.models.PersonId import ch.openolitor.stammdaten.models.{ Person, PersonContactPermissionModify, PersonEmailData } import ch.openolitor.mailtemplates.engine.MailTemplateService -import akka.actor.ActorSystem +import akka.actor.{ ActorRef, ActorSystem } import ch.openolitor.core.security.Subject +import ch.openolitor.stammdaten.{ DefaultMailCommandForwarderComponent, MailCommandForwarderComponent, ProjektHelper } import scalikejdbc._ import scala.concurrent.ExecutionContext.Implicits._ @@ -41,13 +42,17 @@ import scala.util._ object ArbeitseinsatzCommandHandler { case class ArbeitsangebotArchivedCommand(id: ArbeitsangebotId, originator: PersonId = PersonId(100)) extends UserCommand + case class SendEmailToArbeitsangebotPersonenCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[ArbeitsangebotId]) extends UserCommand + case class SendEmailToArbeitsangebotPersonenEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: ArbeitsangebotMailContext) extends PersistentGeneratedEvent with JSONSerializable + case class ChangeContactPermissionForUserCommand(originator: PersonId, subject: Subject, personId: PersonId) extends UserCommand } -trait ArbeitseinsatzCommandHandler extends CommandHandler with ArbeitseinsatzDBMappings with ConnectionPoolContextAware with MailTemplateService { - self: ArbeitseinsatzReadRepositorySyncComponent => +trait ArbeitseinsatzCommandHandler extends CommandHandler with ArbeitseinsatzDBMappings with ConnectionPoolContextAware with MailTemplateService with ProjektHelper { + self: ArbeitseinsatzReadRepositorySyncComponent with MailCommandForwarderComponent => + import ArbeitseinsatzCommandHandler._ import EntityStore._ @@ -56,63 +61,73 @@ trait ArbeitseinsatzCommandHandler extends CommandHandler with ArbeitseinsatzDBM /* * Custom update command handling */ - case ArbeitsangebotArchivedCommand(id, personId) => idFactory => meta => - DB readOnly { implicit session => - arbeitseinsatzReadRepository.getById(arbeitsangebotMapping, id) map { arbeitsangebot => - arbeitsangebot.status match { - case (Bereit) => - val copy = arbeitsangebot.copy(status = Archiviert) - Success(Seq(EntityUpdateEvent(id, copy))) - case _ => - Failure(new InvalidStateException("Der Arbeitseinsatz muss 'Bereit' sein.")) - } - } getOrElse Failure(new InvalidStateException(s"Keine Arbeitseinsatz zu Id $id gefunden")) - } + case ArbeitsangebotArchivedCommand(id, personId) => idFactory => + meta => + DB readOnly { implicit session => + arbeitseinsatzReadRepository.getById(arbeitsangebotMapping, id) map { arbeitsangebot => + arbeitsangebot.status match { + case (Bereit) => + val copy = arbeitsangebot.copy(status = Archiviert) + Success(Seq(EntityUpdateEvent(id, copy))) + case _ => + Failure(new InvalidStateException("Der Arbeitseinsatz muss 'Bereit' sein.")) + } + } getOrElse Failure(new InvalidStateException(s"Keine Arbeitseinsatz zu Id $id gefunden")) + } + + case SendEmailToArbeitsangebotPersonenCommand(personId, subject, body, replyTo, ids) => idFactory => + meta => + DB readOnly { implicit session => + if (checkTemplateArbeitsangebot(body, subject, ids)) { + val events = ids flatMap { arbeitsangebotId: ArbeitsangebotId => + arbeitseinsatzReadRepository.getById(arbeitsangebotMapping, arbeitsangebotId) map { arbeitsangebot => + arbeitseinsatzReadRepository.getPersonenByArbeitsangebot(arbeitsangebotId) map { person => + val personEmailData = copyTo[Person, PersonEmailData](person) + val mailContext = ArbeitsangebotMailContext(personEmailData, arbeitsangebot) - case SendEmailToArbeitsangebotPersonenCommand(personId, subject, body, replyTo, ids) => idFactory => meta => - DB readOnly { implicit session => - if (checkTemplateArbeitsangebot(body, subject, ids)) { - val events = ids flatMap { arbeitsangebotId: ArbeitsangebotId => - arbeitseinsatzReadRepository.getById(arbeitsangebotMapping, arbeitsangebotId) map { arbeitsangebot => - arbeitseinsatzReadRepository.getPersonenByArbeitsangebot(arbeitsangebotId) map { person => - val personEmailData = copyTo[Person, PersonEmailData](person) - val mailContext = ArbeitsangebotMailContext(personEmailData, arbeitsangebot) - DefaultResultingEvent(factory => SendEmailToArbeitsangebotPersonenEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + mailCommandForwarder.sendEmail(meta, subject, body, replyTo, determineBcc, personEmailData, None, mailContext) + + DefaultResultingEvent(factory => SendEmailToArbeitsangebotPersonenEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + } } } + Success(events.flatten) + } else { + Failure(new InvalidStateException("The template is not valid")) } - Success(events.flatten) - } else { - Failure(new InvalidStateException("The template is not valid")) } - } - case ChangeContactPermissionForUserCommand(originator, subject, personId) => idFactory => meta => - DB readOnly { implicit session => - val entityToSave = arbeitseinsatzReadRepository.getById(personMapping, personId) map { user => - copyTo[Person, PersonContactPermissionModify](user, "contactPermission" -> !user.contactPermission) - } - entityToSave match { - case Some(personContactPermissionModify) => Success(Seq(EntityUpdateEvent(personId, personContactPermissionModify))) - case _ => Failure(new InvalidStateException(s"This person was not found.")) + case ChangeContactPermissionForUserCommand(originator, subject, personId) => idFactory => + meta => + DB readOnly { implicit session => + val entityToSave = arbeitseinsatzReadRepository.getById(personMapping, personId) map { user => + copyTo[Person, PersonContactPermissionModify](user, "contactPermission" -> !user.contactPermission) + } + entityToSave match { + case Some(personContactPermissionModify) => Success(Seq(EntityUpdateEvent(personId, personContactPermissionModify))) + case _ => Failure(new InvalidStateException(s"This person was not found.")) + } } - } /* * Insert command handling */ - case e @ InsertEntityCommand(personId, entity: ArbeitskategorieModify) => idFactory => meta => - handleEntityInsert[ArbeitskategorieModify, ArbeitskategorieId](idFactory, meta, entity, ArbeitskategorieId.apply) - case e @ InsertEntityCommand(personId, entity: ArbeitsangebotModify) => idFactory => meta => - handleEntityInsert[ArbeitsangebotModify, ArbeitsangebotId](idFactory, meta, entity, ArbeitsangebotId.apply) - case e @ InsertEntityCommand(personId, entity: ArbeitseinsatzModify) => idFactory => meta => - handleEntityInsert[ArbeitseinsatzModify, ArbeitseinsatzId](idFactory, meta, entity, ArbeitseinsatzId.apply) - case e @ InsertEntityCommand(personId, entity: ArbeitsangeboteDuplicate) => idFactory => meta => - val events = entity.daten.map { datum => - val arbeitsangebotDuplicate = copyTo[ArbeitsangeboteDuplicate, ArbeitsangebotDuplicate](entity, "zeitVon" -> datum) - insertEntityEvent[ArbeitsangebotDuplicate, ArbeitsangebotId](idFactory, meta, arbeitsangebotDuplicate, ArbeitsangebotId.apply) - } - Success(events) + case e @ InsertEntityCommand(personId, entity: ArbeitskategorieModify) => idFactory => + meta => + handleEntityInsert[ArbeitskategorieModify, ArbeitskategorieId](idFactory, meta, entity, ArbeitskategorieId.apply) + case e @ InsertEntityCommand(personId, entity: ArbeitsangebotModify) => idFactory => + meta => + handleEntityInsert[ArbeitsangebotModify, ArbeitsangebotId](idFactory, meta, entity, ArbeitsangebotId.apply) + case e @ InsertEntityCommand(personId, entity: ArbeitseinsatzModify) => idFactory => + meta => + handleEntityInsert[ArbeitseinsatzModify, ArbeitseinsatzId](idFactory, meta, entity, ArbeitseinsatzId.apply) + case e @ InsertEntityCommand(personId, entity: ArbeitsangeboteDuplicate) => idFactory => + meta => + val events = entity.daten.map { datum => + val arbeitsangebotDuplicate = copyTo[ArbeitsangeboteDuplicate, ArbeitsangebotDuplicate](entity, "zeitVon" -> datum) + insertEntityEvent[ArbeitsangebotDuplicate, ArbeitsangebotId](idFactory, meta, arbeitsangebotDuplicate, ArbeitsangebotId.apply) + } + Success(events) } private def checkTemplateArbeitsangebot(body: String, subject: String, ids: Seq[ArbeitsangebotId])(implicit session: DBSession): Boolean = { @@ -132,7 +147,9 @@ trait ArbeitseinsatzCommandHandler extends CommandHandler with ArbeitseinsatzDBM } } -class DefaultArbeitseinsatzCommandHandler(override val sysConfig: SystemConfig, override val system: ActorSystem) extends ArbeitseinsatzCommandHandler - with DefaultArbeitseinsatzReadRepositorySyncComponent { +class DefaultArbeitseinsatzCommandHandler(override val sysConfig: SystemConfig, override val system: ActorSystem, override val mailService: ActorRef) extends ArbeitseinsatzCommandHandler + with DefaultArbeitseinsatzReadRepositorySyncComponent + with DefaultMailCommandForwarderComponent { + override def projektReadRepository = arbeitseinsatzReadRepository } diff --git a/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzEntityStoreView.scala b/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzEntityStoreView.scala index f38013e44..d6c9003c6 100644 --- a/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzEntityStoreView.scala +++ b/src/main/scala/ch/openolitor/arbeitseinsatz/ArbeitseinsatzEntityStoreView.scala @@ -29,10 +29,10 @@ import ch.openolitor.core.db.ConnectionPoolContextAware import ch.openolitor.core.domain._ object ArbeitseinsatzEntityStoreView { - def props(mailService: ActorRef, dbEvolutionActor: ActorRef, airbrakeNotifier: ActorRef)(implicit sysConfig: SystemConfig, system: ActorSystem): Props = Props(classOf[DefaultArbeitseinsatzEntityStoreView], mailService, dbEvolutionActor, sysConfig, system, airbrakeNotifier) + def props(dbEvolutionActor: ActorRef, airbrakeNotifier: ActorRef)(implicit sysConfig: SystemConfig, system: ActorSystem): Props = Props(classOf[DefaultArbeitseinsatzEntityStoreView], dbEvolutionActor, sysConfig, system, airbrakeNotifier) } -class DefaultArbeitseinsatzEntityStoreView(override val mailService: ActorRef, val dbEvolutionActor: ActorRef, implicit val sysConfig: SystemConfig, implicit val system: ActorSystem, val airbrakeNotifier: ActorRef) extends ArbeitseinsatzEntityStoreView +class DefaultArbeitseinsatzEntityStoreView(val dbEvolutionActor: ActorRef, implicit val sysConfig: SystemConfig, implicit val system: ActorSystem, val airbrakeNotifier: ActorRef) extends ArbeitseinsatzEntityStoreView with DefaultArbeitseinsatzWriteRepositoryComponent /** @@ -48,11 +48,11 @@ trait ArbeitseinsatzEntityStoreView extends EntityStoreView /** * Instanzieren der jeweiligen Insert, Update und Delete Child Actors */ -trait ArbeitseinsatzEntityStoreViewComponent extends EntityStoreViewComponent with ActorSystemReference with MailServiceReference with SystemConfigReference { +trait ArbeitseinsatzEntityStoreViewComponent extends EntityStoreViewComponent with ActorSystemReference with SystemConfigReference { override val insertService = ArbeitseinsatzInsertService(sysConfig, system) override val updateService = ArbeitseinsatzUpdateService(sysConfig, system) override val deleteService = ArbeitseinsatzDeleteService(sysConfig, system) - override val aktionenService = ArbeitseinsatzAktionenService(sysConfig, system, mailService) + override val aktionenService = ArbeitseinsatzAktionenService(sysConfig, system) } diff --git a/src/main/scala/ch/openolitor/arbeitseinsatz/repositories/ArbeitseinsatzReadRepositorySync.scala b/src/main/scala/ch/openolitor/arbeitseinsatz/repositories/ArbeitseinsatzReadRepositorySync.scala index 8c37c8799..20f425488 100644 --- a/src/main/scala/ch/openolitor/arbeitseinsatz/repositories/ArbeitseinsatzReadRepositorySync.scala +++ b/src/main/scala/ch/openolitor/arbeitseinsatz/repositories/ArbeitseinsatzReadRepositorySync.scala @@ -25,11 +25,12 @@ package ch.openolitor.arbeitseinsatz.repositories import ch.openolitor.arbeitseinsatz.models._ import ch.openolitor.core.repositories._ import ch.openolitor.stammdaten.models.{ KundeId, Person, Projekt } +import ch.openolitor.stammdaten.repositories.{ ProjektReadRepositorySync, ProjektReadRepositorySyncImpl } import ch.openolitor.util.parsing.{ GeschaeftsjahrFilter, QueryFilter } import com.typesafe.scalalogging.LazyLogging import scalikejdbc.DBSession -trait ArbeitseinsatzReadRepositorySync extends BaseReadRepositorySync { +trait ArbeitseinsatzReadRepositorySync extends BaseReadRepositorySync with ProjektReadRepositorySync { def getArbeitskategorien(implicit session: DBSession): List[Arbeitskategorie] def getArbeitsangebote(implicit session: DBSession, gjFilter: Option[GeschaeftsjahrFilter], queryString: Option[QueryFilter]): List[Arbeitsangebot] @@ -43,10 +44,9 @@ trait ArbeitseinsatzReadRepositorySync extends BaseReadRepositorySync { def getArbeitseinsatzabrechnung(implicit session: DBSession, queryString: Option[QueryFilter]): List[ArbeitseinsatzAbrechnung] def getArbeitseinsatzDetailByArbeitsangebot(arbeitsangebotId: ArbeitsangebotId)(implicit session: DBSession): List[ArbeitseinsatzDetail] def getPersonenByArbeitsangebot(arbeitsangebotId: ArbeitsangebotId)(implicit session: DBSession): List[Person] - def getProjekt(implicit session: DBSession): Option[Projekt] } -trait ArbeitseinsatzReadRepositorySyncImpl extends ArbeitseinsatzReadRepositorySync with LazyLogging with ArbeitseinsatzRepositoryQueries { +trait ArbeitseinsatzReadRepositorySyncImpl extends ArbeitseinsatzReadRepositorySync with LazyLogging with ArbeitseinsatzRepositoryQueries with ProjektReadRepositorySyncImpl { def getArbeitskategorien(implicit session: DBSession): List[Arbeitskategorie] = { getArbeitskategorienQuery.apply() } @@ -94,8 +94,4 @@ trait ArbeitseinsatzReadRepositorySyncImpl extends ArbeitseinsatzReadRepositoryS def getPersonenByArbeitsangebot(arbeitsangebotId: ArbeitsangebotId)(implicit session: DBSession): List[Person] = { getPersonenByArbeitsangebotQuery(arbeitsangebotId).apply() } - - def getProjekt(implicit session: DBSession): Option[Projekt] = { - getProjektQuery.apply() - } } diff --git a/src/main/scala/ch/openolitor/arbeitseinsatz/repositories/ArbeitseinsatzRepositoryQueries.scala b/src/main/scala/ch/openolitor/arbeitseinsatz/repositories/ArbeitseinsatzRepositoryQueries.scala index 2cc1bed43..5be3727dd 100644 --- a/src/main/scala/ch/openolitor/arbeitseinsatz/repositories/ArbeitseinsatzRepositoryQueries.scala +++ b/src/main/scala/ch/openolitor/arbeitseinsatz/repositories/ArbeitseinsatzRepositoryQueries.scala @@ -28,6 +28,7 @@ import ch.openolitor.stammdaten.models._ import ch.openolitor.core.Macros._ import ch.openolitor.stammdaten.StammdatenDBMappings import ch.openolitor.stammdaten.models.KundeId +import ch.openolitor.stammdaten.repositories.StammdatenProjektRepositoryQueries import ch.openolitor.util.querybuilder.UriQueryParamToSQLSyntaxBuilder import ch.openolitor.util.parsing.{ GeschaeftsjahrFilter, QueryFilter } import com.typesafe.scalalogging.LazyLogging @@ -36,7 +37,7 @@ import scalikejdbc._ import scala.language.postfixOps -trait ArbeitseinsatzRepositoryQueries extends LazyLogging with ArbeitseinsatzDBMappings with StammdatenDBMappings { +trait ArbeitseinsatzRepositoryQueries extends LazyLogging with ArbeitseinsatzDBMappings with StammdatenDBMappings with StammdatenProjektRepositoryQueries { lazy val arbeitskategorie = arbeitskategorieMapping.syntax("arbeitskategorie") lazy val arbeitsangebot = arbeitsangebotMapping.syntax("arbeitsangebot") @@ -48,7 +49,6 @@ trait ArbeitseinsatzRepositoryQueries extends LazyLogging with ArbeitseinsatzDBM lazy val depotlieferungAbo = depotlieferungAboMapping.syntax("depotlieferungAbo") lazy val heimlieferungAbo = heimlieferungAboMapping.syntax("heimlieferungAbo") lazy val postlieferungAbo = postlieferungAboMapping.syntax("postlieferungAbo") - lazy val projekt = projektMapping.syntax("projekt") protected def getArbeitskategorienQuery = { withSQL { @@ -252,12 +252,4 @@ trait ArbeitseinsatzRepositoryQueries extends LazyLogging with ArbeitseinsatzDBM .where.eq(arbeitseinsatz.arbeitsangebotId, arbeitsangebotId) }.map(personMapping(person)).list } - - protected def getProjektQuery = { - withSQL { - select - .from(projektMapping as projekt) - }.map(projektMapping(projekt)).single - } - } diff --git a/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungAktionenService.scala b/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungAktionenService.scala index 5edb36d2d..8c7cedfa9 100644 --- a/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungAktionenService.scala +++ b/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungAktionenService.scala @@ -26,7 +26,6 @@ import ch.openolitor.core._ import ch.openolitor.core.db._ import ch.openolitor.core.domain._ import ch.openolitor.buchhaltung.models._ -import ch.openolitor.stammdaten.models.{ KundeId, Person, PersonEmailData, Projekt } import ch.openolitor.core.models.PersonId import scalikejdbc._ import com.typesafe.scalalogging.LazyLogging @@ -42,35 +41,33 @@ import ch.openolitor.buchhaltung.repositories.DefaultBuchhaltungWriteRepositoryC import ch.openolitor.buchhaltung.repositories.BuchhaltungWriteRepositoryComponent import ch.openolitor.core.repositories.EventPublishingImplicits._ import ch.openolitor.core.repositories.EventPublisher -import ch.openolitor.stammdaten.EmailHandler +import ch.openolitor.stammdaten.MailCommandForwarder import scala.concurrent.duration._ import scala.concurrent.ExecutionContext object BuchhaltungAktionenService { - def apply(implicit sysConfig: SystemConfig, system: ActorSystem, mailService: ActorRef): BuchhaltungAktionenService = new DefaultBuchhaltungAktionenService(sysConfig, system, mailService) + def apply(implicit sysConfig: SystemConfig, system: ActorSystem): BuchhaltungAktionenService = new DefaultBuchhaltungAktionenService(sysConfig, system) } -class DefaultBuchhaltungAktionenService(sysConfig: SystemConfig, override val system: ActorSystem, override val mailService: ActorRef) - extends BuchhaltungAktionenService(sysConfig, mailService, system.dispatcher) with DefaultBuchhaltungWriteRepositoryComponent {} +class DefaultBuchhaltungAktionenService(sysConfig: SystemConfig, override val system: ActorSystem) + extends BuchhaltungAktionenService(sysConfig, system.dispatcher) with DefaultBuchhaltungWriteRepositoryComponent {} /** * Actor zum Verarbeiten der Aktionen für das Buchhaltung Modul */ -class BuchhaltungAktionenService(override val sysConfig: SystemConfig, override val mailService: ActorRef, override implicit val executionContext: ExecutionContext) extends EventService[PersistentEvent] +class BuchhaltungAktionenService(override val sysConfig: SystemConfig, override implicit val executionContext: ExecutionContext) extends EventService[PersistentEvent] with LazyLogging with AsyncConnectionPoolContextAware with BuchhaltungDBMappings - with MailServiceReference with BuchhaltungEventStoreSerializer with MailTemplateService - with EmailHandler with SystemConfigReference with ExecutionContextAware { self: BuchhaltungWriteRepositoryComponent => val Zero = 0 - override val False = false + val False = false implicit val timeout = Timeout(config.getStringOption("openolitor.emailTimeOut").getOrElse("15").toInt.seconds) @@ -97,8 +94,6 @@ class BuchhaltungAktionenService(override val sysConfig: SystemConfig, override rechnungPDFStored(meta, rechnungId, fileStoreId) case MahnungPDFStoredEvent(meta, rechnungId, fileStoreId) => mahnungPDFStored(meta, rechnungId, fileStoreId) - case SendEmailToInvoiceSubscribersEvent(meta, subject, body, replyTo, invoice, context) => - checkBccAndSend(meta, subject, body, replyTo, context.person, invoice, context, mailService) case e => logger.warn(s"Unknown event:$e") } @@ -285,16 +280,4 @@ class BuchhaltungAktionenService(override val sysConfig: SystemConfig, override } } } - - protected def checkBccAndSend(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], person: PersonEmailData, invoiceReference: Option[String], context: Product, mailService: ActorRef)(implicit originator: PersonId = meta.originator): Unit = { - DB localTxPostPublish { implicit session => implicit publisher => - lazy val bccAddress = config.getString("smtp.bcc") - buchhaltungWriteRepository.getProjekt map { projekt: Projekt => - projekt.sendEmailToBcc match { - case true => sendEmail(meta, subject, body, replyTo, Some(bccAddress), person, invoiceReference, context, mailService) - case false => sendEmail(meta, subject, body, replyTo, None, person, invoiceReference, context, mailService) - } - } - } - } } diff --git a/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungCommandHandler.scala b/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungCommandHandler.scala index cf2dee3cd..7b1f0810b 100644 --- a/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungCommandHandler.scala +++ b/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungCommandHandler.scala @@ -33,7 +33,7 @@ import scalikejdbc.DB import scalikejdbc.DBSession import ch.openolitor.buchhaltung.models._ import ch.openolitor.core.exceptions.InvalidStateException -import akka.actor.ActorSystem +import akka.actor.{ ActorRef, ActorSystem } import ch.openolitor.core._ import ch.openolitor.core.filestore.FileTypeFilenameMapping import ch.openolitor.core.db.ConnectionPoolContextAware @@ -42,6 +42,7 @@ import ch.openolitor.core.db.AsyncConnectionPoolContextAware import ch.openolitor.buchhaltung.repositories.DefaultBuchhaltungReadRepositorySyncComponent import ch.openolitor.buchhaltung.repositories.BuchhaltungReadRepositorySyncComponent import ch.openolitor.core.Macros.copyTo +import ch.openolitor.stammdaten.{ DefaultMailCommandForwarderComponent, MailCommandForwarderComponent, ProjektHelper } import scala.concurrent.ExecutionContext @@ -92,8 +93,9 @@ trait BuchhaltungCommandHandler extends CommandHandler with AsyncConnectionPoolContextAware with FileTypeFilenameMapping with MailTemplateService + with ProjektHelper with ExecutionContextAware { - self: BuchhaltungReadRepositorySyncComponent => + self: BuchhaltungReadRepositorySyncComponent with MailCommandForwarderComponent => import BuchhaltungCommandHandler._ import EntityStore._ @@ -262,8 +264,13 @@ trait BuchhaltungCommandHandler extends CommandHandler val mailContext = RechnungMailContext(personEmailData, rechnung) if (attachInvoice) { val documentToAttach = if (rechnung.mahnungFileStoreIds.isEmpty) rechnung.fileStoreId else Some(rechnung.mahnungFileStoreIds.head) + + mailCommandForwarder.sendEmail(meta, subject, body, replyTo, determineBcc, personEmailData, documentToAttach, mailContext) + DefaultResultingEvent(factory => SendEmailToInvoiceSubscribersEvent(factory.newMetadata(), subject, body, replyTo, documentToAttach, mailContext)) } else { + mailCommandForwarder.sendEmail(meta, subject, body, replyTo, determineBcc, personEmailData, None, mailContext) + DefaultResultingEvent(factory => SendEmailToInvoiceSubscribersEvent(factory.newMetadata(), subject, body, replyTo, None, mailContext)) } } @@ -395,7 +402,9 @@ trait BuchhaltungCommandHandler extends CommandHandler } } -class DefaultBuchhaltungCommandHandler(override val sysConfig: SystemConfig, override val system: ActorSystem) extends BuchhaltungCommandHandler - with DefaultBuchhaltungReadRepositorySyncComponent { +class DefaultBuchhaltungCommandHandler(override val sysConfig: SystemConfig, override val system: ActorSystem, override val mailService: ActorRef) extends BuchhaltungCommandHandler + with DefaultBuchhaltungReadRepositorySyncComponent with DefaultMailCommandForwarderComponent with MailServiceReference { override implicit protected val executionContext: ExecutionContext = system.dispatcher + + override def projektReadRepository = buchhaltungReadRepository } diff --git a/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungEntityStoreView.scala b/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungEntityStoreView.scala index eaba479bd..d3e83bf6a 100644 --- a/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungEntityStoreView.scala +++ b/src/main/scala/ch/openolitor/buchhaltung/BuchhaltungEntityStoreView.scala @@ -32,10 +32,10 @@ import ch.openolitor.buchhaltung.repositories.BuchhaltungWriteRepositoryComponen import akka.actor.ActorRef object BuchhaltungEntityStoreView { - def props(mailService: ActorRef, dbEvolutionActor: ActorRef, airbrakeNotifier: ActorRef)(implicit sysConfig: SystemConfig, system: ActorSystem): Props = Props(classOf[DefaultBuchhaltungEntityStoreView], mailService, dbEvolutionActor, sysConfig, system, airbrakeNotifier) + def props(dbEvolutionActor: ActorRef, airbrakeNotifier: ActorRef)(implicit sysConfig: SystemConfig, system: ActorSystem): Props = Props(classOf[DefaultBuchhaltungEntityStoreView], dbEvolutionActor, sysConfig, system, airbrakeNotifier) } -class DefaultBuchhaltungEntityStoreView(override val mailService: ActorRef, override val dbEvolutionActor: ActorRef, implicit val sysConfig: SystemConfig, implicit val system: ActorSystem, val airbrakeNotifier: ActorRef) extends BuchhaltungEntityStoreView +class DefaultBuchhaltungEntityStoreView(override val dbEvolutionActor: ActorRef, implicit val sysConfig: SystemConfig, implicit val system: ActorSystem, val airbrakeNotifier: ActorRef) extends BuchhaltungEntityStoreView with DefaultBuchhaltungWriteRepositoryComponent /** @@ -51,7 +51,7 @@ trait BuchhaltungEntityStoreView extends EntityStoreView /** * Instanzieren der jeweiligen Insert, Update und Delete Child Actors */ -trait BuchhaltungEntityStoreViewComponent extends EntityStoreViewComponent with MailServiceReference { +trait BuchhaltungEntityStoreViewComponent extends EntityStoreViewComponent { val sysConfig: SystemConfig val system: ActorSystem @@ -59,5 +59,5 @@ trait BuchhaltungEntityStoreViewComponent extends EntityStoreViewComponent with override val updateService = BuchhaltungUpdateService(sysConfig, system) override val deleteService = BuchhaltungDeleteService(sysConfig, system) - override val aktionenService = BuchhaltungAktionenService(sysConfig, system, mailService) + override val aktionenService = BuchhaltungAktionenService(sysConfig, system) } \ No newline at end of file diff --git a/src/main/scala/ch/openolitor/buchhaltung/reporting/RechnungReportData.scala b/src/main/scala/ch/openolitor/buchhaltung/reporting/RechnungReportData.scala index e6901274f..a9322fb6c 100644 --- a/src/main/scala/ch/openolitor/buchhaltung/reporting/RechnungReportData.scala +++ b/src/main/scala/ch/openolitor/buchhaltung/reporting/RechnungReportData.scala @@ -94,7 +94,7 @@ trait RechnungReportData extends AsyncConnectionPoolContextAware with Buchhaltun case _ => Language.DE } billFormat.setLanguage(language) - billFormat.setOutputSize(OutputSize.QR_BILL_WITH_HORIZONTAL_LINE) + billFormat.setOutputSize(OutputSize.QR_BILL_EXTRA_SPACE) billFormat.setSeparatorType(SeparatorType.DASHED_LINE_WITH_SCISSORS) bill.setFormat(billFormat) //this value is mandatory for the qrCode. In case of generating qrCode diff --git a/src/main/scala/ch/openolitor/buchhaltung/repositories/BuchhaltungReadRepositorySync.scala b/src/main/scala/ch/openolitor/buchhaltung/repositories/BuchhaltungReadRepositorySync.scala index a08cbffb3..9ea903cad 100644 --- a/src/main/scala/ch/openolitor/buchhaltung/repositories/BuchhaltungReadRepositorySync.scala +++ b/src/main/scala/ch/openolitor/buchhaltung/repositories/BuchhaltungReadRepositorySync.scala @@ -27,9 +27,10 @@ import ch.openolitor.core.repositories._ import ch.openolitor.stammdaten.models._ import com.typesafe.scalalogging.LazyLogging import ch.openolitor.buchhaltung.models._ +import ch.openolitor.stammdaten.repositories.{ ProjektReadRepositorySync, ProjektReadRepositorySyncImpl } import ch.openolitor.util.parsing.{ FilterExpr, QueryFilter } -trait BuchhaltungReadRepositorySync extends BaseReadRepositorySync { +trait BuchhaltungReadRepositorySync extends BaseReadRepositorySync with ProjektReadRepositorySync { def getRechnungen(implicit session: DBSession, cpContext: ConnectionPoolContext): List[Rechnung] def getKundenRechnungen(kundeId: KundeId)(implicit session: DBSession, cpContext: ConnectionPoolContext): List[Rechnung] def getRechnungDetail(id: RechnungId)(implicit session: DBSession, cpContext: ConnectionPoolContext): Option[RechnungDetail] @@ -47,10 +48,9 @@ trait BuchhaltungReadRepositorySync extends BaseReadRepositorySync { def getKontoDatenProjekt(implicit session: DBSession): Option[KontoDaten] def getKontoDatenKunde(id: KundeId)(implicit session: DBSession): Option[KontoDaten] - def getProjekt(implicit session: DBSession): Option[Projekt] } -trait BuchhaltungReadRepositorySyncImpl extends BuchhaltungReadRepositorySync with LazyLogging with BuchhaltungRepositoryQueries { +trait BuchhaltungReadRepositorySyncImpl extends BuchhaltungReadRepositorySync with LazyLogging with BuchhaltungRepositoryQueries with ProjektReadRepositorySyncImpl { def getRechnungen(implicit session: DBSession, cpContext: ConnectionPoolContext): List[Rechnung] = { getRechnungenQuery(None, None, None).apply() } @@ -102,8 +102,4 @@ trait BuchhaltungReadRepositorySyncImpl extends BuchhaltungReadRepositorySync wi def getZahlungsExportDetail(id: ZahlungsExportId)(implicit session: DBSession, cpContext: ConnectionPoolContext): Option[ZahlungsExport] = { getZahlungsExportQuery(id).apply() } - - def getProjekt(implicit session: DBSession): Option[Projekt] = { - getProjektQuery.apply() - } } diff --git a/src/main/scala/ch/openolitor/buchhaltung/repositories/BuchhaltungRepositoryQueries.scala b/src/main/scala/ch/openolitor/buchhaltung/repositories/BuchhaltungRepositoryQueries.scala index e5176da1e..f8942f0a0 100644 --- a/src/main/scala/ch/openolitor/buchhaltung/repositories/BuchhaltungRepositoryQueries.scala +++ b/src/main/scala/ch/openolitor/buchhaltung/repositories/BuchhaltungRepositoryQueries.scala @@ -27,12 +27,13 @@ import ch.openolitor.buchhaltung.BuchhaltungDBMappings import ch.openolitor.core.Macros._ import ch.openolitor.stammdaten.models._ import ch.openolitor.stammdaten.StammdatenDBMappings +import ch.openolitor.stammdaten.repositories.StammdatenProjektRepositoryQueries import ch.openolitor.util.parsing.{ FilterAttributeList, FilterExpr, GeschaeftsjahrFilter, QueryFilter } import ch.openolitor.util.querybuilder.UriQueryParamToSQLSyntaxBuilder import com.typesafe.scalalogging.LazyLogging import scalikejdbc._ -trait BuchhaltungRepositoryQueries extends LazyLogging with BuchhaltungDBMappings with StammdatenDBMappings { +trait BuchhaltungRepositoryQueries extends LazyLogging with BuchhaltungDBMappings with StammdatenDBMappings with StammdatenProjektRepositoryQueries { lazy val rechnung = rechnungMapping.syntax("rechnung") lazy val rechnungsPosition = rechnungsPositionMapping.syntax("rechnungsPosition") lazy val kunde = kundeMapping.syntax("kunde") @@ -45,7 +46,6 @@ trait BuchhaltungRepositoryQueries extends LazyLogging with BuchhaltungDBMapping lazy val zusatzAbo = zusatzAboMapping.syntax("zusatzAbo") lazy val kontoDaten = kontoDatenMapping.syntax("kontoDaten") lazy val person = personMapping.syntax("pers") - lazy val projekt = projektMapping.syntax("projekt") protected def getRechnungenQuery(filter: Option[FilterExpr], gjFilter: Option[GeschaeftsjahrFilter], queryString: Option[QueryFilter]) = { queryString match { @@ -254,11 +254,4 @@ trait BuchhaltungRepositoryQueries extends LazyLogging with BuchhaltungDBMapping .where.eq(kontoDaten.kunde, kundeId) }.map(kontoDatenMapping(kontoDaten)).single } - - protected def getProjektQuery = { - withSQL { - select - .from(projektMapping as projekt) - }.map(projektMapping(projekt)).single - } } diff --git a/src/main/scala/ch/openolitor/core/ExecutionContextAware.scala b/src/main/scala/ch/openolitor/core/ExecutionContextAware.scala index 1beb5d4e2..7fddf9eb6 100644 --- a/src/main/scala/ch/openolitor/core/ExecutionContextAware.scala +++ b/src/main/scala/ch/openolitor/core/ExecutionContextAware.scala @@ -1,3 +1,25 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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 import scala.concurrent.ExecutionContext diff --git a/src/main/scala/ch/openolitor/core/FileStoreReference.scala b/src/main/scala/ch/openolitor/core/FileStoreReference.scala index c5adf614c..fbad5917e 100644 --- a/src/main/scala/ch/openolitor/core/FileStoreReference.scala +++ b/src/main/scala/ch/openolitor/core/FileStoreReference.scala @@ -1,3 +1,25 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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 import ch.openolitor.core.filestore.FileStore diff --git a/src/main/scala/ch/openolitor/core/StartingServices.scala b/src/main/scala/ch/openolitor/core/StartingServices.scala index 06d63aa1d..ea3c48ebc 100644 --- a/src/main/scala/ch/openolitor/core/StartingServices.scala +++ b/src/main/scala/ch/openolitor/core/StartingServices.scala @@ -1,3 +1,25 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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 import akka.actor.{ ActorRef, ActorSystem } @@ -60,12 +82,13 @@ trait StartingServices extends LazyLogging { logger.debug(s"oo-system:$system") val dbEvolutionActor = Await.result(system ? SystemActor.Child(DBEvolutionActor.props(evolution), "db-evolution"), duration).asInstanceOf[ActorRef] logger.debug(s"oo-system:$system -> dbEvolutionActor:$dbEvolutionActor") - val entityStore = Await.result(system ? SystemActor.Child(EntityStore.props(dbEvolutionActor, evolution), "entity-store"), duration).asInstanceOf[ActorRef] + val mailService = Await.result(system ? SystemActor.Child(MailService.props(dbEvolutionActor, fileStore), "mail-service"), duration).asInstanceOf[ActorRef] + logger.debug(s"oo-system:$system -> eventStore:$mailService") + + val entityStore = Await.result(system ? SystemActor.Child(EntityStore.props(dbEvolutionActor, evolution, mailService), "entity-store"), duration).asInstanceOf[ActorRef] logger.debug(s"oo-system:$system -> entityStore:$entityStore") val eventStore = Await.result(system ? SystemActor.Child(SystemEventStore.props(dbEvolutionActor), "event-store"), duration).asInstanceOf[ActorRef] logger.debug(s"oo-system:$system -> eventStore:$eventStore") - val mailService = Await.result(system ? SystemActor.Child(MailService.props(dbEvolutionActor, fileStore), "mail-service"), duration).asInstanceOf[ActorRef] - logger.debug(s"oo-system:$system -> eventStore:$mailService") // STAMMDATEN val stammdatenEntityStoreView = Await.result(system ? SystemActor.Child(StammdatenEntityStoreView.props(mailService, dbEvolutionActor, airbrakeNotifier), "stammdaten-entity-store-view"), duration).asInstanceOf[ActorRef] @@ -78,14 +101,14 @@ trait StartingServices extends LazyLogging { Await.result(system ? SystemActor.Child(StammdatenGeneratedEventsListener.props, "stammdaten-generated-events-listener"), duration).asInstanceOf[ActorRef] // BUCHHALTUNG - val buchhaltungEntityStoreView = Await.result(system ? SystemActor.Child(BuchhaltungEntityStoreView.props(mailService, dbEvolutionActor, airbrakeNotifier), "buchhaltung-entity-store-view"), duration).asInstanceOf[ActorRef] + val buchhaltungEntityStoreView = Await.result(system ? SystemActor.Child(BuchhaltungEntityStoreView.props(dbEvolutionActor, airbrakeNotifier), "buchhaltung-entity-store-view"), duration).asInstanceOf[ActorRef] // start listeners for buchhaltung Await.result(system ? SystemActor.Child(BuchhaltungDBEventEntityListener.props, "buchhaltung-dbevent-entity-listener"), duration).asInstanceOf[ActorRef] Await.result(system ? SystemActor.Child(BuchhaltungReportEventListener.props(entityStore), "buchhaltung-report-event-listener"), duration).asInstanceOf[ActorRef] // ARBEITSEINSATZ - val arbeitseinsatzEntityStoreView = Await.result(system ? SystemActor.Child(ArbeitseinsatzEntityStoreView.props(mailService, dbEvolutionActor, airbrakeNotifier), "arbeitseinsatz-entity-store-view"), duration).asInstanceOf[ActorRef] + val arbeitseinsatzEntityStoreView = Await.result(system ? SystemActor.Child(ArbeitseinsatzEntityStoreView.props(dbEvolutionActor, airbrakeNotifier), "arbeitseinsatz-entity-store-view"), duration).asInstanceOf[ActorRef] // MAILTEMPLATE val mailTemplateEntityStoreView = Await.result(system ? SystemActor.Child(MailTemplateEntityStoreView.props(dbEvolutionActor, airbrakeNotifier), "mailtemplate-entity-store-view"), duration).asInstanceOf[ActorRef] diff --git a/src/main/scala/ch/openolitor/core/db/evolution/scripts/v2/OO69_kunde_aktiv.scala b/src/main/scala/ch/openolitor/core/db/evolution/scripts/v2/OO69_kunde_aktiv.scala index fa5f3e5b3..7016409d4 100644 --- a/src/main/scala/ch/openolitor/core/db/evolution/scripts/v2/OO69_kunde_aktiv.scala +++ b/src/main/scala/ch/openolitor/core/db/evolution/scripts/v2/OO69_kunde_aktiv.scala @@ -1,3 +1,25 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.db.evolution.scripts.v2 import ch.openolitor.core.SystemConfig diff --git a/src/main/scala/ch/openolitor/core/db/evolution/scripts/v2/OO829_add_zusatzabo_info_to_abo.scala b/src/main/scala/ch/openolitor/core/db/evolution/scripts/v2/OO829_add_zusatzabo_info_to_abo.scala index 562d27897..0cb68a19c 100644 --- a/src/main/scala/ch/openolitor/core/db/evolution/scripts/v2/OO829_add_zusatzabo_info_to_abo.scala +++ b/src/main/scala/ch/openolitor/core/db/evolution/scripts/v2/OO829_add_zusatzabo_info_to_abo.scala @@ -8,7 +8,7 @@ * * * 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, * +* 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 * diff --git a/src/main/scala/ch/openolitor/core/domain/AggregateRoot.scala b/src/main/scala/ch/openolitor/core/domain/AggregateRoot.scala index 8d4984367..c9cc0c4e6 100644 --- a/src/main/scala/ch/openolitor/core/domain/AggregateRoot.scala +++ b/src/main/scala/ch/openolitor/core/domain/AggregateRoot.scala @@ -69,13 +69,11 @@ trait AggregateRoot extends PersistentActor with ActorLogging with PersistenceEv log.debug(s"$persistenceId: initialize aquire transaction nr to $lastAquiredTransactionNr") } - protected def afterEventPersisted(evt: PersistentEvent): Unit = { + protected final def afterEventPersisted(evt: PersistentEvent): Unit = { updateState(recovery = false)(evt) publish(evt) setLastProcessedSequenceNr(evt.meta) - - sender() ! state } protected def aquireTransactionNr(): Long = { diff --git a/src/main/scala/ch/openolitor/core/domain/CommandHandlerComponent.scala b/src/main/scala/ch/openolitor/core/domain/CommandHandlerComponent.scala index 2c68d56ae..8413bd1b0 100644 --- a/src/main/scala/ch/openolitor/core/domain/CommandHandlerComponent.scala +++ b/src/main/scala/ch/openolitor/core/domain/CommandHandlerComponent.scala @@ -26,10 +26,10 @@ import ch.openolitor.buchhaltung.DefaultBuchhaltungCommandHandler import ch.openolitor.core.SystemConfig import ch.openolitor.kundenportal.DefaultKundenportalCommandHandler import ch.openolitor.arbeitseinsatz.DefaultArbeitseinsatzCommandHandler -import ch.openolitor.stammdaten.DefaultStammdatenCommandHandler +import ch.openolitor.stammdaten.{ DefaultMailCommandForwarderComponent, DefaultStammdatenCommandHandler, MailCommandForwarder } import ch.openolitor.reports.DefaultReportsCommandHandler -import akka.actor.ActorSystem -import ch.openolitor.mailtemplates.{ DefaultMailTemplateCommandHanlder } +import akka.actor.{ ActorRef, ActorSystem } +import ch.openolitor.mailtemplates.DefaultMailTemplateCommandHanlder trait CommandHandlerComponent { val stammdatenCommandHandler: CommandHandler @@ -44,12 +44,13 @@ trait CommandHandlerComponent { trait DefaultCommandHandlerComponent extends CommandHandlerComponent { val sysConfig: SystemConfig val system: ActorSystem + val mailService: ActorRef - override val stammdatenCommandHandler = new DefaultStammdatenCommandHandler(sysConfig, system) - override val buchhaltungCommandHandler = new DefaultBuchhaltungCommandHandler(sysConfig, system) - override val arbeitseinsatzCommandHandler = new DefaultArbeitseinsatzCommandHandler(sysConfig, system) - override val reportsCommandHandler = new DefaultReportsCommandHandler(sysConfig, system) + override val stammdatenCommandHandler: CommandHandler = new DefaultStammdatenCommandHandler(sysConfig, system, mailService) + override val buchhaltungCommandHandler: CommandHandler = new DefaultBuchhaltungCommandHandler(sysConfig, system, mailService) + override val arbeitseinsatzCommandHandler: CommandHandler = new DefaultArbeitseinsatzCommandHandler(sysConfig, system, mailService) + override val reportsCommandHandler: CommandHandler = new DefaultReportsCommandHandler(sysConfig, system) override val mailTemplateCommandHandler: CommandHandler = new DefaultMailTemplateCommandHanlder(sysConfig, system) - override val kundenportalCommandHandler = new DefaultKundenportalCommandHandler(sysConfig, system) - override val baseCommandHandler = new BaseCommandHandler() + override val kundenportalCommandHandler: CommandHandler = new DefaultKundenportalCommandHandler(sysConfig, system) + override val baseCommandHandler: CommandHandler = new BaseCommandHandler() } diff --git a/src/main/scala/ch/openolitor/core/domain/EntityStore.scala b/src/main/scala/ch/openolitor/core/domain/EntityStore.scala index 8183bf2c5..a389a1911 100644 --- a/src/main/scala/ch/openolitor/core/domain/EntityStore.scala +++ b/src/main/scala/ch/openolitor/core/domain/EntityStore.scala @@ -51,7 +51,7 @@ object EntityStore { val persistenceId = "entity-store" case class EntityStoreState(dbSeeds: Map[Class[_ <: BaseId], Long]) extends State - def props(dbEvolutionActor: ActorRef, evolution: Evolution)(implicit sysConfig: SystemConfig): Props = Props(classOf[DefaultEntityStore], sysConfig, dbEvolutionActor, evolution) + def props(dbEvolutionActor: ActorRef, evolution: Evolution, mailService: ActorRef)(implicit sysConfig: SystemConfig): Props = Props(classOf[DefaultEntityStore], sysConfig, dbEvolutionActor, evolution, mailService) //base commands case class InsertEntityCommand[E <: AnyRef](originator: PersonId, entity: E) extends UserCommand { @@ -288,7 +288,7 @@ trait EntityStore extends AggregateRoot override val receiveCommand: Receive = uninitialized } -class DefaultEntityStore(override val sysConfig: SystemConfig, override val dbEvolutionActor: ActorRef, override val evolution: Evolution) extends EntityStore +class DefaultEntityStore(override val sysConfig: SystemConfig, override val dbEvolutionActor: ActorRef, override val evolution: Evolution, override val mailService: ActorRef) extends EntityStore with DefaultCommandHandlerComponent { lazy val system: ActorSystem = context.system } diff --git a/src/main/scala/ch/openolitor/core/domain/Events.scala b/src/main/scala/ch/openolitor/core/domain/Events.scala index 82ab74b13..4c8a00f25 100644 --- a/src/main/scala/ch/openolitor/core/domain/Events.scala +++ b/src/main/scala/ch/openolitor/core/domain/Events.scala @@ -40,6 +40,17 @@ case class EventTransactionMetadata(originator: PersonId, version: Int, timestam } } +/** + * Event metadata managed by OpenOlitor. + * + * @param originator the originator of the command that led to this event metadata. + * @param version the version of the event store or more specific aggregate root. + * @param timestamp the timestamp when the event has been created by the event store. + * @param transactionNr the transaction number identifying one or more events that resulted out of a command. + * @param seqNr this is not to be confused with the sequenceNr which is managed by akka persistence. It is just representing the sequence nr of + * events within the given transactionNr. The name is misleading and unfortunately often mistaken for akka persistence's sequenceId. + * @param source the persistence id of the aggregate root that created the event with this metadata. + */ case class EventMetadata(originator: PersonId, version: Int, timestamp: DateTime, transactionNr: Long, seqNr: Long, source: String) trait PersistentEvent extends Serializable { @@ -57,7 +68,10 @@ object SystemEvents { val SystemPersonId = PersonId(0) case class PersonLoggedIn(personId: PersonId, timestamp: DateTime, secondFactorType: Option[SecondFactorType]) extends SystemEvent with JSONSerializable + case class PersonChangedOtpSecret(personId: PersonId, secret: String) extends SystemEvent with JSONSerializable + case class PersonChangedSecondFactorType(personId: PersonId, secondFactorType: Option[SecondFactorType]) extends SystemEvent with JSONSerializable + case class SystemStarted(timestamp: DateTime) extends SystemEvent } diff --git a/src/main/scala/ch/openolitor/core/mailservice/MailService.scala b/src/main/scala/ch/openolitor/core/mailservice/MailService.scala index db6fecb75..bba8adc88 100644 --- a/src/main/scala/ch/openolitor/core/mailservice/MailService.scala +++ b/src/main/scala/ch/openolitor/core/mailservice/MailService.scala @@ -25,10 +25,10 @@ package ch.openolitor.core.mailservice import java.util.UUID import akka.actor._ import akka.persistence.SnapshotMetadata -import ch.openolitor.core.domain.{ AggregateRoot, _ } +import ch.openolitor.core.domain.{AggregateRoot, _} import ch.openolitor.core.models.PersonId import ch.openolitor.core.db.ConnectionPoolContextAware -import ch.openolitor.core.{ JSONSerializable, SystemConfig } +import ch.openolitor.core.{JSONSerializable, SystemConfig} import ch.openolitor.core.filestore._ import ch.openolitor.util.ConfigUtil._ import courier._ @@ -41,7 +41,7 @@ import scala.collection.compat.immutable.ArraySeq import scala.collection.immutable.TreeSet import scala.concurrent.duration._ import scala.concurrent.Await -import scala.util.{ Failure, Success } +import scala.util.{Failure, Success} object MailService { @@ -50,22 +50,62 @@ object MailService { case class MailServiceState(startTime: DateTime, mailQueue: TreeSet[MailEnqueued]) extends State - case class SendMailCommandWithCallback[M <: AnyRef](originator: PersonId, entity: Mail, retryDuration: Option[Duration], commandMeta: M)(implicit p: Persister[M, _]) extends UserCommand + case class SendMailCommandWithCallback[M <: AnyRef](originator: PersonId, entity: Mail, retryDuration: Option[Duration], commandMeta: M)(implicit + p: Persister[M, + _]) extends + UserCommand + case class SendMailCommand(originator: PersonId, entity: Mail, retryDuration: Option[Duration]) extends UserCommand //events raised by this aggregateroot case class MailServiceInitialized(meta: EventMetadata) extends PersistentEvent + // resulting send mail event - case class SendMailEvent(meta: EventMetadata, uid: String, mail: Mail, expires: DateTime, commandMeta: Option[AnyRef]) extends PersistentEvent with JSONSerializable + case class SendMailEvent(meta: EventMetadata, uid: String, mail: Mail, expires: DateTime, commandMeta: Option[AnyRef]) extends PersistentEvent with + JSONSerializable + case class MailSentEvent(meta: EventMetadata, uid: String, commandMeta: Option[AnyRef]) extends PersistentEvent with JSONSerializable - case class SendMailFailedEvent(meta: EventMetadata, uid: String, numberOfRetries: Int, commandMeta: Option[AnyRef]) extends PersistentEvent with JSONSerializable - def props(dbEvolutionActor: ActorRef, fileStore: FileStore)(implicit sysConfig: SystemConfig): Props = Props(classOf[DefaultMailService], sysConfig, dbEvolutionActor, fileStore) + case class SendMailFailedEvent(meta: EventMetadata, uid: String, numberOfRetries: Int, commandMeta: Option[AnyRef]) extends PersistentEvent with + JSONSerializable + + def props(dbEvolutionActor: ActorRef, fileStore: FileStore)(implicit sysConfig: SystemConfig): Props = Props(classOf[DefaultMailService], sysConfig, + dbEvolutionActor, fileStore) case object CheckMailQueue + case object CheckMailQueueComplete } +/** + * TODO: rename to MailEventStore or MailStore. + * + * The mail event store should be called from other commands and not out of event processing. Make sure to check and filter already sent mails beforehand + * when calling it from processing events. This can be achieved by managing an entity that has a `sent: Option[DateTime]`. + * + * The following pattern requires filtering as mentioned above: + * ┌─────────┐ ┌──────────┐ vvvvvv ┌────────────┐ ┌──────────┐ + * │ Command ├──────► Event(s) ├─── Filter ───► Command(s) ├──────► Event(s) │ + * └─────────┘ └──────────┘ ^^^^^^ └────────────┘ └──────────┘ + * + * The preferred method is to trigger it via [[ch.openolitor.stammdaten.MailCommandForwarder]] which can be caked in using the component: + * [[ch.openolitor.stammdaten.MailCommandForwarderComponent]]. + * + * This results in mail sending commands being triggered by the initial command: + * ┌─────────┐ ┌────────────┐ ┌──────────┐ + * │ Command ├──────► Command(s) ├──────► Event(s) │ + * └────┬────┘ └────────────┘ └──────────┘ + * │ │ + * │ │ ┌──────────┐ + * │ └───────────► Event(s) │ + * │ └──────────┘ + * + * Note that there is no [[EntityStoreView]] that reads the `journal` based on [[MailService.persistenceId]]. Therefore, this aggregate root only reacts to + * [[ch.openolitor.core.mailservice.MailService.SendMailEvent]]s in live mode and does not replay them on recovery. + * + * In the case of encountering resending of emails, we can assume that they are created because `entity-store` events are reprocessed that trigger sending + * email commands. + */ trait MailService extends AggregateRoot with ConnectionPoolContextAware with FileStoreComponent @@ -76,7 +116,9 @@ trait MailService extends AggregateRoot import MailService._ override val fileStore: FileStore = null + override def persistenceId: String = MailService.persistenceId + type S = MailServiceState lazy val fromAddress: String = sysConfig.mandantConfiguration.config.getString("smtp.from") lazy val maxNumberOfRetries: Int = sysConfig.mandantConfiguration.config.getInt("smtp.number-of-retries") @@ -98,11 +140,6 @@ trait MailService extends AggregateRoot override var state: MailServiceState = MailServiceState(DateTime.now, TreeSet.empty[MailEnqueued]) - override protected def afterEventPersisted(evt: PersistentEvent): Unit = { - updateState(recovery = false)(evt) - publish(evt) - } - def initialize(): Unit = { // start mail queue checker context.system.scheduler.scheduleWithFixedDelay(0 seconds, 10 seconds, self, CheckMailQueue)(context.system.dispatcher) @@ -161,7 +198,7 @@ trait MailService extends AggregateRoot val result = envelope match { case Right(e) => Await.ready(mailer(e), 20 seconds).value match { case Some(e) => Right(e) - case None => Left("Error sending the email") + case None => Left("Error sending the email") } case Left(e) => Left(e) } @@ -225,10 +262,10 @@ trait MailService extends AggregateRoot override def restoreFromSnapshot(metadata: SnapshotMetadata, state: State): Unit = { log.debug(s"restoreFromSnapshot:$state") state match { - case Removed => context become removed - case Created => context become uninitialized + case Removed => context become removed + case Created => context become uninitialized case s: MailServiceState => this.state = s - case other: Any => log.error(s"Received unsupported state:$other") + case other: Any => log.error(s"Received unsupported state:$other") } } @@ -311,7 +348,6 @@ trait MailService extends AggregateRoot } class DefaultMailService(override val sysConfig: SystemConfig, override val dbEvolutionActor: ActorRef, override val fileStore: FileStore) extends MailService - with DefaultCommandHandlerComponent with DefaultMailRetryHandler { lazy val system: ActorSystem = context.system } diff --git a/src/main/scala/ch/openolitor/core/security/BasicAuthenticatorProvider.scala b/src/main/scala/ch/openolitor/core/security/BasicAuthenticatorProvider.scala index a45d8ef9f..31737357d 100644 --- a/src/main/scala/ch/openolitor/core/security/BasicAuthenticatorProvider.scala +++ b/src/main/scala/ch/openolitor/core/security/BasicAuthenticatorProvider.scala @@ -1,3 +1,25 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.security import akka.http.scaladsl.model.headers.{ BasicHttpCredentials, HttpChallenges } diff --git a/src/main/scala/ch/openolitor/core/security/LoginService.scala b/src/main/scala/ch/openolitor/core/security/LoginService.scala index 54c64aa2b..aca4e60f1 100644 --- a/src/main/scala/ch/openolitor/core/security/LoginService.scala +++ b/src/main/scala/ch/openolitor/core/security/LoginService.scala @@ -1,17 +1,39 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.security -import akka.http.caching.scaladsl.{ Cache, CachingSettings } +import akka.http.caching.scaladsl.{Cache, CachingSettings} import akka.http.caching.LfuCache import akka.pattern.ask import akka.util.Timeout -import ch.openolitor.core.{ ActorReferences, ExecutionContextAware, SystemConfigReference } +import ch.openolitor.core.{ActorReferences, ExecutionContextAware, SystemConfigReference} import ch.openolitor.core.Macros.copyTo import ch.openolitor.core.db.AsyncConnectionPoolContextAware import ch.openolitor.core.domain.SystemEvents import ch.openolitor.core.mailservice.Mail -import ch.openolitor.core.mailservice.MailService.{ SendMailCommand, SendMailEvent } +import ch.openolitor.core.mailservice.MailService.{MailServiceState, SendMailCommand, SendMailEvent} import ch.openolitor.core.models.PersonId -import ch.openolitor.stammdaten.StammdatenCommandHandler.{ PasswortGewechseltEvent, PasswortResetCommand, PasswortResetGesendetEvent, PasswortWechselCommand } +import ch.openolitor.stammdaten.StammdatenCommandHandler.{PasswortGewechseltEvent, PasswortResetCommand, PasswortResetGesendetEvent, PasswortWechselCommand} import ch.openolitor.stammdaten.models._ import ch.openolitor.stammdaten.repositories.StammdatenReadRepositoryAsyncComponent import ch.openolitor.util.ConfigUtil._ @@ -351,7 +373,7 @@ trait LoginService extends LazyLogging val mail = Mail(1, person.email.get, None, None, None, "OpenOlitor Second Factor", s"""Code: ${secondFactor.code}""", None) mailService ? SendMailCommand(SystemEvents.SystemPersonId, mail, Some(5 minutes)) map { - case _: SendMailEvent => + case _: SendMailEvent | MailServiceState => true.right case other => logger.debug(s"Sending Mail failed resulting in $other") diff --git a/src/main/scala/ch/openolitor/core/ws/ClientMessagesRouteService.scala b/src/main/scala/ch/openolitor/core/ws/ClientMessagesRouteService.scala index 4a993dd63..08789e583 100644 --- a/src/main/scala/ch/openolitor/core/ws/ClientMessagesRouteService.scala +++ b/src/main/scala/ch/openolitor/core/ws/ClientMessagesRouteService.scala @@ -1,3 +1,25 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.ws import akka.actor.{ ActorRef, ActorSystem } diff --git a/src/main/scala/ch/openolitor/mailtemplates/MailTemplateCommandHandler.scala b/src/main/scala/ch/openolitor/mailtemplates/MailTemplateCommandHandler.scala index 1b98ebc48..af1b562e4 100644 --- a/src/main/scala/ch/openolitor/mailtemplates/MailTemplateCommandHandler.scala +++ b/src/main/scala/ch/openolitor/mailtemplates/MailTemplateCommandHandler.scala @@ -1,3 +1,25 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.mailtemplates import akka.actor.ActorSystem diff --git a/src/main/scala/ch/openolitor/mailtemplates/MailTemplateEntityStoreView.scala b/src/main/scala/ch/openolitor/mailtemplates/MailTemplateEntityStoreView.scala index f7fd92ee9..b7bb6cc3c 100644 --- a/src/main/scala/ch/openolitor/mailtemplates/MailTemplateEntityStoreView.scala +++ b/src/main/scala/ch/openolitor/mailtemplates/MailTemplateEntityStoreView.scala @@ -1,3 +1,25 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.mailtemplates import akka.actor.{ ActorRef, ActorSystem, Props } diff --git a/src/main/scala/ch/openolitor/mailtemplates/engine/MailTemplateService.scala b/src/main/scala/ch/openolitor/mailtemplates/engine/MailTemplateService.scala index e86c2bc41..d4f246649 100644 --- a/src/main/scala/ch/openolitor/mailtemplates/engine/MailTemplateService.scala +++ b/src/main/scala/ch/openolitor/mailtemplates/engine/MailTemplateService.scala @@ -1,3 +1,25 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.mailtemplates.engine import scala.concurrent._ @@ -14,7 +36,7 @@ import org.joda.time.DateTime /** * This trait provides functionality to generate mail payloads based on either custom or a default template */ -trait MailTemplateService extends SystemConfigReference with LazyLogging { +trait MailTemplateService extends LazyLogging { val format = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ssZ") /** diff --git a/src/main/scala/ch/openolitor/stammdaten/DefaultMailCommandForwarder.scala b/src/main/scala/ch/openolitor/stammdaten/DefaultMailCommandForwarder.scala new file mode 100644 index 000000000..5bc70fbb6 --- /dev/null +++ b/src/main/scala/ch/openolitor/stammdaten/DefaultMailCommandForwarder.scala @@ -0,0 +1,65 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.stammdaten + +import akka.actor.ActorRef +import akka.pattern.ask +import akka.util.Timeout +import ch.openolitor.core.db.AsyncConnectionPoolContextAware +import ch.openolitor.core.domain.EventTransactionMetadata +import ch.openolitor.core.eventsourcing.CoreEventStoreSerializer +import ch.openolitor.core.mailservice.MailService._ +import ch.openolitor.core.models._ +import ch.openolitor.mailtemplates.engine.MailTemplateService +import ch.openolitor.mailtemplates.eventsourcing.MailTemplateEventStoreSerializer +import ch.openolitor.stammdaten.eventsourcing.StammdatenEventStoreSerializer +import ch.openolitor.stammdaten.models._ +import com.typesafe.scalalogging.LazyLogging + +import scala.concurrent.duration._ +import scala.concurrent.ExecutionContext +import scala.util._ + +class DefaultMailCommandForwarder(mailService: ActorRef) extends MailCommandForwarder with MailTemplateService with CoreEventStoreSerializer with LazyLogging { + implicit val timeout = Timeout(15.seconds) // sending mails might take a little longer + + def sendEmail(meta: EventTransactionMetadata, emailSubject: String, body: String, replyTo: Option[String], bcc: Option[String], person: PersonEmailData, docReference: Option[String], mailContext: Product)(implicit originator: PersonId = meta.originator, executionContext: ExecutionContext): Unit = { + generateMail(emailSubject, body, mailContext) match { + case Success(mailPayload) => + person.email map { email => + val mail = bcc match { + case Some(bccAddress) => mailPayload.toMail(1, email, None, Some(bccAddress), replyTo, docReference) + case None => mailPayload.toMail(1, email, None, None, replyTo, docReference) + } + mailService ? SendMailCommandWithCallback(originator, mail, Some(60 minutes), person.id) map { + case _: SendMailEvent | MailServiceState => + //ok + case other => + logger.debug(s"Sending Mail failed resulting in $other") + } + } + case Failure(e) => + logger.warn(s"Failed preparing mail", e) + } + } +} diff --git a/src/main/scala/ch/openolitor/stammdaten/EmailHandler.scala b/src/main/scala/ch/openolitor/stammdaten/MailCommandForwarder.scala similarity index 63% rename from src/main/scala/ch/openolitor/stammdaten/EmailHandler.scala rename to src/main/scala/ch/openolitor/stammdaten/MailCommandForwarder.scala index 573f1e596..3c5d38dcb 100644 --- a/src/main/scala/ch/openolitor/stammdaten/EmailHandler.scala +++ b/src/main/scala/ch/openolitor/stammdaten/MailCommandForwarder.scala @@ -24,44 +24,29 @@ package ch.openolitor.stammdaten import ch.openolitor.core.models._ import ch.openolitor.stammdaten.models._ -import ch.openolitor.core.domain.EventMetadata +import ch.openolitor.core.domain.{ EventMetadata, EventTransactionMetadata } import ch.openolitor.mailtemplates.engine.MailTemplateService import ch.openolitor.core.mailservice.MailService._ import ch.openolitor.core.repositories.EventPublishingImplicits._ import ch.openolitor.core.db.AsyncConnectionPoolContextAware import ch.openolitor.stammdaten.eventsourcing.StammdatenEventStoreSerializer import ch.openolitor.core.EventStream - import akka.actor.ActorRef import akka.pattern.ask import akka.util.Timeout import scalikejdbc._ import com.typesafe.scalalogging.LazyLogging + import scala.util._ import scala.concurrent.duration._ import scala.concurrent.ExecutionContext -trait EmailHandler extends MailTemplateService with AsyncConnectionPoolContextAware with StammdatenEventStoreSerializer with LazyLogging { +trait MailCommandForwarder { + def sendEmail(meta: EventTransactionMetadata, emailSubject: String, body: String, replyTo: Option[String], bcc: Option[String], person: PersonEmailData, docReference: Option[String], mailContext: Product)(implicit originator: PersonId = meta.originator, executionContext: ExecutionContext): Unit +} - def sendEmail(meta: EventMetadata, emailSubject: String, body: String, replyTo: Option[String], bcc: Option[String], person: PersonEmailData, docReference: Option[String], mailContext: Product, mailService: ActorRef)(implicit originator: PersonId = meta.originator, timeout: Timeout, executionContext: ExecutionContext, eventStream: EventStream): Unit = { - DB localTxPostPublish { implicit session => implicit publisher => - generateMail(emailSubject, body, mailContext) match { - case Success(mailPayload) => - person.email map { email => - val mail = bcc match { - case Some(bccAddress) => mailPayload.toMail(1, email, None, Some(bccAddress), replyTo, docReference) - case None => mailPayload.toMail(1, email, None, None, replyTo, docReference) - } - mailService ? SendMailCommandWithCallback(originator, mail, Some(60 minutes), person.id) map { - case _: SendMailEvent => - //ok - case other => - logger.debug(s"Sending Mail failed resulting in $other") - } - } - case Failure(e) => - logger.warn(s"Failed preparing mail", e) - } - } +object MailCommandForwarder { + def apply(mailService: ActorRef): MailCommandForwarder = { + new DefaultMailCommandForwarder(mailService) } -} +} \ No newline at end of file diff --git a/src/main/scala/ch/openolitor/stammdaten/MailCommandForwarderComponent.scala b/src/main/scala/ch/openolitor/stammdaten/MailCommandForwarderComponent.scala new file mode 100644 index 000000000..f4101edfd --- /dev/null +++ b/src/main/scala/ch/openolitor/stammdaten/MailCommandForwarderComponent.scala @@ -0,0 +1,33 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.stammdaten + +import ch.openolitor.core.MailServiceReference + +trait MailCommandForwarderComponent { + val mailCommandForwarder: MailCommandForwarder +} + +trait DefaultMailCommandForwarderComponent extends MailCommandForwarderComponent with MailServiceReference { + override lazy val mailCommandForwarder = MailCommandForwarder(mailService) +} diff --git a/src/main/scala/ch/openolitor/stammdaten/ProjektHelper.scala b/src/main/scala/ch/openolitor/stammdaten/ProjektHelper.scala new file mode 100644 index 000000000..7251dbda1 --- /dev/null +++ b/src/main/scala/ch/openolitor/stammdaten/ProjektHelper.scala @@ -0,0 +1,40 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.stammdaten + +import ch.openolitor.core.SystemConfigReference +import ch.openolitor.stammdaten.repositories.ProjektReadRepositorySync +import scalikejdbc.DBSession + +trait ProjektHelper extends SystemConfigReference { + lazy val bccAddress = config.getString("smtp.bcc") + + def projektReadRepository: ProjektReadRepositorySync + + def determineBcc(implicit session: DBSession): Option[String] = { + projektReadRepository.getProjekt flatMap { projekt => + Option.when(projekt.sendEmailToBcc)(bccAddress) + } + } +} diff --git a/src/main/scala/ch/openolitor/stammdaten/StammdatenAktionenService.scala b/src/main/scala/ch/openolitor/stammdaten/StammdatenAktionenService.scala index fa720752a..5a7687789 100644 --- a/src/main/scala/ch/openolitor/stammdaten/StammdatenAktionenService.scala +++ b/src/main/scala/ch/openolitor/stammdaten/StammdatenAktionenService.scala @@ -44,6 +44,7 @@ import ch.openolitor.util.ConfigUtil._ import scalikejdbc.DBSession import ch.openolitor.core.repositories.EventPublishingImplicits._ import ch.openolitor.core.repositories.EventPublisher +import ch.openolitor.mailtemplates.engine.MailTemplateService import scala.concurrent.ExecutionContext.Implicits._ import scalikejdbc.DB @@ -71,7 +72,7 @@ abstract class StammdatenAktionenService(override val sysConfig: SystemConfig, o with SammelbestellungenHandler with LieferungHandler with SystemConfigReference - with EmailHandler { + with MailTemplateService { self: StammdatenWriteRepositoryComponent with MailTemplateReadRepositoryComponent => // implicitly expose the eventStream @@ -109,20 +110,6 @@ abstract class StammdatenAktionenService(override val sysConfig: SystemConfig, o changeRolle(meta, personId, rolle) case OtpResetEvent(meta, _, personId, otpSecret) => resetOtp(meta, personId, otpSecret) - case SendEmailToPersonEvent(meta, subject, body, replyTo, context) => - checkBccAndSend(meta, subject, body, replyTo, context.person, context, mailService) - case SendEmailToKundeEvent(meta, subject, body, replyTo, context) => - checkBccAndSend(meta, subject, body, replyTo, context.person, context, mailService) - case SendEmailToAbotypSubscriberEvent(meta, subject, body, replyTo, context) => - checkBccAndSend(meta, subject, body, replyTo, context.person, context, mailService) - case SendEmailToZusatzabotypSubscriberEvent(meta, subject, body, replyTo, context) => - checkBccAndSend(meta, subject, body, replyTo, context.person, context, mailService) - case SendEmailToTourSubscriberEvent(meta, subject, body, replyTo, context) => - checkBccAndSend(meta, subject, body, replyTo, context.person, context, mailService) - case SendEmailToDepotSubscriberEvent(meta, subject, body, replyTo, context) => - checkBccAndSend(meta, subject, body, replyTo, context.person, context, mailService) - case SendEmailToAboSubscriberEvent(meta, subject, body, replyTo, context) => - checkBccAndSend(meta, subject, body, replyTo, context.person, context, mailService) case e => logger.warn(s"Unknown event:$e") } @@ -177,8 +164,6 @@ abstract class StammdatenAktionenService(override val sysConfig: SystemConfig, o } def sammelbestellungVersenden(meta: EventMetadata, id: SammelbestellungId)(implicit personId: PersonId = meta.originator) = { - val format = DateTimeFormat.forPattern("dd.MM.yyyy") - DB localTxPostPublish { implicit session => implicit publisher => //send mails to Produzenten stammdatenWriteRepository.getProjekt map { projekt => @@ -203,7 +188,7 @@ abstract class StammdatenAktionenService(override val sysConfig: SystemConfig, o val mail = mailPayload.toMail(1, produzent.email, None, None, None, None) mailService ? SendMailCommandWithCallback(personId, mail, Some(60 minutes), id) map { - case _: SendMailEvent => + case _: SendMailEvent | MailServiceState => //ok case other => logger.debug(s"Sending Mail failed resulting in $other") @@ -262,18 +247,6 @@ abstract class StammdatenAktionenService(override val sysConfig: SystemConfig, o } } - def checkBccAndSend(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], person: PersonEmailData, context: Product, mailService: ActorRef)(implicit originator: PersonId = meta.originator): Unit = { - DB localTxPostPublish { implicit session => implicit publisher => - lazy val bccAddress = config.getString("smtp.bcc") - stammdatenWriteRepository.getProjekt map { projekt => - projekt.sendEmailToBcc match { - case true => sendEmail(meta, subject, body, replyTo, Some(bccAddress), person, None, context, mailService) - case false => sendEmail(meta, subject, body, replyTo, None, person, None, context, mailService) - } - } - } - } - private def sendEinladung(meta: EventMetadata, einladungCreate: EinladungCreate, baseLink: String, mailTemplateType: TemplateType)(implicit originator: PersonId): Unit = { DB localTxPostPublish { implicit session => implicit publisher => stammdatenWriteRepository.getById(personMapping, einladungCreate.personId) map { person => @@ -301,7 +274,7 @@ abstract class StammdatenAktionenService(override val sysConfig: SystemConfig, o val mail = mailPayload.toMail(1, person.email.get, None, None, None, None) mailService ? SendMailCommandWithCallback(originator, mail, Some(60 minutes), einladung.id) map { - case _: SendMailEvent => + case _: SendMailEvent | MailServiceState => //ok case other => logger.debug(s"Sending Mail failed resulting in $other") diff --git a/src/main/scala/ch/openolitor/stammdaten/StammdatenCommandHandler.scala b/src/main/scala/ch/openolitor/stammdaten/StammdatenCommandHandler.scala index bad02785b..2e83f0b7a 100644 --- a/src/main/scala/ch/openolitor/stammdaten/StammdatenCommandHandler.scala +++ b/src/main/scala/ch/openolitor/stammdaten/StammdatenCommandHandler.scala @@ -22,7 +22,7 @@ \* */ package ch.openolitor.stammdaten -import ch.openolitor.buchhaltung.models.{ RechnungsPositionStatus, RechnungsPositionTyp } +import ch.openolitor.buchhaltung.models.{RechnungsPositionStatus, RechnungsPositionTyp} import ch.openolitor.core.domain._ import ch.openolitor.core.models._ import ch.openolitor.util.ConfigUtil._ @@ -33,7 +33,8 @@ import scalikejdbc.DB import ch.openolitor.stammdaten.models._ import ch.openolitor.stammdaten.repositories._ import ch.openolitor.core.exceptions._ -import akka.actor.ActorSystem +import akka.actor.{ActorRef, ActorSystem} +import akka.persistence.journal.EmptyEventSeq import ch.openolitor.core._ import ch.openolitor.core.db.ConnectionPoolContextAware import ch.openolitor.core.Macros._ @@ -41,72 +42,138 @@ import ch.openolitor.mailtemplates.engine.MailTemplateService import ch.openolitor.buchhaltung.models.RechnungsPositionCreate import ch.openolitor.buchhaltung.models.RechnungsPositionId import ch.openolitor.util.OtpUtil -import org.joda.time.{ DateTime, LocalDate, LocalTime } +import org.joda.time.{DateTime, LocalDate, LocalTime} import java.util.UUID import scalikejdbc.DBSession object StammdatenCommandHandler { case class LieferplanungAbschliessenCommand(originator: PersonId, id: LieferplanungId) extends UserCommand + case class LieferplanungModifyCommand(originator: PersonId, lieferplanungModify: LieferplanungPositionenModify) extends UserCommand + case class LieferplanungAbrechnenCommand(originator: PersonId, id: LieferplanungId) extends UserCommand + case class AbwesenheitCreateCommand(originator: PersonId, abw: AbwesenheitCreate) extends UserCommand + case class SammelbestellungAnProduzentenVersendenCommand(originator: PersonId, id: SammelbestellungId) extends UserCommand + case class PasswortWechselCommand(originator: PersonId, personId: PersonId, passwort: Array[Char], einladung: Option[EinladungId]) extends UserCommand + case class AuslieferungenAlsAusgeliefertMarkierenCommand(originator: PersonId, ids: Seq[AuslieferungId]) extends UserCommand - case class CreateAnzahlLieferungenRechnungsPositionenCommand(originator: PersonId, aboRechnungCreate: AboRechnungsPositionBisAnzahlLieferungenCreate) extends UserCommand + + case class CreateAnzahlLieferungenRechnungsPositionenCommand(originator: PersonId, aboRechnungCreate: AboRechnungsPositionBisAnzahlLieferungenCreate) + extends UserCommand + case class CreateBisGuthabenRechnungsPositionenCommand(originator: PersonId, aboRechnungCreate: AboRechnungsPositionBisGuthabenCreate) extends UserCommand + case class LoginDeaktivierenCommand(originator: PersonId, kundeId: KundeId, personId: PersonId) extends UserCommand + case class LoginAktivierenCommand(originator: PersonId, kundeId: KundeId, personId: PersonId) extends UserCommand + case class EinladungSendenCommand(originator: PersonId, kundeId: KundeId, personId: PersonId) extends UserCommand + case class SammelbestellungenAlsAbgerechnetMarkierenCommand(originator: PersonId, datum: DateTime, ids: Seq[SammelbestellungId]) extends UserCommand + case class PasswortResetCommand(originator: PersonId, personId: PersonId) extends UserCommand + case class RolleWechselnCommand(originator: PersonId, kundeId: KundeId, personId: PersonId, rolle: Rolle) extends UserCommand + case class OtpResetCommand(originator: PersonId, kundeId: KundeId, personId: PersonId) extends UserCommand + case class UpdateKundeCommand(originator: PersonId, kundeId: KundeId, kunde: KundeModify) extends UserCommand + case class CreateKundeCommand(originator: PersonId, kunde: KundeModify) extends UserCommand + case class RemoveLieferungCommand(originator: PersonId, lieferungId: LieferungId) extends UserCommand + // TODO person id for calculations case class AboAktivierenCommand(aboId: AboId, originator: PersonId = PersonId(100)) extends UserCommand + case class AboDeaktivierenCommand(aboId: AboId, originator: PersonId = PersonId(100)) extends UserCommand case class DeleteAbwesenheitCommand(originator: PersonId, id: AbwesenheitId) extends UserCommand + case class SendEmailToKundenCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[KundeId]) extends UserCommand + case class SendEmailToPersonenCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[PersonId]) extends UserCommand - case class SendEmailToAbosSubscribersCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[AboId]) extends UserCommand - case class SendEmailToAbotypSubscribersCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[AbotypId]) extends UserCommand - case class SendEmailToZusatzabotypSubscribersCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[AbotypId]) extends UserCommand - case class SendEmailToTourSubscribersCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[TourId]) extends UserCommand - case class SendEmailToDepotSubscribersCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[DepotId]) extends UserCommand + + case class SendEmailToAbosSubscribersCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[AboId]) extends + UserCommand + + case class SendEmailToAbotypSubscribersCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[AbotypId]) extends + UserCommand + + case class SendEmailToZusatzabotypSubscribersCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[AbotypId]) + extends UserCommand + + case class SendEmailToTourSubscribersCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[TourId]) extends + UserCommand + + case class SendEmailToDepotSubscribersCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[DepotId]) extends + UserCommand case class LieferplanungAbschliessenEvent(meta: EventMetadata, id: LieferplanungId) extends PersistentEvent with JSONSerializable + case class LieferplanungAbrechnenEvent(meta: EventMetadata, id: LieferplanungId) extends PersistentEvent with JSONSerializable + case class LieferplanungDataModifiedEvent(meta: EventMetadata, result: LieferplanungDataModify) extends PersistentEvent with JSONSerializable + case class AbwesenheitCreateEvent(meta: EventMetadata, id: AbwesenheitId, abw: AbwesenheitCreate) extends PersistentEvent with JSONSerializable + @Deprecated case class BestellungVersendenEvent(meta: EventMetadata, id: BestellungId) extends PersistentEvent with JSONSerializable + case class SammelbestellungVersendenEvent(meta: EventMetadata, id: SammelbestellungId) extends PersistentEvent with JSONSerializable - case class PasswortGewechseltEvent(meta: EventMetadata, personId: PersonId, passwort: Array[Char], einladungId: Option[EinladungId]) extends PersistentEvent with JSONSerializable + + case class PasswortGewechseltEvent(meta: EventMetadata, personId: PersonId, passwort: Array[Char], einladungId: Option[EinladungId]) extends + PersistentEvent with JSONSerializable + case class LoginDeaktiviertEvent(meta: EventMetadata, kundeId: KundeId, personId: PersonId) extends PersistentEvent with JSONSerializable + case class LoginAktiviertEvent(meta: EventMetadata, kundeId: KundeId, personId: PersonId) extends PersistentEvent with JSONSerializable + case class EinladungGesendetEvent(meta: EventMetadata, einladung: EinladungCreate) extends PersistentEvent with JSONSerializable + case class AuslieferungAlsAusgeliefertMarkierenEvent(meta: EventMetadata, id: AuslieferungId) extends PersistentEvent with JSONSerializable + @Deprecated case class BestellungAlsAbgerechnetMarkierenEvent(meta: EventMetadata, datum: DateTime, id: BestellungId) extends PersistentEvent with JSONSerializable - case class SammelbestellungAlsAbgerechnetMarkierenEvent(meta: EventMetadata, datum: DateTime, id: SammelbestellungId) extends PersistentEvent with JSONSerializable + + case class SammelbestellungAlsAbgerechnetMarkierenEvent(meta: EventMetadata, datum: DateTime, id: SammelbestellungId) extends PersistentEvent with + JSONSerializable + case class PasswortResetGesendetEvent(meta: EventMetadata, einladung: EinladungCreate) extends PersistentEvent with JSONSerializable + case class RolleGewechseltEvent(meta: EventMetadata, kundeId: KundeId, personId: PersonId, rolle: Rolle) extends PersistentEvent with JSONSerializable + case class OtpResetEvent(meta: EventMetadata, kundeId: KundeId, personId: PersonId, otpSecret: String) extends PersistentEvent with JSONSerializable case class AboAktiviertEvent(meta: EventMetadata, aboId: AboId) extends PersistentGeneratedEvent with JSONSerializable + case class AboDeaktiviertEvent(meta: EventMetadata, aboId: AboId) extends PersistentGeneratedEvent with JSONSerializable - case class SendEmailToPersonEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: PersonMailContext) extends PersistentGeneratedEvent with JSONSerializable - case class SendEmailToKundeEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: KundeMailContext) extends PersistentGeneratedEvent with JSONSerializable - case class SendEmailToAboSubscriberEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: AboMailContext) extends PersistentGeneratedEvent with JSONSerializable - case class SendEmailToAbotypSubscriberEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: AbotypMailContext) extends PersistentGeneratedEvent with JSONSerializable - case class SendEmailToZusatzabotypSubscriberEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: ZusatzabotypMailContext) extends PersistentGeneratedEvent with JSONSerializable - case class SendEmailToTourSubscriberEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: TourMailContext) extends PersistentGeneratedEvent with JSONSerializable - case class SendEmailToDepotSubscriberEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: DepotMailContext) extends PersistentGeneratedEvent with JSONSerializable + + case class SendEmailToPersonEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: PersonMailContext) extends + PersistentGeneratedEvent with JSONSerializable + + case class SendEmailToKundeEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: KundeMailContext) extends + PersistentGeneratedEvent with JSONSerializable + + case class SendEmailToAboSubscriberEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: AboMailContext) extends + PersistentGeneratedEvent with JSONSerializable + + case class SendEmailToAbotypSubscriberEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: AbotypMailContext) + extends PersistentGeneratedEvent with JSONSerializable + + case class SendEmailToZusatzabotypSubscriberEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], + context: ZusatzabotypMailContext) extends PersistentGeneratedEvent with JSONSerializable + + case class SendEmailToTourSubscriberEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: TourMailContext) extends + PersistentGeneratedEvent with JSONSerializable + + case class SendEmailToDepotSubscriberEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: DepotMailContext) extends + PersistentGeneratedEvent with JSONSerializable + case class UpdateKundeEvent(meta: EventMetadata, kundeId: KundeId, kunde: KundeModify) extends PersistentGeneratedEvent with JSONSerializable } @@ -115,257 +182,295 @@ trait StammdatenCommandHandler extends CommandHandler with ConnectionPoolContextAware with LieferungDurchschnittspreisHandler with MailTemplateService - with ExecutionContextAware { + with MailCommandForwarderComponent + with ExecutionContextAware + with ProjektHelper { self: StammdatenReadRepositorySyncComponent => + import StammdatenCommandHandler._ import EntityStore._ override val handle: PartialFunction[UserCommand, IdFactory => EventTransactionMetadata => Try[Seq[ResultingEvent]]] = { - case DeleteAbwesenheitCommand(_, id) => _ => _ => - DB readOnly { implicit session => - stammdatenReadRepository.getLieferung(id) map { lieferung => - lieferung.status match { - case (Offen | Ungeplant) => - Success(Seq(EntityDeleteEvent(id))) - case _ => - Failure(new InvalidStateException("Die der Abwesenheit zugeordnete Lieferung muss Offen oder Ungeplant sein.")) - } - } getOrElse Failure(new InvalidStateException(s"Keine Lieferung zu Abwesenheit Nr. $id gefunden")) - } + case DeleteAbwesenheitCommand(_, id) => _ => + _ => + DB readOnly { implicit session => + stammdatenReadRepository.getLieferung(id) map { lieferung => + lieferung.status match { + case (Offen | Ungeplant) => + Success(Seq(EntityDeleteEvent(id))) + case _ => + Failure(new InvalidStateException("Die der Abwesenheit zugeordnete Lieferung muss Offen oder Ungeplant sein.")) + } + } getOrElse Failure(new InvalidStateException(s"Keine Lieferung zu Abwesenheit Nr. $id gefunden")) + } - case SendEmailToKundenCommand(personId, subject, body, replyTo, ids) => idFactory => meta => - DB readOnly { implicit session => - if (checkTemplateKunden(body, subject, ids)) { - val events = ids flatMap { kundeId => - stammdatenReadRepository.getPersonen(kundeId) flatMap { person => - val personData = copyTo[Person, PersonData](person) - stammdatenReadRepository.getById(kundeMapping, kundeId) map { kunde => - val personEmailData = copyTo[Person, PersonEmailData](person) - val mailContext = KundeMailContext(personEmailData, kunde) - DefaultResultingEvent(factory => SendEmailToKundeEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + case SendEmailToKundenCommand(personId, subject, body, replyTo, ids) => idFactory => + meta => + DB readOnly { implicit session => + if (checkTemplateKunden(body, subject, ids)) { + val events = ids flatMap { kundeId => + stammdatenReadRepository.getPersonen(kundeId) flatMap { person => + val personData = copyTo[Person, PersonData](person) + stammdatenReadRepository.getById(kundeMapping, kundeId) map { kunde => + val personEmailData = copyTo[Person, PersonEmailData](person) + val mailContext = KundeMailContext(personEmailData, kunde) + + mailCommandForwarder.sendEmail(meta, subject, body, replyTo, determineBcc, personEmailData, None, mailContext) + + DefaultResultingEvent(factory => SendEmailToKundeEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + } } } + Success(events) + } else { + Failure(new InvalidStateException("The template is not valid")) } - Success(events) - } else { - Failure(new InvalidStateException("The template is not valid")) } - } - case SendEmailToPersonenCommand(personId, subject, body, replyTo, ids) => idFactory => meta => - DB readOnly { implicit session => - if (checkTemplatePersonen(body, subject, ids)) { - val events = ids flatMap { id => - stammdatenReadRepository.getById(personMapping, id) map { person => - val personEmailData = copyTo[Person, PersonEmailData](person) - val mailContext = PersonMailContext(personEmailData) - DefaultResultingEvent(factory => SendEmailToPersonEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + case SendEmailToPersonenCommand(personId, subject, body, replyTo, ids) => idFactory => + meta => + DB readOnly { implicit session => + if (checkTemplatePersonen(body, subject, ids)) { + val events = ids flatMap { id => + stammdatenReadRepository.getById(personMapping, id) map { person => + val personEmailData = copyTo[Person, PersonEmailData](person) + val mailContext = PersonMailContext(personEmailData) + + mailCommandForwarder.sendEmail(meta, subject, body, replyTo, determineBcc, personEmailData, None, mailContext) + + DefaultResultingEvent(factory => SendEmailToPersonEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + } } + Success(events) + } else { + Failure(new InvalidStateException("The template is not valid")) } - Success(events) - } else { - Failure(new InvalidStateException("The template is not valid")) } - } - case SendEmailToAbotypSubscribersCommand(personId, subject, body, replyTo, ids) => idFactory => meta => - DB readOnly { implicit session => - if (checkTemplateAbotyp(body, subject, ids)) { - val events = ids flatMap { abotypId => - stammdatenReadRepository.getPersonenForAbotyp(abotypId) flatMap { person => - val personData = copyTo[Person, PersonData](person) - stammdatenReadRepository.getAbotypById(abotypId) map { abotypId => - val personEmailData = copyTo[Person, PersonEmailData](person) - val mailContext = AbotypMailContext(personEmailData, abotypId) - DefaultResultingEvent(factory => SendEmailToAbotypSubscriberEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + case SendEmailToAbotypSubscribersCommand(personId, subject, body, replyTo, ids) => idFactory => + meta => + DB readOnly { implicit session => + if (checkTemplateAbotyp(body, subject, ids)) { + val events = ids flatMap { abotypId => + stammdatenReadRepository.getPersonenForAbotyp(abotypId) flatMap { person => + val personData = copyTo[Person, PersonData](person) + stammdatenReadRepository.getAbotypById(abotypId) map { abotypId => + val personEmailData = copyTo[Person, PersonEmailData](person) + val mailContext = AbotypMailContext(personEmailData, abotypId) + + mailCommandForwarder.sendEmail(meta, subject, body, replyTo, determineBcc, personEmailData, None, mailContext) + + DefaultResultingEvent(factory => SendEmailToAbotypSubscriberEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + } } } + Success(events) + } else { + Failure(new InvalidStateException("The template is not valid")) } - Success(events) - } else { - Failure(new InvalidStateException("The template is not valid")) } - } - case SendEmailToZusatzabotypSubscribersCommand(personId, subject, body, replyTo, ids) => idFactory => meta => - DB readOnly { implicit session => - if (checkTemplateAbotyp(body, subject, ids)) { - val events = ids flatMap { abotypId => - stammdatenReadRepository.getPersonenForZusatzabotyp(abotypId) flatMap { person => - val personData = copyTo[Person, PersonData](person) - stammdatenReadRepository.getAbotypById(abotypId) map { abotypId => - val personEmailData = copyTo[Person, PersonEmailData](person) - val mailContext = AbotypMailContext(personEmailData, abotypId) - DefaultResultingEvent(factory => SendEmailToAbotypSubscriberEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + case SendEmailToZusatzabotypSubscribersCommand(personId, subject, body, replyTo, ids) => idFactory => + meta => + DB readOnly { implicit session => + if (checkTemplateAbotyp(body, subject, ids)) { + val events = ids flatMap { abotypId => + stammdatenReadRepository.getPersonenForZusatzabotyp(abotypId) flatMap { person => + val personData = copyTo[Person, PersonData](person) + stammdatenReadRepository.getAbotypById(abotypId) map { abotypId => + val personEmailData = copyTo[Person, PersonEmailData](person) + val mailContext = AbotypMailContext(personEmailData, abotypId) + + mailCommandForwarder.sendEmail(meta, subject, body, replyTo, determineBcc, personEmailData, None, mailContext) + + DefaultResultingEvent(factory => SendEmailToAbotypSubscriberEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + } } } + Success(events) + } else { + Failure(new InvalidStateException("The template is not valid")) } - Success(events) - } else { - Failure(new InvalidStateException("The template is not valid")) } - } - case SendEmailToTourSubscribersCommand(personId, subject, body, replyTo, ids) => idFactory => meta => - DB readOnly { implicit session => - if (checkTemplateTour(body, subject, ids)) { - val events = ids flatMap { tourId => - stammdatenReadRepository.getPersonen(tourId) flatMap { person => - val personData = copyTo[Person, PersonData](person) - stammdatenReadRepository.getById(tourMapping, tourId) map { tour => - val personEmailData = copyTo[Person, PersonEmailData](person) - val mailContext = TourMailContext(personEmailData, tour) - DefaultResultingEvent(factory => SendEmailToTourSubscriberEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + case SendEmailToTourSubscribersCommand(personId, subject, body, replyTo, ids) => idFactory => + meta => + DB readOnly { implicit session => + if (checkTemplateTour(body, subject, ids)) { + val events = ids flatMap { tourId => + stammdatenReadRepository.getPersonen(tourId) flatMap { person => + val personData = copyTo[Person, PersonData](person) + stammdatenReadRepository.getById(tourMapping, tourId) map { tour => + val personEmailData = copyTo[Person, PersonEmailData](person) + val mailContext = TourMailContext(personEmailData, tour) + + mailCommandForwarder.sendEmail(meta, subject, body, replyTo, determineBcc, personEmailData, None, mailContext) + + DefaultResultingEvent(factory => SendEmailToTourSubscriberEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + } } } + Success(events) + } else { + Failure(new InvalidStateException("The template is not valid")) } - Success(events) - } else { - Failure(new InvalidStateException("The template is not valid")) } - } - case SendEmailToDepotSubscribersCommand(personId, subject, body, replyTo, ids) => idFactory => meta => - DB readOnly { implicit session => - if (checkTemplateDepot(body, subject, ids)) { - val events = ids flatMap { depotId => - stammdatenReadRepository.getPersonen(depotId) flatMap { person => - val personData = copyTo[Person, PersonData](person) - stammdatenReadRepository.getById(depotMapping, depotId) map { depot => - val personEmailData = copyTo[Person, PersonEmailData](person) - val mailContext = DepotMailContext(personEmailData, depot) - DefaultResultingEvent(factory => SendEmailToDepotSubscriberEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + case SendEmailToDepotSubscribersCommand(personId, subject, body, replyTo, ids) => idFactory => + meta => + DB readOnly { implicit session => + if (checkTemplateDepot(body, subject, ids)) { + val events = ids flatMap { depotId => + stammdatenReadRepository.getPersonen(depotId) flatMap { person => + val personData = copyTo[Person, PersonData](person) + stammdatenReadRepository.getById(depotMapping, depotId) map { depot => + val personEmailData = copyTo[Person, PersonEmailData](person) + val mailContext = DepotMailContext(personEmailData, depot) + + mailCommandForwarder.sendEmail(meta, subject, body, replyTo, determineBcc, personEmailData, None, mailContext) + + DefaultResultingEvent(factory => SendEmailToDepotSubscriberEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) + } } } + Success(events) + } else { + Failure(new InvalidStateException("The template is not valid")) } - Success(events) - } else { - Failure(new InvalidStateException("The template is not valid")) } - } - case SendEmailToAbosSubscribersCommand(personId, subject, body, replyTo, ids) => idFactory => meta => - DB readOnly { implicit session => - if (checkTemplateAbosSubscribers(body, subject, ids)) { - val events = ids flatMap { aboId: AboId => - stammdatenReadRepository.getById(zusatzAboMapping, aboId) orElse - stammdatenReadRepository.getById(depotlieferungAboMapping, aboId) orElse - stammdatenReadRepository.getById(heimlieferungAboMapping, aboId) orElse - stammdatenReadRepository.getById(postlieferungAboMapping, aboId) map { abo => + case SendEmailToAbosSubscribersCommand(personId, subject, body, replyTo, ids) => idFactory => + meta => + DB readOnly { implicit session => + if (checkTemplateAbosSubscribers(body, subject, ids)) { + val events = ids flatMap { aboId: AboId => + stammdatenReadRepository.getById(zusatzAboMapping, aboId) orElse + stammdatenReadRepository.getById(depotlieferungAboMapping, aboId) orElse + stammdatenReadRepository.getById(heimlieferungAboMapping, aboId) orElse + stammdatenReadRepository.getById(postlieferungAboMapping, aboId) map { abo => stammdatenReadRepository.getPersonen(abo.kundeId) map { person => val personEmailData = copyTo[Person, PersonEmailData](person) val mailContext = AboMailContext(personEmailData, abo) + + mailCommandForwarder.sendEmail(meta, subject, body, replyTo, determineBcc, personEmailData, None, mailContext) + DefaultResultingEvent(factory => SendEmailToAboSubscriberEvent(factory.newMetadata(), subject, body, replyTo, mailContext)) } } + } + Success(events.flatten) + } else { + Failure(new InvalidStateException("The template is not valid")) } - Success(events.flatten) - } else { - Failure(new InvalidStateException("The template is not valid")) } - } - - case LieferplanungAbschliessenCommand(personId, id) => idFactory => meta => - DB readOnly { implicit session => - stammdatenReadRepository.getById(lieferplanungMapping, id) map { lieferplanung => - lieferplanung.status match { - case Offen => - stammdatenReadRepository.countEarlierLieferungOffen(id) match { - case Some(0) => - val distinctSammelbestellungen = getDistinctSammelbestellungModifyByLieferplan(lieferplanung.id) - val bestellEvents = distinctSammelbestellungen.map { sammelbestellungCreate => - val insertEvent = EntityInsertEvent(idFactory.newId(SammelbestellungId.apply), sammelbestellungCreate) - - // TODO OO-589 - //val bestellungVersendenEvent = SammelbestellungVersendenEvent(factory.newMetadata(meta), sammelbestellungId) - Seq(insertEvent) //, bestellungVersendenEvent) - }.toSeq.flatten - - val lpAbschliessenEvent = DefaultResultingEvent(factory => LieferplanungAbschliessenEvent(factory.newMetadata(), id)) - - val createAuslieferungHeimEvent = getCreateAuslieferungHeimEvent(idFactory, meta, lieferplanung)(personId, session) - val createAuslieferungDepotPostEvent = getCreateDepotAuslieferungAndPostAusliferungEvent(idFactory, meta, lieferplanung)(personId, session) - Success(lpAbschliessenEvent +: bestellEvents ++: createAuslieferungHeimEvent ++: createAuslieferungDepotPostEvent) - case _ => - Failure(new InvalidStateException("Es dürfen keine früheren Lieferungen in offnen Lieferplanungen hängig sein.")) - } - case _ => - Failure(new InvalidStateException("Eine Lieferplanung kann nur im Status 'Offen' abgeschlossen werden")) - } - } getOrElse Failure(new InvalidStateException(s"Keine Lieferplanung mit der Nr. $id gefunden")) - } + case LieferplanungAbschliessenCommand(personId, id) => idFactory => + meta => + DB readOnly { implicit session => + stammdatenReadRepository.getById(lieferplanungMapping, id) map { lieferplanung => + lieferplanung.status match { + case Offen => + stammdatenReadRepository.countEarlierLieferungOffen(id) match { + case Some(0) => + val distinctSammelbestellungen = getDistinctSammelbestellungModifyByLieferplan(lieferplanung.id) + + val bestellEvents = distinctSammelbestellungen.map { sammelbestellungCreate => + val insertEvent = EntityInsertEvent(idFactory.newId(SammelbestellungId.apply), sammelbestellungCreate) + + Seq(insertEvent) + }.toSeq.flatten + + val lpAbschliessenEvent = DefaultResultingEvent(factory => LieferplanungAbschliessenEvent(factory.newMetadata(), id)) + + val createAuslieferungHeimEvent = getCreateAuslieferungHeimEvent(idFactory, meta, lieferplanung)(personId, session) + val createAuslieferungDepotPostEvent = getCreateDepotAuslieferungAndPostAusliferungEvent(idFactory, meta, lieferplanung)(personId, session) + Success(lpAbschliessenEvent +: bestellEvents ++: createAuslieferungHeimEvent ++: createAuslieferungDepotPostEvent) + case _ => + Failure(new InvalidStateException("Es dürfen keine früheren Lieferungen in offnen Lieferplanungen hängig sein.")) + } + case _ => + Failure(new InvalidStateException("Eine Lieferplanung kann nur im Status 'Offen' abgeschlossen werden")) + } + } getOrElse Failure(new InvalidStateException(s"Keine Lieferplanung mit der Nr. $id gefunden")) + } - case LieferplanungModifyCommand(_, lieferplanungPositionenModify) => idFactory => _ => - DB readOnly { implicit session => - stammdatenReadRepository.getById(lieferplanungMapping, lieferplanungPositionenModify.id) map { lieferplanung => - lieferplanung.status match { - case _@ Offen => - Success(DefaultResultingEvent(factory => LieferplanungDataModifiedEvent(factory.newMetadata(), LieferplanungDataModify(lieferplanungPositionenModify.id, Set.empty, lieferplanungPositionenModify.lieferungen))) :: Nil) - case _@ Abgeschlossen => - val allLieferpositionen = lieferplanungPositionenModify.lieferungen flatMap { lieferungPositionenModify => - lieferungPositionenModify.lieferpositionen.lieferpositionen - } + case LieferplanungModifyCommand(_, lieferplanungPositionenModify) => idFactory => + _ => + DB readOnly { implicit session => + stammdatenReadRepository.getById(lieferplanungMapping, lieferplanungPositionenModify.id) map { lieferplanung => + lieferplanung.status match { + case _@Offen => + Success(DefaultResultingEvent(factory => LieferplanungDataModifiedEvent(factory.newMetadata(), LieferplanungDataModify + (lieferplanungPositionenModify.id, Set.empty, lieferplanungPositionenModify.lieferungen))) :: Nil) + case _@Abgeschlossen => + val allLieferpositionen = lieferplanungPositionenModify.lieferungen flatMap { lieferungPositionenModify => + lieferungPositionenModify.lieferpositionen.lieferpositionen + } - val allLieferungpositionPerProduzent = allLieferpositionen.groupBy(_.produzentId) + val allLieferungpositionPerProduzent = allLieferpositionen.groupBy(_.produzentId) - val sammelBestellungToCreate = allLieferungpositionPerProduzent flatMap { l => - stammdatenReadRepository.getById(lieferungMapping, l._2.head.lieferungId) map { lieferung: Lieferung => - stammdatenReadRepository.getSammelbestellungenByProduzent(l._1, lieferplanung.id) match { - case Nil => Some(SammelbestellungCreate(idFactory.newId(SammelbestellungId.apply), l._1, lieferplanung.id, lieferung.datum)) - case _ => None + val sammelBestellungToCreate = allLieferungpositionPerProduzent flatMap { l => + stammdatenReadRepository.getById(lieferungMapping, l._2.head.lieferungId) map { lieferung: Lieferung => + stammdatenReadRepository.getSammelbestellungenByProduzent(l._1, lieferplanung.id) match { + case Nil => Some(SammelbestellungCreate(idFactory.newId(SammelbestellungId.apply), l._1, lieferplanung.id, lieferung.datum)) + case _ => None + } } } - } - Success(DefaultResultingEvent(factory => LieferplanungDataModifiedEvent(factory.newMetadata(), LieferplanungDataModify(lieferplanungPositionenModify.id, sammelBestellungToCreate.flatten.toSet, lieferplanungPositionenModify.lieferungen))) :: Nil) - case _ => - Failure(new InvalidStateException("Eine Lieferplanung kann nur im Status 'Offen' oder 'Abgeschlossen' aktualisiert werden")) - } - } getOrElse Failure(new InvalidStateException(s"Keine Lieferplanung mit der Nr. ${lieferplanungPositionenModify.id} gefunden")) - } - - case LieferplanungAbrechnenCommand(_, id: LieferplanungId) => _ => _ => - DB readOnly { implicit session => - stammdatenReadRepository.getById(lieferplanungMapping, id) map { lieferplanung => - lieferplanung.status match { - case Abgeschlossen => - Success(Seq(DefaultResultingEvent(factory => LieferplanungAbrechnenEvent(factory.newMetadata(), id)))) - case _ => - Failure(new InvalidStateException("Eine Lieferplanung kann nur im Status 'Abgeschlossen' verrechnet werden")) - } - } getOrElse Failure(new InvalidStateException(s"Keine Lieferplanung mit der Nr. $id gefunden")) - } + Success(DefaultResultingEvent(factory => LieferplanungDataModifiedEvent(factory.newMetadata(), LieferplanungDataModify + (lieferplanungPositionenModify.id, sammelBestellungToCreate.flatten.toSet, lieferplanungPositionenModify.lieferungen))) :: Nil) + case _ => + Failure(new InvalidStateException("Eine Lieferplanung kann nur im Status 'Offen' oder 'Abgeschlossen' aktualisiert werden")) + } + } getOrElse Failure(new InvalidStateException(s"Keine Lieferplanung mit der Nr. ${lieferplanungPositionenModify.id} gefunden")) + } - case AbwesenheitCreateCommand(_, abw: AbwesenheitCreate) => idFactory => meta => - DB readOnly { implicit session => - stammdatenReadRepository.countAbwesend(abw.lieferungId, abw.aboId) match { - case Some(0) => - handleEntityInsert[AbwesenheitCreate, AbwesenheitId](idFactory, meta, abw, AbwesenheitId.apply) - case _ => - Failure(new InvalidStateException("Eine Abwesenheit kann nur einmal erfasst werden")) + case LieferplanungAbrechnenCommand(_, id: LieferplanungId) => _ => + _ => + DB readOnly { implicit session => + stammdatenReadRepository.getById(lieferplanungMapping, id) map { lieferplanung => + lieferplanung.status match { + case Abgeschlossen => + Success(Seq(DefaultResultingEvent(factory => LieferplanungAbrechnenEvent(factory.newMetadata(), id)))) + case _ => + Failure(new InvalidStateException("Eine Lieferplanung kann nur im Status 'Abgeschlossen' verrechnet werden")) + } + } getOrElse Failure(new InvalidStateException(s"Keine Lieferplanung mit der Nr. $id gefunden")) } - } - case SammelbestellungAnProduzentenVersendenCommand(_, id: SammelbestellungId) => _ => _ => - DB readOnly { implicit session => - stammdatenReadRepository.getById(sammelbestellungMapping, id) map { sammelbestellung => - sammelbestellung.status match { - case Offen | Abgeschlossen => - Success(Seq(DefaultResultingEvent(factory => SammelbestellungVersendenEvent(factory.newMetadata(), id)))) + case AbwesenheitCreateCommand(_, abw: AbwesenheitCreate) => idFactory => + meta => + DB readOnly { implicit session => + stammdatenReadRepository.countAbwesend(abw.lieferungId, abw.aboId) match { + case Some(0) => + handleEntityInsert[AbwesenheitCreate, AbwesenheitId](idFactory, meta, abw, AbwesenheitId.apply) case _ => - Failure(new InvalidStateException("Eine Bestellung kann nur in den Status 'Offen' oder 'Abgeschlossen' versendet werden")) + Failure(new InvalidStateException("Eine Abwesenheit kann nur einmal erfasst werden")) } - } getOrElse Failure(new InvalidStateException(s"Keine Bestellung mit der Nr. $id gefunden")) - } + } + + case SammelbestellungAnProduzentenVersendenCommand(_, id: SammelbestellungId) => _ => + _ => + DB readOnly { implicit session => + stammdatenReadRepository.getById(sammelbestellungMapping, id) map { sammelbestellung => + sammelbestellung.status match { + case Offen | Abgeschlossen => + Success(Seq(DefaultResultingEvent(factory => SammelbestellungVersendenEvent(factory.newMetadata(), id)))) + case _ => + Failure(new InvalidStateException("Eine Bestellung kann nur in den Status 'Offen' oder 'Abgeschlossen' versendet werden")) + } + } getOrElse Failure(new InvalidStateException(s"Keine Bestellung mit der Nr. $id gefunden")) + } - case AuslieferungenAlsAusgeliefertMarkierenCommand(_, ids: Seq[AuslieferungId]) => _ => _ => - DB readOnly { implicit session => - val (events, _) = ids map { id => - stammdatenReadRepository.getById(depotAuslieferungMapping, id) orElse - stammdatenReadRepository.getById(tourAuslieferungMapping, id) orElse - stammdatenReadRepository.getById(postAuslieferungMapping, id) map { auslieferung => + case AuslieferungenAlsAusgeliefertMarkierenCommand(_, ids: Seq[AuslieferungId]) => _ => + _ => + DB readOnly { implicit session => + val (events, _) = ids map { id => + stammdatenReadRepository.getById(depotAuslieferungMapping, id) orElse + stammdatenReadRepository.getById(tourAuslieferungMapping, id) orElse + stammdatenReadRepository.getById(postAuslieferungMapping, id) map { auslieferung => auslieferung.status match { case Erfasst => val copy = auslieferung match { @@ -381,199 +486,254 @@ trait StammdatenCommandHandler extends CommandHandler Failure(new InvalidStateException(s"Eine Auslieferung kann nur im Status 'Erfasst' als ausgeliefert markiert werden. Nr. $id")) } } getOrElse Failure(new InvalidStateException(s"Keine Auslieferung mit der Nr. $id gefunden")) - } partition (_.isSuccess) + } partition (_.isSuccess) - if (events.isEmpty) { - Failure(new InvalidStateException(s"Keine der Auslieferungen konnte abgearbeitet werden")) - } else { - Success(events map (_.get)) + if (events.isEmpty) { + Failure(new InvalidStateException(s"Keine der Auslieferungen konnte abgearbeitet werden")) + } else { + Success(events map (_.get)) + } } - } - case SammelbestellungenAlsAbgerechnetMarkierenCommand(_, datum, ids: Seq[SammelbestellungId]) => _ => _ => - DB readOnly { implicit session => - val (events, _) = ids map { id => - stammdatenReadRepository.getById(sammelbestellungMapping, id) map { sammelbestellung => - sammelbestellung.status match { - case Abgeschlossen => - Success(DefaultResultingEvent(factory => SammelbestellungAlsAbgerechnetMarkierenEvent(factory.newMetadata(), datum, id))) - case _ => - Failure(new InvalidStateException(s"Eine Sammelbestellung kann nur im Status 'Abgeschlossen' als abgerechnet markiert werden. Nr. $id")) - } - } getOrElse Failure(new InvalidStateException(s"Keine Sammelbestellung mit der Nr. $id gefunden")) - } partition (_.isSuccess) + case SammelbestellungenAlsAbgerechnetMarkierenCommand(_, datum, ids: Seq[SammelbestellungId]) => _ => + _ => + DB readOnly { implicit session => + val (events, _) = ids map { id => + stammdatenReadRepository.getById(sammelbestellungMapping, id) map { sammelbestellung => + sammelbestellung.status match { + case Abgeschlossen => + Success(DefaultResultingEvent(factory => SammelbestellungAlsAbgerechnetMarkierenEvent(factory.newMetadata(), datum, id))) + case _ => + Failure(new InvalidStateException(s"Eine Sammelbestellung kann nur im Status 'Abgeschlossen' als abgerechnet markiert werden. Nr. $id")) + } + } getOrElse Failure(new InvalidStateException(s"Keine Sammelbestellung mit der Nr. $id gefunden")) + } partition (_.isSuccess) - if (events.isEmpty) { - Failure(new InvalidStateException(s"Keine der Sammelbestellung konnte abgearbeitet werden")) - } else { - Success(events map (_.get)) + if (events.isEmpty) { + Failure(new InvalidStateException(s"Keine der Sammelbestellung konnte abgearbeitet werden")) + } else { + Success(events map (_.get)) + } } - } - case CreateAnzahlLieferungenRechnungsPositionenCommand(_, aboRechnungCreate) => idFactory => meta => - createAboRechnungsPositionenAnzahlLieferungen(idFactory, meta, aboRechnungCreate) + case CreateAnzahlLieferungenRechnungsPositionenCommand(_, aboRechnungCreate) => idFactory => + meta => + createAboRechnungsPositionenAnzahlLieferungen(idFactory, meta, aboRechnungCreate) - case CreateBisGuthabenRechnungsPositionenCommand(_, aboRechnungCreate) => idFactory => meta => - createAboRechnungsPositionenBisGuthaben(idFactory, meta, aboRechnungCreate) + case CreateBisGuthabenRechnungsPositionenCommand(_, aboRechnungCreate) => idFactory => + meta => + createAboRechnungsPositionenBisGuthaben(idFactory, meta, aboRechnungCreate) - case PasswortWechselCommand(_, personId, pwd, einladungId) => _ => _ => - Success(Seq(DefaultResultingEvent(factory => PasswortGewechseltEvent(factory.newMetadata(), personId, pwd, einladungId)))) + case PasswortWechselCommand(_, personId, pwd, einladungId) => _ => + _ => + Success(Seq(DefaultResultingEvent(factory => PasswortGewechseltEvent(factory.newMetadata(), personId, pwd, einladungId)))) - case LoginDeaktivierenCommand(originator, kundeId, personId) if originator != personId => _ => _ => - Success(Seq(DefaultResultingEvent(factory => LoginDeaktiviertEvent(factory.newMetadata(), kundeId, personId)))) + case LoginDeaktivierenCommand(originator, kundeId, personId) if originator != personId => _ => + _ => + Success(Seq(DefaultResultingEvent(factory => LoginDeaktiviertEvent(factory.newMetadata(), kundeId, personId)))) - case LoginAktivierenCommand(originator, kundeId, personId) if originator != personId => _ => _ => - Success(Seq(DefaultResultingEvent(factory => LoginAktiviertEvent(factory.newMetadata(), kundeId, personId)))) + case LoginAktivierenCommand(originator, kundeId, personId) if originator != personId => _ => + _ => + Success(Seq(DefaultResultingEvent(factory => LoginAktiviertEvent(factory.newMetadata(), kundeId, personId)))) - case EinladungSendenCommand(originator, kundeId, personId) if originator != personId => idFactory => meta => - sendEinladung(idFactory, meta, kundeId, personId) + case EinladungSendenCommand(originator, kundeId, personId) if originator != personId => idFactory => + meta => + sendEinladung(idFactory, meta, kundeId, personId) - case PasswortResetCommand(_, personId) => idFactory => meta => - sendPasswortReset(idFactory, meta, personId) + case PasswortResetCommand(_, personId) => idFactory => + meta => + sendPasswortReset(idFactory, meta, personId) - case RolleWechselnCommand(originator, kundeId, personId, rolle) if originator != personId => idFactory => meta => - changeRolle(idFactory, meta, kundeId, personId, rolle) - case OtpResetCommand(_, kundeId, personId) => _ => _ => - Success(Seq(DefaultResultingEvent(factory => OtpResetEvent(factory.newMetadata(), kundeId, personId, OtpUtil.generateOtpSecretString)))) + case RolleWechselnCommand(originator, kundeId, personId, rolle) if originator != personId => idFactory => + meta => + changeRolle(idFactory, meta, kundeId, personId, rolle) + case OtpResetCommand(_, kundeId, personId) => _ => + _ => + Success(Seq(DefaultResultingEvent(factory => OtpResetEvent(factory.newMetadata(), kundeId, personId, OtpUtil.generateOtpSecretString)))) - case AboAktivierenCommand(aboId, _) => _ => _ => - Success(Seq(DefaultResultingEvent(factory => AboAktiviertEvent(factory.newMetadata(), aboId)))) + case AboAktivierenCommand(aboId, _) => _ => + _ => + Success(Seq(DefaultResultingEvent(factory => AboAktiviertEvent(factory.newMetadata(), aboId)))) - case AboDeaktivierenCommand(aboId, _) => _ => _ => - Success(Seq(DefaultResultingEvent(factory => AboDeaktiviertEvent(factory.newMetadata(), aboId)))) + case AboDeaktivierenCommand(aboId, _) => _ => + _ => + Success(Seq(DefaultResultingEvent(factory => AboDeaktiviertEvent(factory.newMetadata(), aboId)))) - case UpdateKundeCommand(_, kundeId, kunde) => idFactory => meta => - updateKunde(idFactory, meta, kundeId, kunde) + case UpdateKundeCommand(_, kundeId, kunde) => idFactory => + meta => + updateKunde(idFactory, meta, kundeId, kunde) - case CreateKundeCommand(_, kunde) => idFactory => meta => - createKunde(idFactory, meta, kunde) + case CreateKundeCommand(_, kunde) => idFactory => + meta => + createKunde(idFactory, meta, kunde) - case RemoveLieferungCommand(_, lieferungId) => idFactory => meta => - removeLieferung(idFactory, meta, lieferungId) + case RemoveLieferungCommand(_, lieferungId) => idFactory => + meta => + removeLieferung(idFactory, meta, lieferungId) /* * Insert command handling */ - case e @ InsertEntityCommand(_, entity: CustomKundentypCreate) => idFactory => meta => - handleEntityInsert[CustomKundentypCreate, CustomKundentypId](idFactory, meta, entity, CustomKundentypId.apply) - case e @ InsertEntityCommand(_, entity: LieferungenAbotypCreate) => idFactory => meta => - val events = entity.daten.map { datum => - val lieferungCreate = copyTo[LieferungenAbotypCreate, LieferungAbotypCreate](entity, "datum" -> datum) - insertEntityEvent[LieferungAbotypCreate, LieferungId](idFactory, meta, lieferungCreate, LieferungId.apply) - } - Success(events) - case e @ InsertEntityCommand(_, entity: LieferungAbotypCreate) => idFactory => meta => - handleEntityInsert[LieferungAbotypCreate, LieferungId](idFactory, meta, entity, LieferungId.apply) - case e @ InsertEntityCommand(_, entity: LieferplanungCreate) => idFactory => meta => - handleEntityInsert[LieferplanungCreate, LieferplanungId](idFactory, meta, entity, LieferplanungId.apply) - case e @ InsertEntityCommand(_, entity: LieferungPlanungAdd) => idFactory => meta => - handleEntityInsert[LieferungPlanungAdd, LieferungId](idFactory, meta, entity, LieferungId.apply) - case e @ InsertEntityCommand(_, entity: LieferpositionenModify) => idFactory => meta => - handleEntityInsert[LieferpositionenModify, LieferpositionId](idFactory, meta, entity, LieferpositionId.apply) - case e @ InsertEntityCommand(_, entity: PendenzModify) => idFactory => meta => - handleEntityInsert[PendenzModify, PendenzId](idFactory, meta, entity, PendenzId.apply) - case e @ InsertEntityCommand(_, entity: PersonCreate) => idFactory => meta => - handleEntityInsert[PersonCreate, PersonId](idFactory, meta, entity, PersonId.apply) - case e @ InsertEntityCommand(_, entity: PersonCategoryCreate) => idFactory => meta => - handleEntityInsert[PersonCategoryCreate, PersonCategoryId](idFactory, meta, entity, PersonCategoryId.apply) - case e @ InsertEntityCommand(_, entity: ProduzentModify) => idFactory => meta => - handleEntityInsert[ProduzentModify, ProduzentId](idFactory, meta, entity, ProduzentId.apply) - case e @ InsertEntityCommand(_, entity: ProduktModify) => idFactory => meta => - handleEntityInsert[ProduktModify, ProduktId](idFactory, meta, entity, ProduktId.apply) - case e @ InsertEntityCommand(_, entity: ProduktProduktekategorie) => idFactory => meta => - handleEntityInsert[ProduktProduktekategorie, ProduktProduktekategorieId](idFactory, meta, entity, ProduktProduktekategorieId.apply) - case e @ InsertEntityCommand(_, entity: ProduktProduzent) => idFactory => meta => - handleEntityInsert[ProduktProduzent, ProduktProduzentId](idFactory, meta, entity, ProduktProduzentId.apply) - case e @ InsertEntityCommand(_, entity: ProduktekategorieModify) => idFactory => meta => - handleEntityInsert[ProduktekategorieModify, ProduktekategorieId](idFactory, meta, entity, ProduktekategorieId.apply) - case e @ InsertEntityCommand(_, entity: ProjektModify) => idFactory => meta => - handleEntityInsert[ProjektModify, ProjektId](idFactory, meta, entity, ProjektId.apply) - case e @ InsertEntityCommand(_, entity: TourCreate) => idFactory => meta => - handleEntityInsert[TourCreate, TourId](idFactory, meta, entity, TourId.apply) - case e @ InsertEntityCommand(_, entity: AbwesenheitCreate) => idFactory => meta => - handleEntityInsert[AbwesenheitCreate, AbwesenheitId](idFactory, meta, entity, AbwesenheitId.apply) - case e @ InsertEntityCommand(_, entity: AbotypModify) => idFactory => meta => - handleEntityInsert[AbotypModify, AbotypId](idFactory, meta, entity, AbotypId.apply) - case e @ InsertEntityCommand(_, entity: ZusatzAbotypModify) => idFactory => meta => - handleEntityInsert[ZusatzAbotypModify, AbotypId](idFactory, meta, entity, AbotypId.apply) - case e @ InsertEntityCommand(_, entity: DepotModify) => idFactory => meta => - handleEntityInsert[DepotModify, DepotId](idFactory, meta, entity, DepotId.apply) - case e @ InsertEntityCommand(_, entity: DepotlieferungModify) => idFactory => meta => - handleEntityInsert[DepotlieferungModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) - case e @ InsertEntityCommand(_, entity: HeimlieferungModify) => idFactory => meta => - handleEntityInsert[HeimlieferungModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) - case e @ InsertEntityCommand(_, entity: PostlieferungModify) => idFactory => meta => - handleEntityInsert[PostlieferungModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) - case e @ InsertEntityCommand(_, entity: DepotlieferungAbotypModify) => idFactory => meta => - handleEntityInsert[DepotlieferungAbotypModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) - case e @ InsertEntityCommand(_, entity: HeimlieferungAbotypModify) => idFactory => meta => - handleEntityInsert[HeimlieferungAbotypModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) - case e @ InsertEntityCommand(_, entity: PostlieferungAbotypModify) => idFactory => meta => - handleEntityInsert[PostlieferungAbotypModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) - case e @ InsertEntityCommand(_, entity: DepotlieferungAboCreate) => idFactory => meta => - handleEntityInsert[DepotlieferungAboCreate, AboId](idFactory, meta, entity, AboId.apply) - case e @ InsertEntityCommand(_, entity: HeimlieferungAboCreate) => idFactory => meta => - handleEntityInsert[HeimlieferungAboCreate, AboId](idFactory, meta, entity, AboId.apply) - case e @ InsertEntityCommand(_, entity: PostlieferungAboCreate) => idFactory => meta => - handleEntityInsert[PostlieferungAboCreate, AboId](idFactory, meta, entity, AboId.apply) - case e @ InsertEntityCommand(_, entity: ZusatzAboModify) => idFactory => meta => - handleEntityInsert[ZusatzAboModify, AboId](idFactory, meta, entity, AboId.apply) - case e @ InsertEntityCommand(_, entity: ZusatzAboCreate) => idFactory => meta => - handleEntityInsert[ZusatzAboCreate, AboId](idFactory, meta, entity, AboId.apply) - case e @ InsertEntityCommand(_, entity: PendenzCreate) => idFactory => meta => - handleEntityInsert[PendenzCreate, PendenzId](idFactory, meta, entity, PendenzId.apply) - case e @ InsertEntityCommand(_, entity: VertriebModify) => idFactory => meta => - handleEntityInsert[VertriebModify, VertriebId](idFactory, meta, entity, VertriebId.apply) - case e @ InsertEntityCommand(_, entity: ProjektVorlageCreate) => idFactory => meta => - handleEntityInsert[ProjektVorlageCreate, ProjektVorlageId](idFactory, meta, entity, ProjektVorlageId.apply) + case e@InsertEntityCommand(_, entity: CustomKundentypCreate) => idFactory => + meta => + handleEntityInsert[CustomKundentypCreate, CustomKundentypId](idFactory, meta, entity, CustomKundentypId.apply) + case e@InsertEntityCommand(_, entity: LieferungenAbotypCreate) => idFactory => + meta => + val events = entity.daten.map { datum => + val lieferungCreate = copyTo[LieferungenAbotypCreate, LieferungAbotypCreate](entity, "datum" -> datum) + insertEntityEvent[LieferungAbotypCreate, LieferungId](idFactory, meta, lieferungCreate, LieferungId.apply) + } + Success(events) + case e@InsertEntityCommand(_, entity: LieferungAbotypCreate) => idFactory => + meta => + handleEntityInsert[LieferungAbotypCreate, LieferungId](idFactory, meta, entity, LieferungId.apply) + case e@InsertEntityCommand(_, entity: LieferplanungCreate) => idFactory => + meta => + handleEntityInsert[LieferplanungCreate, LieferplanungId](idFactory, meta, entity, LieferplanungId.apply) + case e@InsertEntityCommand(_, entity: LieferungPlanungAdd) => idFactory => + meta => + handleEntityInsert[LieferungPlanungAdd, LieferungId](idFactory, meta, entity, LieferungId.apply) + case e@InsertEntityCommand(_, entity: LieferpositionenModify) => idFactory => + meta => + handleEntityInsert[LieferpositionenModify, LieferpositionId](idFactory, meta, entity, LieferpositionId.apply) + case e@InsertEntityCommand(_, entity: PendenzModify) => idFactory => + meta => + handleEntityInsert[PendenzModify, PendenzId](idFactory, meta, entity, PendenzId.apply) + case e@InsertEntityCommand(_, entity: PersonCreate) => idFactory => + meta => + handleEntityInsert[PersonCreate, PersonId](idFactory, meta, entity, PersonId.apply) + case e@InsertEntityCommand(_, entity: PersonCategoryCreate) => idFactory => + meta => + handleEntityInsert[PersonCategoryCreate, PersonCategoryId](idFactory, meta, entity, PersonCategoryId.apply) + case e@InsertEntityCommand(_, entity: ProduzentModify) => idFactory => + meta => + handleEntityInsert[ProduzentModify, ProduzentId](idFactory, meta, entity, ProduzentId.apply) + case e@InsertEntityCommand(_, entity: ProduktModify) => idFactory => + meta => + handleEntityInsert[ProduktModify, ProduktId](idFactory, meta, entity, ProduktId.apply) + case e@InsertEntityCommand(_, entity: ProduktProduktekategorie) => idFactory => + meta => + handleEntityInsert[ProduktProduktekategorie, ProduktProduktekategorieId](idFactory, meta, entity, ProduktProduktekategorieId.apply) + case e@InsertEntityCommand(_, entity: ProduktProduzent) => idFactory => + meta => + handleEntityInsert[ProduktProduzent, ProduktProduzentId](idFactory, meta, entity, ProduktProduzentId.apply) + case e@InsertEntityCommand(_, entity: ProduktekategorieModify) => idFactory => + meta => + handleEntityInsert[ProduktekategorieModify, ProduktekategorieId](idFactory, meta, entity, ProduktekategorieId.apply) + case e@InsertEntityCommand(_, entity: ProjektModify) => idFactory => + meta => + handleEntityInsert[ProjektModify, ProjektId](idFactory, meta, entity, ProjektId.apply) + case e@InsertEntityCommand(_, entity: TourCreate) => idFactory => + meta => + handleEntityInsert[TourCreate, TourId](idFactory, meta, entity, TourId.apply) + case e@InsertEntityCommand(_, entity: AbwesenheitCreate) => idFactory => + meta => + handleEntityInsert[AbwesenheitCreate, AbwesenheitId](idFactory, meta, entity, AbwesenheitId.apply) + case e@InsertEntityCommand(_, entity: AbotypModify) => idFactory => + meta => + handleEntityInsert[AbotypModify, AbotypId](idFactory, meta, entity, AbotypId.apply) + case e@InsertEntityCommand(_, entity: ZusatzAbotypModify) => idFactory => + meta => + handleEntityInsert[ZusatzAbotypModify, AbotypId](idFactory, meta, entity, AbotypId.apply) + case e@InsertEntityCommand(_, entity: DepotModify) => idFactory => + meta => + handleEntityInsert[DepotModify, DepotId](idFactory, meta, entity, DepotId.apply) + case e@InsertEntityCommand(_, entity: DepotlieferungModify) => idFactory => + meta => + handleEntityInsert[DepotlieferungModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) + case e@InsertEntityCommand(_, entity: HeimlieferungModify) => idFactory => + meta => + handleEntityInsert[HeimlieferungModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) + case e@InsertEntityCommand(_, entity: PostlieferungModify) => idFactory => + meta => + handleEntityInsert[PostlieferungModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) + case e@InsertEntityCommand(_, entity: DepotlieferungAbotypModify) => idFactory => + meta => + handleEntityInsert[DepotlieferungAbotypModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) + case e@InsertEntityCommand(_, entity: HeimlieferungAbotypModify) => idFactory => + meta => + handleEntityInsert[HeimlieferungAbotypModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) + case e@InsertEntityCommand(_, entity: PostlieferungAbotypModify) => idFactory => + meta => + handleEntityInsert[PostlieferungAbotypModify, VertriebsartId](idFactory, meta, entity, VertriebsartId.apply) + case e@InsertEntityCommand(_, entity: DepotlieferungAboCreate) => idFactory => + meta => + handleEntityInsert[DepotlieferungAboCreate, AboId](idFactory, meta, entity, AboId.apply) + case e@InsertEntityCommand(_, entity: HeimlieferungAboCreate) => idFactory => + meta => + handleEntityInsert[HeimlieferungAboCreate, AboId](idFactory, meta, entity, AboId.apply) + case e@InsertEntityCommand(_, entity: PostlieferungAboCreate) => idFactory => + meta => + handleEntityInsert[PostlieferungAboCreate, AboId](idFactory, meta, entity, AboId.apply) + case e@InsertEntityCommand(_, entity: ZusatzAboModify) => idFactory => + meta => + handleEntityInsert[ZusatzAboModify, AboId](idFactory, meta, entity, AboId.apply) + case e@InsertEntityCommand(_, entity: ZusatzAboCreate) => idFactory => + meta => + handleEntityInsert[ZusatzAboCreate, AboId](idFactory, meta, entity, AboId.apply) + case e@InsertEntityCommand(_, entity: PendenzCreate) => idFactory => + meta => + handleEntityInsert[PendenzCreate, PendenzId](idFactory, meta, entity, PendenzId.apply) + case e@InsertEntityCommand(_, entity: VertriebModify) => idFactory => + meta => + handleEntityInsert[VertriebModify, VertriebId](idFactory, meta, entity, VertriebId.apply) + case e@InsertEntityCommand(_, entity: ProjektVorlageCreate) => idFactory => + meta => + handleEntityInsert[ProjektVorlageCreate, ProjektVorlageId](idFactory, meta, entity, ProjektVorlageId.apply) /* * Custom update command handling */ - case UpdateEntityCommand(personId, id: KundeId, entity: KundeModify) => idFactory => _ => - updateKundeEntity(idFactory, personId, id, entity) - - case UpdateEntityCommand(_, id: AboId, entity: AboGuthabenModify) => idFactory => meta => - DB readOnly { implicit session => - //TODO: assemble text using gettext - stammdatenReadRepository.getAboDetail(id) match { - case Some(abo) => { - val text = s"Guthaben manuell angepasst. Abo Nr.: ${id.id}; Bisher: ${abo.guthaben}; Neu: ${entity.guthabenNeu}; Grund: ${entity.bemerkung}" - val pendenzEvent = addKundenPendenz(idFactory, meta, id, text, Erledigt) - Success(Seq(Some(EntityUpdateEvent(id, entity)), pendenzEvent).flatten) + case UpdateEntityCommand(personId, id: KundeId, entity: KundeModify) => idFactory => + _ => + updateKundeEntity(idFactory, personId, id, entity) + + case UpdateEntityCommand(_, id: AboId, entity: AboGuthabenModify) => idFactory => + meta => + DB readOnly { implicit session => + //TODO: assemble text using gettext + stammdatenReadRepository.getAboDetail(id) match { + case Some(abo) => { + val text = s"Guthaben manuell angepasst. Abo Nr.: ${id.id}; Bisher: ${abo.guthaben}; Neu: ${entity.guthabenNeu}; Grund: ${entity.bemerkung}" + val pendenzEvent = addKundenPendenz(idFactory, meta, id, text, Erledigt) + Success(Seq(Some(EntityUpdateEvent(id, entity)), pendenzEvent).flatten) + } + case None => + Failure(new InvalidStateException(s"UpdateEntityCommand: Abo konnte nicht gefunden werden")) } - case None => - Failure(new InvalidStateException(s"UpdateEntityCommand: Abo konnte nicht gefunden werden")) } - } - case UpdateEntityCommand(_, id: AboId, entity: AboVertriebsartModify) => idFactory => meta => - DB readOnly { implicit session => - //TODO: assemble text using gettext - val newLieferungen = stammdatenReadRepository.getLieferungen(entity.vertriebIdNeu) - stammdatenReadRepository.getAbo(id) match { - case Some(abo) => - val text = s"Vertriebsart angepasst. Abo Nr.: ${id.id}, Neu: ${entity.vertriebsartIdNeu}; Grund: ${entity.bemerkung}" - var absencesText = "" - var pendenzStatus: PendenzStatus = Erledigt - stammdatenReadRepository.getById(vertriebMapping, abo.vertriebId) match { - case Some(vertrieb) => - stammdatenReadRepository.getLieferungen(vertrieb.id).filter(lOld => (lOld.datum isAfter DateTime.now) && (newLieferungen.count(l => l.datum == lOld.datum) == 0)) map { l => - if (stammdatenReadRepository.getAbwesenheit(abo.id, l.datum).length > 0) { - absencesText = s"; Bitte Abwesenheiten prüfen!" - pendenzStatus = Ausstehend + case UpdateEntityCommand(_, id: AboId, entity: AboVertriebsartModify) => idFactory => + meta => + DB readOnly { implicit session => + //TODO: assemble text using gettext + val newLieferungen = stammdatenReadRepository.getLieferungen(entity.vertriebIdNeu) + stammdatenReadRepository.getAbo(id) match { + case Some(abo) => + val text = s"Vertriebsart angepasst. Abo Nr.: ${id.id}, Neu: ${entity.vertriebsartIdNeu}; Grund: ${entity.bemerkung}" + var absencesText = "" + var pendenzStatus: PendenzStatus = Erledigt + stammdatenReadRepository.getById(vertriebMapping, abo.vertriebId) match { + case Some(vertrieb) => + stammdatenReadRepository.getLieferungen(vertrieb.id).filter(lOld => (lOld.datum isAfter DateTime.now) && (newLieferungen.count(l => l.datum + == lOld.datum) == 0)) map { l => + if (stammdatenReadRepository.getAbwesenheit(abo.id, l.datum).length > 0) { + absencesText = s"; Bitte Abwesenheiten prüfen!" + pendenzStatus = Ausstehend + } } - } - case None => Failure(new InvalidStateException(s"UpdateEntityCommand: Some error happened when creating a pendenz")) - } - val pendenzEvent = addKundenPendenz(idFactory, meta, id, text + absencesText, pendenzStatus) - Success(Seq(Some(EntityUpdateEvent(id, entity)), pendenzEvent).flatten) - case None => - Failure(new InvalidStateException(s"UpdateEntityCommand: Some error happened when creating a pendenz")) + case None => Failure(new InvalidStateException(s"UpdateEntityCommand: Some error happened when creating a pendenz")) + } + val pendenzEvent = addKundenPendenz(idFactory, meta, id, text + absencesText, pendenzStatus) + Success(Seq(Some(EntityUpdateEvent(id, entity)), pendenzEvent).flatten) + case None => + Failure(new InvalidStateException(s"UpdateEntityCommand: Some error happened when creating a pendenz")) + } } - } } - def addKundenPendenz(idFactory: IdFactory, meta: EventTransactionMetadata, id: AboId, bemerkung: String, status: PendenzStatus)(implicit session: DBSession): Option[ResultingEvent] = { + def addKundenPendenz(idFactory: IdFactory, meta: EventTransactionMetadata, id: AboId, bemerkung: String, status: PendenzStatus)(implicit + session: DBSession) + : Option[ResultingEvent] = { // zusätzlich eine pendenz erstellen ((stammdatenReadRepository.getById(depotlieferungAboMapping, id) map { abo => DepotlieferungAboModify @@ -590,7 +750,8 @@ trait StammdatenCommandHandler extends CommandHandler } } - def createAboRechnungsPositionenAnzahlLieferungen(idFactory: IdFactory, meta: EventTransactionMetadata, aboRechnungCreate: AboRechnungsPositionBisAnzahlLieferungenCreate) = { + def createAboRechnungsPositionenAnzahlLieferungen(idFactory: IdFactory, meta: EventTransactionMetadata, + aboRechnungCreate: AboRechnungsPositionBisAnzahlLieferungenCreate) = { DB readOnly { implicit session => // HauptAbos val abos: List[Abo] = stammdatenReadRepository.getByIds(depotlieferungAboMapping, aboRechnungCreate.ids) ::: @@ -623,10 +784,11 @@ trait StammdatenCommandHandler extends CommandHandler val repoType = abotyp match { case a: ZusatzAbotyp => RechnungsPositionTyp.ZusatzAbo - case _ => RechnungsPositionTyp.Abo + case _ => RechnungsPositionTyp.Abo } - val hauptRechnungPosition = createRechnungPositionEvent(abo, aboRechnungCreate.titel, anzahlLieferungen, hauptAboBetrag, aboRechnungCreate.waehrung, None, repoType) + val hauptRechnungPosition = createRechnungPositionEvent(abo, aboRechnungCreate.titel, anzahlLieferungen, hauptAboBetrag, aboRechnungCreate + .waehrung, None, repoType) val parentRechnungPositionId = idFactory.newId(RechnungsPositionId.apply) Success(List(EntityInsertEvent(parentRechnungPositionId, hauptRechnungPosition))) @@ -673,7 +835,9 @@ trait StammdatenCommandHandler extends CommandHandler Success(updateEvent +: (newPersonsEvents ++ newPendenzenEvents)) } - private def createRechnungPositionEvent(abo: Abo, titel: String, anzahlLieferungen: Int, betrag: BigDecimal, waehrung: Waehrung, parentRechnungsPositionId: Option[RechnungsPositionId] = None, rechnungsPositionTyp: RechnungsPositionTyp.RechnungsPositionTyp = RechnungsPositionTyp.Abo): RechnungsPositionCreate = { + private def createRechnungPositionEvent(abo: Abo, titel: String, anzahlLieferungen: Int, betrag: BigDecimal, waehrung: Waehrung, + parentRechnungsPositionId: Option[RechnungsPositionId] = None, rechnungsPositionTyp: RechnungsPositionTyp + .RechnungsPositionTyp = RechnungsPositionTyp.Abo): RechnungsPositionCreate = { RechnungsPositionCreate( abo.kundeId, Some(abo.id), @@ -687,7 +851,8 @@ trait StammdatenCommandHandler extends CommandHandler ) } - def createAboRechnungsPositionenBisGuthaben(idFactory: IdFactory, meta: EventTransactionMetadata, aboRechnungCreate: AboRechnungsPositionBisGuthabenCreate) = { + def createAboRechnungsPositionenBisGuthaben(idFactory: IdFactory, meta: EventTransactionMetadata, aboRechnungCreate: AboRechnungsPositionBisGuthabenCreate) + = { DB readOnly { implicit session => val abos: List[Abo] = stammdatenReadRepository.getByIds(depotlieferungAboMapping, aboRechnungCreate.ids) ::: stammdatenReadRepository.getByIds(postlieferungAboMapping, aboRechnungCreate.ids) ::: @@ -712,14 +877,15 @@ trait StammdatenCommandHandler extends CommandHandler val hauptabo = stammdatenReadRepository.getHauptAbo(zusatzAbo.id) hauptabo.get.guthaben case abo: HauptAbo => abo.guthaben - case abo => throw new InvalidStateException(s"Unexpected abo type found:$abo") + case abo => throw new InvalidStateException(s"Unexpected abo type found:$abo") } val anzahlLieferungen = math.max((aboRechnungCreate.bisGuthaben - guthaben), 0) if (anzahlLieferungen > 0) { val hauptAboBetrag = abo.price.getOrElse(abotyp.preis) * anzahlLieferungen - val hauptRechnungPosition = createRechnungPositionEvent(abo, aboRechnungCreate.titel, anzahlLieferungen, hauptAboBetrag, aboRechnungCreate.waehrung) + val hauptRechnungPosition = createRechnungPositionEvent(abo, aboRechnungCreate.titel, anzahlLieferungen, hauptAboBetrag, aboRechnungCreate + .waehrung) val parentRechnungPositionId = idFactory.newId(RechnungsPositionId.apply) Success(List(EntityInsertEvent(parentRechnungPositionId, hauptRechnungPosition))) @@ -817,8 +983,9 @@ trait StammdatenCommandHandler extends CommandHandler //Konto daten creation val kontoDaten = kunde.kontoDaten match { - case Some(kd) => KontoDatenModify(kd.iban, kd.bic, None, None, kd.bankName, kd.nameAccountHolder, kd.addressAccountHolder, Some(kundeId), None, None, None) - case None => KontoDatenModify(None, None, None, None, None, None, None, Some(kundeId), None, None, None) + case Some(kd) => KontoDatenModify(kd.iban, kd.bic, None, None, kd.bankName, kd.nameAccountHolder, kd.addressAccountHolder, Some(kundeId), None, + None, None) + case None => KontoDatenModify(None, None, None, None, None, None, None, Some(kundeId), None, None, None) } logger.debug(s"created => Insert entity:$kontoDaten") val kontoDatenEvent = EntityInsertEvent(KontoDatenId(kundeId.id), kontoDaten) @@ -863,14 +1030,18 @@ trait StammdatenCommandHandler extends CommandHandler } case _ => None } - } else { None } + } else { + None + } case _ => None } }.flatten.isEmpty } } - private def getCreateAuslieferungHeimEvent(idFactory: IdFactory, meta: EventTransactionMetadata, lieferplanung: Lieferplanung)(implicit personId: PersonId, session: DBSession): Seq[ResultingEvent] = { + private def getCreateAuslieferungHeimEvent(idFactory: IdFactory, meta: EventTransactionMetadata, lieferplanung: Lieferplanung)(implicit personId: PersonId, + session: DBSession) + : Seq[ResultingEvent] = { val lieferungen = stammdatenReadRepository.getLieferungen(lieferplanung.id) //handle Tourenlieferungen: Group all entries with the same TourId on the same Date @@ -881,7 +1052,9 @@ trait StammdatenCommandHandler extends CommandHandler (h.tourId, tour.name, lieferung.datum) -> h.id } } - }).flatten.groupBy(_._1).view.mapValues(_ map { _._2 }) + }).flatten.groupBy(_._1).view.mapValues(_ map { + _._2 + }) (vertriebsartenDaten flatMap { case ((tourId, tourName, lieferdatum), vertriebsartIds) => { @@ -896,8 +1069,12 @@ trait StammdatenCommandHandler extends CommandHandler EntityUpdateEvent(korb.id, KorbAuslieferungModify(tourAuslieferung.id, tourlieferungen find (_.id == korb.aboId) flatMap (_.sort))) } EntityInsertEvent(tourAuslieferung.id, tourAuslieferung) :: updates - } else { Nil } - } else { Nil } + } else { + Nil + } + } else { + Nil + } } }).toSeq } @@ -906,14 +1083,15 @@ trait StammdatenCommandHandler extends CommandHandler val hauptAboKoerbe = koerbe map { korb => stammdatenReadRepository.getAbo(korb.aboId) match { case Some(abo: ZusatzAbo) => None - case None => None - case _ => Some(korb) + case None => None + case _ => Some(korb) } } hauptAboKoerbe.flatten.size } - private def getCreateDepotAuslieferungAndPostAusliferungEvent(idFactory: IdFactory, meta: EventTransactionMetadata, lieferplanung: Lieferplanung)(implicit personId: PersonId, session: DBSession): Seq[ResultingEvent] = { + private def getCreateDepotAuslieferungAndPostAusliferungEvent(idFactory: IdFactory, meta: EventTransactionMetadata, lieferplanung: Lieferplanung)(implicit + personId: PersonId, session: DBSession): Seq[ResultingEvent] = { val lieferungen = stammdatenReadRepository.getLieferungen(lieferplanung.id) val updates1 = handleLieferplanungAbgeschlossen(idFactory, meta, lieferungen) @@ -923,14 +1101,18 @@ trait StammdatenCommandHandler extends CommandHandler updates1 ::: updates2 ::: updates3 } - private def handleLieferplanungAbgeschlossen(idFactory: IdFactory, meta: EventTransactionMetadata, lieferungen: List[Lieferung])(implicit personId: PersonId, session: DBSession): List[ResultingEvent] = { + private def handleLieferplanungAbgeschlossen(idFactory: IdFactory, meta: EventTransactionMetadata, lieferungen: List[Lieferung])(implicit + personId: PersonId, + session: DBSession) + : List[ResultingEvent] = { //handle Depot- and Postlieferungen: Group all entries with the same VertriebId on the same Date val vertriebeDaten = lieferungen.map(l => (l.vertriebId, l.datum)).distinct getDepotAuslieferungEvents(idFactory, meta, getDepotAuslieferungAsAMapGrouped(vertriebeDaten)) ::: getPostAuslieferungEvents(idFactory, meta, getPostAuslieferungAsAMapGrouped(vertriebeDaten)) } - private def getDepotAuslieferungAsAMapGrouped(vertriebeDaten: List[(VertriebId, DateTime)])(implicit personId: PersonId, session: DBSession): Map[(DepotId, DateTime), List[DepotlieferungDetail]] = { + private def getDepotAuslieferungAsAMapGrouped(vertriebeDaten: List[(VertriebId, DateTime)])(implicit personId: PersonId, session: DBSession): Map[(DepotId, + DateTime), List[DepotlieferungDetail]] = { val depotAuslieferungMap = (vertriebeDaten map { case (vertriebId, lieferungDatum) => { logger.debug(s"handleLieferplanungAbgeschlossen Depot: ${vertriebId}:${lieferungDatum}.") @@ -946,7 +1128,8 @@ trait StammdatenCommandHandler extends CommandHandler } } - private def getDepotAuslieferungEvents(idFactory: IdFactory, meta: EventTransactionMetadata, depotAuslieferungGroupedMap: Map[(DepotId, DateTime), List[DepotlieferungDetail]])(implicit personId: PersonId, session: DBSession): List[ResultingEvent] = { + private def getDepotAuslieferungEvents(idFactory: IdFactory, meta: EventTransactionMetadata, depotAuslieferungGroupedMap: Map[(DepotId, DateTime), + List[DepotlieferungDetail]])(implicit personId: PersonId, session: DBSession): List[ResultingEvent] = { val events = for { ((depotId, date), listDepotLieferungDetail) <- depotAuslieferungGroupedMap } yield { @@ -955,7 +1138,8 @@ trait StammdatenCommandHandler extends CommandHandler events.flatten.toList } - private def getPostAuslieferungAsAMapGrouped(vertriebeDaten: List[(VertriebId, DateTime)])(implicit personId: PersonId, session: DBSession): Map[DateTime, List[PostlieferungDetail]] = { + private def getPostAuslieferungAsAMapGrouped(vertriebeDaten: List[(VertriebId, DateTime)])(implicit personId: PersonId, session: DBSession): Map[DateTime, + List[PostlieferungDetail]] = { val postAuslieferungMap = (vertriebeDaten map { case (vertriebId, lieferungDatum) => { logger.debug(s"handleLieferplanungAbgeschlossen (Post): ${vertriebId}:${lieferungDatum}.") @@ -972,7 +1156,8 @@ trait StammdatenCommandHandler extends CommandHandler } } - private def getPostAuslieferungEvents(idFactory: IdFactory, meta: EventTransactionMetadata, postAuslieferungGroupedMap: Map[DateTime, List[PostlieferungDetail]])(implicit personId: PersonId, session: DBSession): List[ResultingEvent] = { + private def getPostAuslieferungEvents(idFactory: IdFactory, meta: EventTransactionMetadata, postAuslieferungGroupedMap: Map[DateTime, + List[PostlieferungDetail]])(implicit personId: PersonId, session: DBSession): List[ResultingEvent] = { val events = for { (date, listPostLieferungDetail) <- postAuslieferungGroupedMap } yield { @@ -981,7 +1166,9 @@ trait StammdatenCommandHandler extends CommandHandler events.flatten.toList } - private def getPostAndDepotAuslieferungEvents(idFactory: IdFactory, meta: EventTransactionMetadata, date: DateTime, listVertriebsartDetail: List[VertriebsartDetail])(implicit personId: PersonId, session: DBSession): List[ResultingEvent] = { + private def getPostAndDepotAuslieferungEvents(idFactory: IdFactory, meta: EventTransactionMetadata, date: DateTime, + listVertriebsartDetail: List[VertriebsartDetail])(implicit personId: PersonId, session: DBSession) + : List[ResultingEvent] = { val koerbe = getAllKoerbeForDepotOrPost(date, listVertriebsartDetail) if (!koerbe.isEmpty) { val newAuslieferung = createAuslieferungDepotPost(idFactory, meta, date, listVertriebsartDetail.head, countHauptAbos(koerbe)).get @@ -994,14 +1181,16 @@ trait StammdatenCommandHandler extends CommandHandler } } - private def getAllKoerbeForDepotOrPost(date: DateTime, vertriebsartDetailList: List[VertriebsartDetail])(implicit personId: PersonId, session: DBSession): List[Korb] = { + private def getAllKoerbeForDepotOrPost(date: DateTime, vertriebsartDetailList: List[VertriebsartDetail])(implicit personId: PersonId, session: DBSession) + : List[Korb] = { val koerbe = vertriebsartDetailList map { vertriebsartDetail => stammdatenReadRepository.getKoerbe(date, vertriebsartDetail.id, WirdGeliefert) } koerbe.flatten } - private def recalculateValuesForLieferplanungAbgeschlossen(lieferungen: List[Lieferung])(implicit personId: PersonId, session: DBSession): List[ResultingEvent] = { + private def recalculateValuesForLieferplanungAbgeschlossen(lieferungen: List[Lieferung])(implicit personId: PersonId, session: DBSession) + : List[ResultingEvent] = { //calculate new values lieferungen flatMap { lieferung => //calculate total of lieferung @@ -1035,7 +1224,8 @@ trait StammdatenCommandHandler extends CommandHandler } } - private def updateSammelbestellungStatus(lieferungen: List[Lieferung], lieferplanung: Lieferplanung)(implicit personId: PersonId, session: DBSession): List[ResultingEvent] = { + private def updateSammelbestellungStatus(lieferungen: List[Lieferung], lieferplanung: Lieferplanung)(implicit personId: PersonId, session: DBSession) + : List[ResultingEvent] = { (stammdatenReadRepository.getSammelbestellungen(lieferplanung.id) map { sammelbestellung => @@ -1044,11 +1234,14 @@ trait StammdatenCommandHandler extends CommandHandler val sammelbestellungStatusModifyCopy = SammelbestellungStatusModify(sammelbestellungCopy.status) Seq(EntityUpdateEvent(sammelbestellungCopy.id, sammelbestellungStatusModifyCopy)) - } else { Nil } + } else { + Nil + } }).filter(_.nonEmpty).flatten } - private def createAuslieferungDepotPost(idFactory: IdFactory, meta: EventTransactionMetadata, lieferungDatum: DateTime, vertriebsart: VertriebsartDetail, anzahlKoerbe: Int)(implicit personId: PersonId): Option[Auslieferung] = { + private def createAuslieferungDepotPost(idFactory: IdFactory, meta: EventTransactionMetadata, lieferungDatum: DateTime, vertriebsart: VertriebsartDetail, + anzahlKoerbe: Int)(implicit personId: PersonId): Option[Auslieferung] = { val auslieferungId = idFactory.newId(AuslieferungId.apply) vertriebsart match { @@ -1100,7 +1293,8 @@ trait StammdatenCommandHandler extends CommandHandler stammdatenReadRepository.getTourAuslieferung(tourId, datum).isDefined } - private def createTourAuslieferungHeim(idFactory: IdFactory, meta: EventTransactionMetadata, lieferungDatum: DateTime, tourId: TourId, tourName: String, anzahlKoerbe: Int)(implicit personId: PersonId): TourAuslieferung = { + private def createTourAuslieferungHeim(idFactory: IdFactory, meta: EventTransactionMetadata, lieferungDatum: DateTime, tourId: TourId, tourName: String, + anzahlKoerbe: Int)(implicit personId: PersonId): TourAuslieferung = { val auslieferungId = idFactory.newId(AuslieferungId.apply) TourAuslieferung( auslieferungId, @@ -1129,15 +1323,15 @@ trait StammdatenCommandHandler extends CommandHandler stammdatenReadRepository.getById(depotlieferungAboMapping, aboId) orElse stammdatenReadRepository.getById(heimlieferungAboMapping, aboId) orElse stammdatenReadRepository.getById(postlieferungAboMapping, aboId) map { abo => - stammdatenReadRepository.getPersonen(abo.kundeId) map { person => - val personEmailData = copyTo[Person, PersonEmailData](person) - val mailContext = AboMailContext(personEmailData, abo) - generateMail(subject, body, mailContext) match { - case Success(mailPayload) => true - case Failure(e) => false - } + stammdatenReadRepository.getPersonen(abo.kundeId) map { person => + val personEmailData = copyTo[Person, PersonEmailData](person) + val mailContext = AboMailContext(personEmailData, abo) + generateMail(subject, body, mailContext) match { + case Success(mailPayload) => true + case Failure(e) => false } } + } } templateCorrect.flatten.forall(x => x == true) } @@ -1151,7 +1345,7 @@ trait StammdatenCommandHandler extends CommandHandler val mailContext = KundeMailContext(personEmailData, kunde) generateMail(subject, body, mailContext) match { case Success(mailPayload) => true - case Failure(e) => false + case Failure(e) => false } } } @@ -1168,7 +1362,7 @@ trait StammdatenCommandHandler extends CommandHandler val mailContext = AbotypMailContext(personEmailData, abotyp) generateMail(subject, body, mailContext) match { case Success(mailPayload) => true - case Failure(e) => false + case Failure(e) => false } } } @@ -1185,7 +1379,7 @@ trait StammdatenCommandHandler extends CommandHandler val mailContext = AbotypMailContext(personEmailData, abotyp) generateMail(subject, body, mailContext) match { case Success(mailPayload) => true - case Failure(e) => false + case Failure(e) => false } } } @@ -1202,7 +1396,7 @@ trait StammdatenCommandHandler extends CommandHandler val mailContext = TourMailContext(personEmailData, tour) generateMail(subject, body, mailContext) match { case Success(mailPayload) => true - case Failure(e) => false + case Failure(e) => false } } } @@ -1219,7 +1413,7 @@ trait StammdatenCommandHandler extends CommandHandler val mailContext = DepotMailContext(personEmailData, depot) generateMail(subject, body, mailContext) match { case Success(mailPayload) => true - case Failure(e) => false + case Failure(e) => false } } } @@ -1234,7 +1428,7 @@ trait StammdatenCommandHandler extends CommandHandler val mailContext = PersonMailContext(personEmailData) generateMail(subject, body, mailContext) match { case Success(mailPayload) => true - case Failure(e) => false + case Failure(e) => false } } } @@ -1242,7 +1436,11 @@ trait StammdatenCommandHandler extends CommandHandler } } -class DefaultStammdatenCommandHandler(override val sysConfig: SystemConfig, override val system: ActorSystem) extends StammdatenCommandHandler - with DefaultStammdatenReadRepositorySyncComponent { +class DefaultStammdatenCommandHandler(override val sysConfig: SystemConfig, override val system: ActorSystem, override val mailService: ActorRef) extends StammdatenCommandHandler + with DefaultStammdatenReadRepositorySyncComponent + with DefaultMailCommandForwarderComponent + with MailServiceReference { override implicit protected val executionContext: ExecutionContext = system.dispatcher + + override def projektReadRepository = stammdatenReadRepository } diff --git a/src/main/scala/ch/openolitor/stammdaten/StammdatenEntityStoreView.scala b/src/main/scala/ch/openolitor/stammdaten/StammdatenEntityStoreView.scala index 41bbf383d..c5305dc3b 100644 --- a/src/main/scala/ch/openolitor/stammdaten/StammdatenEntityStoreView.scala +++ b/src/main/scala/ch/openolitor/stammdaten/StammdatenEntityStoreView.scala @@ -50,7 +50,7 @@ trait StammdatenEntityStoreView extends EntityStoreView /** * Instanzieren der jeweiligen Insert, Update und Delete Child Actors */ -trait StammdatenEntityStoreViewComponent extends EntityStoreViewComponent with ActorSystemReference with MailServiceReference with SystemConfigReference { +trait StammdatenEntityStoreViewComponent extends EntityStoreViewComponent with ActorSystemReference with SystemConfigReference with MailServiceReference { override val insertService = StammdatenInsertService(sysConfig, system) override val updateService = StammdatenUpdateService(sysConfig, system) diff --git a/src/main/scala/ch/openolitor/stammdaten/StammdatenRoutes.scala b/src/main/scala/ch/openolitor/stammdaten/StammdatenRoutes.scala index 91c4790eb..77488d8db 100644 --- a/src/main/scala/ch/openolitor/stammdaten/StammdatenRoutes.scala +++ b/src/main/scala/ch/openolitor/stammdaten/StammdatenRoutes.scala @@ -44,7 +44,7 @@ import ch.openolitor.stammdaten.reporting._ import com.typesafe.scalalogging.LazyLogging import ch.openolitor.core.filestore._ import akka.actor._ -import akka.http.scaladsl.model.headers.{ `Content-Disposition`, ContentDispositionTypes } +import akka.http.scaladsl.model.headers.{`Content-Disposition`, ContentDispositionTypes} import akka.http.scaladsl.server.Route import ch.openolitor.buchhaltung.repositories.BuchhaltungReadRepositoryAsyncComponent import ch.openolitor.buchhaltung.repositories.DefaultBuchhaltungReadRepositoryAsyncComponent @@ -52,7 +52,8 @@ import ch.openolitor.buchhaltung.BuchhaltungJsonProtocol import ch.openolitor.core.BaseJsonProtocol.IdResponse import ch.openolitor.core.security.Subject import ch.openolitor.stammdaten.repositories._ -import ch.openolitor.util.parsing.{ QueryFilter, GeschaeftsjahrFilter, FilterExpr, UriQueryParamFilterParser, UriQueryParamGeschaeftsjahrParser, UriQueryFilterParser } +import ch.openolitor.util.parsing.{QueryFilter, GeschaeftsjahrFilter, FilterExpr, UriQueryParamFilterParser, UriQueryParamGeschaeftsjahrParser, + UriQueryFilterParser} import scala.concurrent.ExecutionContext @@ -102,7 +103,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("kontodaten" / kontoDatenIdPath) { id => get(detail(stammdatenReadRepository.getKontoDatenProjekt)) ~ - (put | post)(update[KontoDatenModify, KontoDatenId](id)) + (put | post) (update[KontoDatenModify, KontoDatenId](id)) } private def kundenRoute(implicit subject: Subject, filter: Option[FilterExpr], queryString: Option[QueryFilter]): Route = @@ -124,7 +125,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("kunden" / kundeIdPath) { id => get(detail(stammdatenReadRepository.getKundeDetail(id))) ~ - (put | post)( + (put | post) ( entity(as[KundeModify]) { kunde => val allEmailAddresses = kunde.ansprechpersonen.map(_.email).flatten.filter(_.nonEmpty) if (allEmailAddresses.distinct.length == allEmailAddresses.length) { @@ -170,13 +171,13 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences delete(remove(aboId)) } ~ path("kunden" / kundeIdPath / "abos" / aboIdPath / "aktionen" / "guthabenanpassen") { (kundeId, aboId) => - (put | post)(update[AboGuthabenModify, AboId](aboId)) + (put | post) (update[AboGuthabenModify, AboId](aboId)) } ~ path("kunden" / kundeIdPath / "abos" / aboIdPath / "aktionen" / "vertriebsartanpassen") { (kundeId, aboId) => - (put | post)(update[AboVertriebsartModify, AboId](aboId)) + (put | post) (update[AboVertriebsartModify, AboId](aboId)) } ~ path("kunden" / kundeIdPath / "abos" / aboIdPath / "aktionen" / "priceanpassen") { (kundeId, aboId) => - (put | post)(update[AboPriceModify, AboId](aboId)) + (put | post) (update[AboPriceModify, AboId](aboId)) } ~ path("kunden" / kundeIdPath / "abos" / aboIdPath / "koerbe") { (_, aboId) => get(list(stammdatenReadRepository.getKoerbe(aboId))) @@ -199,7 +200,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("kunden" / kundeIdPath / "abos" / aboIdPath / "zusatzAbos" / aboIdPath) { (kundeId, hauptAboId, id) => get(detail(stammdatenReadRepository.getZusatzAboDetail(id))) ~ - (put | post)(update[ZusatzAboModify, AboId](id)) ~ + (put | post) (update[ZusatzAboModify, AboId](id)) ~ delete(remove(id)) } ~ path("kunden" / kundeIdPath / "pendenzen") { kundeId => @@ -214,7 +215,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("kunden" / kundeIdPath / "pendenzen" / pendenzIdPath) { (kundeId, pendenzId) => get(detail(stammdatenReadRepository.getPendenzDetail(pendenzId))) ~ - (put | post)(update[PendenzModify, PendenzId](pendenzId)) ~ + (put | post) (update[PendenzModify, PendenzId](pendenzId)) ~ delete(remove(pendenzId)) } ~ path("kunden" / kundeIdPath / "personen" / personIdPath) { (kundeId, personId) => @@ -257,7 +258,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences post(create[PersonCategoryCreate, PersonCategoryId](PersonCategoryId.apply _)) } ~ path("personCategories" / personCategoryIdPath) { (personCategoryId) => - (put | post)(update[PersonCategoryModify, PersonCategoryId](personCategoryId)) ~ + (put | post) (update[PersonCategoryModify, PersonCategoryId](personCategoryId)) ~ delete(remove(personCategoryId)) } @@ -267,7 +268,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences post(create[CustomKundentypCreate, CustomKundentypId](CustomKundentypId.apply _)) } ~ path("kundentypen" / kundentypIdPath) { (kundentypId) => - (put | post)(update[CustomKundentypModify, CustomKundentypId](kundentypId)) ~ + (put | post) (update[CustomKundentypModify, CustomKundentypId](kundentypId)) ~ delete(remove(kundentypId)) } @@ -289,7 +290,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("abotypen" / abotypIdPath) { id => get(detail(stammdatenReadRepository.getAbotypDetail(id))) ~ - (put | post)(update[AbotypModify, AbotypId](id)) ~ + (put | post) (update[AbotypModify, AbotypId](id)) ~ delete(remove(id)) } ~ path("abotypen" / abotypIdPath / "vertriebe") { abotypId => @@ -298,7 +299,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("abotypen" / abotypIdPath / "vertriebe" / vertriebIdPath) { (abotypId, vertriebId) => get(detail(stammdatenReadRepository.getVertrieb(vertriebId))) ~ - (put | post)(update[VertriebModify, VertriebId](vertriebId)) ~ + (put | post) (update[VertriebModify, VertriebId](vertriebId)) ~ delete(remove(vertriebId)) } ~ path("abotypen" / abotypIdPath / "vertriebe" / vertriebIdPath / "vertriebsarten") { (abotypId, vertriebId) => @@ -363,7 +364,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("zusatzAbotypen" / zusatzAbotypIdPath) { id => get(detail(stammdatenReadRepository.getZusatzAbotypDetail(id))) ~ - (put | post)(update[ZusatzAbotypModify, AbotypId](id)) ~ + (put | post) (update[ZusatzAbotypModify, AbotypId](id)) ~ delete(remove(id)) } @@ -385,7 +386,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("depots" / depotIdPath) { id => get(detail(stammdatenReadRepository.getDepotDetail(id))) ~ - (put | post)(update[DepotModify, DepotId](id)) ~ + (put | post) (update[DepotModify, DepotId](id)) ~ delete(remove(id)) } @@ -413,7 +414,8 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } } - private def zusatzaboRoute(implicit subject: Subject, filter: Option[FilterExpr], gjFilter: Option[GeschaeftsjahrFilter], queryString: Option[QueryFilter]): Route = + private def zusatzaboRoute(implicit subject: Subject, filter: Option[FilterExpr], gjFilter: Option[GeschaeftsjahrFilter], queryString: Option[QueryFilter]) + : Route = path("zusatzabos" ~ exportFormatPath.?) { exportFormat => parameter("x".as[AbosComplexFlags].?) { xFlags: Option[AbosComplexFlags] => get(list(stammdatenReadRepository.getZusatzAbos(xFlags), exportFormat)) @@ -432,7 +434,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences get(list(stammdatenReadRepository.getPendenzen, exportFormat)) } ~ path("pendenzen" / pendenzIdPath) { pendenzId => - (put | post)(update[PendenzModify, PendenzId](pendenzId)) + (put | post) (update[PendenzModify, PendenzId](pendenzId)) } private def produkteRoute(implicit subject: Subject): Route = @@ -441,7 +443,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences post(create[ProduktModify, ProduktId](ProduktId.apply _)) } ~ path("produkte" / produktIdPath) { id => - (put | post)(update[ProduktModify, ProduktId](id)) ~ + (put | post) (update[ProduktModify, ProduktId](id)) ~ delete(remove(id)) } @@ -451,7 +453,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences post(create[ProduktekategorieModify, ProduktekategorieId](ProduktekategorieId.apply _)) } ~ path("produktekategorien" / produktekategorieIdPath) { id => - (put | post)(update[ProduktekategorieModify, ProduktekategorieId](id)) ~ + (put | post) (update[ProduktekategorieModify, ProduktekategorieId](id)) ~ delete(remove(id)) } @@ -462,7 +464,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("produzenten" / produzentIdPath) { id => get(detail(stammdatenReadRepository.getProduzentDetail(id))) ~ - (put | post)(update[ProduzentModify, ProduzentId](id)) ~ + (put | post) (update[ProduzentModify, ProduzentId](id)) ~ delete(remove(id)) } ~ path("produzenten" / "berichte" / "produzentenbrief") { @@ -486,7 +488,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences parameter("aktiveOrPlanned".as[Boolean].?) { aktiveOrPlanned: Option[Boolean] => get(detail(stammdatenReadRepository.getTourDetail(id, aktiveOrPlanned.getOrElse(false)))) } ~ - (put | post)(update[TourModify, TourId](id)) ~ + (put | post) (update[TourModify, TourId](id)) ~ delete(remove(id)) } @@ -497,24 +499,24 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("projekt" / projektIdPath) { id => get(detail(stammdatenReadRepository.getProjekt)) ~ - (put | post)(update[ProjektModify, ProjektId](id)) + (put | post) (update[ProjektModify, ProjektId](id)) } ~ path("projekt" / projektIdPath / "logo") { id => get(download(ProjektStammdaten, "logo")) ~ - (put | post)(uploadStored(ProjektStammdaten, Some("logo")) { (id, metadata) => + (put | post) (uploadStored(ProjektStammdaten, Some("logo")) { (id, metadata) => //TODO: update projekt stammdaten entity complete("Logo uploaded") }) } ~ path("projekt" / projektIdPath / "style-admin") { id => get(download(ProjektStammdaten, "style-admin")) ~ - (put | post)(uploadStored(ProjektStammdaten, Some("style-admin")) { (id, metadata) => + (put | post) (uploadStored(ProjektStammdaten, Some("style-admin")) { (id, metadata) => complete("Style 'style-admin' uploaded") }) } ~ path("projekt" / projektIdPath / "style-kundenportal") { id => get(download(ProjektStammdaten, "style-kundenportal")) ~ - (put | post)(uploadStored(ProjektStammdaten, Some("style-kundenportal")) { (id, metadata) => + (put | post) (uploadStored(ProjektStammdaten, Some("style-kundenportal")) { (id, metadata) => complete("Style 'style-kundenportal' uploaded") }) } ~ @@ -529,7 +531,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("lieferplanungen" / lieferplanungIdPath) { id => get(detail(stammdatenReadRepository.getLieferplanung(id))) ~ - (put | post)(update[LieferplanungModify, LieferplanungId](id)) ~ + (put | post) (update[LieferplanungModify, LieferplanungId](id)) ~ delete(remove(id)) } ~ path("lieferplanungen" / lieferplanungIdPath / "lieferungen") { lieferplanungId => @@ -539,7 +541,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences get(list(stammdatenReadRepository.getVerfuegbareLieferungen(lieferplanungId))) } ~ path("lieferplanungen" / lieferplanungIdPath / "lieferungen" / lieferungIdPath) { (lieferplanungId, lieferungId) => - (put | post)(create[LieferungPlanungAdd, LieferungId]((x: Long) => lieferungId)) ~ + (put | post) (create[LieferungPlanungAdd, LieferungId]((x: Long) => lieferungId)) ~ delete(lieferplanungRemoveLieferung(lieferungId)) } ~ path("lieferplanungen" / lieferplanungIdPath / korbStatusPath / "aboIds") { (lieferplanungId, korbStatus) => @@ -548,14 +550,16 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences path("lieferplanungen" / lieferplanungIdPath / "auslieferungen") { (lieferplanungId) => get(list(stammdatenReadRepository.getAuslieferungen(lieferplanungId))) } ~ - path("lieferplanungen" / lieferplanungIdPath / "lieferungen" / lieferungIdPath / korbStatusPath / "aboIds") { (lieferplanungId, lieferungId, korbStatus) => + path("lieferplanungen" / lieferplanungIdPath / "lieferungen" / lieferungIdPath / korbStatusPath / "aboIds") { (lieferplanungId, lieferungId, + korbStatus) => get(list(stammdatenReadRepository.getAboIds(lieferungId, korbStatus))) } ~ - path("lieferplanungen" / lieferplanungIdPath / "lieferungen" / lieferungIdPath / korbStatusPath / "hauptaboIds") { (lieferplanungId, lieferungId, korbStatus) => + path("lieferplanungen" / lieferplanungIdPath / "lieferungen" / lieferungIdPath / korbStatusPath / "hauptaboIds") { (lieferplanungId, lieferungId, + korbStatus) => get(list(stammdatenReadRepository.getZusatzaboIds(lieferungId, korbStatus))) } ~ path("lieferplanungen" / lieferplanungIdPath / "aktionen" / "abschliessen") { id => - (post)(lieferplanungAbschliessen(id)) + (post) (lieferplanungAbschliessen(id)) } ~ path("lieferplanungen" / lieferplanungIdPath / "aktionen" / "modifizieren") { id => post { @@ -567,16 +571,18 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } } ~ path("lieferplanungen" / lieferplanungIdPath / "aktionen" / "verrechnen") { id => - (post)(lieferplanungVerrechnen(id)) + (post) (lieferplanungVerrechnen(id)) } ~ path("lieferplanungen" / lieferplanungIdPath / "sammelbestellungen") { lieferplanungId => get(list(stammdatenReadRepository.getSammelbestellungen(lieferplanungId))) } ~ - path("lieferplanungen" / lieferplanungIdPath / "sammelbestellungen" / sammelbestellungIdPath / "bestellungen" / bestellungIdPath / "positionen") { (lieferplanungId, sammelbestellungId, bestellungId) => + path("lieferplanungen" / lieferplanungIdPath / "sammelbestellungen" / sammelbestellungIdPath / "bestellungen" / bestellungIdPath / "positionen") { + (lieferplanungId, sammelbestellungId, bestellungId) => get(list(stammdatenReadRepository.getBestellpositionen(bestellungId))) } ~ - path("lieferplanungen" / lieferplanungIdPath / "sammelbestellungen" / sammelbestellungIdPath / "aktionen" / "erneutBestellen") { (lieferplanungId, sammelbestellungId) => - (post)(sammelbestellungErneutVersenden(sammelbestellungId)) + path("lieferplanungen" / lieferplanungIdPath / "sammelbestellungen" / sammelbestellungIdPath / "aktionen" / "erneutBestellen") { (lieferplanungId, + sammelbestellungId) => + (post) (sammelbestellungErneutVersenden(sammelbestellungId)) } ~ path("lieferplanungen" / "berichte" / "lieferplanung") { implicit val personId = subject.personId @@ -589,7 +595,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences case UserCommandFailed => complete(StatusCodes.BadRequest, s"Could not transit Lieferplanung to status Abschliessen") case _ => - // TODO OO-589 + // TODO OO-589: SammelbestellungAnProduzentenVersendenCommand is not called on Abschliessen of Lieferplanung => Future task OO-589 if (false) { stammdatenReadRepository.getSammelbestellungen(id) map { _ map { sammelbestellung => @@ -618,7 +624,8 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } } - private def lieferplanungModifizieren(lieferplanungModify: LieferplanungPositionenModify)(implicit idPersister: Persister[LieferplanungId, _], subject: Subject): Route = { + private def lieferplanungModifizieren(lieferplanungModify: LieferplanungPositionenModify)(implicit idPersister: Persister[LieferplanungId, _], + subject: Subject): Route = { implicit val timeout = Timeout(30.seconds) onSuccess(entityStore ? StammdatenCommandHandler.LieferplanungModifyCommand(subject.personId, lieferplanungModify)) { case UserCommandFailed => @@ -647,6 +654,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences complete("") } } + private def abwesenheitCreate(abw: AbwesenheitCreate)(implicit idPersister: Persister[AbwesenheitId, _], subject: Subject): Route = { onSuccess(entityStore ? StammdatenCommandHandler.AbwesenheitCreateCommand(subject.personId, abw)) { case UserCommandFailed => @@ -674,7 +682,8 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } } - private def lieferantenRoute(implicit subject: Subject, filter: Option[FilterExpr], gjFilter: Option[GeschaeftsjahrFilter], queryString: Option[QueryFilter]): Route = + private def lieferantenRoute(implicit subject: Subject, filter: Option[FilterExpr], gjFilter: Option[GeschaeftsjahrFilter], + queryString: Option[QueryFilter]): Route = path("lieferanten" / "sammelbestellungen" ~ exportFormatPath.?) { exportFormat => get(list(stammdatenReadRepository.getSammelbestellungen, exportFormat)) } ~ @@ -695,7 +704,8 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences generateReport[SammelbestellungId](None, generateProduzentenabrechnungReports(VorlageProduzentenabrechnung) _)(SammelbestellungId.apply) } - private def sammelbestellungenAlsAbgerechnetMarkieren(datum: DateTime, ids: Seq[SammelbestellungId])(implicit idPersister: Persister[SammelbestellungId, _], subject: Subject): Route = { + private def sammelbestellungenAlsAbgerechnetMarkieren(datum: DateTime, ids: Seq[SammelbestellungId])(implicit idPersister: Persister[SammelbestellungId, + _], subject: Subject): Route = { onSuccess(entityStore ? StammdatenCommandHandler.SammelbestellungenAlsAbgerechnetMarkierenCommand(subject.personId, datum, ids)) { case UserCommandFailed => complete(StatusCodes.BadRequest, s"Die Bestellungen konnten nicht als abgerechnet markiert werden.") @@ -704,7 +714,8 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } } - private def auslieferungenRoute(implicit subject: Subject, filter: Option[FilterExpr], gjFilter: Option[GeschaeftsjahrFilter], queryString: Option[QueryFilter]): Route = + private def auslieferungenRoute(implicit subject: Subject, filter: Option[FilterExpr], gjFilter: Option[GeschaeftsjahrFilter], + queryString: Option[QueryFilter]): Route = path("depotauslieferungen" ~ exportFormatPath.?) { exportFormat => get(list(stammdatenReadRepository.getDepotAuslieferungen, exportFormat)) } ~ @@ -716,7 +727,7 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } ~ path("tourauslieferungen" / auslieferungIdPath) { auslieferungId => get(detail(stammdatenReadRepository.getTourAuslieferungDetail(auslieferungId))) ~ - (put | post)(update[TourAuslieferungModify, AuslieferungId](auslieferungId)) + (put | post) (update[TourAuslieferungModify, AuslieferungId](auslieferungId)) } ~ path("postauslieferungen" ~ exportFormatPath.?) { exportFormat => get(list(stammdatenReadRepository.getPostAuslieferungen, exportFormat)) @@ -824,7 +835,9 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } } - private def createAnzahlLieferungenRechnungsPositionen(rechnungCreate: AboRechnungsPositionBisAnzahlLieferungenCreate)(implicit idPersister: Persister[AboId, _], subject: Subject): Route = { + private def createAnzahlLieferungenRechnungsPositionen(rechnungCreate: AboRechnungsPositionBisAnzahlLieferungenCreate)(implicit + idPersister: Persister[AboId, _], + subject: Subject): Route = { onSuccess(entityStore ? StammdatenCommandHandler.CreateAnzahlLieferungenRechnungsPositionenCommand(subject.personId, rechnungCreate)) { case UserCommandFailed => complete(StatusCodes.BadRequest, s"Es konnten nicht alle Rechnungen für die gegebenen AboIds erstellt werden.") @@ -833,7 +846,8 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } } - private def createBisGuthabenRechnungsPositionen(rechnungCreate: AboRechnungsPositionBisGuthabenCreate)(implicit idPersister: Persister[AboId, _], subject: Subject): Route = { + private def createBisGuthabenRechnungsPositionen(rechnungCreate: AboRechnungsPositionBisGuthabenCreate)(implicit idPersister: Persister[AboId, _], + subject: Subject): Route = { onSuccess(entityStore ? StammdatenCommandHandler.CreateBisGuthabenRechnungsPositionenCommand(subject.personId, rechnungCreate)) { case UserCommandFailed => complete(StatusCodes.BadRequest, s"Es konnten nicht alle Rechnungen für die gegebenen AboIds erstellt werden.") @@ -910,13 +924,13 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } } }) ~ - (put | post)(uploadStored(vorlageType, Some(defaultFileTypeId(vorlageType))) { (id, metadata) => + (put | post) (uploadStored(vorlageType, Some(defaultFileTypeId(vorlageType))) { (id, metadata) => complete("Standardvorlage gespeichert") }) } ~ //Projektvorlagen path("vorlagen" / projektVorlageIdPath) { id => - (put | post)(update[ProjektVorlageModify, ProjektVorlageId](id)) ~ + (put | post) (update[ProjektVorlageModify, ProjektVorlageId](id)) ~ //TODO: remove from filestore as well delete(remove(id)) } ~ @@ -976,7 +990,8 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences post { extractRequest { request => entity(as[ZusatzabotypMailRequest]) { zusatzabotypMailRequest => - sendEmailsToZuzatzabotypSubscribers(zusatzabotypMailRequest.subject, zusatzabotypMailRequest.body, zusatzabotypMailRequest.replyTo, zusatzabotypMailRequest.ids) + sendEmailsToZuzatzabotypSubscribers(zusatzabotypMailRequest.subject, zusatzabotypMailRequest.body, zusatzabotypMailRequest.replyTo, + zusatzabotypMailRequest.ids) } } } @@ -1036,7 +1051,8 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } } - private def sendEmailsToZuzatzabotypSubscribers(emailSubject: String, body: String, replyTo: Option[String], ids: Seq[AbotypId])(implicit subject: Subject) = { + private def sendEmailsToZuzatzabotypSubscribers(emailSubject: String, body: String, replyTo: Option[String], ids: Seq[AbotypId])(implicit subject: Subject) + = { onSuccess((entityStore ? StammdatenCommandHandler.SendEmailToZusatzabotypSubscribersCommand(subject.personId, emailSubject, body, replyTo, ids))) { case UserCommandFailed => complete(StatusCodes.BadRequest, s"Something went wrong with the mail generation, please check the correctness of the template.") @@ -1078,16 +1094,16 @@ trait StammdatenRoutes extends BaseRouteService with ActorReferences } class DefaultStammdatenRoutes( - override val dbEvolutionActor: ActorRef, - override val entityStore: ActorRef, - override val eventStore: ActorRef, - override val mailService: ActorRef, - override val reportSystem: ActorRef, - override val sysConfig: SystemConfig, - override val system: ActorSystem, - override val airbrakeNotifier: ActorRef, - override val jobQueueService: ActorRef -) extends StammdatenRoutes + override val dbEvolutionActor: ActorRef, + override val entityStore: ActorRef, + override val eventStore: ActorRef, + override val mailService: ActorRef, + override val reportSystem: ActorRef, + override val sysConfig: SystemConfig, + override val system: ActorSystem, + override val airbrakeNotifier: ActorRef, + override val jobQueueService: ActorRef + ) extends StammdatenRoutes with DefaultStammdatenReadRepositoryAsyncComponent with DefaultBuchhaltungReadRepositoryAsyncComponent with DefaultFileStoreComponent { diff --git a/src/main/scala/ch/openolitor/stammdaten/models/KontoDaten.scala b/src/main/scala/ch/openolitor/stammdaten/models/KontoDaten.scala index 743258c26..6787ac1ff 100644 --- a/src/main/scala/ch/openolitor/stammdaten/models/KontoDaten.scala +++ b/src/main/scala/ch/openolitor/stammdaten/models/KontoDaten.scala @@ -1,3 +1,26 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.stammdaten.models import ch.openolitor.core.models._ diff --git a/src/main/scala/ch/openolitor/stammdaten/repositories/ProjektReadRepositorySync.scala b/src/main/scala/ch/openolitor/stammdaten/repositories/ProjektReadRepositorySync.scala new file mode 100644 index 000000000..cc52a54ee --- /dev/null +++ b/src/main/scala/ch/openolitor/stammdaten/repositories/ProjektReadRepositorySync.scala @@ -0,0 +1,36 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.stammdaten.repositories + +import ch.openolitor.stammdaten.models.Projekt +import scalikejdbc.DBSession + +trait ProjektReadRepositorySync { + def getProjekt(implicit session: DBSession): Option[Projekt] +} + +trait ProjektReadRepositorySyncImpl extends ProjektReadRepositorySync with StammdatenProjektRepositoryQueries { + def getProjekt(implicit session: DBSession): Option[Projekt] = { + getProjektQuery.apply() + } +} diff --git a/src/main/scala/ch/openolitor/stammdaten/repositories/StammdatenProjektRepositoryQueries.scala b/src/main/scala/ch/openolitor/stammdaten/repositories/StammdatenProjektRepositoryQueries.scala new file mode 100644 index 000000000..146dc80a2 --- /dev/null +++ b/src/main/scala/ch/openolitor/stammdaten/repositories/StammdatenProjektRepositoryQueries.scala @@ -0,0 +1,39 @@ +/* *\ +* ____ ____ ___ __ * +* / __ \____ ___ ____ / __ \/ (_) /_____ _____ * +* / / / / __ \/ _ \/ __ \/ / / / / / __/ __ \/ ___/ 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.stammdaten.repositories + +import ch.openolitor.stammdaten.StammdatenDBMappings + +import scalikejdbc._ + +trait StammdatenProjektRepositoryQueries extends StammdatenDBMappings { + lazy val projekt = projektMapping.syntax("projekt") + + protected def getProjektQuery = { + withSQL { + select + .from(projektMapping as projekt) + }.map(projektMapping(projekt)).single + } +} diff --git a/src/main/scala/ch/openolitor/stammdaten/repositories/StammdatenReadRepositorySync.scala b/src/main/scala/ch/openolitor/stammdaten/repositories/StammdatenReadRepositorySync.scala index b50337748..04bffb394 100644 --- a/src/main/scala/ch/openolitor/stammdaten/repositories/StammdatenReadRepositorySync.scala +++ b/src/main/scala/ch/openolitor/stammdaten/repositories/StammdatenReadRepositorySync.scala @@ -29,7 +29,7 @@ import org.joda.time.DateTime import org.joda.time.LocalDate import com.typesafe.scalalogging.LazyLogging -trait StammdatenReadRepositorySync extends BaseReadRepositorySync { +trait StammdatenReadRepositorySync extends BaseReadRepositorySync with ProjektReadRepositorySync { def getAbotypDetail(id: AbotypId)(implicit session: DBSession): Option[Abotyp] def getZusatzAbotypDetail(id: AbotypId)(implicit session: DBSession): Option[ZusatzAbotyp] def getAboDetail(id: AboId)(implicit session: DBSession): Option[AboDetail] @@ -45,8 +45,6 @@ trait StammdatenReadRepositorySync extends BaseReadRepositorySync { def getHauptAbo(id: AboId)(implicit session: DBSession): Option[HauptAbo] def getExistingZusatzAbotypen(lieferungId: LieferungId)(implicit session: DBSession): List[ZusatzAbotyp] def getAbotypById(id: AbotypId)(implicit session: DBSession): Option[IAbotyp] - def getProjekt(implicit session: DBSession): Option[Projekt] - @deprecated("Exists for compatibility purposes only", "OO 2.2 (Arbeitseinsatz)") def getProjektV1(implicit session: DBSession): Option[ProjektV1] def getKontoDatenProjekt(implicit session: DBSession): Option[KontoDaten] @@ -144,7 +142,7 @@ trait StammdatenReadRepositorySync extends BaseReadRepositorySync { def getAbo(id: AboId)(implicit session: DBSession): Option[Abo] } -trait StammdatenReadRepositorySyncImpl extends StammdatenReadRepositorySync with LazyLogging with StammdatenRepositoryQueries { +trait StammdatenReadRepositorySyncImpl extends StammdatenReadRepositorySync with LazyLogging with StammdatenRepositoryQueries with ProjektReadRepositorySyncImpl { def getAbotypById(id: AbotypId)(implicit session: DBSession): Option[IAbotyp] = { getById(abotypMapping, id) orElse getById(zusatzAbotypMapping, id) @@ -232,10 +230,6 @@ trait StammdatenReadRepositorySyncImpl extends StammdatenReadRepositorySync with getExistingZusatzAbotypenQuery(lieferungId).apply() } - def getProjekt(implicit session: DBSession): Option[Projekt] = { - getProjektQuery.apply() - } - @deprecated("Exists for compatibility purposes only", "OO 2.2 (Arbeitseinsatz)") def getProjektV1(implicit session: DBSession): Option[ProjektV1] = { getProjektV1Query.apply() diff --git a/src/main/scala/ch/openolitor/stammdaten/repositories/StammdatenRepositoryQueries.scala b/src/main/scala/ch/openolitor/stammdaten/repositories/StammdatenRepositoryQueries.scala index eb246c641..840746097 100644 --- a/src/main/scala/ch/openolitor/stammdaten/repositories/StammdatenRepositoryQueries.scala +++ b/src/main/scala/ch/openolitor/stammdaten/repositories/StammdatenRepositoryQueries.scala @@ -38,7 +38,7 @@ import scalikejdbc.jodatime.JodaParameterBinderFactory import scala.annotation.nowarn -trait StammdatenRepositoryQueries extends LazyLogging with StammdatenDBMappings with ArbeitseinsatzDBMappings { +trait StammdatenRepositoryQueries extends LazyLogging with StammdatenDBMappings with ArbeitseinsatzDBMappings with StammdatenProjektRepositoryQueries { lazy val aboTyp = abotypMapping.syntax("atyp") lazy val zusatzAboTyp = zusatzAbotypMapping.syntax("zatyp") @@ -68,7 +68,6 @@ trait StammdatenRepositoryQueries extends LazyLogging with StammdatenDBMappings lazy val produkt = produktMapping.syntax("produkt") lazy val produktekategorie = produktekategorieMapping.syntax("produktekategorie") lazy val produzent = produzentMapping.syntax("produzent") - lazy val projekt = projektMapping.syntax("projekt") @nowarn("cat=deprecation") lazy val projektV1 = projektV1Mapping.syntax("projektV1") lazy val kontoDaten = kontoDatenMapping.syntax("kontoDaten") @@ -1295,13 +1294,6 @@ trait StammdatenRepositoryQueries extends LazyLogging with StammdatenDBMappings }).list } - protected def getProjektQuery = { - withSQL { - select - .from(projektMapping as projekt) - }.map(projektMapping(projekt)).single - } - @deprecated("Exists for compatibility purposes only", "OO 2.2 (Arbeitseinsatz)") protected def getProjektV1Query = { withSQL { diff --git a/src/test/scala/ch/openolitor/mailtemplates/MailTemplateServiceSpec.scala b/src/test/scala/ch/openolitor/mailtemplates/MailTemplateServiceSpec.scala index 7e71feb12..22970a955 100644 --- a/src/test/scala/ch/openolitor/mailtemplates/MailTemplateServiceSpec.scala +++ b/src/test/scala/ch/openolitor/mailtemplates/MailTemplateServiceSpec.scala @@ -185,6 +185,4 @@ class MailTemplateServiceMock extends MailTemplateService with Mockito with Mail val mailTemplateReadRepositoryAsync: MailTemplateReadRepositoryAsync = mock[MailTemplateReadRepositoryAsync] val mailTemplateReadRepositorySync: MailTemplateReadRepositorySync = mock[MailTemplateReadRepositorySync] val sysConfig: SystemConfig = mock[SystemConfig] - - override lazy val config = ConfigFactory.parseString("""mailtemplates.max-file-store-resolve-timeout=1.day""") }