aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala
blob: b10f67a8fff35ec3f36ed20d22f70b22a11ea66c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package xyz.driver.pdsuidomain.entities

import java.time.LocalDateTime

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.core.{JsonGenerator, JsonParser}
import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.databind.annotation.{JsonDeserialize, JsonSerialize}
import xyz.driver.pdsuicommon.domain.{LongId, TextJson, User}
import xyz.driver.pdsuicommon.logging._
import xyz.driver.pdsuicommon.utils.Utils
import xyz.driver.pdsuicommon.validation.Validators
import xyz.driver.pdsuicommon.validation.Validators.Validator
import xyz.driver.pdsuidomain.entities.Document.Meta
import xyz.driver.pdsuicommon.compat.Implicits._

final case class ProviderType(id: LongId[ProviderType], name: String)

object ProviderType {
  implicit def toPhiString(x: ProviderType): PhiString = {
    import x._
    phi"ProviderType(id=$id, category=${Unsafe(name)})"
  }
}

final case class DocumentType(id: LongId[DocumentType], name: String)

object DocumentType {
  implicit def toPhiString(x: DocumentType): PhiString = {
    import x._
    phi"DocumentType(id=$id, name=${Unsafe(name)})"
  }
}

object Document {

  case class Meta(predicted: Option[Boolean], startPage: Double, endPage: Double) {

    /**
      * Return a regular meta: this meta is considered as not predicted
      */
    def confirmed: Meta = copy(predicted = predicted.map(_ => false))
  }

  class DocumentStatusSerializer extends JsonSerializer[Status] {
    def serialize(value: Status, gen: JsonGenerator, serializers: SerializerProvider): Unit = {
      gen.writeString(value.toString.toUpperCase)
    }
  }

  class DocumentStatusDeserializer extends JsonDeserializer[Status] {
    def deserialize(parser: JsonParser, context: DeserializationContext): Status = {
      val value = parser.getValueAsString
      Option(value).fold[Document.Status](Status.New /* Default document status */ ) { v =>
        Status.All.find(_.toString.toUpperCase == v) match {
          case None         => throw new RuntimeJsonMappingException(s"$v is not valid Document.Status")
          case Some(status) => status
        }
      }
    }
  }

  // Product with Serializable fixes issue:
  // Set(New, Organized) has type Set[Status with Product with Serializable]
  @JsonDeserialize(using = classOf[DocumentStatusDeserializer])
  @JsonSerialize(using = classOf[DocumentStatusSerializer])
  sealed trait Status extends Product with Serializable {

    def oneOf(xs: Status*): Boolean = xs.contains(this)

    def oneOf(xs: Set[Status]): Boolean = xs.contains(this)

  }
  object Status {
    case object New       extends Status
    case object Organized extends Status
    case object Extracted extends Status
    case object Done      extends Status
    case object Flagged   extends Status
    case object Archived  extends Status

    val All         = Set[Status](New, Organized, Extracted, Done, Flagged, Archived)
    val AllPrevious = Set[Status](Organized, Extracted)

    implicit def toPhiString(x: Status): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass))
  }

  implicit def toPhiString(x: Document): PhiString = {
    import x._
    phi"Document(id=$id, status=$status, assignee=$assignee, previousAssignee=$previousAssignee, recordId=$recordId)"
  }

  val validator: Validator[Document, Document] = { input =>
    for {
      typeId <- Validators.nonEmpty("typeId")(input.typeId)

      providerTypeId <- Validators.nonEmpty("providerTypeId")(input.providerTypeId)

      meta <- Validators.nonEmpty("meta")(input.meta)

      startDate <- Validators.nonEmpty("startDate")(input.startDate)

      isOrderRight <- input.endDate match {
                       case Some(endDate) if startDate.isAfter(endDate) =>
                         Validators.fail("The start date should be less, than the end one")

                       case _ => Validators.success(true)
                     }

      areDatesInThePast <- {
        val dates      = List(input.startDate, input.endDate).flatten
        val now        = LocalDateTime.now()
        val hasInvalid = dates.exists(_.isAfter(now))

        if (hasInvalid) Validators.fail("Dates should be in the past")
        else Validators.success(true)
      }
    } yield input
  }

}

@JsonIgnoreProperties(value = Array("valid"))
case class Document(id: LongId[Document] = LongId(0L),
                    status: Document.Status,
                    previousStatus: Option[Document.Status],
                    assignee: Option[LongId[User]],
                    previousAssignee: Option[LongId[User]],
                    recordId: LongId[MedicalRecord],
                    physician: Option[String],
                    typeId: Option[LongId[DocumentType]], // not null
                    providerName: Option[String], // not null
                    providerTypeId: Option[LongId[ProviderType]], // not null
                    meta: Option[TextJson[Meta]], // not null
                    startDate: Option[LocalDateTime], // not null
                    endDate: Option[LocalDateTime],
                    lastUpdate: LocalDateTime) {

  import Document.Status._

  if (previousStatus.nonEmpty) {
    assert(AllPrevious.contains(previousStatus.get), s"Previous status has invalid value: ${previousStatus.get}")
  }

  //  TODO: with the current business logic code this constraint sometimes harmful
  //  require(status match {
  //    case Document.Status.New if assignee.isDefined => false
  //    case Document.Status.Done if assignee.isDefined => false
  //    case _ => true
  //  }, "Assignee can't be defined in New or Done statuses")

}