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")
}