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.{LocalDate, 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 {
final 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 = LocalDate.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"))
final 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[LocalDate], // not null
endDate: Option[LocalDate],
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")
}
|