#33 use the latest luxmed api

This commit is contained in:
Eugene Zadyra
2022-06-01 12:49:41 +02:00
committed by Yevhen Zadyra
parent 389b84117d
commit 017be02ba0
84 changed files with 1414 additions and 1785 deletions

View File

@@ -1,7 +1,7 @@
dependencies {
compile project(':common')
compile group: "org.scalaj", name: "scalaj-http_2.12", version: "2.4.1"
compile group: "org.scalaj", name: "scalaj-http_2.12", version: "2.4.2"
compile group: "org.json4s", name: "json4s-jackson_2.12", version: "3.6.0-M3"
compile group: "com.softwaremill.quicklens", name: "quicklens_2.12", version: "1.4.12"
}

View File

@@ -1,26 +1,52 @@
package com.lbs.api
import com.lbs.api.http.Session
import com.lbs.api.http.headers._
import scalaj.http.{BaseHttp, HttpRequest}
import java.net.HttpCookie
object ApiHttp extends BaseHttp(
userAgent = "PatientPortal/4.20.5 (pl.luxmed.pp.LUX-MED; build:853; iOS 13.5.1) Alamofire/4.9.1"
userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
)
trait ApiBase {
private val CommonHeaders =
Map(
Host -> "portalpacjenta.luxmed.pl",
`Custom-User-Agent` -> "PatientPortal; 4.20.5; 4380E6AC-D291-4895-8B1B-F774C318BD7D; iOS; 14.5.1; iPhone8,1",
Accept -> "*/*",
Connection -> "keep-alive",
`Accept-Encoding` -> "gzip;q=1.0, compress;q=0.5",
`Accept-Language` -> "en;q=1.0, en-PL;q=0.9, pl-PL;q=0.8, ru-PL;q=0.7, uk-PL;q=0.6"
Origin -> "https://portalpacjenta.luxmed.pl",
`Custom-User-Agent` -> "Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
`User-Agent` -> "Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
Accept -> "application/json, text/plain, */*",
`Accept-Encoding` -> "gzip, deflate, br",
`Accept-Language` -> "pl;q=1.0, pl;q=0.9, en;q=0.8"
)
protected def http(url: String): HttpRequest = {
ApiHttp(s"https://portalpacjenta.luxmed.pl/PatientPortalMobileAPI/api/$url").headers(CommonHeaders)
protected def httpUnauthorized(url: String): HttpRequest = {
ApiHttp(s"https://portalpacjenta.luxmed.pl/PatientPortalMobileAPI/api/$url")
.headers(CommonHeaders)
}
protected def http(url: String, session: Session): HttpRequest = {
ApiHttp(s"https://portalpacjenta.luxmed.pl/PatientPortalMobileAPI/api/$url")
.headers(CommonHeaders)
.cookies(session.cookies)
.header(Authorization, s"${session.tokenType} ${session.accessToken}")
}
protected def httpNewApi(url: String, session: Session, cookiesMaybe: Option[Seq[HttpCookie]] = None): HttpRequest = {
val req = ApiHttp(s"https://portalpacjenta.luxmed.pl/PatientPortal/$url")
.headers(CommonHeaders)
.header(Authorization, session.accessToken)
cookiesMaybe.map(cookies => req.cookies(cookies)).getOrElse(req.cookies(session.cookies))
}
protected def httpNewApi(url: String, cookies: IndexedSeq[HttpCookie]): HttpRequest = {
ApiHttp(s"https://portalpacjenta.luxmed.pl/PatientPortal/$url")
.headers(CommonHeaders)
.cookies(cookies)
}
}

View File

@@ -1,25 +1,24 @@
package com.lbs.api
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import cats.implicits.toFunctorOps
import com.lbs.api.ApiResponseMutators._
import com.lbs.api.http._
import com.lbs.api.http.headers._
import com.lbs.api.json.JsonSerializer.extensions._
import com.lbs.api.json.model.{AvailableTermsResponse, ReservationFilterResponse, ReservedVisitsResponse, VisitsHistoryResponse, _}
import com.lbs.api.json.model.{EventsResponse, TermsIndexResponse, _}
import scalaj.http.{HttpRequest, HttpResponse}
import java.time.format.DateTimeFormatter
import java.time.{LocalDateTime, ZonedDateTime}
import scala.language.higherKinds
class LuxmedApi[F[_] : ThrowableMonad] extends ApiBase {
private val dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private val dateFormatNewPortal = DateTimeFormatter.ofPattern("yyyy-MM-dd")
private val dateFormatEvents = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ")
def login(username: String, password: String, clientId: String = "iPhone"): F[LoginResponse] = {
val request = http("token").
def login(username: String, password: String, clientId: String = "iPhone"): F[HttpResponse[LoginResponse]] = {
val request = httpUnauthorized("token").
header(`Content-Type`, "application/x-www-form-urlencoded").
header(`x-api-client-identifier`, clientId).
param("client_id", clientId).
@@ -29,160 +28,132 @@ class LuxmedApi[F[_] : ThrowableMonad] extends ApiBase {
post[LoginResponse](request)
}
def refreshToken(refreshToken: String, clientId: String = "iPhone"): F[LoginResponse] = {
val request = http("token").
header(`Content-Type`, "application/x-www-form-urlencoded").
header(`x-api-client-identifier`, clientId).
param("client_id", clientId).
param("grant_type", "refresh_token").
param("refresh_token", refreshToken)
post[LoginResponse](request)
def loginToApp(session: Session): F[HttpResponse[Unit]] = {
val request = httpNewApi("Account/LogInToApp?app=search&lang=pl&client=2&paymentSupported=true", session)
.header(Authorization, session.accessToken)
getVoid(request)
}
def reservedVisits(accessToken: String, tokenType: String, fromDate: ZonedDateTime = ZonedDateTime.now(),
toDate: ZonedDateTime = ZonedDateTime.now().plusMonths(3)): F[ReservedVisitsResponse] = {
val request = http("visits/reserved").
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken").
param("fromDate", dateFormat.format(fromDate)).
param("toDate", dateFormat.format(toDate))
get[ReservedVisitsResponse](request).mutate
def getForgeryToken(session: Session): F[HttpResponse[ForgeryTokenResponse]] = {
val request = httpNewApi("security/getforgerytoken", session)
get[ForgeryTokenResponse](request)
}
def visitsHistory(accessToken: String, tokenType: String, fromDate: ZonedDateTime = ZonedDateTime.now().minusYears(1),
toDate: ZonedDateTime = ZonedDateTime.now(), page: Int = 1, pageSize: Int = 100): F[VisitsHistoryResponse] = {
val request = http("visits/history").
def events(session: Session, fromDate: ZonedDateTime = ZonedDateTime.now().minusYears(1),
toDate: ZonedDateTime = ZonedDateTime.now()): F[EventsResponse] = {
val request = http("Events", session).
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken").
param("fromDate", dateFormat.format(fromDate)).
param("toDate", dateFormat.format(toDate)).
param("page", page.toString).
param("pageSize", pageSize.toString)
get[VisitsHistoryResponse](request).mutate
param("filter.filterDateFrom", dateFormatEvents.format(fromDate)).
param("filter.filterDateTo", dateFormatEvents.format(toDate))
get[EventsResponse](request).map(_.body)
}
def reservationFilter(accessToken: String, tokenType: String, fromDate: ZonedDateTime = ZonedDateTime.now(),
toDate: Option[ZonedDateTime] = None, cityId: Option[Long] = None, clinicId: Option[Long] = None,
serviceId: Option[Long] = None): F[ReservationFilterResponse] = {
val request = http("visits/available-terms/reservation-filter").
def dictionaryCities(session: Session): F[List[DictionaryCity]] = {
val request = httpNewApi("NewPortal/Dictionary/cities", session).
header(`Content-Type`, "application/json")
getList[DictionaryCity](request).map(_.body)
}
def dictionaryServiceVariants(session: Session): F[List[DictionaryServiceVariants]] = {
val request = httpNewApi("NewPortal/Dictionary/serviceVariantsGroups", session).
header(`Content-Type`, "application/json")
getList[DictionaryServiceVariants](request).map(_.body)
}
def dictionaryFacilitiesAndDoctors(session: Session, cityId: Option[Long], serviceVariantId: Option[Long]): F[FacilitiesAndDoctors] = {
val request = httpNewApi("NewPortal/Dictionary/facilitiesAndDoctors", session).
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken").
param("cityId", cityId.map(_.toString)).
param("clinicId", clinicId.map(_.toString)).
param("fromDate", dateFormat.format(fromDate)).
param("toDate", toDate.map(dateFormat.format)).
param("serviceId", serviceId.map(_.toString))
get[ReservationFilterResponse](request).mutate
param("serviceVariantId", serviceVariantId.map(_.toString))
get[FacilitiesAndDoctors](request).map(_.body)
}
def availableTerms(accessToken: String, tokenType: String, payerId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long, doctorId: Option[Long],
fromDate: ZonedDateTime = ZonedDateTime.now(), toDate: Option[ZonedDateTime] = None, timeOfDay: Int = 0,
languageId: Long = 10, findFirstFreeTerm: Boolean = false): F[AvailableTermsResponse] = {
val request = http("visits/available-terms").
def termsIndex(session: Session, cityId: Long, clinicId: Option[Long], serviceId: Long, doctorId: Option[Long],
fromDate: LocalDateTime = LocalDateTime.now(), toDate: LocalDateTime, languageId: Long = 10): F[TermsIndexResponse] = {
val request = httpNewApi("NewPortal/terms/index", session).
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken").
param("cityId", cityId.toString).
param("doctorId", doctorId.map(_.toString)).
param("findFirstFreeTerm", findFirstFreeTerm.toString).
param("fromDate", dateFormat.format(fromDate)).
param("serviceVariantId", serviceId.toString).
param("languageId", languageId.toString).
param("payerId", payerId.toString).
param("clinicId", clinicId.map(_.toString)).
param("serviceId", serviceId.toString).
param("timeOfDay", timeOfDay.toString).
param("toDate", dateFormat.format(toDate.getOrElse(fromDate.plusMonths(3))))
get[AvailableTermsResponse](request).mutate
param("searchDateFrom", dateFormatNewPortal.format(fromDate)).
param("searchDateTo", dateFormatNewPortal.format(toDate)).
param("searchDatePreset", 14.toString).
param("facilitiesIds", clinicId.map(_.toString)).
param("doctorsIds", doctorId.map(_.toString)).
param("nextSearch", false.toString).
param("searchByMedicalSpecialist", false.toString)
get[TermsIndexResponse](request).map(_.body)
}
def temporaryReservation(accessToken: String, tokenType: String, temporaryReservationRequest: TemporaryReservationRequest): F[TemporaryReservationResponse] = {
val request = http("visits/temporary-reservation").
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken")
post[TemporaryReservationResponse](request, bodyOpt = Some(temporaryReservationRequest))
def reservationLockterm(session: Session, xsrfToken: XsrfToken, reservationLocktermRequest: ReservationLocktermRequest): F[ReservationLocktermResponse] = {
val request = httpNewApi("NewPortal/reservation/lockterm", session, Some(session.cookies ++ xsrfToken.cookies)).
header(`Content-Type`, "application/json")
.header(`xsrf-token`, xsrfToken.token)
post[ReservationLocktermResponse](request, bodyOpt = Some(reservationLocktermRequest)).map(_.body)
}
def deleteTemporaryReservation(accessToken: String, tokenType: String, temporaryReservationId: Long): F[HttpResponse[String]] = {
val request = http(s"visits/temporary-reservation/$temporaryReservationId").
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken")
def deleteTemporaryReservation(session: Session, xsrfToken: XsrfToken, temporaryReservationId: Long): F[Unit] = {
val request = httpNewApi(s"NewPortal/reservation/releaseterm?reservationId=$temporaryReservationId", session, Some(session.cookies ++ xsrfToken.cookies)).
header(`Content-Type`, "application/json")
.header(`xsrf-token`, xsrfToken.token)
postVoid(request, bodyOpt = Some(Empty()))
}
def reservationConfirm(session: Session, xsrfToken: XsrfToken, reservationConfirmRequest: ReservationConfirmRequest): F[ReservationConfirmResponse] = {
val request = httpNewApi("NewPortal/reservation/confirm", session, Some(session.cookies ++ xsrfToken.cookies)).
header(`Content-Type`, "application/json")
.header(`xsrf-token`, xsrfToken.token)
post[ReservationConfirmResponse](request, bodyOpt = Some(reservationConfirmRequest)).map(_.body)
}
def reservationChangeTerm(session: Session, xsrfToken: XsrfToken, reservationChangetermRequest: ReservationChangetermRequest): F[ReservationConfirmResponse] = {
val request = httpNewApi("NewPortal/reservation/changeterm", session, Some(session.cookies ++ xsrfToken.cookies)).
header(`Content-Type`, "application/json")
.header(`xsrf-token`, xsrfToken.token)
post[ReservationConfirmResponse](request, bodyOpt = Some(reservationChangetermRequest)).map(_.body)
}
def reservationDelete(session: Session, reservationId: Long): F[HttpResponse[String]] = {
val request = http(s"events/Visit/$reservationId", session).
header(`Content-Type`, "application/json")
delete(request)
}
def valuations(accessToken: String, tokenType: String, valuationsRequest: ValuationsRequest): F[ValuationsResponse] = {
val request = http("visits/available-terms/valuations").
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken")
post[ValuationsResponse](request, bodyOpt = Some(valuationsRequest))
private def get[T <: SerializableJsonObject](request: HttpRequest)(implicit mf: scala.reflect.Manifest[T]): F[HttpResponse[T]] = {
request.invoke.map(r => r.copy(body = r.body.as[T]))
}
def reservation(accessToken: String, tokenType: String, reservationRequest: ReservationRequest): F[ReservationResponse] = {
val request = http("visits/reserved").
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken")
post[ReservationResponse](request, bodyOpt = Some(reservationRequest))
private def getList[T <: SerializableJsonObject](request: HttpRequest)(implicit mf: scala.reflect.Manifest[T]): F[HttpResponse[List[T]]] = {
request.invoke.map(r => r.copy(body = r.body.asList[T]))
}
def deleteReservation(accessToken: String, tokenType: String, reservationId: Long): F[HttpResponse[String]] = {
val request = http(s"visits/reserved/$reservationId").
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken")
delete(request)
private def getVoid[T <: SerializableJsonObject](request: HttpRequest)(implicit mf: scala.reflect.Manifest[T]): F[HttpResponse[Unit]] = {
request.invoke.map(r => r.copy(body = {}))
}
//204 means OK?
def canTermBeChanged(accessToken: String, tokenType: String, reservationId: Long): F[HttpResponse[String]] = {
val request = http(s"visits/reserved/$reservationId/can-term-be-changed").
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken")
request.invoke
}
def detailToChangeTerm(accessToken: String, tokenType: String, reservationId: Long): F[ChangeTermDetailsResponse] = {
val request = http(s"visits/reserved/$reservationId/details-to-change-term").
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken")
get[ChangeTermDetailsResponse](request)
}
def temporaryReservationToChangeTerm(accessToken: String, tokenType: String, reservationId: Long, temporaryReservationRequest: TemporaryReservationRequest): F[TemporaryReservationResponse] = {
val request = http(s"visits/reserved/$reservationId/temporary-reservation-to-change-term").
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken")
post[TemporaryReservationResponse](request, bodyOpt = Some(temporaryReservationRequest))
}
def valuationToChangeTerm(accessToken: String, tokenType: String, reservationId: Long, valuationsRequest: ValuationsRequest): F[ValuationsResponse] = {
val request = http(s"visits/reserved/$reservationId/valuations-to-change-term").
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken")
post[ValuationsResponse](request, bodyOpt = Some(valuationsRequest))
}
def changeTerm(accessToken: String, tokenType: String, reservationId: Long, reservationRequest: ReservationRequest): F[ChangeTermResponse] = {
val request = http(s"visits/reserved/$reservationId/term").
header(`Content-Type`, "application/json").
header(Authorization, s"$tokenType $accessToken")
put[ChangeTermResponse](request, bodyOpt = Some(reservationRequest))
}
private def get[T <: SerializableJsonObject](request: HttpRequest)(implicit mf: scala.reflect.Manifest[T]): F[T] = {
request.invoke.map(_.body.as[T])
}
private def post[T <: SerializableJsonObject](request: HttpRequest, bodyOpt: Option[SerializableJsonObject] = None)(implicit mf: scala.reflect.Manifest[T]): F[T] = {
private def post[T <: SerializableJsonObject](request: HttpRequest, bodyOpt: Option[SerializableJsonObject] = None)(implicit mf: scala.reflect.Manifest[T]): F[HttpResponse[T]] = {
val postRequest = bodyOpt match {
case Some(body) => request.postData(body.asJson)
case None => request.postForm
}
postRequest.invoke.map(_.body.as[T])
postRequest.invoke.map(r => r.copy(body = r.body.as[T]))
}
private def put[T <: SerializableJsonObject](request: HttpRequest, bodyOpt: Option[SerializableJsonObject] = None)(implicit mf: scala.reflect.Manifest[T]): F[T] = {
private def put[T <: SerializableJsonObject](request: HttpRequest, bodyOpt: Option[SerializableJsonObject] = None)(implicit mf: scala.reflect.Manifest[T]): F[HttpResponse[T]] = {
val putRequest = bodyOpt match {
case Some(body) => request.put(body.asJson)
case None => request.method("PUT")
}
putRequest.invoke.map(_.body.as[T])
putRequest.invoke.map(r => r.copy(body = r.body.as[T]))
}
private def postVoid(request: HttpRequest, bodyOpt: Option[SerializableJsonObject] = None): F[Unit] = {
val postRequest = bodyOpt match {
case Some(body) => request.postData(body.asJson)
case None => request.postForm
}
postRequest.invoke.void
}
private def delete(request: HttpRequest): F[HttpResponse[String]] = {

View File

@@ -1,4 +0,0 @@
package com.lbs.api.exception
class ServiceIsAlreadyBookedException extends ApiException("You have already booked this service")

View File

@@ -9,33 +9,31 @@ import com.lbs.api.json.model._
import com.lbs.common.Logger
import scalaj.http.{HttpRequest, HttpResponse}
import java.net.HttpCookie
import scala.language.higherKinds
import scala.util.{Failure, Success, Try}
package object http extends Logger {
case class Session(accessToken: String, tokenType: String, cookies: Seq[HttpCookie])
object headers {
val `Content-Type` = "Content-Type"
val `xsrf-token` = "xsrf-token"
val Host = "Host"
val Origin = "Origin"
val Accept = "Accept"
val Connection = "Connection"
val `Accept-Encoding` = "Accept-Encoding"
val `User-Agent` = "User-Agent"
val `Custom-User-Agent` = "Custom-User-Agent"
val `x-api-client-identifier` = "x-api-client-identifier"
val `Accept-Language` = "Accept-Language"
val `Accept-Language` = "accept-language"
val Authorization = "Authorization"
}
private val SensitiveHeaders = List("passw", "access_token", "refresh_token", "authorization")
implicit class HttpResponseWithJsonDeserializationSupport(httpResponse: HttpResponse[String]) {
def asEntity[T <: SerializableJsonObject](implicit mf: scala.reflect.Manifest[T]): HttpResponse[T] = {
httpResponse.copy(body = httpResponse.body.as[T])
}
}
implicit class ExtendedHttpRequest[F[_] : ThrowableMonad](httpRequest: HttpRequest) {
def invoke: F[HttpResponse[String]] = {
val me = MonadError[F, Throwable]
@@ -65,8 +63,6 @@ package object http extends Logger {
val errorMessage = message.toLowerCase
if (errorMessage.contains("invalid login or password"))
new InvalidLoginOrPasswordException
else if (errorMessage.contains("already booked this service"))
new ServiceIsAlreadyBookedException
else if (errorMessage.contains("session has expired"))
new SessionExpiredException
else
@@ -77,7 +73,6 @@ package object http extends Logger {
val body = httpResponse.body
val code = httpResponse.code
Try(body.as[LuxmedErrorsMap])
.orElse(Try(body.as[LuxmedErrorsList]))
.orElse(Try(body.as[LuxmedError]))
.map(error => luxmedErrorToApiException(code, error))
.toOption

View File

@@ -1,31 +1,48 @@
package com.lbs.api.json
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import com.lbs.api.json.model.SerializableJsonObject
import com.lbs.common.Logger
import org.json4s._
import org.json4s.jackson.JsonMethods._
import java.time.format.DateTimeFormatter
import java.time.{LocalDateTime, LocalTime, ZonedDateTime}
object JsonSerializer {
private val localDateTimeSerializer = new CustomSerializer[ZonedDateTime](_ => ( {
object JsonSerializer extends Logger {
private val zonedDateTimeSerializer = new CustomSerializer[ZonedDateTime](_ => ( {
case JString(str) => ZonedDateTime.parse(str, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
}, {
case zonedDateTime: ZonedDateTime => JString(zonedDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
}
))
private implicit val formats: Formats = DefaultFormats.withStrictArrayExtraction + localDateTimeSerializer
private val localTimeSerializer = new CustomSerializer[LocalTime](_ => ( {
case JString(str) => LocalTime.parse(str)
}, {
case localTime: LocalTime => JString(localTime.toString)
}
))
def extract[T <: SerializableJsonObject](jsonString: String)(implicit mf: scala.reflect.Manifest[T]): T = {
private val localDateTimeSerializer = new CustomSerializer[LocalDateTime](_ => ( {
case JString(str) => LocalDateTime.parse(str)
}, {
case localTime: LocalDateTime => JString(localTime.toString)
}
))
private implicit val formats: Formats = DefaultFormats.withStrictArrayExtraction + zonedDateTimeSerializer + localTimeSerializer + localDateTimeSerializer
def extract[T](jsonString: String)(implicit mf: scala.reflect.Manifest[T]): T = {
parse(jsonString).camelizeKeys.extract[T]
}
def write[T <: SerializableJsonObject](jsonObject: T): String = {
pretty(render(Extraction.decompose(jsonObject).pascalizeKeys))
def write[T](jsonObject: T): String = {
val json = pretty(render(Extraction.decompose(jsonObject)))
info(json)
json
}
object extensions {
@@ -34,6 +51,10 @@ object JsonSerializer {
def as[T <: SerializableJsonObject](implicit mf: scala.reflect.Manifest[T]): T = {
extract[T](jsonString)
}
def asList[T <: SerializableJsonObject](implicit mf: scala.reflect.Manifest[T]): List[T] = {
extract[List[T]](jsonString)
}
}
implicit class JsonObjectToString[T <: SerializableJsonObject](jsonObject: T) {

View File

@@ -1,64 +0,0 @@
package com.lbs.api.json.model
/**
*
{
"AvailableVisitsTermPresentation": [
{
"Clinic": {
"Id": 6,
"Name": "LX Wrocław - Szewska 3A"
},
"Doctor": {
"Id": 38275,
"Name": "lek. med. ANNA ABRAMCZYK"
},
"Impediment": {
"ImpedimentText": "",
"IsImpediment": false
},
"IsFree": false,
"PayerDetailsList": [
{
"BrandId": 2,
"ContractId": 1111111,
"PayerId": 22222,
"PayerName": "FIRMA POLAND SP. Z O.O.",
"ProductElementId": 3333333,
"ProductId": 44444,
"ProductInContractId": 555555,
"ServaAppId": 0,
"ServaId": 6666
},
{
"BrandId": 2,
"ContractId": 1111111,
"PayerId": 22222,
"PayerName": "FIRMA POLAND SP. Z O.O.",
"ProductElementId": 8547135,
"ProductId": 44444,
"ProductInContractId": 555555,
"ServaAppId": 1,
"ServaId": 6666
}
],
"ReferralRequiredByProduct": false,
"ReferralRequiredByService": false,
"RoomId": 543,
"ScheduleId": 3331908,
"ServiceId": 6666,
"VisitDate": {
"FormattedDate": "26th April, Thu. at 12:40 pm",
"StartDateTime": "2018-02-23T11:30:00+02:00"
}
}
]
}
*/
case class AvailableTermsResponse(availableVisitsTermPresentation: List[AvailableVisitsTermPresentation]) extends SerializableJsonObject
case class AvailableVisitsTermPresentation(clinic: IdName, doctor: IdName, payerDetailsList: List[PayerDetails],
referralRequiredByProduct: Boolean, referralRequiredByService: Boolean,
roomId: Long, scheduleId: Long, serviceId: Long, visitDate: VisitDate) extends SerializableJsonObject

View File

@@ -1,23 +0,0 @@
package com.lbs.api.json.model
/**
{
"AvailableNewPayers": [
{
"Id": 45185,
"IsFeeForService": false,
"Name": "Moja firma SP. Z O.O."
}
],
"CityId": 5,
"Payer": {
"Id": 45185,
"IsFeeForService": false,
"Name": "Moja firma SP. Z O.O."
}
}
*/
case class ChangeTermDetailsResponse(availableNewPayers: ShortPayerDetails, cityId: Long, payer: ShortPayerDetails) extends SerializableJsonObject
case class ShortPayerDetails(id: Long, isFeeForService: Boolean, name: String) extends SerializableJsonObject

View File

@@ -1,11 +0,0 @@
package com.lbs.api.json.model
/**
{
"PreparationInfo": {
"IsPreparationRequired": true
}
}
*/
case class ChangeTermResponse(preparationInfo: PreparationInfo) extends SerializableJsonObject

View File

@@ -0,0 +1,21 @@
package com.lbs.api.json.model
/**
[
{
"id": 70,
"name": "Białystok"
},
{
"id": 12,
"name": "Bielsk Podlaski"
},
{
"id": 100,
"name": "Bielsko-Biała"
}
]
*
*/
case class DictionaryCity(override val id: Long, override val name: String) extends Identified with SerializableJsonObject

View File

@@ -0,0 +1,175 @@
package com.lbs.api.json.model
/**
[
{
"actionCode": "",
"children": [
{
"actionCode": null,
"children": [],
"expanded": false,
"id": 4502,
"isTelemedicine": false,
"name": "Consultation with a general practitioner",
"paymentType": 2,
"type": 0
},
{
"actionCode": null,
"children": [],
"expanded": false,
"id": 4480,
"isTelemedicine": false,
"name": "Gynaecological consultation",
"paymentType": 2,
"type": 0
}
],
"expanded": true,
"id": 2,
"isTelemedicine": false,
"name": "Most popular",
"paymentType": 0,
"type": 2
},
{
"actionCode": "",
"children": [
{
"actionCode": null,
"children": [],
"expanded": false,
"id": 4387,
"isTelemedicine": false,
"name": "Allergologist consultation",
"paymentType": 2,
"type": 0
}
],
"expanded": true,
"id": 1,
"isTelemedicine": false,
"name": "On-site consultations",
"paymentType": 0,
"type": 1
},
{
"actionCode": "",
"children": [
{
"actionCode": null,
"children": [],
"expanded": false,
"id": 13764,
"isTelemedicine": true,
"name": "Telephone consultation - Allergist",
"paymentType": 2,
"type": 0
},
{
"actionCode": null,
"children": [],
"expanded": false,
"id": 13775,
"isTelemedicine": true,
"name": "Telephone consultation - Cardiologist",
"paymentType": 2,
"type": 0
},
{
"actionCode": null,
"children": [],
"expanded": false,
"id": 13800,
"isTelemedicine": true,
"name": "Telephone consultation - Dentist",
"paymentType": 2,
"type": 0
},
{
"actionCode": null,
"children": [],
"expanded": false,
"id": 13766,
"isTelemedicine": true,
"name": "Telephone consultation - Dermatologist",
"paymentType": 2,
"type": 0
}
],
"expanded": false,
"id": 13,
"isTelemedicine": false,
"name": "Telephone consultations",
"paymentType": 0,
"type": 1
},
{
"actionCode": "",
"children": [
{
"actionCode": null,
"children": [],
"expanded": false,
"id": 8904,
"isTelemedicine": false,
"name": "Arranging an appointment with a dental surgeon",
"paymentType": 2,
"type": 0
},
{
"actionCode": null,
"children": [],
"expanded": false,
"id": 6817,
"isTelemedicine": false,
"name": "Arranging an appointment with a dental hygienist",
"paymentType": 2,
"type": 0
},
{
"actionCode": null,
"children": [],
"expanded": false,
"id": 6621,
"isTelemedicine": false,
"name": "Arranging an appointment with a dentist",
"paymentType": 2,
"type": 0
}
],
"expanded": false,
"id": 4,
"isTelemedicine": false,
"name": "Dentist",
"paymentType": 0,
"type": 1
},
{
"actionCode": "WIZYTY_DOMOWE",
"children": [],
"expanded": false,
"id": 14,
"isTelemedicine": false,
"name": "Home visits",
"paymentType": 0,
"type": 0
},
{
"actionCode": "NFZ",
"children": [],
"expanded": false,
"id": 12,
"isTelemedicine": false,
"name": "NHF visits",
"paymentType": 0,
"type": 0
}
]
*
*/
case class DictionaryServiceVariants(override val id: Long, override val name: String, expanded: Boolean, children: List[DictionaryServiceVariants], isTelemedicine: Boolean, paymentType: Long) extends Identified with SerializableJsonObject {
def flatten: List[DictionaryServiceVariants] = List(this) ::: children.flatMap(_.flatten)
}

View File

@@ -0,0 +1,19 @@
package com.lbs.api.json.model
/**
* {
"academicTitle": "dr n. med.",
"facilityGroupIds": [
78
],
"firstName": "TARAS",
"id": 11111,
"isEnglishSpeaker": true,
"lastName": "SHEVCHENKO"
}
*/
case class Doctor(academicTitle: String, facilityGroupIds: Option[List[Long]], firstName: String, isEnglishSpeaker: Option[Boolean],
genderId: Option[Long], id: Long, lastName: String) extends Identified {
override def name: String = firstName + " " + lastName
}

View File

@@ -0,0 +1,7 @@
package com.lbs.api.json.model
/**
{}
*/
case class Empty() extends SerializableJsonObject

View File

@@ -0,0 +1,109 @@
package com.lbs.api.json.model
import java.time.ZonedDateTime
/**
* {
* "DataAvailableFrom": "2016-09-01T00:00:00+02:00",
* "Events": [
* {
*"AutoConfirmationInfo": {
*"Message": null,
*"Type": "None"
*},
*"Clinic": {
*"Address": "WOŁOWSKA 20",
*"City": "WROCŁAW",
*"Id": 42,
*"Name": "LX Wrocław - Wołowska 20"
*},
*"ConfirmationInfo": null,
*"Date": "2021-06-02T07:45:00+02:00",
*"DateTo": "2021-06-02T08:15:00+02:00",
*"Doctor": {
*"Id": 111111,
*"Lastname": "SHEVCHENKO",
*"Name": "TARAS",
*"Sex": "Male",
*"Title": "lek. med."
*},
*"DownloadLinks": [],
*"EventId": 2222222,
*"EventType": "Visit",
*"FromEreferral": false,
*"HasImpediments": false,
*"HasQuestionnaireBeforeService": false,
*"IsOverbooked": false,
*"IsPaymentRequired": false,
*"IsPreparationRequired": false,
*"IsServiceWithOverbookingRegularDistribution": true,
*"Links": [
*{
*"ApiVersion": 1,
*"Href": "/PatientPortalMobileAPI/api/events/reservation/Visit/2222222/detail",
*"Method": "GET",
*"Rel": "events_detail"
*},
*{
*"ApiVersion": 1,
*"Href": "/PatientPortalMobileAPI/api/events/Visit/2222222",
*"Method": "DELETE",
*"Rel": "delete_reservation"
*},
*{
*"ApiVersion": 1,
*"Href": "/PatientPortalMobileAPI/api/visits/reserved/2222222/can-term-be-changed",
*"Method": "GET",
*"Rel": "get_can_term_be_changed"
*}
*],
*"OnlinePaymentType": "Possible",
*"PaymentState": "None",
*"ReferralType": "None",
*"Status": "Reserved",
*"Title": "Internista",
*"Type": "Timeline_Visit_ReservedVisit"
*},
*{
*"Date": "2021-03-27T16:45:00+02:00",
*"DateTo": "2021-03-27T17:15:00+02:00",
*"Doctor": {
*"Id": 11111,
*"Lastname": "LESJA",
*"Name": "UKRAINKA",
*"Sex": "Female",
*"Title": "lek. med."
*},
*"DownloadLinks": [],
*"EventId": 3333333,
*"EventType": "Telemedicine",
*"HasPrescription": false,
*"HasRecommendations": true,
*"HasReferrals": false,
*"IsServiceWithOverbookingRegularDistribution": true,
*"Links": [
*{
*"ApiVersion": 1,
*"Href": "/PatientPortalMobileAPI/api/events/reservation/Telemedicine/3333333/detail",
*"Method": "GET",
*"Rel": "events_detail"
*}
*],
*"ReferralType": "None",
*"Status": "Realized",
*"Title": "Centrum Leczenia Infekcji - konsultacja telefoniczna",
*"Type": "Timeline_Telemedicine_RealizedTelemedicineVisit"
*}
*],
*"IsEndOfList": false,
*"ServerDateTime": "2021-07-01T14:32:00+02:00"
*}
*/
case class EventsResponse(events: List[Event]) extends SerializableJsonObject
case class Event(date: ZonedDateTime, clinic: Option[EventClinic], doctor: EventDoctor, eventId: Long, status: String, title: String)
case class EventClinic(address: String, city: String)
case class EventDoctor(lastname: String, name: String)

View File

@@ -0,0 +1,42 @@
package com.lbs.api.json.model
/**
{
"doctors": [
{
"academicTitle": "dr n. med.",
"facilityGroupIds": [
78
],
"firstName": "TARAS",
"id": 111111,
"isEnglishSpeaker": true,
"lastName": "SHEVCHENKO"
},
{
"academicTitle": "lek. med.",
"facilityGroupIds": [
78,
127
],
"firstName": "VLADIMIR",
"id": 22222,
"isEnglishSpeaker": false,
"lastName": "ZELENSKIY"
}
],
"facilities": [
{
"id": 78,
"name": "ul. Fabryczna 6"
},
{
"id": 127,
"name": "ul. Kwidzyńska 6"
}
]
}
*
*/
case class FacilitiesAndDoctors(doctors: List[Doctor], facilities: List[IdName]) extends SerializableJsonObject

View File

@@ -0,0 +1,9 @@
package com.lbs.api.json.model
/**
* {
* "token": "IDtjG_ECOd_ETYE2fwrCoTcC6bW935cn_nUh6d3BaEa-jvPlHfPLOY5AkF",
* }
*/
case class ForgeryTokenResponse(token: String) extends SerializableJsonObject

View File

@@ -1,11 +1,16 @@
package com.lbs.api.json.model
trait Identified {
def id: Long
def name: String
def toIdName: IdName = IdName(id, name)
}
case class IdName(id: Long, name: String) extends Identified {
def optionalId: Option[Long] = Option(id).filterNot(_ == -1L)
}
object IdName {
def from(id: java.lang.Long, name: String): IdName = new IdName(if (id != null) id else -1, name)
}
case class IdName(id: Long, name: String) {
def optionalId: Option[Long] = Option(id).filterNot(_ == -1L)
}

View File

@@ -1,8 +0,0 @@
package com.lbs.api.json.model
case class LuxmedErrorsList(errors: List[LuxmedErrorsListElement]) extends SerializableJsonObject with LuxmedBaseError {
override def message: String = errors.map(_.message).mkString("; ")
}
case class LuxmedErrorsListElement(errorCode: Int, message: String, additionalData: Map[String, String]) extends SerializableJsonObject

View File

@@ -0,0 +1,43 @@
package com.lbs.api.json.model
import java.time.LocalTime
/**
* {
* "existingReservationId": 987654321,
*"term": {
*"date": "2021-07-03T05:30:00.000Z",
*"doctorId": 22222,
*"eReferralId": null,
*"facilityId": 33333,
*"parentReservationId": 987654321,
*"referralId": null,
*"referralRequired": false,
*"roomId": 55555,
*"scheduleId": 666666,
*"serviceVariantId": 777777,
*"temporaryReservationId": 8888888,
*"timeFrom": "08:30",
*"valuation": {
*"alternativePrice": null,
*"contractId": 99999,
*"isExternalReferralAllowed": false,
*"isReferralRequired": false,
*"payerId": 9111111,
*"price": 0,
*"productElementId": 9222222,
*"productId": 93333333,
*"productInContractId": 9444444,
*"requireReferralForPP": false,
*"valuationType": 1
*},
*"valuationId": null
*}
*}
*/
case class ReservationChangetermRequest(existingReservationId: Long, term: NewTerm) extends SerializableJsonObject
case class NewTerm(date: String, doctorId: Long, facilityId: Long, parentReservationId: Long, referralRequired: Boolean, roomId: Long,
scheduleId: Long, serviceVariantId: Long, temporaryReservationId: Long, timeFrom: LocalTime,
valuation: Valuation) extends SerializableJsonObject

View File

@@ -0,0 +1,38 @@
package com.lbs.api.json.model
import java.time.LocalTime
/**
* {
* "date": "2021-05-19T08:00:00.000Z",
*"doctorId": 111111,
*"eReferralId": null,
*"facilityId": 2222,
*"parentReservationId": null,
*"referralId": null,
*"referralRequired": false,
*"roomId": 1248,
*"scheduleId": 333333,
*"serviceVariantId": 444444,
*"temporaryReservationId": 4111111,
*"timeFrom": "18:45",
*"valuation": {
*"alternativePrice": null,
*"contractId": 555555,
*"isExternalReferralAllowed": false,
*"isReferralRequired": false, false,
*"payerId": 66666,
*"price": 0.0,
*"productElementId": 7777777,
*"productId": 888888,
*"productInContractId": 9999999,
*"requireReferralForPP": false,
*"valuationType": 1
*},
*"valuationId": null
*}
*/
case class ReservationConfirmRequest(date: String, doctorId: Long, facilityId: Long, roomId: Long, scheduleId: Long, serviceVariantId: Long,
temporaryReservationId: Long, timeFrom: LocalTime, valuation: Valuation) extends SerializableJsonObject

View File

@@ -0,0 +1,25 @@
package com.lbs.api.json.model
/**
{
"errors": [],
"hasErrors": false,
"hasWarnings": false,
"value": {
"canSelfConfirm": false,
"eventType": 1,
"isTelemedicine": false,
"npsToken": "babababa-9282-1662-a525-ababbabaa",
"reservationId": 2222222,
"serviceInstanceId": 33333333
},
"warnings": []
}
*/
case class ReservationConfirmResponse(errors: List[String], warnings: List[String], hasErrors: Boolean, hasWarnings: Boolean,
value: ReservationConfirmValue) extends SerializableJsonObject
case class ReservationConfirmValue(canSelfConfirm: Boolean, eventType: Long, isTelemedicine: Boolean, npsToken: String,
reservationId: Long, serviceInstanceId: Long) extends SerializableJsonObject

View File

@@ -0,0 +1,42 @@
package com.lbs.api.json.model
/**
* {
* "correlationId": "00000000-0000-0000-0000-000000000000",
*"date": "2021-05-19T08:00:00.000Z",
*"doctor": {
*"academicTitle": "dr n.med.",
*"firstName": "TARAS",
*"id": 11111,
*"lastName": "SHEVCHENKO"
*},
*"doctorId": 22222,
*"eReferralId": null,
*"facilityId": 33,
*"facilityName": "Telephone consultation",
*"impedimentText": "",
*"isAdditional": false,
*"isImpediment": false,
*"isPreparationRequired": false,
*"isTelemedicine": true,
*"parentReservationId": null,
*"preparationItems": [],
*"referralId": null,
*"referralTypeId": null,
*"roomId": 3333,
*"scheduleId": 444444,
*"serviceVariantId": 555555,
*"serviceVariantName": "Telephone consultation - General practitioner",
*"timeFrom": "12:00",
*"timeTo": "12:15"
*}
*/
case class ReservationLocktermRequest(date: String, doctor: Doctor, doctorId: Long, eReferralId: String = null,
facilityId: Long,
impedimentText:String, isAdditional: Boolean, isImpediment: Boolean,
isPreparationRequired: Boolean, isTelemedicine: Boolean, parentReservationId: String = null,
preparationItems: List[PreparationItem], referralId: String = null, referralTypeId: String = null,
roomId: Long, scheduleId: Long, serviceVariantId: Long,
timeFrom: String, timeTo: String) extends SerializableJsonObject

View File

@@ -0,0 +1,74 @@
package com.lbs.api.json.model
import java.time.LocalTime
/**
* {
* "errors": [],
* "hasErrors": false,
* "hasWarnings": false,
*"value": {
*"askForReferral": false,
*"changeTermAvailable": true,
*"conflictedVisit": null,
*"doctorDetails": {
*"academicTitle": "lek. med.",
*"firstName": "TARAS",
*"genderId": 1,
*"id": 11111,
*"lastName": "SHEVCHENKO"
*},
*"isBloodExamination": false,
*"isStomatology": false,
*"relatedVisits": [
*{
*"date": "2021-06-03T05:20:00",
*"doctor": {
*"academicTitle": "lek. med.",
*"firstName": "LESYA",
*"genderId": 2,
*"id": 0,
*"lastName": "UKRAINKA"
*},
*"facilityName": "LX Wrocław - Szewska 3A",
*"isAsdk": false,
*"isTelemedicine": false,
*"payerName": null,
*"reservationId": 333333,
*"serviceInstanceId": 9999999,
*"serviceVariantId": 111111,
*"serviceVariantName": "Consultation with a general practitioner",
*"timeFrom": "07:30:00",
*"timeTo": "07:45:00"
*}
*],
*"temporaryReservationId": 222222,
*"valuations": [
*{
*"alternativePrice": null,
*"contractId": 333333,
*"isExternalReferralAllowed": false,
*"isReferralRequired": false,
*"payerId": 44444,
*"price": 0.0,
*"productElementId": 555555,
*"productId": 666666,
*"productInContractId": 777777,
*"requireReferralForPP": false,
*"valuationType": 1
*}
*]
*},
*"warnings": []
*}
*/
case class ReservationLocktermResponse(errors: List[String], warnings: List[String], hasErrors: Boolean, hasWarnings: Boolean,
value: ReservationLocktermResponseValue) extends SerializableJsonObject
case class ReservationLocktermResponseValue(changeTermAvailable: Boolean, conflictedVisit: Option[String], doctorDetails: Doctor,
relatedVisits: List[RelatedVisit], temporaryReservationId: Long, valuations: List[Valuation]) extends SerializableJsonObject
case class RelatedVisit(doctor: Doctor, facilityName: String, isTelemedicine: Boolean, reservationId: Long,
timeFrom: LocalTime, timeTo: LocalTime)

View File

@@ -1,28 +0,0 @@
package com.lbs.api.json.model
import java.time.ZonedDateTime
/**
{
"ClinicId": 6,
"DoctorId": 38509,
"PayerData": {
"ContractId": 1111111,
"PayerId": 22222,
"PayerName": "FIRMA POLAND SP. Z O.O.",
"ProductElementId": 8547100,
"ProductId": 44444,
"ProductInContractId": 555555,
"ServaAppId": 0,
"ServaId": 6621
},
"RoomId": 159,
"ServiceId": 6621,
"StartDateTime": "2018-06-04T11:00:00+02:00",
"TemporaryReservationId": 250303839
}
*/
case class ReservationRequest(clinicId: Long, doctorId: Long, payerData: PayerDetails, roomId: Long, serviceId: Long,
startDateTime: ZonedDateTime, temporaryReservationId: Long) extends SerializableJsonObject

View File

@@ -1,24 +0,0 @@
package com.lbs.api.json.model
/**
{
"PreparationInfo": {
"IsPreparationRequired": true
},
"ReservedVisitsLimitInfo": {
"CanReserve": true,
"HasPatientLimit": false,
"MaxReservedVisitsCount": null,
"Message": "",
"ReservedVisitsCount": null
}
}
*/
case class ReservationResponse(preparationInfo: PreparationInfo, reservedVisitsLimitInfo: ReservedVisitsLimitInfo) extends SerializableJsonObject
case class PreparationInfo(isPreparationRequired: Boolean) extends SerializableJsonObject
case class ReservedVisitsLimitInfo(canReserve: Boolean, hasPatientLimit: Boolean, maxReservedVisitsCount: Option[Int],
message: String, reservedVisitsCount: Option[Int]) extends SerializableJsonObject

View File

@@ -1,44 +0,0 @@
package com.lbs.api.json.model
/**
*
* {
* "ReservedVisits": [
*{
*"CanBeCanceled": true,
*"Clinic": {
*"Id": 6,
*"Name": "LX Wrocław - Szewska 3A"
*},
*"DoctorName": "lek. stom. TARAS SHEVCZENKO",
*"Impediment": {
*"ImpedimentText": "",
*"IsImpediment": false
*},
*"IsAdditional": false,
*"IsPreparationRequired": false,
*"Links": [
*{
*"Href": "/PatientPortalMobileAPI/api/visits/preparations/6621",
*"Method": "GET",
*"Rel": "get_preparations"
*}
*],
*"ReservationId": 888888888,
*"Service": {
*"Id": 6621,
*"Name": "Umówienie wizyty u stomatologa"
*},
*"VisitDate": {
*"FormattedDate": "21rd May, Mon. at 3:00 pm",
*"StartDateTime": "2018-05-21T15:00:00+02:00"
*}
*}
*]
*}
*/
case class ReservedVisitsResponse(reservedVisits: List[ReservedVisit]) extends SerializableJsonObject
case class ReservedVisit(canBeCanceled: Boolean, clinic: IdName, doctorName: String,
reservationId: Long, service: IdName, visitDate: VisitDate) extends SerializableJsonObject

View File

@@ -1,43 +0,0 @@
package com.lbs.api.json.model
import java.time.ZonedDateTime
/**
{
"ClinicId": 6,
"DoctorId": 38275,
"PayerDetailsList": [
{
"BrandId": 2,
"ContractId": 1111111,
"PayerId": 22222,
"PayerName": "FIRMA POLAND SP. Z O.O.",
"ProductElementId": 3333333,
"ProductId": 44444,
"ProductInContractId": 555555,
"ServaAppId": 0,
"ServaId": 6666
},
{
"BrandId": 2,
"ContractId": 1111111,
"PayerId": 22222,
"PayerName": "FIRMA POLAND SP. Z O.O.",
"ProductElementId": 8547135,
"ProductId": 44444,
"ProductInContractId": 555555,
"ServaAppId": 1,
"ServaId": 6666
}
],
"ReferralRequiredByService": false,
"RoomId": 543,
"ServiceId": 6666,
"StartDateTime": "2018-02-23T11:30:00+02:00"
}
*/
case class TemporaryReservationRequest(clinicId: Long, doctorId: Long, payerDetailsList: List[PayerDetails],
referralRequiredByService: Boolean, roomId: Long, serviceId: Long,
startDateTime: ZonedDateTime) extends SerializableJsonObject

View File

@@ -1,6 +0,0 @@
package com.lbs.api.json.model
case class TemporaryReservationResponse(hasReferralRequired: Boolean, id: Long,
informationMessages: List[String],
mustTermOfReservedVisitBeChanged: Boolean) extends SerializableJsonObject

View File

@@ -0,0 +1,134 @@
package com.lbs.api.json.model
import java.time.LocalDateTime
/**
*
* {
* "correlationId": "00000000-0000-0000-0000-000000000000",
* "pMode": 500,
* "termsForService": {
*"additionalData": {
*"anyTermForFacilityVisit": false,
*"anyTermForTelemedicine": true,
*"isPreparationRequired": false,
*"nextTermsAvailable": false,
*"preparationItems": [],
*"previousTermsAvailable": false
*},
*"serviceVariantId": 111111,
*"termsForDays": [
*{
*"correlationId": "00000000-0000-0000-0000-000000000000",
*"day": "2022-05-31T00:00:00",
*"terms": [
*{
*"clinic": "LX Wrocław - Fabryczna 6",
*"clinicGroup": "ul. Fabryczna 6",
*"clinicGroupId": 11,
*"clinicId": 2222,
*"dateTimeFrom": "2021-05-21T18:45:00",
*"dateTimeTo": "2021-05-21T19:00:00",
*"doctor": {
*"academicTitle": "lek. med.",
*"firstName": "TARAS",
*"genderId": 0,
*"id": 33333,
*"lastName": "GRYGORYCH"
*},
*"impedimentText": "",
*"isAdditional": false,
*"isImpediment": false,
*"isInfectionTreatmentCenter": false,
*"isTelemedicine": true,
*"partOfDay": 3,
*"roomId": 4444,
*"scheduleId": 555555,
*"serviceId": 66666
*},
*{
*"clinic": "LX Wrocław - Fabryczna 6",
*"clinicGroup": "ul. Fabryczna 6",
*"clinicGroupId": 77,
*"clinicId": 88888,
*"dateTimeFrom": "2021-05-21T18:45:00",
*"dateTimeTo": "2021-05-21T19:10:00",
*"doctor": {
*"academicTitle": "lek. med.",
*"firstName": "VASYL",
*"genderId": 0,
*"id": 99999,
*"lastName": "STUS"
*},
*"impedimentText": "",
*"isAdditional": false,
*"isImpediment": false,
*"isInfectionTreatmentCenter": false,
*"isTelemedicine": true,
*"partOfDay": 3,
*"roomId": 11111,
*"scheduleId": 1222222,
*"serviceId": 133333
*}
*]
*}
*],
*"termsInfoForDays": [
*{
*"day": "2021-05-22T00:00:00",
*"isLastDayWithLoadedTerms": true,
*"isLimitedDay": false,
*"isMoreTermsThanCounter": null,
*"message": "We can propose visits on the searched day but in other locations, at other doctors or another hour range.",
*"termsCounter": {
*"partialTermsCounters": [],
*"termsNumber": 41
*},
*"termsStatus": 0
*},
*{
*"day": "2021-05-23T00:00:00",
*"isLastDayWithLoadedTerms": false,
*"isLimitedDay": false,
*"isMoreTermsThanCounter": null,
*"message": "Available visits have been already booked. Check later, additonal visits appear regularly.",
*"termsCounter": {
*"partialTermsCounters": [],
*"termsNumber": 0
*},
*"termsStatus": 5
*},
*{
*"day": "2021-05-24T00:00:00",
*"isLastDayWithLoadedTerms": false,
*"isLimitedDay": false,
*"isMoreTermsThanCounter": null,
*"message": "Schedules for that day are not available yet. Check in 1 day.",
*"termsCounter": {
*"partialTermsCounters": [],
*"termsNumber": 0
*},
*"termsStatus": 4
*}
*]
*}
*}
*
*/
case class TermsIndexResponse(correlationId: String, termsForService: TermsForService) extends SerializableJsonObject
case class TermsForService(additionalData: AdditionalData, termsForDays: List[TermsForDay]) extends SerializableJsonObject
case class PreparationItem(header: Option[String], text: Option[String])
case class AdditionalData(isPreparationRequired: Boolean, preparationItems: List[PreparationItem])
case class TermsForDay(day: LocalDateTime, terms: List[Term]) extends SerializableJsonObject
case class Term(clinic: String, clinicId: Long, dateTimeFrom: LocalDateTime, dateTimeTo: LocalDateTime, doctor: Doctor,
impedimentText: String, isAdditional: Boolean, isImpediment: Boolean, isTelemedicine: Boolean, roomId: Long,
scheduleId: Long, serviceId: Long) extends SerializableJsonObject
case class TermExt(additionalData: AdditionalData, term: Term) extends SerializableJsonObject

View File

@@ -0,0 +1,23 @@
package com.lbs.api.json.model
/**
* {
"alternativePrice": null,
"contractId": 555555,
"isExternalReferralAllowed": false,
"isReferralRequired": false, false,
"payerId": 66666,
"price": 0.0,
"productElementId": 7777777,
"productId": 888888,
"productInContractId": 9999999,
"requireReferralForPP": false,
"valuationType": 1
}
*/
case class Valuation(alternativePrice: Option[String], contractId: Long, isExternalReferralAllowed: Boolean,
isReferralRequired: Boolean, payerId: Long, price: Double, productElementId: Long, productId: Long,
productInContractId: Long, requireReferralForPP: Boolean, valuationType: Long) {
}

View File

@@ -1,43 +0,0 @@
package com.lbs.api.json.model
import java.time.ZonedDateTime
/**
{
"ClinicId": 6,
"DoctorId": 38275,
"PayerDetailsList": [
{
"BrandId": 2,
"ContractId": 1111111,
"PayerId": 22222,
"PayerName": "FIRMA POLAND SP. Z O.O.",
"ProductElementId": 3333333,
"ProductId": 44444,
"ProductInContractId": 555555,
"ServaAppId": 0,
"ServaId": 6666
},
{
"BrandId": 2,
"ContractId": 1111111,
"PayerId": 22222,
"PayerName": "FIRMA POLAND SP. Z O.O.",
"ProductElementId": 8547135,
"ProductId": 44444,
"ProductInContractId": 555555,
"ServaAppId": 1,
"ServaId": 6666
}
],
"ReferralRequiredByService": false,
"RoomId": 543,
"ServiceId": 6666,
"StartDateTime": "2018-02-23T11:30:00+02:00"
}
*/
case class ValuationsRequest(clinicId: Long, doctorId: Long, payerDetailsList: List[PayerDetails],
referralRequiredByService: Boolean, roomId: Long, serviceId: Long,
startDateTime: ZonedDateTime) extends SerializableJsonObject

View File

@@ -1,41 +0,0 @@
package com.lbs.api.json.model
/**
{
"OptionsQuestion": "Would you like to confirm your appointment booking?",
"VisitTermVariants": [
{
"CanBeReserve": true,
"InfoMessage": "During the appointment, the physician will indicate the services to be provided and will inform you of the relevant fee, if any. The services will be provided in accordance with the scope of the agreement.",
"IsStomatology": true,
"OptionMessage": "I do not have the required referral",
"PaymentMessage": "",
"ReferralRequired": false,
"ValuationDetail": {
"PayerData": {
"BrandId": null,
"ContractId": 1111111,
"PayerId": 22222,
"PayerName": "FIRMA POLAND SP. Z O.O.",
"ProductElementId": 8547100,
"ProductId": 44444,
"ProductInContractId": 555555,
"ServaAppId": 0,
"ServaId": 6621
},
"Price": 0.0,
"ValuationType": 1
},
"WarningMessage": ""
}
]
}
*/
case class ValuationsResponse(optionsQuestion: Option[String], visitTermVariants: List[VisitTermVariant]) extends SerializableJsonObject
case class VisitTermVariant(canBeReserve: Boolean, infoMessage: String, isStomatology: Boolean, optionMessage: String, paymentMessage: String,
referralRequired: Boolean, valuationDetail: ValuationDetail, warningMessage: String) extends SerializableJsonObject
case class ValuationDetail(payerData: PayerDetails, price: BigDecimal, valuationType: Int) extends SerializableJsonObject

View File

@@ -1,77 +0,0 @@
package com.lbs.api.json.model
/**
{
"AreMoreVisits": false,
"HistoricVisits": [
{
"ClinicName": "LX Wrocław - Szewska 3A",
"DoctorName": "lek. stom. TARAS SHEVCZENKO",
"HasRecommendations": false,
"HasReferrals": false,
"IsAdditional": false,
"Links": [
{
"Href": "/PatientPortalMobileAPI/api/visits/recommendations/222222222",
"Method": "GET",
"Rel": "get_recommendations"
}
],
"QuestionToVisit": {
"IsAnswered": false,
"IsAsked": false,
"IsQuestionToVisitAvailable": false
},
"RateVisit": {
"IsRatingAvailable": false,
"IsVisitRated": false
},
"ReservationId": 222222222,
"Service": {
"Id": 6621,
"Name": "Umówienie wizyty u stomatologa"
},
"VisitDate": {
"FormattedDate": "17th Jan 2018, at 1:00 pm",
"StartDateTime": "2018-01-17T13:00:00+02:00"
}
},
{
"ClinicName": "LX Wrocław - Szewska 3A",
"DoctorName": "lek. stom. TARAS SHEVCZENKO",
"HasRecommendations": false,
"HasReferrals": false,
"IsAdditional": false,
"Links": [
{
"Href": "/PatientPortalMobileAPI/api/visits/recommendations/999999999",
"Method": "GET",
"Rel": "get_recommendations"
}
],
"QuestionToVisit": {
"IsAnswered": false,
"IsAsked": false,
"IsQuestionToVisitAvailable": false
},
"RateVisit": {
"IsRatingAvailable": false,
"IsVisitRated": false
},
"ReservationId": 999999999,
"Service": {
"Id": 3589,
"Name": "Wypełnienie ubytku korony zęba na 2 powierzchniach"
},
"VisitDate": {
"FormattedDate": "17th Jan 2018, at 1:00 pm",
"StartDateTime": "2018-01-17T13:00:00+02:00"
}
}
]
}
*/
case class VisitsHistoryResponse(areMoreVisits: Boolean, historicVisits: List[HistoricVisit]) extends SerializableJsonObject
case class HistoricVisit(clinicName: String, doctorName: String, reservationId: Long, service: IdName, visitDate: VisitDate)

View File

@@ -0,0 +1,5 @@
package com.lbs.api.json.model
import java.net.HttpCookie
case class XsrfToken(token: String, cookies: Seq[HttpCookie])

View File

@@ -2,48 +2,11 @@
package com.lbs
import cats.MonadError
import cats.implicits._
import com.lbs.api.json.model.{AvailableTermsResponse, ReservationFilterResponse, ReservedVisitsResponse, VisitsHistoryResponse}
import com.softwaremill.quicklens._
import scala.language.higherKinds
import scala.util.matching.Regex
package object api {
type ThrowableMonad[F[_]] = MonadError[F, Throwable]
object ApiResponseMutators {
private val DoctorPrefixes: Regex = """\s*(dr\s*n.\s*med.|dr\s*hab.\s*n.\s*med|lek.\s*med.|lek.\s*stom.)\s*""".r
private def cleanupDoctorName(name: String) = DoctorPrefixes.replaceFirstIn(name, "")
trait ResponseMutator[T] {
def mutate(response: T): T
}
implicit class ResponseOps[T: ResponseMutator, F[_] : ThrowableMonad](response: F[T]) {
def mutate: F[T] = {
val mutator = implicitly[ResponseMutator[T]]
response.map(mutator.mutate)
}
}
implicit val ReservedVisitsResponseMutator: ResponseMutator[ReservedVisitsResponse] = (response: ReservedVisitsResponse) => {
response.modify(_.reservedVisits.each.doctorName).using(cleanupDoctorName)
}
implicit val VisitsHistoryResponseMutator: ResponseMutator[VisitsHistoryResponse] = (response: VisitsHistoryResponse) => {
response.modify(_.historicVisits.each.doctorName).using(cleanupDoctorName)
}
implicit val ReservationFilterResponseMutator: ResponseMutator[ReservationFilterResponse] = (response: ReservationFilterResponse) => {
response.modify(_.doctors.each.name).using(cleanupDoctorName)
}
implicit val AvailableTermsResponseMutator: ResponseMutator[AvailableTermsResponse] = (response: AvailableTermsResponse) => {
response.modify(_.availableVisitsTermPresentation.each.doctor.name).using(cleanupDoctorName)
}
}
}

View File

@@ -1,86 +0,0 @@
package com.lbs.api
import com.lbs.api.json.model.{AvailableTermsResponse, AvailableVisitsTermPresentation, HistoricVisit, IdName, ReservationFilterResponse, ReservedVisit, ReservedVisitsResponse, VisitsHistoryResponse}
import org.scalatest.{FunSuiteLike, Matchers}
class ApiResponseMutatorsSpec extends FunSuiteLike with Matchers {
test("ReservationFilterResponseMutator") {
val mutated =
ApiResponseMutators.ReservationFilterResponseMutator.mutate(
ReservationFilterResponse(
cities = Nil,
clinics = Nil,
defaultPayer = None,
doctors = List(IdName(1, "AGNIESZKA dr n. med.")),
languages = Nil,
payers = Nil,
services = Nil
)
)
assert(mutated.doctors === List(IdName(1, "AGNIESZKA")))
}
test("ReservedVisitsResponseMutator") {
val mutated =
ApiResponseMutators.ReservedVisitsResponseMutator.mutate(
ReservedVisitsResponse(
List(
ReservedVisit(
canBeCanceled = false,
clinic = null,
doctorName = "AGNIESZKA dr n. med.",
reservationId = 1L,
service = null,
visitDate = null
)
)
)
)
assert(mutated.reservedVisits.head.doctorName === "AGNIESZKA")
}
test("VisitsHistoryResponseMutator") {
val mutated =
ApiResponseMutators.VisitsHistoryResponseMutator.mutate(
VisitsHistoryResponse(
areMoreVisits = false,
historicVisits = List(
HistoricVisit(
clinicName = null,
doctorName = "AGNIESZKA dr n. med.",
reservationId = 1L,
service = null,
visitDate = null
)
)
)
)
assert(mutated.historicVisits.head.doctorName === "AGNIESZKA")
}
test("AvailableTermsResponseMutator") {
val mutated =
ApiResponseMutators.AvailableTermsResponseMutator.mutate(
AvailableTermsResponse(
availableVisitsTermPresentation = List(
AvailableVisitsTermPresentation(
clinic = null,
doctor = IdName(1, "AGNIESZKA dr n. med."),
payerDetailsList = Nil,
referralRequiredByProduct = false,
referralRequiredByService = false,
roomId = 1L,
scheduleId = 1L,
serviceId = 1L,
visitDate = null
)
)
)
)
assert(mutated.availableVisitsTermPresentation.head.doctor === IdName(1, "AGNIESZKA"))
}
}

View File

@@ -1,21 +0,0 @@
package com.lbs.api.json.model
import org.scalatest.Matchers
trait CommonSpec {
_: Matchers =>
private type SimpleEntity = {val id: Long; val name: String}
protected def testSimpleEntity(simpleEntity: SimpleEntity, expectedId: Long, expectedName: String): Unit = {
simpleEntity.id should be(expectedId)
simpleEntity.name should be(expectedName)
}
protected def testSimpleEntities(simpleEntities: List[SimpleEntity], expectedSize: Int, expectedId: Long, expectedName: String): Unit = {
simpleEntities.size should be(expectedSize)
val simpleEntity = simpleEntities.head
testSimpleEntity(simpleEntity, expectedId, expectedName)
}
}

View File

@@ -1,26 +0,0 @@
package com.lbs.api.json.model
import com.lbs.api.json.JsonSerializer.extensions._
import org.scalatest.{FunSuiteLike, Matchers}
class LoginResponseSpec extends FunSuiteLike with Matchers {
test("deserialization") {
val json =
"""
|{
| "access_token": "RmC6qccJMJ1uVhqJZ-6sBYdfT_LznEoGuH2di0",
| "expires_in": 599,
| "refresh_token": "7854cd0b-8545-483e-88d7-d07eda90995d",
| "token_type": "bearer"
|}
""".stripMargin
val response = json.as[LoginResponse]
response.accessToken should be("RmC6qccJMJ1uVhqJZ-6sBYdfT_LznEoGuH2di0")
response.expiresIn should be(599)
response.refreshToken should be("7854cd0b-8545-483e-88d7-d07eda90995d")
response.tokenType should be("bearer")
}
}

View File

@@ -1,18 +0,0 @@
package com.lbs.api.json.model
import com.lbs.api.json.JsonSerializer.extensions._
import org.scalatest.{FunSuiteLike, Matchers}
class LuxmedErrorsListSpec extends FunSuiteLike with Matchers with CommonSpec {
test("deserialization") {
val json =
"""
|{"Errors":[{"ErrorCode":16000006,"Message":"You have already booked this service","AdditionalData":{}}]}
""".stripMargin
val response = json.as[LuxmedErrorsList]
response should be(LuxmedErrorsList(List(LuxmedErrorsListElement(16000006, "You have already booked this service", Map.empty))))
}
}

View File

@@ -1,17 +0,0 @@
package com.lbs.api.json.model
import com.lbs.api.json.JsonSerializer.extensions._
import org.scalatest.{FunSuiteLike, Matchers}
class LuxmedErrorsMapSpec extends FunSuiteLike with Matchers with CommonSpec {
test("deserialization") {
val json =
"""
|{"Errors":{"ToDate.Date":["'To Date. Date' must be greater than or equal to '06/04/2018 00:00:00'."]}}
""".stripMargin
val response = json.as[LuxmedErrorsMap]
response should be(LuxmedErrorsMap(Map("toDate.Date" -> List("'To Date. Date' must be greater than or equal to '06/04/2018 00:00:00'."))))
}
}

View File

@@ -1,84 +0,0 @@
package com.lbs.api.json.model
import com.lbs.api.json.JsonSerializer.extensions._
import org.scalatest.{FunSuiteLike, Matchers}
class ReservationFilterResponseSpec extends FunSuiteLike with Matchers with CommonSpec {
test("deserialization") {
val json =
"""
|{
| "Cities": [
| {
| "Id": 5,
| "Name": "Wrocław"
| }
| ],
| "Clinics": [
| {
| "Id": 1405,
| "Name": "Legnicka 40"
| },
| {
| "Id": 7,
| "Name": "Kwidzyńska 6"
| }
| ],
| "DefaultPayer": {
| "Id": 22222,
| "Name": "FIRMA"
| },
| "Doctors": [
| {
| "Id": 38275,
| "Name": "ANNA ABRAMCZYK"
| },
| {
| "Id": 15565,
| "Name": "ANDRZEJ ANDEWSKI"
| }
| ],
| "Languages": [
| {
| "Id": 11,
| "Name": "english"
| },
| {
| "Id": 10,
| "Name": "polish"
| }
| ],
| "Payers": [
| {
| "Id": 22222,
| "Name": "FIRMA"
| }
| ],
| "Services": [
| {
| "Id": 5857,
| "Name": "Audiometr standardowy"
| },
| {
| "Id": 7976,
| "Name": "Audiometr standardowy - audiometria nadprogowa"
| }
| ]
|}
""".stripMargin
val response = json.as[ReservationFilterResponse]
testSimpleEntities(response.cities, 1, 5L, "Wrocław")
testSimpleEntities(response.clinics, 2, 1405L, "Legnicka 40")
response.defaultPayer should be(_: Some[IdName])
testSimpleEntity(response.defaultPayer.get, 22222L, "FIRMA")
testSimpleEntities(response.doctors, 2, 38275L, "ANNA ABRAMCZYK")
testSimpleEntities(response.languages, 2, 11L, "english")
testSimpleEntities(response.payers, 1, 22222L, "FIRMA")
testSimpleEntities(response.services, 2, 5857L, "Audiometr standardowy")
}
}

View File

@@ -1,62 +0,0 @@
package com.lbs.api.json.model
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import com.lbs.api.json.JsonSerializer.extensions._
import org.scalatest.{FunSuiteLike, Matchers}
class ReservedVisitsResponseSpec extends FunSuiteLike with Matchers with CommonSpec {
test("deserialization") {
val json =
"""
|{
| "ReservedVisits": [
| {
| "CanBeCanceled": true,
| "Clinic": {
| "Id": 6,
| "Name": "Szewska 3A"
| },
| "DoctorName": "TARAS SHEVCZENKO",
| "Impediment": {
| "ImpedimentText": "",
| "IsImpediment": false
| },
| "IsAdditional": false,
| "IsPreparationRequired": false,
| "Links": [
| {
| "Href": "/PatientPortalMobileAPI/api/visits/preparations/6621",
| "Method": "GET",
| "Rel": "get_preparations"
| }
| ],
| "ReservationId": 888888888,
| "Service": {
| "Id": 6621,
| "Name": "stomatolog"
| },
| "VisitDate": {
| "FormattedDate": "21rd May, Mon. at 3:00 pm",
| "StartDateTime": "2018-05-21T15:00:00+02:00"
| }
| }
| ]
|}
""".stripMargin
val response = json.as[ReservedVisitsResponse]
response.reservedVisits.size should be(1)
val reservedVisit = response.reservedVisits.head
reservedVisit.canBeCanceled should be(true)
testSimpleEntity(reservedVisit.clinic, 6L, "Szewska 3A")
reservedVisit.doctorName should be("TARAS SHEVCZENKO")
reservedVisit.reservationId should be(888888888L)
testSimpleEntity(reservedVisit.service, 6621L, "stomatolog")
reservedVisit.visitDate.formattedDate should be("21rd May, Mon. at 3:00 pm")
reservedVisit.visitDate.startDateTime should be(ZonedDateTime.parse("2018-05-21T15:00:00+02:00", DateTimeFormatter.ISO_OFFSET_DATE_TIME))
}
}

View File

@@ -1,49 +0,0 @@
package com.lbs.api.json.model
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import com.lbs.api.json.JsonSerializer.extensions._
import org.scalatest.{FunSuiteLike, Matchers}
class TemporaryReservationRequestSpec extends FunSuiteLike with Matchers with CommonSpec {
test("serialization") {
val json =
"""
|{
| "ClinicId": 6,
| "DoctorId": 38275,
| "PayerDetailsList": [
| {
| "BrandId": 2,
| "ContractId": 1111111,
| "PayerId": 22222,
| "PayerName": "FIRMA",
| "ProductElementId": 3333333,
| "ProductId": 44444,
| "ProductInContractId": 555555,
| "ServaAppId": 0,
| "ServaId": 6666
| }
| ],
| "ReferralRequiredByService": false,
| "RoomId": 543,
| "ServiceId": 6666,
| "StartDateTime": "2018-02-23T11:30:00+02:00"
|}
""".stripMargin
val request = TemporaryReservationRequest(clinicId = 6L, doctorId = 38275L, payerDetailsList = List(
PayerDetails(brandId = Some(2L), contractId = 1111111L, payerId = 22222L, payerName = "FIRMA",
productElementId = Some(3333333L), productId = 44444L, productInContractId = 555555L, servaAppId = 0L, servaId = 6666L)
), referralRequiredByService = false, roomId = 543L, serviceId = 6666L,
startDateTime = ZonedDateTime.parse("2018-02-23T11:30:00+02:00", DateTimeFormatter.ISO_OFFSET_DATE_TIME))
val requestJson = request.asJson
val requestActual = requestJson.as[TemporaryReservationRequest]
val requestExpected = json.as[TemporaryReservationRequest]
requestActual should be(requestExpected)
}
}

View File

@@ -1,49 +0,0 @@
package com.lbs.api.json.model
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import com.lbs.api.json.JsonSerializer.extensions._
import org.scalatest.{FunSuiteLike, Matchers}
class ValuationsRequestSpec extends FunSuiteLike with Matchers with CommonSpec {
test("serialization") {
val json =
"""
|{
| "ClinicId": 6,
| "DoctorId": 38275,
| "PayerDetailsList": [
| {
| "BrandId": 2,
| "ContractId": 1111111,
| "PayerId": 22222,
| "PayerName": "FIRMA",
| "ProductElementId": 3333333,
| "ProductId": 44444,
| "ProductInContractId": 555555,
| "ServaAppId": 0,
| "ServaId": 6666
| }
| ],
| "ReferralRequiredByService": false,
| "RoomId": 543,
| "ServiceId": 6666,
| "StartDateTime": "2018-02-23T11:30:00+02:00"
|}
""".stripMargin
val request = ValuationsRequest(clinicId = 6L, doctorId = 38275L, payerDetailsList = List(
PayerDetails(brandId = Some(2L), contractId = 1111111L, payerId = 22222L, payerName = "FIRMA",
productElementId = Some(3333333L), productId = 44444L, productInContractId = 555555L, servaAppId = 0L, servaId = 6666L)
), referralRequiredByService = false, roomId = 543L, serviceId = 6666L,
startDateTime = ZonedDateTime.parse("2018-02-23T11:30:00+02:00", DateTimeFormatter.ISO_OFFSET_DATE_TIME))
val requestJson = request.asJson
val requestActual = requestJson.as[ValuationsRequest]
val requestExpected = json.as[ValuationsRequest]
requestActual should be(requestExpected)
}
}

View File

@@ -1,94 +0,0 @@
package com.lbs.api.json.model
import com.lbs.api.json.JsonSerializer.extensions._
import org.scalatest.{FunSuiteLike, Matchers}
class ValuationsResponseSpec extends FunSuiteLike with Matchers with CommonSpec {
test("deserialization") {
val json =
"""
|{
| "VisitTermVariants": [
| {
| "ValuationDetail": {
| "PayerData": {
| "PayerId": 12345,
| "PayerName": "ZBIGNEW",
| "ContractId": 123456,
| "ProductInContractId": 234567,
| "ProductId": 34567,
| "BrandId": null,
| "ProductElementId": 2536352,
| "ServaId": 12345,
| "ServaAppId": 0
| },
| "ValuationType": 1,
| "Price": 0.000000
| },
| "InfoMessage": "Package-covered service",
| "PaymentMessage": "Price with a valid referral: 0.00 zł \r\nPrice without a valid referral: 10.30 zł",
| "OptionMessage": "Yes, from a LUX MED Group or a subcontractor facility physician",
| "WarningMessage": "In accordance with your medical package terms and conditions, you must have a valid referral from a LUX MED Group or a subcontractor facility physician",
| "CanBeReserve": true,
| "ReferralRequired": true,
| "IsStomatology": false
| },
| {
| "ValuationDetail": {
| "PayerData": {
| "PayerId": 123456,
| "PayerName": "ZBIGNEW",
| "ContractId": 12345,
| "ProductInContractId": 345678,
| "ProductId": 23467,
| "BrandId": null,
| "ProductElementId": null,
| "ServaId": 1234,
| "ServaAppId": 0
| },
| "ValuationType": 4,
| "Price": 10.30
| },
| "InfoMessage": "Out-of-package service",
| "PaymentMessage": "Price: 10.30 zł",
| "OptionMessage": "I do not have the required referral",
| "WarningMessage": "",
| "CanBeReserve": true,
| "ReferralRequired": false,
| "IsStomatology": false
| }
| ],
| "OptionsQuestion": "Do you have a valid referral for this service?",
| "IsReferralRequired": false
|}
""".stripMargin
val responseActual = json.as[ValuationsResponse]
val responseExpected = ValuationsResponse(
Some("Do you have a valid referral for this service?"),
List(
VisitTermVariant(
canBeReserve = true,
"Package-covered service",
isStomatology = false,
"Yes, from a LUX MED Group or a subcontractor facility physician", "Price with a valid referral: 0.00 zł \r\nPrice without a valid referral: 10.30 zł",
referralRequired = true,
ValuationDetail(PayerDetails(None, 123456, 12345, "ZBIGNEW", Some(2536352), 34567, 234567, 0, 12345), 0.0, 1),
"In accordance with your medical package terms and conditions, you must have a valid referral from a LUX MED Group or a subcontractor facility physician"
),
VisitTermVariant(
canBeReserve = true,
"Out-of-package service",
isStomatology = false,
"I do not have the required referral",
"Price: 10.30 zł",
referralRequired = false,
ValuationDetail(PayerDetails(None, 12345, 123456, "ZBIGNEW", None, 23467, 345678, 0, 1234), 10.3, 4),
"")
)
)
responseActual should be(responseExpected)
}
}

View File

@@ -1,97 +0,0 @@
package com.lbs.api.json.model
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import com.lbs.api.json.JsonSerializer.extensions._
import org.scalatest.{FunSuiteLike, Matchers}
class VisitsHistoryResponseSpec extends FunSuiteLike with Matchers with CommonSpec {
test("deserialization") {
val json =
"""
|{
| "AreMoreVisits": false,
| "HistoricVisits": [
| {
| "ClinicName": "Szewska 3A",
| "DoctorName": "TARAS SHEVCZENKO",
| "HasRecommendations": false,
| "HasReferrals": false,
| "IsAdditional": false,
| "Links": [
| {
| "Href": "/PatientPortalMobileAPI/api/visits/recommendations/222222222",
| "Method": "GET",
| "Rel": "get_recommendations"
| }
| ],
| "QuestionToVisit": {
| "IsAnswered": false,
| "IsAsked": false,
| "IsQuestionToVisitAvailable": false
| },
| "RateVisit": {
| "IsRatingAvailable": false,
| "IsVisitRated": false
| },
| "ReservationId": 222222222,
| "Service": {
| "Id": 6621,
| "Name": "stomatolog"
| },
| "VisitDate": {
| "FormattedDate": "17th Jan 2018, at 1:00 pm",
| "StartDateTime": "2018-01-17T13:00:00+02:00"
| }
| },
| {
| "ClinicName": "LX Wrocław - Szewska 3A",
| "DoctorName": "lek. stom. TARAS SHEVCZENKO",
| "HasRecommendations": false,
| "HasReferrals": false,
| "IsAdditional": false,
| "Links": [
| {
| "Href": "/PatientPortalMobileAPI/api/visits/recommendations/999999999",
| "Method": "GET",
| "Rel": "get_recommendations"
| }
| ],
| "QuestionToVisit": {
| "IsAnswered": false,
| "IsAsked": false,
| "IsQuestionToVisitAvailable": false
| },
| "RateVisit": {
| "IsRatingAvailable": false,
| "IsVisitRated": false
| },
| "ReservationId": 999999999,
| "Service": {
| "Id": 3589,
| "Name": "Wypełnienie ubytku korony zęba na 2 powierzchniach"
| },
| "VisitDate": {
| "FormattedDate": "17th Jan 2018, at 1:00 pm",
| "StartDateTime": "2018-01-17T13:00:00+02:00"
| }
| }
| ]
|}
""".stripMargin
val response = json.as[VisitsHistoryResponse]
response.areMoreVisits should be(false)
response.historicVisits.size should be(2)
val historicVisit = response.historicVisits.head
historicVisit.clinicName should be("Szewska 3A")
historicVisit.doctorName should be("TARAS SHEVCZENKO")
historicVisit.reservationId should be(222222222L)
testSimpleEntity(historicVisit.service, 6621L, "stomatolog")
historicVisit.visitDate.formattedDate should be("17th Jan 2018, at 1:00 pm")
historicVisit.visitDate.startDateTime should be(ZonedDateTime.parse("2018-01-17T13:00:00+02:00", DateTimeFormatter.ISO_OFFSET_DATE_TIME))
}
}

View File

@@ -1,6 +1,6 @@
dependencies {
compile project(':common')
compile group: "com.bot4s", name: "telegram-core_2.12", version: "5.1.0"
compile group: "com.bot4s", name: "telegram-akka_2.12", version: "4.0.0-RC2"
compile group: "com.bot4s", name: "telegram-core_2.12", version: "5.6.0"
compile group: "com.bot4s", name: "telegram-akka_2.12", version: "5.6.0"
}

View File

@@ -2,22 +2,19 @@
package com.lbs.bot.telegram
import cats.implicits.toFunctorOps
import com.bot4s.telegram.api.RequestHandler
import com.bot4s.telegram.api.declarative.{Callbacks, Commands}
import com.bot4s.telegram.clients.FutureSttpClient
import com.bot4s.telegram.api.{AkkaTelegramBot, RequestHandler}
import com.bot4s.telegram.clients.AkkaHttpClient
import com.bot4s.telegram.future.{Polling, TelegramBot => TelegramBoT}
import com.bot4s.telegram.methods._
import com.bot4s.telegram.models.{InlineKeyboardMarkup, InputFile, Message}
import com.lbs.common.Logger
import sttp.client3.SttpBackend
import sttp.client3.okhttp.OkHttpFutureBackend
import scala.concurrent.Future
class TelegramClient(onReceive: TelegramEvent => Unit, botToken: String) extends TelegramBoT with Polling with Commands[Future] with Callbacks[Future] with Logger {
class TelegramClient(onReceive: TelegramEvent => Unit, botToken: String) extends AkkaTelegramBot with TelegramBoT with Polling with Commands[Future] with Callbacks[Future] with Logger {
private implicit val backend: SttpBackend[Future, Any] = OkHttpFutureBackend()
override val client: RequestHandler[Future] = new FutureSttpClient(botToken)
override val client: RequestHandler[Future] = new AkkaHttpClient(botToken)
def sendMessage(chatId: Long, text: String): Future[Message] =
loggingRequest(SendMessage(chatId, text, parseMode = Some(ParseMode.HTML)))
@@ -32,7 +29,7 @@ class TelegramClient(onReceive: TelegramEvent => Unit, botToken: String) extends
loggingRequest(EditMessageText(Some(chatId), Some(messageId), text = text, parseMode = Some(ParseMode.HTML), replyMarkup = replyMarkup))
def sendFile(chatId: Long, filename: String, contents: Array[Byte], caption: Option[String] = None): Future[Message] =
loggingRequest(SendDocument(chatId, InputFile(filename, contents), caption))
loggingRequest(SendDocument(chatId, InputFile(filename, contents), caption = caption))
private def loggingRequest[R: Manifest](req: Request[R]): Future[R] = {
debug(s"Sending telegram request: $req")

View File

@@ -2,7 +2,6 @@
package com.lbs.common
import java.util.Optional
import scala.language.implicitConversions
object Implicits {

View File

@@ -2,7 +2,6 @@
package com.lbs.common
import java.util.concurrent.{Executors, ScheduledFuture}
import scala.concurrent.duration.FiniteDuration
class Scheduler(poolSize: Int) extends Logger {

View File

@@ -2,7 +2,7 @@
package com.lbs.server
import akka.actor.ActorSystem
import com.lbs.api.json.model.{AvailableVisitsTermPresentation, HistoricVisit, ReservedVisit}
import com.lbs.api.json.model.{Event, TermExt}
import com.lbs.bot.Bot
import com.lbs.bot.telegram.TelegramBot
import com.lbs.server.conversation._
@@ -74,12 +74,12 @@ class BootConfig {
userId => new MonitoringsHistory(userId, bot, monitoringService, localization, monitoringsHistoryPagerFactory, bookWithTemplateFactory)(actorSystem)
@Bean
def historyFactory: UserIdTo[History] =
userId => new History(userId, bot, apiService, localization, historyPagerFactory)(actorSystem)
def historyFactory: UserIdTo[HistoryViewer] =
userId => new HistoryViewer(userId, bot, apiService, localization, historyPagerFactory)(actorSystem)
@Bean
def visitsFactory: UserIdTo[Visits] =
userId => new Visits(userId, bot, apiService, localization, visitsPagerFactory)(actorSystem)
def reservedVisitsFactory: UserIdTo[ReservedVisitsViewer] =
userId => new ReservedVisitsViewer(userId, bot, apiService, localization, reservedVisitsPagerFactory)(actorSystem)
@Bean
def settingsFactory: UserIdTo[Settings] =
@@ -92,7 +92,7 @@ class BootConfig {
@Bean
def chatFactory: UserIdTo[Chat] =
userId => new Chat(userId, dataService, monitoringService, bookFactory, helpFactory,
monitoringsFactory, monitoringsHistoryFactory, historyFactory, visitsFactory, settingsFactory, accountFactory)(actorSystem)
monitoringsFactory, monitoringsHistoryFactory, historyFactory, reservedVisitsFactory, settingsFactory, accountFactory)(actorSystem)
@Bean
def datePickerFactory: UserIdWithOriginatorTo[DatePicker] = (userId, originator) =>
@@ -107,24 +107,24 @@ class BootConfig {
new StaticData(userId, bot, localization, originator)(actorSystem)
@Bean
def termsPagerFactory: UserIdWithOriginatorTo[Pager[AvailableVisitsTermPresentation]] = (userId, originator) =>
new Pager[AvailableVisitsTermPresentation](userId, bot,
(term: AvailableVisitsTermPresentation, page: Int, index: Int) => lang(userId).termEntry(term, page, index),
def termsPagerFactory: UserIdWithOriginatorTo[Pager[TermExt]] = (userId, originator) =>
new Pager[TermExt](userId, bot,
(term: TermExt, page: Int, index: Int) => lang(userId).termEntry(term, page, index),
(page: Int, pages: Int) => lang(userId).termsHeader(page, pages),
Some("book"), localization, originator)(actorSystem)
@Bean
def visitsPagerFactory: UserIdWithOriginatorTo[Pager[ReservedVisit]] = (userId, originator) =>
new Pager[ReservedVisit](userId, bot,
(visit: ReservedVisit, page: Int, index: Int) => lang(userId).upcomingVisitEntry(visit, page, index),
(page: Int, pages: Int) => lang(userId).upcomingVisitsHeader(page, pages),
def reservedVisitsPagerFactory: UserIdWithOriginatorTo[Pager[Event]] = (userId, originator) =>
new Pager[Event](userId, bot,
(visit: Event, page: Int, index: Int) => lang(userId).reservedVisitEntry(visit, page, index),
(page: Int, pages: Int) => lang(userId).reservedVisitsHeader(page, pages),
Some("cancel"), localization, originator)(actorSystem)
@Bean
def historyPagerFactory: UserIdWithOriginatorTo[Pager[HistoricVisit]] = (userId, originator) =>
new Pager[HistoricVisit](userId, bot,
(visit: HistoricVisit, page: Int, index: Int) => lang(userId).historyEntry(visit, page, index),
def historyPagerFactory: UserIdWithOriginatorTo[Pager[Event]] = (userId, originator) =>
new Pager[Event](userId, bot,
(event: Event, page: Int, index: Int) => lang(userId).historyEntry(event, page, index),
(page: Int, pages: Int) => lang(userId).historyHeader(page, pages),
None, localization, originator)(actorSystem)

View File

@@ -1,8 +1,6 @@
package com.lbs.server.conversation
import java.time.{LocalTime, ZonedDateTime}
import akka.actor.ActorSystem
import com.lbs.api.json.model._
import com.lbs.bot._
@@ -20,9 +18,11 @@ import com.lbs.server.service.{ApiService, DataService, MonitoringService}
import com.lbs.server.util.MessageExtractors._
import com.lbs.server.util.ServerModelConverters._
import java.time.{LocalDateTime, LocalTime}
class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: DataService, monitoringService: MonitoringService,
val localization: Localization, datePickerFactory: UserIdWithOriginatorTo[DatePicker], timePickerFactory: UserIdWithOriginatorTo[TimePicker],
staticDataFactory: UserIdWithOriginatorTo[StaticData], termsPagerFactory: UserIdWithOriginatorTo[Pager[AvailableVisitsTermPresentation]])(implicit val actorSystem: ActorSystem) extends Conversation[BookingData] with StaticDataForBooking with Localizable {
staticDataFactory: UserIdWithOriginatorTo[StaticData], termsPagerFactory: UserIdWithOriginatorTo[Pager[TermExt]])(implicit val actorSystem: ActorSystem) extends Conversation[BookingData] with StaticDataForBooking with Localizable {
private val datePicker = datePickerFactory(userId, self)
private val timePicker = timePickerFactory(userId, self)
@@ -33,66 +33,37 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
private def askCity: Step =
staticData(cityConfig) { bd: BookingData =>
withFunctions(
withFunctions[DictionaryCity](
latestOptions = dataService.getLatestCities(userId.accountId),
staticOptions = apiService.getAllCities(userId.accountId),
applyId = id => bd.copy(cityId = id))
}(requestNext = askClinic)
private def askClinic: Step =
staticData(clinicConfig) { bd: BookingData =>
withFunctions(
latestOptions = dataService.getLatestClinicsByCityId(userId.accountId, bd.cityId.id),
staticOptions = apiService.getAllClinics(userId.accountId, bd.cityId.id),
applyId = id => bd.copy(clinicId = id))
applyId = id => bd.copy(cityId = id.toIdName))
}(requestNext = askService)
private def askService: Step =
staticData(serviceConfig) { bd: BookingData =>
withFunctions(
latestOptions = dataService.getLatestServicesByCityIdAndClinicId(userId.accountId, bd.cityId.id, bd.clinicId.optionalId),
staticOptions = apiService.getAllServices(userId.accountId, bd.cityId.id, bd.clinicId.optionalId),
applyId = id => bd.copy(serviceId = id))
withFunctions[DictionaryServiceVariants](
latestOptions = dataService.getLatestServicesByCityIdAndClinicId(userId.accountId, bd.cityId.id, None),
staticOptions = apiService.getAllServices(userId.accountId),
applyId = id => bd.copy(serviceId = id.toIdName))
}(requestNext = askClinic)
private def askClinic: Step =
staticData(clinicConfig) { bd: BookingData =>
withFunctions[IdName](
latestOptions = dataService.getLatestClinicsByCityId(userId.accountId, bd.cityId.id),
staticOptions = apiService.getAllFacilities(userId.accountId, bd.cityId.id, bd.serviceId.id),
applyId = id => bd.copy(clinicId = id))
}(requestNext = askDoctor)
private def askDoctor: Step =
staticData(doctorConfig) { bd: BookingData =>
withFunctions(
withFunctions[IdName](
latestOptions = dataService.getLatestDoctorsByCityIdAndClinicIdAndServiceId(userId.accountId, bd.cityId.id, bd.clinicId.optionalId, bd.serviceId.id),
staticOptions = apiService.getAllDoctors(userId.accountId, bd.cityId.id, bd.clinicId.optionalId, bd.serviceId.id),
applyId = id => bd.copy(doctorId = id))
}(requestNext = determinePayer)
private def determinePayer: Step =
process { bookingData =>
val response = apiService.getPayers(userId.accountId, bookingData.cityId.id, bookingData.clinicId.optionalId, bookingData.serviceId.id)
response match {
case Left(ex) =>
warn(s"Can't determine payers for account ${userId.accountId}, city ${bookingData.cityId.id}, " +
s"clinic ${bookingData.clinicId.optionalId} and service ${bookingData.serviceId.id}", ex)
bot.sendMessage(userId.source, lang.canNotDetectPayer(ex.getMessage))
end()
case Right((defaultPayerMaybe, payers)) =>
defaultPayerMaybe match {
case Some(defaultPayer) =>
goto(requestDateFrom) using bookingData.copy(payerId = defaultPayer.id)
case None =>
goto(askPayer) using bookingData.copy(payers = payers)
}
}
}
private def askPayer: Step =
ask { bookingData =>
bot.sendMessage(
userId.source,
lang.pleaseChoosePayer,
inlineKeyboard = createInlineKeyboard(bookingData.payers.map(payer => Button(payer.name, payer.id)))
)
} onReply {
case Msg(CallbackCommand(LongString(payerId)), bookingData) =>
goto(requestDateFrom) using bookingData.copy(payerId = payerId)
}
staticOptions = apiService.getAllDoctors(userId.accountId, bd.cityId.id, bd.serviceId.id)
.map(_.filterNot(_.facilityGroupIds.exists(z => bd.clinicId == null || z.contains(bd.clinicId.id)))
.map(_.toIdName)),
applyId = id => bd.copy(doctorId = id.toIdName))
}(requestNext = requestDateFrom)
private def requestDateFrom: Step =
ask { bookingData =>
@@ -103,7 +74,7 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
case Msg(cmd: Command, _) =>
datePicker ! cmd
stay()
case Msg(date: ZonedDateTime, bookingData: BookingData) =>
case Msg(date: LocalDateTime, bookingData: BookingData) =>
goto(requestDateTo) using bookingData.copy(dateFrom = date)
}
@@ -116,7 +87,7 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
case Msg(cmd: Command, _) =>
datePicker ! cmd
stay()
case Msg(date: ZonedDateTime, bookingData: BookingData) =>
case Msg(date: LocalDateTime, bookingData: BookingData) =>
goto(requestTimeFrom) using bookingData.copy(dateTo = date)
}
@@ -158,33 +129,42 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
case Msg(CallbackCommand(Tags.FindTerms), _) =>
goto(requestTerm)
case Msg(CallbackCommand(Tags.ModifyDate), bookingData) =>
goto(requestDateFrom) using bookingData.copy(dateFrom = ZonedDateTime.now(),
dateTo = ZonedDateTime.now().plusDays(1L))
goto(requestDateFrom) using bookingData.copy(dateFrom = LocalDateTime.now(),
dateTo = LocalDateTime.now().plusDays(1L))
}
private def requestTerm: Step =
ask { bookingData =>
val availableTerms = apiService.getAvailableTerms(userId.accountId, bookingData.payerId, bookingData.cityId.id,
val availableTerms = apiService.getAvailableTerms(userId.accountId, bookingData.cityId.id,
bookingData.clinicId.optionalId, bookingData.serviceId.id, bookingData.doctorId.optionalId,
bookingData.dateFrom, Some(bookingData.dateTo), timeFrom = bookingData.timeFrom, timeTo = bookingData.timeTo)
bookingData.dateFrom, bookingData.dateTo, timeFrom = bookingData.timeFrom, timeTo = bookingData.timeTo)
termsPager.restart()
termsPager ! availableTerms.map(new SimpleItemsProvider(_))
} onReply {
case Msg(cmd: Command, _) =>
termsPager ! cmd
stay()
case Msg(term: AvailableVisitsTermPresentation, bookingData) =>
val response = apiService.temporaryReservation(userId.accountId, term.mapTo[TemporaryReservationRequest], term.mapTo[ValuationsRequest])
case Msg(term: TermExt, bookingData) =>
val response = for {
xsrfToken <- apiService.getXsrfToken(userId.accountId)
lockTermResponse <- apiService.reservationLockterm(userId.accountId, xsrfToken, term.mapTo[ReservationLocktermRequest])
} yield (lockTermResponse, xsrfToken)
response match {
case Left(ex) =>
warn(s"Service [${bookingData.serviceId.name}] is already booked. Ask to update term", ex)
bot.sendMessage(userId.source, lang.visitAlreadyExists,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes))))
goto(awaitRebookDecision) using bookingData.copy(term = Some(term))
case Right((temporaryReservation, valuations)) =>
bot.sendMessage(userId.source, lang.confirmAppointment(term, valuations),
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.cancel, Tags.Cancel), Button(lang.book, Tags.Book))))
goto(awaitReservation) using bookingData.copy(term = Some(term), temporaryReservationId = Some(temporaryReservation.id), valuations = Some(valuations))
error("Can not lock term", ex)
bot.sendMessage(userId.source, ex.getMessage)
end()
case Right((reservationLocktermResponse, xsrfToken)) =>
if (reservationLocktermResponse.value.changeTermAvailable) {
warn(s"Service [${bookingData.serviceId.name}] is already booked. Ask to update term")
bot.sendMessage(userId.source, lang.visitAlreadyExists,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes))))
goto(awaitRebookDecision) using bookingData.copy(term = Some(term), xsrfToken = Some(xsrfToken), reservationLocktermResponse = Some(reservationLocktermResponse))
} else {
bot.sendMessage(userId.source, lang.confirmAppointment(term),
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.cancel, Tags.Cancel), Button(lang.book, Tags.Book))))
goto(awaitReservation) using bookingData.copy(term = Some(term), xsrfToken = Some(xsrfToken), reservationLocktermResponse = Some(reservationLocktermResponse))
}
}
case Msg(Pager.NoItemsFound, _) =>
goto(askNoTermsAction)
@@ -196,8 +176,8 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
createInlineKeyboard(Seq(Button(lang.modifyDate, Tags.ModifyDate), Button(lang.createMonitoring, Tags.CreateMonitoring))))
} onReply {
case Msg(CallbackCommand(Tags.ModifyDate), bookingData) =>
goto(requestDateFrom) using bookingData.copy(dateFrom = ZonedDateTime.now(),
dateTo = ZonedDateTime.now().plusDays(1L))
goto(requestDateFrom) using bookingData.copy(dateFrom = LocalDateTime.now(),
dateTo = LocalDateTime.now().plusDays(1L))
case Msg(CallbackCommand(Tags.CreateMonitoring), bookingData) =>
val settingsMaybe = dataService.findSettings(userId.userId)
val (defaultOffset, askOffset) = settingsMaybe match {
@@ -212,7 +192,8 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
private def awaitRebookDecision: Step =
monologue {
case Msg(CallbackCommand(Tags.Yes), bookingData: BookingData) =>
apiService.updateReservedVisit(userId.accountId, bookingData.term.get) match {
apiService.reservationChangeTerm(userId.accountId, bookingData.xsrfToken.get,
(bookingData.reservationLocktermResponse.get, bookingData.term.get).mapTo[ReservationChangetermRequest]) match {
case Right(success) =>
debug(s"Successfully confirmed: $success")
bot.sendMessage(userId.source, lang.appointmentIsConfirmed)
@@ -229,7 +210,7 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
private def awaitReservation: Step =
monologue {
case Msg(CallbackCommand(Tags.Cancel), bookingData: BookingData) =>
apiService.deleteTemporaryReservation(userId.accountId, bookingData.temporaryReservationId.get)
apiService.deleteTemporaryReservation(userId.accountId, bookingData.xsrfToken.get, bookingData.reservationLocktermResponse.get.value.temporaryReservationId)
stay()
case Msg(CallbackCommand(Tags.Book), bookingData: BookingData) =>
makeReservation(bookingData)
@@ -238,15 +219,13 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
private def makeReservation(bookingData: BookingData): Unit = {
val reservationRequestMaybe = for {
tmpReservationId <- bookingData.temporaryReservationId
valuations <- bookingData.valuations
visitTermVariant <- valuations.visitTermVariants.headOption
reservationLocktermResponse <- bookingData.reservationLocktermResponse
term <- bookingData.term
} yield (tmpReservationId, visitTermVariant, term).mapTo[ReservationRequest]
} yield (reservationLocktermResponse, term).mapTo[ReservationConfirmRequest]
reservationRequestMaybe match {
case Some(reservationRequest) =>
apiService.reservation(userId.accountId, reservationRequest) match {
apiService.reservationConfirm(userId.accountId, bookingData.xsrfToken.get, reservationRequest) match {
case Left(ex) =>
error("Error during reservation", ex)
bot.sendMessage(userId.source, ex.getMessage)
@@ -322,11 +301,11 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
object Book {
case class BookingData(cityId: IdName = null, clinicId: IdName = null,
serviceId: IdName = null, doctorId: IdName = null, dateFrom: ZonedDateTime = ZonedDateTime.now(),
dateTo: ZonedDateTime = ZonedDateTime.now().plusDays(1L), timeFrom: LocalTime = LocalTime.of(7, 0),
serviceId: IdName = null, doctorId: IdName = null, dateFrom: LocalDateTime = LocalDateTime.now(),
dateTo: LocalDateTime = LocalDateTime.now().plusDays(1L), timeFrom: LocalTime = LocalTime.of(7, 0),
timeTo: LocalTime = LocalTime.of(21, 0), autobook: Boolean = false, rebookIfExists: Boolean = false,
term: Option[AvailableVisitsTermPresentation] = None, temporaryReservationId: Option[Long] = None,
valuations: Option[ValuationsResponse] = None, offset: Int = 0, payerId: Long = 0, payers: Seq[IdName] = Seq())
term: Option[TermExt] = None, reservationLocktermResponse: Option[ReservationLocktermResponse] = None,
offset: Int = 0, payerId: Long = 0, payers: Seq[IdName] = Seq(), xsrfToken: Option[XsrfToken] = None)
object Tags {
val Cancel = "cancel"

View File

@@ -1,8 +1,6 @@
package com.lbs.server.conversation
import java.time.{LocalTime, ZonedDateTime}
import akka.actor.ActorSystem
import com.lbs.api.json.model._
import com.lbs.bot._
@@ -19,9 +17,11 @@ import com.lbs.server.service.{ApiService, DataService, MonitoringService}
import com.lbs.server.util.MessageExtractors._
import com.lbs.server.util.ServerModelConverters._
import java.time.{LocalDateTime, LocalTime}
class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dataService: DataService, monitoringService: MonitoringService,
val localization: Localization, datePickerFactory: UserIdWithOriginatorTo[DatePicker], timePickerFactory: UserIdWithOriginatorTo[TimePicker],
termsPagerFactory: UserIdWithOriginatorTo[Pager[AvailableVisitsTermPresentation]])(implicit val actorSystem: ActorSystem) extends Conversation[BookingData] with Localizable {
termsPagerFactory: UserIdWithOriginatorTo[Pager[TermExt]])(implicit val actorSystem: ActorSystem) extends Conversation[BookingData] with Localizable {
private val datePicker = datePickerFactory(userId, self)
private val timePicker = timePickerFactory(userId, self)
@@ -37,42 +37,11 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
clinicId = IdName.from(monitoring.clinicId, monitoring.clinicName),
serviceId = IdName.from(monitoring.serviceId, monitoring.serviceName),
doctorId = IdName.from(monitoring.doctorId, monitoring.doctorName),
dateFrom = monitoring.dateFrom,
dateTo = monitoring.dateTo,
dateFrom = monitoring.dateFrom.toLocalDateTime,
dateTo = monitoring.dateTo.toLocalDateTime,
timeFrom = monitoring.timeFrom,
timeTo = monitoring.timeTo)
goto(determinePayer) using bookingData
}
private def determinePayer: Step =
process { bookingData =>
val response = apiService.getPayers(userId.accountId, bookingData.cityId.id, bookingData.clinicId.optionalId, bookingData.serviceId.id)
response match {
case Left(ex) =>
warn(s"Can't determine payers for account ${userId.accountId}, city ${bookingData.cityId.id}, " +
s"clinic ${bookingData.clinicId.optionalId} and service ${bookingData.serviceId.id}", ex)
bot.sendMessage(userId.source, lang.canNotDetectPayer(ex.getMessage))
end()
case Right((defaultPayerMaybe, payers)) =>
defaultPayerMaybe match {
case Some(defaultPayer) =>
goto(requestDateFrom) using bookingData.copy(payerId = defaultPayer.id)
case None =>
goto(askPayer) using bookingData.copy(payers = payers)
}
}
}
private def askPayer: Step =
ask { bookingData =>
bot.sendMessage(
userId.source,
lang.pleaseChoosePayer,
inlineKeyboard = createInlineKeyboard(bookingData.payers.map(payer => Button(payer.name, payer.id)))
)
} onReply {
case Msg(CallbackCommand(LongString(payerId)), bookingData) =>
goto(requestDateFrom) using bookingData.copy(payerId = payerId)
goto(requestDateFrom) using bookingData
}
private def requestDateFrom: Step =
@@ -84,7 +53,7 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
case Msg(cmd: Command, _) =>
datePicker ! cmd
stay()
case Msg(date: ZonedDateTime, bookingData: BookingData) =>
case Msg(date: LocalDateTime, bookingData: BookingData) =>
goto(requestDateTo) using bookingData.copy(dateFrom = date)
}
@@ -97,7 +66,7 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
case Msg(cmd: Command, _) =>
datePicker ! cmd
stay()
case Msg(date: ZonedDateTime, bookingData: BookingData) =>
case Msg(date: LocalDateTime, bookingData: BookingData) =>
goto(requestTimeFrom) using bookingData.copy(dateTo = date)
}
@@ -139,33 +108,36 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
case Msg(CallbackCommand(Tags.FindTerms), _) =>
goto(requestTerm)
case Msg(CallbackCommand(Tags.ModifyDate), bookingData) =>
goto(requestDateFrom) using bookingData.copy(dateFrom = ZonedDateTime.now(),
dateTo = ZonedDateTime.now().plusDays(1L))
goto(requestDateFrom) using bookingData.copy(dateFrom = LocalDateTime.now(),
dateTo = LocalDateTime.now().plusDays(1L))
}
private def requestTerm: Step =
ask { bookingData =>
val availableTerms = apiService.getAvailableTerms(userId.accountId, bookingData.payerId, bookingData.cityId.id,
val availableTerms = apiService.getAvailableTerms(userId.accountId, bookingData.cityId.id,
bookingData.clinicId.optionalId, bookingData.serviceId.id, bookingData.doctorId.optionalId,
bookingData.dateFrom, Some(bookingData.dateTo), timeFrom = bookingData.timeFrom, timeTo = bookingData.timeTo)
bookingData.dateFrom, bookingData.dateTo, timeFrom = bookingData.timeFrom, timeTo = bookingData.timeTo)
termsPager.restart()
termsPager ! availableTerms.map(new SimpleItemsProvider(_))
} onReply {
case Msg(cmd: Command, _) =>
termsPager ! cmd
stay()
case Msg(term: AvailableVisitsTermPresentation, bookingData) =>
val response = apiService.temporaryReservation(userId.accountId, term.mapTo[TemporaryReservationRequest], term.mapTo[ValuationsRequest])
case Msg(term: TermExt, bookingData) =>
val response = for {
xsrfToken <- apiService.getXsrfToken(userId.accountId)
lockTermResponse <- apiService.reservationLockterm(userId.accountId, xsrfToken, term.mapTo[ReservationLocktermRequest])
} yield (lockTermResponse, xsrfToken)
response match {
case Left(ex) =>
warn(s"Service [${bookingData.serviceId.name}] is already booked. Ask to update term", ex)
bot.sendMessage(userId.source, lang.visitAlreadyExists,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes))))
goto(awaitRebookDecision) using bookingData.copy(term = Some(term))
case Right((temporaryReservation, valuations)) =>
bot.sendMessage(userId.source, lang.confirmAppointment(term, valuations),
case Right((reservationLocktermResponse, xsrfToken)) =>
bot.sendMessage(userId.source, lang.confirmAppointment(term),
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.cancel, Tags.Cancel), Button(lang.book, Tags.Book))))
goto(awaitReservation) using bookingData.copy(term = Some(term), temporaryReservationId = Some(temporaryReservation.id), valuations = Some(valuations))
goto(awaitReservation) using bookingData.copy(term = Some(term), xsrfToken = Some(xsrfToken), reservationLocktermResponse = Some(reservationLocktermResponse))
}
case Msg(Pager.NoItemsFound, _) =>
goto(askNoTermsAction)
@@ -177,8 +149,8 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
createInlineKeyboard(Seq(Button(lang.modifyDate, Tags.ModifyDate), Button(lang.createMonitoring, Tags.CreateMonitoring))))
} onReply {
case Msg(CallbackCommand(Tags.ModifyDate), bookingData) =>
goto(requestDateFrom) using bookingData.copy(dateFrom = ZonedDateTime.now(),
dateTo = ZonedDateTime.now().plusDays(1L))
goto(requestDateFrom) using bookingData.copy(dateFrom = LocalDateTime.now(),
dateTo = LocalDateTime.now().plusDays(1L))
case Msg(CallbackCommand(Tags.CreateMonitoring), bookingData) =>
val settingsMaybe = dataService.findSettings(userId.userId)
val (defaultOffset, askOffset) = settingsMaybe match {
@@ -193,7 +165,7 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
private def awaitRebookDecision: Step =
monologue {
case Msg(CallbackCommand(Tags.Yes), bookingData: BookingData) =>
apiService.updateReservedVisit(userId.accountId, bookingData.term.get) match {
apiService.reservationChangeTerm(userId.accountId, bookingData.xsrfToken.get, (bookingData.reservationLocktermResponse.get, bookingData.term.get).mapTo[ReservationChangetermRequest]) match {
case Right(success) =>
debug(s"Successfully confirmed: $success")
bot.sendMessage(userId.source, lang.appointmentIsConfirmed)
@@ -210,7 +182,7 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
private def awaitReservation: Step =
monologue {
case Msg(CallbackCommand(Tags.Cancel), bookingData: BookingData) =>
apiService.deleteTemporaryReservation(userId.accountId, bookingData.temporaryReservationId.get)
apiService.deleteTemporaryReservation(userId.accountId, bookingData.xsrfToken.get, bookingData.reservationLocktermResponse.get.value.temporaryReservationId)
stay()
case Msg(CallbackCommand(Tags.Book), bookingData: BookingData) =>
makeReservation(bookingData)
@@ -219,15 +191,13 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
private def makeReservation(bookingData: BookingData): Unit = {
val reservationRequestMaybe = for {
tmpReservationId <- bookingData.temporaryReservationId
valuations <- bookingData.valuations
visitTermVariant <- valuations.visitTermVariants.headOption
reservationLocktermResponse <- bookingData.reservationLocktermResponse
term <- bookingData.term
} yield (tmpReservationId, visitTermVariant, term).mapTo[ReservationRequest]
} yield (reservationLocktermResponse, term).mapTo[ReservationConfirmRequest]
reservationRequestMaybe match {
case Some(reservationRequest) =>
apiService.reservation(userId.accountId, reservationRequest) match {
apiService.reservationConfirm(userId.accountId, bookingData.xsrfToken.get, reservationRequest) match {
case Left(ex) =>
error("Error during reservation", ex)
bot.sendMessage(userId.source, ex.getMessage)

View File

@@ -13,8 +13,8 @@ import com.lbs.server.util.MessageExtractors._
import scala.util.matching.Regex
class Chat(val userId: UserId, dataService: DataService, monitoringService: MonitoringService, bookingFactory: UserIdTo[Book],
helpFactory: UserIdTo[Help], monitoringsFactory: UserIdTo[Monitorings], monitoringsHistoryFactory: UserIdTo[MonitoringsHistory], historyFactory: UserIdTo[History],
visitsFactory: UserIdTo[Visits], settingsFactory: UserIdTo[Settings], accountFactory: UserIdTo[Account])(val actorSystem: ActorSystem) extends Conversation[Unit] with Logger {
helpFactory: UserIdTo[Help], monitoringsFactory: UserIdTo[Monitorings], monitoringsHistoryFactory: UserIdTo[MonitoringsHistory], historyFactory: UserIdTo[HistoryViewer],
visitsFactory: UserIdTo[ReservedVisitsViewer], settingsFactory: UserIdTo[Settings], accountFactory: UserIdTo[Account])(val actorSystem: ActorSystem) extends Conversation[Unit] with Logger {
private val book = bookingFactory(userId)
private val help = helpFactory(userId)

View File

@@ -1,9 +1,6 @@
package com.lbs.server.conversation
import java.time.format.TextStyle
import java.time.{LocalTime, ZonedDateTime}
import akka.actor.ActorSystem
import com.lbs.bot.model.Button
import com.lbs.bot.{Bot, _}
@@ -14,18 +11,20 @@ import com.lbs.server.lang.{Localizable, Localization}
import com.lbs.server.util.DateTimeUtil._
import com.lbs.server.util.MessageExtractors.{CallbackCommand, TextCommand}
import java.time.format.TextStyle
import java.time.{LocalDateTime, LocalTime}
import scala.util.control.NonFatal
/**
* Date picker Inline Keyboard
*
* ⬆ ⬆ ⬆
* dd MM yyyy
* ⬇ ⬇ ⬇
*
*/
* Date picker Inline Keyboard
*
* ⬆ ⬆ ⬆
* dd MM yyyy
* ⬇ ⬇ ⬇
*
*/
class DatePicker(val userId: UserId, val bot: Bot, val localization: Localization, originator: Interactional)
(val actorSystem: ActorSystem) extends Conversation[ZonedDateTime] with Localizable {
(val actorSystem: ActorSystem) extends Conversation[LocalDateTime] with Localizable {
private var mode: Mode = DateFromMode
@@ -36,7 +35,7 @@ class DatePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
case Msg(newMode: Mode, _) =>
mode = newMode
stay()
case Msg(initialDate: ZonedDateTime, _) =>
case Msg(initialDate: LocalDateTime, _) =>
goto(requestDate) using initialDate
}
@@ -52,7 +51,7 @@ class DatePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
val (message, updatedDate) = mode match {
case DateFromMode =>
val startOfTheDay = finalDate.`with`(LocalTime.MIN)
val dateFrom = if (startOfTheDay.isBefore(ZonedDateTime.now())) finalDate else startOfTheDay
val dateFrom = if (startOfTheDay.isBefore(LocalDateTime.now())) finalDate else startOfTheDay
lang.dateFromIs(dateFrom) -> dateFrom
case DateToMode =>
val dateTo = finalDate.`with`(LocalTime.MAX).minusHours(2)
@@ -85,7 +84,7 @@ class DatePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
stay() using modifiedDate
}
private def modifyDate(date: ZonedDateTime, tag: String) = {
private def modifyDate(date: LocalDateTime, tag: String) = {
val dateModifier = tag match {
case Tags.DayInc => date.plusDays _
case Tags.MonthInc => date.plusMonths _
@@ -97,7 +96,7 @@ class DatePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
dateModifier(1)
}
private def dateButtons(date: ZonedDateTime) = {
private def dateButtons(date: LocalDateTime) = {
val day = date.getDayOfMonth.toString
val dayOfWeek = date.getDayOfWeek.getDisplayName(TextStyle.SHORT, lang.locale)
val month = date.getMonth.getDisplayName(TextStyle.SHORT, lang.locale)

View File

@@ -2,7 +2,7 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
import com.lbs.api.json.model.HistoricVisit
import com.lbs.api.json.model.Event
import com.lbs.bot.Bot
import com.lbs.bot.model.Command
import com.lbs.server.conversation.Login.UserId
@@ -11,8 +11,8 @@ import com.lbs.server.conversation.base.Conversation
import com.lbs.server.lang.{Localizable, Localization}
import com.lbs.server.service.ApiService
class History(val userId: UserId, bot: Bot, apiService: ApiService, val localization: Localization,
historyPagerFactory: UserIdWithOriginatorTo[Pager[HistoricVisit]])(val actorSystem: ActorSystem) extends Conversation[Unit] with Localizable {
class HistoryViewer(val userId: UserId, bot: Bot, apiService: ApiService, val localization: Localization,
historyPagerFactory: UserIdWithOriginatorTo[Pager[Event]])(val actorSystem: ActorSystem) extends Conversation[Unit] with Localizable {
private val historyPager = historyPagerFactory(userId, self)
@@ -20,7 +20,7 @@ class History(val userId: UserId, bot: Bot, apiService: ApiService, val localiza
def prepareData: Step =
process { _ =>
val visits = apiService.visitsHistory(userId.accountId)
val visits = apiService.history(userId.accountId)
historyPager.restart()
historyPager ! visits.map(new SimpleItemsProvider(_))
goto(processResponseFromPager)
@@ -32,9 +32,9 @@ class History(val userId: UserId, bot: Bot, apiService: ApiService, val localiza
historyPager ! cmd
stay()
case Msg(Pager.NoItemsFound, _) =>
bot.sendMessage(userId.source, lang.visitsHistoryIsEmpty)
bot.sendMessage(userId.source, lang.eventsListIsEmpty)
end()
case Msg(_: HistoricVisit, _) =>
case Msg(_: Event, _) =>
end()
}

View File

@@ -41,15 +41,14 @@ class Login(source: MessageSource, bot: Bot, dataService: DataService, apiServic
} onReply {
case Msg(MessageExtractors.TextCommand(plainPassword), username) =>
val password = textEncryptor.encrypt(plainPassword)
val loginResult = apiService.login(username, password)
loginResult match {
apiService.fullLogin(username, password) match {
case Left(error) =>
bot.sendMessage(source, error.getMessage)
goto(requestUsername)
case Right(loggedIn) =>
case Right(session) =>
val credentials = dataService.saveCredentials(source, username, password)
userId = UserId(credentials.userId, credentials.accountId, source)
apiService.addSession(credentials.accountId, loggedIn.accessToken, loggedIn.tokenType)
apiService.addSession(credentials.accountId, session)
bot.sendMessage(source, lang.loginAndPasswordAreOk)
originator ! LoggedIn(forwardCommand, credentials.userId, credentials.accountId)
end()

View File

@@ -2,18 +2,18 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
import com.lbs.api.json.model.ReservedVisit
import com.lbs.api.json.model.Event
import com.lbs.bot.model.{Button, Command}
import com.lbs.bot.{Bot, _}
import com.lbs.server.conversation.Login.UserId
import com.lbs.server.conversation.Pager.SimpleItemsProvider
import com.lbs.server.conversation.Visits.Tags
import com.lbs.server.conversation.ReservedVisitsViewer.Tags
import com.lbs.server.conversation.base.Conversation
import com.lbs.server.lang.{Localizable, Localization}
import com.lbs.server.service.ApiService
class Visits(val userId: UserId, bot: Bot, apiService: ApiService, val localization: Localization,
visitsPagerFactory: UserIdWithOriginatorTo[Pager[ReservedVisit]])(val actorSystem: ActorSystem) extends Conversation[ReservedVisit] with Localizable {
class ReservedVisitsViewer(val userId: UserId, bot: Bot, apiService: ApiService, val localization: Localization,
visitsPagerFactory: UserIdWithOriginatorTo[Pager[Event]])(val actorSystem: ActorSystem) extends Conversation[Event] with Localizable {
private val reservedVisitsPager = visitsPagerFactory(userId, self)
@@ -21,7 +21,7 @@ class Visits(val userId: UserId, bot: Bot, apiService: ApiService, val localizat
def prepareData: Step =
process { _ =>
val visits = apiService.reservedVisits(userId.accountId)
val visits = apiService.reserved(userId.accountId)
reservedVisitsPager.restart()
reservedVisitsPager ! visits.map(new SimpleItemsProvider(_))
goto(processResponseFromPager)
@@ -35,7 +35,7 @@ class Visits(val userId: UserId, bot: Bot, apiService: ApiService, val localizat
case Msg(Pager.NoItemsFound, _) =>
bot.sendMessage(userId.source, lang.noUpcomingVisits)
end()
case Msg(visit: ReservedVisit, _) =>
case Msg(visit: Event, _) =>
goto(askToCancelVisit) using visit
}
@@ -47,10 +47,10 @@ class Visits(val userId: UserId, bot: Bot, apiService: ApiService, val localizat
case Msg(Command(_, _, Some(Tags.No)), _) =>
bot.sendMessage(userId.source, lang.appointmentWasNotCancelled)
end()
case Msg(Command(_, _, Some(Tags.Yes)), visit: ReservedVisit) =>
apiService.deleteReservation(userId.accountId, visit.reservationId) match {
case Msg(Command(_, _, Some(Tags.Yes)), visit: Event) =>
apiService.deleteReservation(userId.accountId, visit.eventId) match {
case Left(ex) => bot.sendMessage(userId.source, lang.unableToCancelUpcomingVisit(ex.getMessage))
case Right(r) => bot.sendMessage(userId.source, lang.appointmentHasBeenCancelled)
case Right(_) => bot.sendMessage(userId.source, lang.appointmentHasBeenCancelled)
}
end()
}
@@ -60,7 +60,7 @@ class Visits(val userId: UserId, bot: Bot, apiService: ApiService, val localizat
}
}
object Visits {
object ReservedVisitsViewer {
object Tags {
val Yes = "yes"

View File

@@ -8,9 +8,9 @@ import com.lbs.server.conversation.Login.UserId
import com.lbs.server.conversation.Settings._
import com.lbs.server.conversation.base.Conversation
import com.lbs.server.lang.{Lang, Localizable, Localization}
import com.lbs.server.repository.model
import com.lbs.server.service.DataService
import com.lbs.server.util.MessageExtractors.{CallbackCommand, IntString, TextCommand}
import com.lbs.server.repository.model
class Settings(val userId: UserId, bot: Bot, dataService: DataService, val localization: Localization)(val actorSystem: ActorSystem) extends Conversation[Unit] with Localizable {

View File

@@ -2,7 +2,7 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
import com.lbs.api.json.model.IdName
import com.lbs.api.json.model.{IdName, Identified}
import com.lbs.bot.model.{Button, Command, TaggedButton}
import com.lbs.bot.{Bot, _}
import com.lbs.server.ThrowableOr
@@ -79,6 +79,6 @@ object StaticData {
case class FindOptions(searchText: String)
case class FoundOptions(option: ThrowableOr[List[IdName]])
case class FoundOptions[T <: Identified](option: ThrowableOr[List[T]])
}

View File

@@ -1,18 +1,18 @@
package com.lbs.server.conversation
import com.lbs.api.json.model.IdName
import com.lbs.api.json.model.{IdName, Identified}
import com.lbs.bot.model.Command
import com.lbs.server.ThrowableOr
import com.lbs.server.conversation.Book.BookingData
import com.lbs.server.conversation.StaticData.{FindOptions, FoundOptions, LatestOptions, StaticDataConfig}
import com.lbs.server.conversation.base.Conversation
import com.lbs.server.ThrowableOr
trait StaticDataForBooking extends Conversation[BookingData] {
private[conversation] def staticData: StaticData
protected def withFunctions(latestOptions: => Seq[IdName], staticOptions: => ThrowableOr[List[IdName]], applyId: IdName => BookingData): Step => MessageProcessorFn = {
protected def withFunctions[T <: Identified](latestOptions: => Seq[IdName], staticOptions: => ThrowableOr[List[T]], applyId: IdName => BookingData): Step => MessageProcessorFn = {
nextStep: Step => {
case Msg(cmd: Command, _) =>
staticData ! cmd
@@ -39,7 +39,7 @@ trait StaticDataForBooking extends Conversation[BookingData] {
}
}
private def filterOptions(options: ThrowableOr[List[IdName]], searchText: String) = {
private def filterOptions[T <: Identified](options: ThrowableOr[List[T]], searchText: String) = {
options.map(opt => opt.filter(c => c.name.toLowerCase.contains(searchText)))
}
}

View File

@@ -1,8 +1,6 @@
package com.lbs.server.conversation
import java.time.LocalTime
import akka.actor.ActorSystem
import com.lbs.bot.model.Button
import com.lbs.bot.{Bot, _}
@@ -13,6 +11,7 @@ import com.lbs.server.lang.{Localizable, Localization}
import com.lbs.server.util.DateTimeUtil.applyHourMinute
import com.lbs.server.util.MessageExtractors.{CallbackCommand, TextCommand}
import java.time.LocalTime
import scala.util.control.NonFatal
/**

View File

@@ -1,13 +1,13 @@
package com.lbs.server.lang
import com.lbs.api.json.model.{AvailableVisitsTermPresentation, HistoricVisit, ReservedVisit, ValuationsResponse}
import com.lbs.api.json.model.{Event, TermExt}
import com.lbs.server.conversation.Book
import com.lbs.server.conversation.StaticData.StaticDataConfig
import com.lbs.server.repository.model.Monitoring
import com.lbs.server.util.DateTimeUtil._
import java.time.{LocalTime, ZonedDateTime}
import java.time.{LocalDateTime, LocalTime}
import java.util.Locale
object En extends Lang {
@@ -36,18 +36,18 @@ object En extends Lang {
override def noUpcomingVisits: String =
" No upcoming visits found"
override def areYouSureToCancelAppointment(visit: ReservedVisit): String =
override def areYouSureToCancelAppointment(event: Event): String =
s"""<b>➡</b> Are you sure want to cancel appointment?
|
|⏱ <b>${formatDateTime(visit.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${visit.doctorName}
|${capitalizeFirstLetter(service)}: ${visit.service.name}
|${capitalizeFirstLetter(clinic)}: ${visit.clinic.name}
|⏱ <b>${formatDateTime(event.date, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${capitalizeFirstLetter(event.doctor.name)} ${capitalizeFirstLetter(event.doctor.lastname)}
|${capitalizeFirstLetter(service)}: ${event.title}
|${capitalizeFirstLetter(clinic)}: ${event.clinic.map(c => s"${capitalizeFirstLetter(c.city)} - ${capitalizeFirstLetter(c.address)}").getOrElse("Telemedicine")}
|""".stripMargin
override def chooseDateFrom(exampleDate: ZonedDateTime): String = s"<b>➡</b> Please choose date from or write it manually using format dd-MM, e.g. ${formatDateShort(exampleDate)}"
override def chooseDateFrom(exampleDate: LocalDateTime): String = s"<b>➡</b> Please choose date from or write it manually using format dd-MM, e.g. ${formatDateShort(exampleDate)}"
override def chooseDateTo(exampleDate: ZonedDateTime): String = s"<b>➡</b> Please choose date to or write it manually using format dd-MM, e.g. ${formatDateShort(exampleDate)}"
override def chooseDateTo(exampleDate: LocalDateTime): String = s"<b>➡</b> Please choose date to or write it manually using format dd-MM, e.g. ${formatDateShort(exampleDate)}"
override def findTerms: String = "🔍 Find terms"
@@ -73,15 +73,13 @@ object En extends Lang {
override def book: String = "Book"
override def confirmAppointment(term: AvailableVisitsTermPresentation, valuations: ValuationsResponse): String =
override def confirmAppointment(term: TermExt): String =
s"""<b>➡</b> ${valuations.optionsQuestion.getOrElse("Would you like to confirm your appointment booking?")}
s"""<b>➡</b> Would you like to confirm your appointment booking?
|
|⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
|
|${valuations.visitTermVariants.head.infoMessage}""".stripMargin
|⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}""".stripMargin
override def appointmentIsConfirmed: String = "👍 Your appointment has been confirmed!"
@@ -165,7 +163,7 @@ object En extends Lang {
override def providePassword: String = "<b>➡</b> Please provide password"
override def visitsHistoryIsEmpty: String = " No visits in your history"
override def eventsListIsEmpty: String = " No visits in your history"
override def help: String =
s""" Non official bot for <b>Portal Pacjenta LUX MED (v.${Lang.version})</b>.
@@ -182,14 +180,14 @@ object En extends Lang {
|/settings - settings, e.g. lang
|/help - the help""".stripMargin
override def dateFromIs(dateFrom: ZonedDateTime): String = s"📅 Date from is ${formatDate(dateFrom, locale)}"
override def dateFromIs(dateFrom: LocalDateTime): String = s"📅 Date from is ${formatDate(dateFrom, locale)}"
override def dateToIs(dateTo: ZonedDateTime): String = s"📅 Date to is ${formatDate(dateTo, locale)}"
override def dateToIs(dateTo: LocalDateTime): String = s"📅 Date to is ${formatDate(dateTo, locale)}"
override def termEntry(term: AvailableVisitsTermPresentation, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
override def termEntry(term: TermExt, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}
|<b>➡</b> /book_$index
|
|""".stripMargin
@@ -197,27 +195,27 @@ object En extends Lang {
override def termsHeader(page: Int, pages: Int): String =
withPages("<b>➡</b> Available terms", page, pages)
override def historyEntry(visit: HistoricVisit, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(visit.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${visit.doctorName}
|${capitalizeFirstLetter(service)}: ${visit.service.name}
|${capitalizeFirstLetter(clinic)}: ${visit.clinicName}
override def historyEntry(event: Event, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(event.date, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${capitalizeFirstLetter(event.doctor.name)} ${capitalizeFirstLetter(event.doctor.lastname)}
|${capitalizeFirstLetter(service)}: ${event.title}
|${capitalizeFirstLetter(clinic)}: ${event.clinic.map(c => s"${capitalizeFirstLetter(c.city)} - ${capitalizeFirstLetter(c.address)}").getOrElse("Telemedicine")}
|
|""".stripMargin
override def historyHeader(page: Int, pages: Int): String =
withPages("<b>➡</b> Conducted visits", page, pages)
override def upcomingVisitEntry(visit: ReservedVisit, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(visit.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${visit.doctorName}
|${capitalizeFirstLetter(service)}: ${visit.service.name}
|${capitalizeFirstLetter(clinic)}: ${visit.clinic.name}
override def reservedVisitEntry(event: Event, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(event.date, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${capitalizeFirstLetter(event.doctor.name)} ${capitalizeFirstLetter(event.doctor.lastname)}
|${capitalizeFirstLetter(service)}: ${event.title}
|${capitalizeFirstLetter(clinic)}: ${event.clinic.map(c => s"${capitalizeFirstLetter(c.city)} - ${capitalizeFirstLetter(c.address)}").getOrElse("Telemedicine")}
|<b>➡</b> /cancel_$index
|
|""".stripMargin
override def upcomingVisitsHeader(page: Int, pages: Int): String =
override def reservedVisitsHeader(page: Int, pages: Int): String =
withPages("<b>➡</b> Reserved visits", page, pages)
override def bugsHeader(page: Int, pages: Int): String =
@@ -258,13 +256,13 @@ object En extends Lang {
|Your monitorings were removed. Please /login again and create new monitorings.
""".stripMargin
override def availableTermEntry(term: AvailableVisitsTermPresentation, monitoring: Monitoring, index: Int): String =
s"""⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
override def availableTermEntry(term: TermExt, monitoring: Monitoring, index: Int): String =
s"""⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|/reserve_${monitoring.recordId}_${term.scheduleId}_${minutesSinceBeginOf2018(term.visitDate.startDateTime)}
|/reserve_${monitoring.recordId}_${term.term.scheduleId}_${minutesSinceBeginOf2018(term.term.dateTimeFrom)}
|
|""".stripMargin
@@ -285,13 +283,13 @@ object En extends Lang {
|
|<b>➡</b> Create new monitoring /book""".stripMargin
override def appointmentIsBooked(term: AvailableVisitsTermPresentation, monitoring: Monitoring): String =
override def appointmentIsBooked(term: TermExt, monitoring: Monitoring): String =
s"""👍 We just booked an appointment for you!
|
|⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
|⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}""".stripMargin
override def maximumMonitoringsLimitExceeded: String = "Maximum monitorings per user is 10"

View File

@@ -1,12 +1,12 @@
package com.lbs.server.lang
import com.lbs.api.json.model.{AvailableVisitsTermPresentation, HistoricVisit, ReservedVisit, ValuationsResponse}
import com.lbs.api.json.model.{Event, TermExt}
import com.lbs.server.conversation.Book.BookingData
import com.lbs.server.conversation.StaticData.StaticDataConfig
import com.lbs.server.repository.model.Monitoring
import java.time.{LocalTime, ZonedDateTime}
import java.time.{LocalDateTime, LocalTime}
import java.util.Locale
import scala.io.Source
import scala.util.Try
@@ -33,8 +33,10 @@ trait Lang {
def label: String
protected def capitalizeFirstLetter(str: String): String = {
val fistCapitalLetter = str.head.toTitleCase
fistCapitalLetter + str.tail
if (str != null && str != "") {
val fistCapitalLetter = str.head.toTitleCase
fistCapitalLetter + str.tail.toLowerCase
} else ""
}
protected def withPages(message: String, page: Int, pages: Int): String
@@ -49,11 +51,11 @@ trait Lang {
def noUpcomingVisits: String
def areYouSureToCancelAppointment(visit: ReservedVisit): String
def areYouSureToCancelAppointment(visit: Event): String
def chooseDateFrom(exampleDate: ZonedDateTime): String
def chooseDateFrom(exampleDate: LocalDateTime): String
def chooseDateTo(exampleDate: ZonedDateTime): String
def chooseDateTo(exampleDate: LocalDateTime): String
def chooseTimeFrom(exampleTime: LocalTime): String
@@ -75,7 +77,7 @@ trait Lang {
def book: String
def confirmAppointment(term: AvailableVisitsTermPresentation, valuations: ValuationsResponse): String
def confirmAppointment(term: TermExt): String
def appointmentIsConfirmed: String
@@ -143,29 +145,29 @@ trait Lang {
def providePassword: String
def visitsHistoryIsEmpty: String
def eventsListIsEmpty: String
def help: String
def dateFromIs(dateFrom: ZonedDateTime): String
def dateFromIs(dateFrom: LocalDateTime): String
def dateToIs(dateTo: ZonedDateTime): String
def dateToIs(dateTo: LocalDateTime): String
def timeFromIs(timeFrom: LocalTime): String
def timeToIs(timeTo: LocalTime): String
def termEntry(term: AvailableVisitsTermPresentation, page: Int, index: Int): String
def termEntry(term: TermExt, page: Int, index: Int): String
def termsHeader(page: Int, pages: Int): String
def historyEntry(visit: HistoricVisit, page: Int, index: Int): String
def historyEntry(event: Event, page: Int, index: Int): String
def historyHeader(page: Int, pages: Int): String
def upcomingVisitEntry(visit: ReservedVisit, page: Int, index: Int): String
def reservedVisitEntry(visit: Event, page: Int, index: Int): String
def upcomingVisitsHeader(page: Int, pages: Int): String
def reservedVisitsHeader(page: Int, pages: Int): String
def bugsHeader(page: Int, pages: Int): String
@@ -179,13 +181,13 @@ trait Lang {
def invalidLoginOrPassword: String
def availableTermEntry(term: AvailableVisitsTermPresentation, monitoring: Monitoring, index: Int): String
def availableTermEntry(term: TermExt, monitoring: Monitoring, index: Int): String
def availableTermsHeader(size: Int): String
def nothingWasFoundByMonitoring(monitoring: Monitoring): String
def appointmentIsBooked(term: AvailableVisitsTermPresentation, monitoring: Monitoring): String
def appointmentIsBooked(term: TermExt, monitoring: Monitoring): String
def maximumMonitoringsLimitExceeded: String

View File

@@ -1,13 +1,13 @@
package com.lbs.server.lang
import java.util.concurrent.ConcurrentHashMap
import com.lbs.server.repository.model
import com.lbs.server.service.DataService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.util.concurrent.ConcurrentHashMap
@Component
class Localization {

View File

@@ -1,13 +1,13 @@
package com.lbs.server.lang
import com.lbs.api.json.model.{AvailableVisitsTermPresentation, HistoricVisit, ReservedVisit, ValuationsResponse}
import com.lbs.api.json.model.{Event, TermExt}
import com.lbs.server.conversation.Book
import com.lbs.server.conversation.StaticData.StaticDataConfig
import com.lbs.server.repository.model.Monitoring
import com.lbs.server.util.DateTimeUtil._
import java.time.{LocalTime, ZonedDateTime}
import java.time.{LocalDateTime, LocalTime}
import java.util.Locale
object Pl extends Lang {
@@ -36,18 +36,18 @@ object Pl extends Lang {
override def noUpcomingVisits: String =
" Nie znaleziono wizyt"
override def areYouSureToCancelAppointment(visit: ReservedVisit): String =
override def areYouSureToCancelAppointment(event: Event): String =
s"""<b>➡</b> Czy na pewno chcesz anulować wizytę?
|
|⏱ <b>${formatDateTime(visit.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${visit.doctorName}
|${capitalizeFirstLetter(service)}: ${visit.service.name}
|${capitalizeFirstLetter(clinic)}: ${visit.clinic.name}
|⏱ <b>${formatDateTime(event.date, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${capitalizeFirstLetter(event.doctor.name)} ${capitalizeFirstLetter(event.doctor.lastname)}
|${capitalizeFirstLetter(service)}: ${event.title}
|${capitalizeFirstLetter(clinic)}: ${event.clinic.map(c => s"${capitalizeFirstLetter(c.city)} - ${capitalizeFirstLetter(c.address)}").getOrElse("Telemedicine")}
|""".stripMargin
override def chooseDateFrom(exampleDate: ZonedDateTime): String = s"<b>➡</b> Wybierz datę albo zapisz ją w formacie dd-MM, np. ${formatDateShort(exampleDate)}"
override def chooseDateFrom(exampleDate: LocalDateTime): String = s"<b>➡</b> Wybierz datę albo zapisz ją w formacie dd-MM, np. ${formatDateShort(exampleDate)}"
override def chooseDateTo(exampleDate: ZonedDateTime): String = s"<b>➡</b> Wybierz datę albo zapisz ją w formacie dd-MM, np. ${formatDateShort(exampleDate)}"
override def chooseDateTo(exampleDate: LocalDateTime): String = s"<b>➡</b> Wybierz datę albo zapisz ją w formacie dd-MM, np. ${formatDateShort(exampleDate)}"
override def findTerms: String = "🔍 Szukaj terminów"
@@ -73,15 +73,13 @@ object Pl extends Lang {
override def book: String = "Zarezerwuj"
override def confirmAppointment(term: AvailableVisitsTermPresentation, valuations: ValuationsResponse): String =
override def confirmAppointment(term: TermExt): String =
s"""<b>➡</b> ${valuations.optionsQuestion.getOrElse("Czy potwierdzasz wizytę?")}
s"""<b>➡</b> Czy potwierdzasz wizytę?
|
|⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
|
|${valuations.visitTermVariants.head.infoMessage}""".stripMargin
|⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}""".stripMargin
override def appointmentIsConfirmed: String = "👍 Twoja wizyta została potwierdzona!"
@@ -165,7 +163,7 @@ object Pl extends Lang {
override def providePassword: String = "<b>➡</b> Podaj hasło"
override def visitsHistoryIsEmpty: String = " Brak wizyt w historii"
override def eventsListIsEmpty: String = " Brak wizyt"
override def help: String =
s""" Nieoficjalny Bot do <b>Portal Pacjenta LUX MED (v.${Lang.version})</b>.
@@ -182,14 +180,14 @@ object Pl extends Lang {
|/settings - ustawienia, np. język
|/help - pomoc""".stripMargin
override def dateFromIs(dateFrom: ZonedDateTime): String = s"📅 Data od ${formatDate(dateFrom, locale)}"
override def dateFromIs(dateFrom: LocalDateTime): String = s"📅 Data od ${formatDate(dateFrom, locale)}"
override def dateToIs(dateTo: ZonedDateTime): String = s"📅 Data do ${formatDate(dateTo, locale)}"
override def dateToIs(dateTo: LocalDateTime): String = s"📅 Data do ${formatDate(dateTo, locale)}"
override def termEntry(term: AvailableVisitsTermPresentation, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
override def termEntry(term: TermExt, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}
|<b>➡</b> /book_$index
|
|""".stripMargin
@@ -197,27 +195,27 @@ object Pl extends Lang {
override def termsHeader(page: Int, pages: Int): String =
withPages("<b>➡</b> Dostępne terminy", page, pages)
override def historyEntry(visit: HistoricVisit, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(visit.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${visit.doctorName}
|${capitalizeFirstLetter(service)}: ${visit.service.name}
|${capitalizeFirstLetter(clinic)}: ${visit.clinicName}
override def historyEntry(event: Event, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(event.date, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${capitalizeFirstLetter(event.doctor.name)} ${capitalizeFirstLetter(event.doctor.lastname)}
|${capitalizeFirstLetter(service)}: ${event.title}
|${capitalizeFirstLetter(clinic)}: ${event.clinic.map(c => s"${capitalizeFirstLetter(c.city)} - ${capitalizeFirstLetter(c.address)}").getOrElse("Telemedicine")}
|
|""".stripMargin
override def historyHeader(page: Int, pages: Int): String =
withPages("<b>➡</b> Odbyte wizyty", page, pages)
override def upcomingVisitEntry(visit: ReservedVisit, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(visit.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${visit.doctorName}
|${capitalizeFirstLetter(service)}: ${visit.service.name}
|${capitalizeFirstLetter(clinic)}: ${visit.clinic.name}
override def reservedVisitEntry(event: Event, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(event.date, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${capitalizeFirstLetter(event.doctor.name)} ${capitalizeFirstLetter(event.doctor.lastname)}
|${capitalizeFirstLetter(service)}: ${event.title}
|${capitalizeFirstLetter(clinic)}: ${event.clinic.map(c => s"${capitalizeFirstLetter(c.city)} - ${capitalizeFirstLetter(c.address)}").getOrElse("Telemedicine")}
|<b>➡</b> /cancel_$index
|
|""".stripMargin
override def upcomingVisitsHeader(page: Int, pages: Int): String =
override def reservedVisitsHeader(page: Int, pages: Int): String =
withPages("<b>➡</b> Zarezerwowane wizyty", page, pages)
override def bugsHeader(page: Int, pages: Int): String =
@@ -258,13 +256,13 @@ object Pl extends Lang {
|Usunięto monitoringi. Zaloguj się przez /login i stwórz nowe monitoringi.
""".stripMargin
override def availableTermEntry(term: AvailableVisitsTermPresentation, monitoring: Monitoring, index: Int): String =
s"""⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
override def availableTermEntry(term: TermExt, monitoring: Monitoring, index: Int): String =
s"""⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|/reserve_${monitoring.recordId}_${term.scheduleId}_${minutesSinceBeginOf2018(term.visitDate.startDateTime)}
|/reserve_${monitoring.recordId}_${term.term.scheduleId}_${minutesSinceBeginOf2018(term.term.dateTimeFrom)}
|
|""".stripMargin
@@ -285,13 +283,13 @@ object Pl extends Lang {
|
|<b>➡</b> Stwórz nowy monitoring przez /book""".stripMargin
override def appointmentIsBooked(term: AvailableVisitsTermPresentation, monitoring: Monitoring): String =
override def appointmentIsBooked(term: TermExt, monitoring: Monitoring): String =
s"""👍 Zarezerwowaliśmy za Ciebie termin!
|
|⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
|⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}""".stripMargin
override def maximumMonitoringsLimitExceeded: String = "Maksymalna liczba monitoringów uzytkownika to 10"

View File

@@ -1,13 +1,13 @@
package com.lbs.server.lang
import com.lbs.api.json.model.{AvailableVisitsTermPresentation, HistoricVisit, ReservedVisit, ValuationsResponse}
import com.lbs.api.json.model.{Event, TermExt}
import com.lbs.server.conversation.Book
import com.lbs.server.conversation.StaticData.StaticDataConfig
import com.lbs.server.repository.model.Monitoring
import com.lbs.server.util.DateTimeUtil._
import java.time.{LocalTime, ZonedDateTime}
import java.time.{LocalDateTime, LocalTime}
import java.util.Locale
object Ua extends Lang {
@@ -36,18 +36,18 @@ object Ua extends Lang {
override def noUpcomingVisits: String =
" Не знайдено жодного майбутнього візиту"
override def areYouSureToCancelAppointment(visit: ReservedVisit): String =
override def areYouSureToCancelAppointment(event: Event): String =
s"""<b>➡</b> Ви впевнені, що хочете скасувати візит?
|
|⏱ <b>${formatDateTime(visit.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${visit.doctorName}
|${capitalizeFirstLetter(service)}: ${visit.service.name}
|${capitalizeFirstLetter(clinic)}: ${visit.clinic.name}
|⏱ <b>${formatDateTime(event.date, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${capitalizeFirstLetter(event.doctor.name)} ${capitalizeFirstLetter(event.doctor.lastname)}
|${capitalizeFirstLetter(service)}: ${event.title}
|${capitalizeFirstLetter(clinic)}: ${event.clinic.map(c => s"${capitalizeFirstLetter(c.city)} - ${capitalizeFirstLetter(c.address)}").getOrElse("Telemedicine")}
|""".stripMargin
override def chooseDateFrom(exampleDate: ZonedDateTime): String = s"<b>➡</b> Будь ласка, виберіть початкову дату або введіть її, використовуючи формат dd-MM, наприклад ${formatDateShort(exampleDate)}"
override def chooseDateFrom(exampleDate: LocalDateTime): String = s"<b>➡</b> Будь ласка, виберіть початкову дату або введіть її, використовуючи формат dd-MM, наприклад ${formatDateShort(exampleDate)}"
override def chooseDateTo(exampleDate: ZonedDateTime): String = s"<b>➡</b> Будь ласка, виберіть кінцеву дату або введіть її, використовуючи формат dd-MM, наприклад ${formatDateShort(exampleDate)}"
override def chooseDateTo(exampleDate: LocalDateTime): String = s"<b>➡</b> Будь ласка, виберіть кінцеву дату або введіть її, використовуючи формат dd-MM, наприклад ${formatDateShort(exampleDate)}"
override def findTerms: String = "🔍 Знайти терміни"
@@ -73,15 +73,13 @@ object Ua extends Lang {
override def book: String = "Зарезервувати"
override def confirmAppointment(term: AvailableVisitsTermPresentation, valuations: ValuationsResponse): String =
override def confirmAppointment(term: TermExt): String =
s"""<b>➡</b> ${valuations.optionsQuestion.getOrElse("Ви хотіли б підтвердити резервацію візиту?")}
s"""<b>➡</b> Ви хотіли б підтвердити резервацію візиту?
|
|⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
|
|${valuations.visitTermVariants.head.infoMessage}""".stripMargin
|⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}""".stripMargin
override def appointmentIsConfirmed: String = "👍 Ваш візит було підтверджено!"
@@ -164,7 +162,7 @@ object Ua extends Lang {
override def providePassword: String = "<b>➡</b> Будь ласка, введіть пароль"
override def visitsHistoryIsEmpty: String = " Немає візитів в вашій історії"
override def eventsListIsEmpty: String = " Немає візитів в вашій історії"
override def help: String =
s""" Це неофіційний бот для <b>Порталу Пацієнта LUX MED (v.${Lang.version})</b>.
@@ -181,14 +179,14 @@ object Ua extends Lang {
|/settings - налаштування
|/help - допомога""".stripMargin
override def dateFromIs(dateFrom: ZonedDateTime): String = s"📅 Початкова дата ${formatDate(dateFrom, locale)}"
override def dateFromIs(dateFrom: LocalDateTime): String = s"📅 Початкова дата ${formatDate(dateFrom, locale)}"
override def dateToIs(dateTo: ZonedDateTime): String = s"📅 Кінцева дата ${formatDate(dateTo, locale)}"
override def dateToIs(dateTo: LocalDateTime): String = s"📅 Кінцева дата ${formatDate(dateTo, locale)}"
override def termEntry(term: AvailableVisitsTermPresentation, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
override def termEntry(term: TermExt, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}
|<b>➡</b> /book_$index
|
|""".stripMargin
@@ -196,27 +194,27 @@ object Ua extends Lang {
override def termsHeader(page: Int, pages: Int): String =
withPages("<b>➡</b> Доступні терміни", page, pages)
override def historyEntry(visit: HistoricVisit, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(visit.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${visit.doctorName}
|${capitalizeFirstLetter(service)}: ${visit.service.name}
|${capitalizeFirstLetter(clinic)}: ${visit.clinicName}
override def historyEntry(event: Event, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(event.date, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${capitalizeFirstLetter(event.doctor.name)} ${capitalizeFirstLetter(event.doctor.lastname)}
|${capitalizeFirstLetter(service)}: ${event.title}
|${capitalizeFirstLetter(clinic)}: ${event.clinic.map(c => s"${capitalizeFirstLetter(c.city)} - ${capitalizeFirstLetter(c.address)}").getOrElse("Telemedicine")}
|
|""".stripMargin
override def historyHeader(page: Int, pages: Int): String =
withPages("<b>➡</b> Завершені візити", page, pages)
override def upcomingVisitEntry(visit: ReservedVisit, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(visit.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${visit.doctorName}
|${capitalizeFirstLetter(service)}: ${visit.service.name}
|${capitalizeFirstLetter(clinic)}: ${visit.clinic.name}
override def reservedVisitEntry(event: Event, page: Int, index: Int): String =
s"""⏱ <b>${formatDateTime(event.date, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${capitalizeFirstLetter(event.doctor.name)} ${capitalizeFirstLetter(event.doctor.lastname)}
|${capitalizeFirstLetter(service)}: ${event.title}
|${capitalizeFirstLetter(clinic)}: ${event.clinic.map(c => s"${capitalizeFirstLetter(c.city)} - ${capitalizeFirstLetter(c.address)}").getOrElse("Telemedicine")}
|<b>➡</b> /cancel_$index
|
|""".stripMargin
override def upcomingVisitsHeader(page: Int, pages: Int): String =
override def reservedVisitsHeader(page: Int, pages: Int): String =
withPages("<b>➡</b> Зарезервовані візити", page, pages)
override def bugsHeader(page: Int, pages: Int): String =
@@ -257,13 +255,13 @@ object Ua extends Lang {
|Ваші моніторинги були видалені. Будь ласка, /login знову і створіть нові моніторинги.
""".stripMargin
override def availableTermEntry(term: AvailableVisitsTermPresentation, monitoring: Monitoring, index: Int): String =
s"""⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
override def availableTermEntry(term: TermExt, monitoring: Monitoring, index: Int): String =
s"""⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|/reserve_${monitoring.recordId}_${term.scheduleId}_${minutesSinceBeginOf2018(term.visitDate.startDateTime)}
|/reserve_${monitoring.recordId}_${term.term.scheduleId}_${minutesSinceBeginOf2018(term.term.dateTimeFrom)}
|
|""".stripMargin
@@ -284,13 +282,13 @@ object Ua extends Lang {
|
|<b>➡</b> Створити новий моніторінг /book""".stripMargin
override def appointmentIsBooked(term: AvailableVisitsTermPresentation, monitoring: Monitoring): String =
override def appointmentIsBooked(term: TermExt, monitoring: Monitoring): String =
s"""👍 Ми зерезевували візит для вас!
|
|⏱ <b>${formatDateTime(term.visitDate.startDateTime, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.doctor.name}
|⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${term.clinic.name}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}""".stripMargin
override def maximumMonitoringsLimitExceeded: String = "Максимальна кількість моніторінгів 10"

View File

@@ -1,13 +1,12 @@
package com.lbs.server.repository
import java.time.ZonedDateTime
import com.lbs.server.repository.model.{CityHistory, ClinicHistory, Credentials, DoctorHistory, JLong, Monitoring, ServiceHistory, Settings, Source, SystemUser}
import javax.persistence.EntityManager
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Repository
import java.time.ZonedDateTime
import javax.persistence.EntityManager
import scala.collection.JavaConverters._
@Repository

View File

@@ -2,9 +2,7 @@
package com.lbs.server.repository.model
import java.time.ZonedDateTime
import javax.persistence.{Access, AccessType, Column, Entity}
import scala.beans.BeanProperty
@Entity

View File

@@ -2,9 +2,7 @@
package com.lbs.server.repository.model
import java.time.ZonedDateTime
import javax.persistence.{Access, AccessType, Column, Entity}
import scala.beans.BeanProperty
@Entity

View File

@@ -2,7 +2,6 @@
package com.lbs.server.repository.model
import javax.persistence._
import scala.beans.BeanProperty
@Entity

View File

@@ -2,9 +2,7 @@
package com.lbs.server.repository.model
import java.time.ZonedDateTime
import javax.persistence.{Access, AccessType, Column, Entity}
import scala.beans.BeanProperty
@Entity

View File

@@ -2,9 +2,7 @@
package com.lbs.server.repository.model
import java.time.{LocalTime, ZonedDateTime}
import javax.persistence.{Access, AccessType, Column, Entity}
import scala.beans.BeanProperty
@Entity

View File

@@ -2,7 +2,6 @@
package com.lbs.server.repository.model
import javax.persistence._
import scala.beans.BeanProperty
@Access(AccessType.FIELD)

View File

@@ -2,9 +2,7 @@
package com.lbs.server.repository.model
import java.time.ZonedDateTime
import javax.persistence.{Access, AccessType, Column, Entity}
import scala.beans.BeanProperty
@Entity

View File

@@ -2,7 +2,6 @@
package com.lbs.server.repository.model
import javax.persistence.{Access, AccessType, Column, Entity}
import scala.beans.BeanProperty
@Entity

View File

@@ -2,7 +2,6 @@
package com.lbs.server.repository.model
import javax.persistence._
import scala.beans.BeanProperty
@Entity

View File

@@ -2,7 +2,6 @@
package com.lbs.server.repository.model
import javax.persistence._
import scala.beans.BeanProperty
@Entity

View File

@@ -1,18 +1,18 @@
package com.lbs.server.service
import java.time.{LocalTime, ZonedDateTime}
import cats.instances.either._
import com.lbs.api.LuxmedApi
import com.lbs.api.http.Session
import com.lbs.api.json.model._
import com.lbs.server.ThrowableOr
import com.lbs.server.util.ServerModelConverters._
import org.jasypt.util.text.TextEncryptor
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import scalaj.http.HttpResponse
import java.time.{LocalDateTime, LocalTime, ZonedDateTime}
@Service
class ApiService extends SessionSupport {
@@ -23,161 +23,100 @@ class ApiService extends SessionSupport {
private val luxmedApi = new LuxmedApi[ThrowableOr]
def getAllCities(accountId: Long): ThrowableOr[List[IdName]] =
def getAllCities(accountId: Long): ThrowableOr[List[DictionaryCity]] =
withSession(accountId) { session =>
luxmedApi.reservationFilter(session.accessToken, session.tokenType).map(_.cities)
luxmedApi.dictionaryCities(session)
}
def getAllClinics(accountId: Long, cityId: Long): ThrowableOr[List[IdName]] =
def getAllFacilities(accountId: Long, cityId: Long, serviceVariantId: Long): ThrowableOr[List[IdName]] =
withSession(accountId) { session =>
luxmedApi.reservationFilter(session.accessToken,
session.tokenType, cityId = Some(cityId)).map(_.clinics)
luxmedApi.dictionaryFacilitiesAndDoctors(session, cityId = Some(cityId), serviceVariantId = Some(serviceVariantId)).map(_.facilities)
}
def getAllServices(accountId: Long, cityId: Long, clinicId: Option[Long]): ThrowableOr[List[IdName]] =
def getAllServices(accountId: Long): ThrowableOr[List[DictionaryServiceVariants]] =
withSession(accountId) { session =>
luxmedApi.reservationFilter(session.accessToken,
session.tokenType, cityId = Some(cityId),
clinicId = clinicId).map(_.services)
luxmedApi.dictionaryServiceVariants(session).map(s => s.flatMap(_.flatten.filterNot(_.children.nonEmpty)))
}
def getAllDoctors(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): ThrowableOr[List[IdName]] =
def getAllDoctors(accountId: Long, cityId: Long, serviceVariantId: Long): ThrowableOr[List[Doctor]] =
withSession(accountId) { session =>
luxmedApi.reservationFilter(session.accessToken,
session.tokenType, cityId = Some(cityId),
clinicId = clinicId, serviceId = Some(serviceId)).map(_.doctors)
luxmedApi.dictionaryFacilitiesAndDoctors(session, cityId = Some(cityId), serviceVariantId = Some(serviceVariantId)).map(_.doctors)
}
def getPayers(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): ThrowableOr[(Option[IdName], Seq[IdName])] =
def getAvailableTerms(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long, doctorId: Option[Long],
fromDate: LocalDateTime, toDate: LocalDateTime, timeFrom: LocalTime, timeTo: LocalTime,
languageId: Long = 10): ThrowableOr[List[TermExt]] =
withSession(accountId) { session =>
val reservationFilterResponse = luxmedApi.reservationFilter(session.accessToken,
session.tokenType, cityId = Some(cityId),
clinicId = clinicId, serviceId = Some(serviceId))
reservationFilterResponse.map { response =>
response.defaultPayer -> response.payers
}
}
def getAvailableTerms(accountId: Long, payerId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long, doctorId: Option[Long],
fromDate: ZonedDateTime = ZonedDateTime.now(), toDate: Option[ZonedDateTime] = None, timeFrom: LocalTime, timeTo: LocalTime,
languageId: Long = 10, findFirstFreeTerm: Boolean = false): ThrowableOr[List[AvailableVisitsTermPresentation]] =
withSession(accountId) { session =>
val termsEither = luxmedApi.availableTerms(session.accessToken, session.tokenType, payerId, cityId, clinicId, serviceId, doctorId,
fromDate, toDate, languageId = languageId, findFirstFreeTerm = findFirstFreeTerm).map(_.availableVisitsTermPresentation)
val termsEither = luxmedApi.termsIndex(session, cityId, clinicId, serviceId, doctorId,
fromDate, toDate, languageId = languageId)
.map(termsIndexResponse => termsIndexResponse.termsForService.termsForDays
.flatMap(_.terms.map(term => TermExt(termsIndexResponse.termsForService.additionalData, term)))
)
termsEither.map { terms =>
terms.filter { term =>
val time = term.visitDate.startDateTime.toLocalTime
time == timeFrom || time == timeTo || (time.isAfter(timeFrom) && time.isBefore(timeTo))
val time = term.term.dateTimeFrom.toLocalTime
val date = term.term.dateTimeFrom
(doctorId.isEmpty || doctorId.contains(term.term.doctor.id)) &&
(clinicId.isEmpty || clinicId.contains(term.term.clinicId)) &&
(time == timeFrom || time == timeTo || (time.isAfter(timeFrom) && time.isBefore(timeTo))) &&
(date == fromDate || date == toDate || (date.isAfter(fromDate) && date.isBefore(toDate)))
}
}
}
def temporaryReservation(accountId: Long, temporaryReservationRequest: TemporaryReservationRequest, valuationsRequest: ValuationsRequest): ThrowableOr[(TemporaryReservationResponse, ValuationsResponse)] =
def reservationLockterm(accountId: Long, xsrfToken: XsrfToken, reservationLocktermRequest: ReservationLocktermRequest): ThrowableOr[ReservationLocktermResponse] =
withSession(accountId) { session =>
for {
temporaryReservation <- luxmedApi.temporaryReservation(session.accessToken, session.tokenType, temporaryReservationRequest)
valuationsResponse <- luxmedApi.valuations(session.accessToken, session.tokenType, valuationsRequest)
} yield temporaryReservation -> valuationsResponse
luxmedApi.reservationLockterm(session, xsrfToken, reservationLocktermRequest)
}
def deleteTemporaryReservation(accountId: Long, temporaryReservationId: Long): ThrowableOr[HttpResponse[String]] =
def deleteTemporaryReservation(accountId: Long, xsrfToken: XsrfToken, temporaryReservationId: Long): ThrowableOr[Unit] =
withSession(accountId) { session =>
luxmedApi.deleteTemporaryReservation(session.accessToken, session.tokenType, temporaryReservationId)
luxmedApi.deleteTemporaryReservation(session, xsrfToken, temporaryReservationId)
}
def reservation(accountId: Long, reservationRequest: ReservationRequest): ThrowableOr[ReservationResponse] =
def reservationConfirm(accountId: Long, xsrfToken: XsrfToken, reservationConfirmRequest: ReservationConfirmRequest): ThrowableOr[ReservationConfirmResponse] =
withSession(accountId) { session =>
luxmedApi.reservation(session.accessToken, session.tokenType, reservationRequest)
luxmedApi.reservationConfirm(session, xsrfToken, reservationConfirmRequest)
}
def reserveVisit(accountId: Long, term: AvailableVisitsTermPresentation): ThrowableOr[ReservationResponse] = {
val temporaryReservationRequest = term.mapTo[TemporaryReservationRequest]
val valuationsRequest = term.mapTo[ValuationsRequest]
for {
okResponse <- temporaryReservation(accountId, temporaryReservationRequest, valuationsRequest)
(temporaryReservation, valuations) = okResponse
temporaryReservationId = temporaryReservation.id
visitTermVariant = valuations.visitTermVariants.head
reservationRequest = (temporaryReservationId, visitTermVariant, term).mapTo[ReservationRequest]
reservation <- reservation(accountId, reservationRequest)
} yield reservation
}
def canTermBeChanged(accountId: Long, reservationId: Long): ThrowableOr[HttpResponse[String]] =
def reservationChangeTerm(accountId: Long, xsrfToken: XsrfToken, reservationChangetermRequest: ReservationChangetermRequest): ThrowableOr[ReservationConfirmResponse] =
withSession(accountId) { session =>
luxmedApi.canTermBeChanged(session.accessToken, session.tokenType, reservationId)
luxmedApi.reservationChangeTerm(session, xsrfToken, reservationChangetermRequest)
}
def detailToChangeTerm(accountId: Long, reservationId: Long): ThrowableOr[ChangeTermDetailsResponse] =
def history(accountId: Long, fromDate: ZonedDateTime = ZonedDateTime.now().minusYears(1),
toDate: ZonedDateTime = ZonedDateTime.now()): ThrowableOr[List[Event]] =
withSession(accountId) { session =>
luxmedApi.detailToChangeTerm(session.accessToken, session.tokenType, reservationId)
luxmedApi.events(session, fromDate, toDate).map(_.events.filter(_.status == "Realized"))
}
def temporaryReservationToChangeTerm(accountId: Long, reservationId: Long, temporaryReservationRequest: TemporaryReservationRequest, valuationsRequest: ValuationsRequest): ThrowableOr[(TemporaryReservationResponse, ValuationsResponse)] =
def reserved(accountId: Long, fromDate: ZonedDateTime = ZonedDateTime.now(),
toDate: ZonedDateTime = ZonedDateTime.now().plusMonths(3)): ThrowableOr[List[Event]] =
withSession(accountId) { session =>
for {
temporaryReservation <- luxmedApi.temporaryReservationToChangeTerm(session.accessToken, session.tokenType, reservationId, temporaryReservationRequest)
valuationsResponse <- luxmedApi.valuationToChangeTerm(session.accessToken, session.tokenType, reservationId, valuationsRequest)
} yield temporaryReservation -> valuationsResponse
}
def valuationToChangeTerm(accountId: Long, reservationId: Long, valuationsRequest: ValuationsRequest): ThrowableOr[ValuationsResponse] =
withSession(accountId) { session =>
luxmedApi.valuationToChangeTerm(session.accessToken, session.tokenType, reservationId, valuationsRequest)
}
def changeTerm(accountId: Long, reservationId: Long, reservationRequest: ReservationRequest): ThrowableOr[ChangeTermResponse] =
withSession(accountId) { session =>
luxmedApi.changeTerm(session.accessToken, session.tokenType, reservationId, reservationRequest)
}
def updateReservedVisit(accountId: Long, term: AvailableVisitsTermPresentation): ThrowableOr[ChangeTermResponse] = {
val reservedVisitMaybe = reservedVisits(accountId, toDate = ZonedDateTime.now().plusMonths(6)).map(_.find(_.service.id == term.serviceId))
reservedVisitMaybe match {
case Right(Some(reservedVisit: ReservedVisit)) =>
val reservationId = reservedVisit.reservationId
val temporaryReservationRequest = term.mapTo[TemporaryReservationRequest]
val valuationsRequest = term.mapTo[ValuationsRequest]
val canTermBeChangedResponse = canTermBeChanged(accountId, reservationId)
if (canTermBeChangedResponse.exists(_.code == 204)) {
for {
okResponse <- temporaryReservationToChangeTerm(accountId, reservationId, temporaryReservationRequest, valuationsRequest)
(temporaryReservation, valuations) = okResponse
temporaryReservationId = temporaryReservation.id
visitTermVariant = valuations.visitTermVariants.head
reservationRequest = (temporaryReservationId, visitTermVariant, term).mapTo[ReservationRequest]
reservation <- changeTerm(accountId, reservationId, reservationRequest)
} yield reservation
} else left(s"Term for reservation [$reservationId] can't be changed")
case Left(ex) =>
Left(ex)
case _ =>
left(s"Existing reservation for service [${term.serviceId}] not found. Nothing to update")
}
}
def visitsHistory(accountId: Long, fromDate: ZonedDateTime = ZonedDateTime.now().minusYears(1),
toDate: ZonedDateTime = ZonedDateTime.now(), page: Int = 1, pageSize: Int = 100): ThrowableOr[List[HistoricVisit]] =
withSession(accountId) { session =>
luxmedApi.visitsHistory(session.accessToken, session.tokenType, fromDate, toDate, page, pageSize).map(_.historicVisits)
}
def reservedVisits(accountId: Long, fromDate: ZonedDateTime = ZonedDateTime.now(),
toDate: ZonedDateTime = ZonedDateTime.now().plusMonths(3)): ThrowableOr[List[ReservedVisit]] =
withSession(accountId) { session =>
luxmedApi.reservedVisits(session.accessToken, session.tokenType, fromDate, toDate).map(_.reservedVisits)
luxmedApi.events(session, fromDate, toDate).map(_.events.filter(_.status == "Reserved"))
}
def deleteReservation(accountId: Long, reservationId: Long): ThrowableOr[HttpResponse[String]] =
withSession(accountId) { session =>
luxmedApi.deleteReservation(session.accessToken, session.tokenType, reservationId)
luxmedApi.reservationDelete(session, reservationId)
}
def login(username: String, password: String): ThrowableOr[LoginResponse] = {
luxmedApi.login(username, textEncryptor.decrypt(password))
override def fullLogin(username: String, encryptedPassword: String): ThrowableOr[Session] = {
val password = textEncryptor.decrypt(encryptedPassword)
for {
r1 <- luxmedApi.login(username, password)
tmpSession = Session(r1.body.accessToken, r1.body.accessToken, r1.cookies)
r2 <- luxmedApi.loginToApp(tmpSession)
cookies = r1.cookies ++ r2.cookies
accessToken = r1.body.accessToken
tokenType = r1.body.tokenType
} yield Session(accessToken, tokenType, cookies)
}
private def left(msg: String) = Left(new RuntimeException(msg))
def getXsrfToken(accountId: Long): ThrowableOr[XsrfToken] = {
withSession(accountId) { session =>
luxmedApi.getForgeryToken(session).map(ft => XsrfToken(ft.body.token, ft.cookies))
}
}
}

View File

@@ -1,18 +1,18 @@
package com.lbs.server.service
import java.time.ZonedDateTime
import com.lbs.api.json.model.IdName
import com.lbs.bot.model.MessageSource
import com.lbs.server.conversation.Book.BookingData
import com.lbs.server.repository.DataRepository
import com.lbs.server.repository.model._
import com.lbs.server.util.ServerModelConverters._
import javax.transaction.Transactional
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import java.time.ZonedDateTime
import javax.transaction.Transactional
@Service
class DataService {

View File

@@ -1,23 +1,24 @@
package com.lbs.server.service
import com.lbs.api.exception.{InvalidLoginOrPasswordException, ServiceIsAlreadyBookedException}
import com.lbs.api.json.model.AvailableVisitsTermPresentation
import com.lbs.api.exception.InvalidLoginOrPasswordException
import com.lbs.api.json.model._
import com.lbs.bot.Bot
import com.lbs.bot.model.{MessageSource, MessageSourceSystem}
import com.lbs.common.{Logger, Scheduler}
import com.lbs.server.lang.Localization
import com.lbs.server.repository.model._
import com.lbs.server.util.DateTimeUtil._
import com.lbs.server.util.ServerModelConverters._
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import java.time.ZonedDateTime
import java.time.{LocalDateTime, ZonedDateTime}
import java.util.concurrent.ScheduledFuture
import javax.annotation.PostConstruct
import scala.collection.mutable
import scala.concurrent.duration._
import scala.util.{Failure, Random}
import scala.util.Random
@Service
class MonitoringService extends Logger {
@@ -49,7 +50,7 @@ class MonitoringService extends Logger {
private var checkedOn: ZonedDateTime = _
def notifyUserAboutTerms(terms: Seq[AvailableVisitsTermPresentation], monitoring: Monitoring): Unit = {
def notifyUserAboutTerms(terms: Seq[TermExt], monitoring: Monitoring): Unit = {
deactivateMonitoring(monitoring.accountId, monitoring.recordId)
val fiveTerms = terms.take(5).zipWithIndex //send only 5 closest terms
@@ -65,9 +66,9 @@ class MonitoringService extends Logger {
private def monitor(monitoring: Monitoring): Unit = {
debug(s"Looking for available terms. Monitoring [#${monitoring.recordId}]")
val dateFrom = optimizeDateFrom(monitoring.dateFrom, monitoring.offset)
val termsEither = apiService.getAvailableTerms(monitoring.accountId, monitoring.payerId, monitoring.cityId, monitoring.clinicId, monitoring.serviceId,
monitoring.doctorId, dateFrom, Some(monitoring.dateTo), timeFrom = monitoring.timeFrom, timeTo = monitoring.timeTo)
val dateFrom = optimizeDateFrom(monitoring.dateFrom.toLocalDateTime, monitoring.offset)
val termsEither = apiService.getAvailableTerms(monitoring.accountId, monitoring.cityId, monitoring.clinicId, monitoring.serviceId,
monitoring.doctorId, dateFrom, monitoring.dateTo.toLocalDateTime, timeFrom = monitoring.timeFrom, timeTo = monitoring.timeTo)
termsEither match {
case Right(terms) =>
if (terms.nonEmpty) {
@@ -92,8 +93,8 @@ class MonitoringService extends Logger {
}
}
private def optimizeDateFrom(date: ZonedDateTime, offset: Int) = {
val nowWithOffset = ZonedDateTime.now().plusHours(offset)
private def optimizeDateFrom(date: LocalDateTime, offset: Int) = {
val nowWithOffset = LocalDateTime.now().plusHours(offset)
if (date.isBefore(nowWithOffset)) nowWithOffset else date
}
@@ -144,13 +145,19 @@ class MonitoringService extends Logger {
dbChecker.schedule(updateMonitorings(), 1.minute)
}
private def bookAppointment(term: AvailableVisitsTermPresentation, monitoring: Monitoring, rebookIfExists: Boolean): Unit = {
apiService.reserveVisit(monitoring.accountId, term).toTry.recoverWith {
case _: ServiceIsAlreadyBookedException if rebookIfExists =>
private def bookAppointment(term: TermExt, monitoring: Monitoring, rebookIfExists: Boolean): Unit = {
val bookingResult = for {
xsrfToken <- apiService.getXsrfToken(monitoring.accountId)
reservationLocktermResponse <- apiService.reservationLockterm(monitoring.accountId, xsrfToken, term.mapTo[ReservationLocktermRequest])
temporaryReservationId = reservationLocktermResponse.value.temporaryReservationId
response <- if (reservationLocktermResponse.value.changeTermAvailable && rebookIfExists) {
info(s"Service [${monitoring.serviceName}] is already booked. Trying to update term")
apiService.updateReservedVisit(monitoring.accountId, term).toTry
case ex => Failure(ex)
}.toEither match {
bookOrUnlockTerm(monitoring.accountId, xsrfToken, temporaryReservationId, apiService.reservationChangeTerm(_, xsrfToken, (reservationLocktermResponse, term).mapTo[ReservationChangetermRequest]))
} else {
bookOrUnlockTerm(monitoring.accountId, xsrfToken, temporaryReservationId, apiService.reservationConfirm(_, xsrfToken, (reservationLocktermResponse, term).mapTo[ReservationConfirmRequest]))
}
} yield response
bookingResult match {
case Right(_) =>
bot.sendMessage(monitoring.source, lang(monitoring.userId).appointmentIsBooked(term, monitoring))
deactivateMonitoring(monitoring.accountId, monitoring.recordId)
@@ -159,6 +166,15 @@ class MonitoringService extends Logger {
}
}
private def bookOrUnlockTerm[T](accountId: Long, xsrfToken: XsrfToken, temporaryReservationId: Long, fn: (Long) => Either[Throwable, T]): Either[Throwable, T] = {
fn(accountId) match {
case r@Left(_) =>
apiService.deleteTemporaryReservation(accountId, xsrfToken, temporaryReservationId)
r
case r => r
}
}
def deactivateMonitoring(accountId: JLong, monitoringId: JLong): Unit = {
val activeMonitoringMaybe = activeMonitorings.remove(monitoringId)
activeMonitoringMaybe match {
@@ -200,11 +216,11 @@ class MonitoringService extends Logger {
val monitoringMaybe = dataService.findMonitoring(accountId, monitoringId)
monitoringMaybe match {
case Some(monitoring) =>
val termsEither = apiService.getAvailableTerms(monitoring.accountId, monitoring.payerId, monitoring.cityId, monitoring.clinicId, monitoring.serviceId,
monitoring.doctorId, monitoring.dateFrom, Some(monitoring.dateTo), timeFrom = monitoring.timeFrom, timeTo = monitoring.timeTo)
val termsEither = apiService.getAvailableTerms(monitoring.accountId, monitoring.cityId, monitoring.clinicId, monitoring.serviceId,
monitoring.doctorId, monitoring.dateFrom.toLocalDateTime, monitoring.dateTo.toLocalDateTime, timeFrom = monitoring.timeFrom, timeTo = monitoring.timeTo)
termsEither match {
case Right(terms) =>
val termMaybe = terms.find(term => term.scheduleId == scheduleId && minutesSinceBeginOf2018(term.visitDate.startDateTime) == time)
val termMaybe = terms.find(term => term.term.scheduleId == scheduleId && minutesSinceBeginOf2018(term.term.dateTimeFrom) == time)
termMaybe match {
case Some(term) =>
bookAppointment(term, monitoring, rebookIfExists = true)

View File

@@ -2,7 +2,7 @@
package com.lbs.server.service
import com.lbs.api.exception.SessionExpiredException
import com.lbs.api.json.model.LoginResponse
import com.lbs.api.http.Session
import com.lbs.common.{Logger, ParametrizedLock}
import com.lbs.server.ThrowableOr
import com.lbs.server.exception.UserNotFoundException
@@ -11,9 +11,7 @@ import scala.collection.mutable
trait SessionSupport extends Logger {
case class Session(accessToken: String, tokenType: String)
def login(username: String, password: String): ThrowableOr[LoginResponse]
def fullLogin(username: String, password: String): ThrowableOr[Session]
protected def dataService: DataService
@@ -27,9 +25,7 @@ trait SessionSupport extends Logger {
def auth: ThrowableOr[Session] = {
val credentialsMaybe = dataService.getCredentials(accountId)
credentialsMaybe match {
case Some(credentials) =>
val loginResponse = login(credentials.username, credentials.password)
loginResponse.map(r => Session(r.accessToken, r.tokenType))
case Some(credentials) => fullLogin(credentials.username, credentials.password)
case None => Left(UserNotFoundException(accountId))
}
}
@@ -67,8 +63,8 @@ trait SessionSupport extends Logger {
} yield result
}
def addSession(accountId: Long, accessToken: String, tokenType: String): Unit =
def addSession(accountId: Long, session: Session): Unit =
lock.obtainLock(accountId).synchronized {
sessions.put(accountId, Session(accessToken, tokenType))
sessions.put(accountId, session)
}
}

View File

@@ -1,10 +1,6 @@
package com.lbs.server
import java.time._
import java.time.format.DateTimeFormatter
import java.util.Locale
import com.lbs.api.json.model._
import com.lbs.bot.model.Command
import com.lbs.common.ModelConverters
@@ -12,6 +8,9 @@ import com.lbs.server.conversation.Book.BookingData
import com.lbs.server.conversation.Login.UserId
import com.lbs.server.repository.model.{History, Monitoring}
import java.time._
import java.time.format.DateTimeFormatter
import java.util.Locale
import scala.language.{higherKinds, implicitConversions}
import scala.util.Try
@@ -38,8 +37,8 @@ package object util {
serviceName = bookingData.serviceId.name,
doctorId = bookingData.doctorId.optionalId,
doctorName = bookingData.doctorId.name,
dateFrom = bookingData.dateFrom,
dateTo = bookingData.dateTo,
dateFrom = bookingData.dateFrom.atZone(DateTimeUtil.Zone),
dateTo = bookingData.dateTo.atZone(DateTimeUtil.Zone),
timeFrom = bookingData.timeFrom,
timeTo = bookingData.timeTo,
autobook = bookingData.autobook,
@@ -48,49 +47,73 @@ package object util {
)
}
implicit val AvailableVisitsTermPresentationToTemporaryReservationRequestConverter:
ObjectConverter[AvailableVisitsTermPresentation, TemporaryReservationRequest] =
(term: AvailableVisitsTermPresentation) => {
TemporaryReservationRequest(
clinicId = term.clinic.id,
implicit val ReservationLocktermResponseAndTermToReservationConfirmRequestConverter:
ObjectConverter[(ReservationLocktermResponse, TermExt), ReservationConfirmRequest] =
(data: (ReservationLocktermResponse, TermExt)) => {
val (reservationLocktermResponse, termExt) = data
val term = termExt.term
ReservationConfirmRequest(
date = term.dateTimeFrom.minusHours(2).toString + ":00.000Z",
doctorId = term.doctor.id,
payerDetailsList = term.payerDetailsList,
referralRequiredByService = term.referralRequiredByService,
facilityId = term.clinicId,
roomId = term.roomId,
serviceId = term.serviceId,
startDateTime = term.visitDate.startDateTime
scheduleId = term.scheduleId,
serviceVariantId = term.serviceId,
temporaryReservationId = reservationLocktermResponse.value.temporaryReservationId,
timeFrom = term.dateTimeFrom.toLocalTime,
valuation = reservationLocktermResponse.value.valuations.head
)
}
implicit val TmpReservationIdWithValuationsToReservationRequestConverter:
ObjectConverter[(Long, VisitTermVariant, AvailableVisitsTermPresentation), ReservationRequest] =
(any: (Long, VisitTermVariant, AvailableVisitsTermPresentation)) => {
val (tmpReservationId, valuations, term) = any
ReservationRequest(
clinicId = term.clinic.id,
doctorId = term.doctor.id,
payerData = valuations.valuationDetail.payerData,
roomId = term.roomId,
serviceId = term.serviceId,
startDateTime = term.visitDate.startDateTime,
temporaryReservationId = tmpReservationId
implicit val ReservationLocktermResponseAndTermToReservationChangeTermRequestConverter:
ObjectConverter[(ReservationLocktermResponse, TermExt), ReservationChangetermRequest] =
(data: (ReservationLocktermResponse, TermExt)) => {
val (reservationLocktermResponse, termExt) = data
val term = termExt.term
val existingReservationId = reservationLocktermResponse.value.relatedVisits.head.reservationId
ReservationChangetermRequest(
existingReservationId = existingReservationId,
term = NewTerm(
date = term.dateTimeFrom.minusHours(2).toString + ":00.000Z",
doctorId = term.doctor.id,
facilityId = term.clinicId,
parentReservationId = existingReservationId,
referralRequired = reservationLocktermResponse.value.valuations.head.isReferralRequired,
roomId = term.roomId,
scheduleId = term.scheduleId,
serviceVariantId = term.serviceId,
temporaryReservationId = reservationLocktermResponse.value.temporaryReservationId,
timeFrom = term.dateTimeFrom.toLocalTime,
valuation = reservationLocktermResponse.value.valuations.head
)
)
}
implicit val AvailableVisitsTermPresentationToValuationRequestConverter:
ObjectConverter[AvailableVisitsTermPresentation, ValuationsRequest] =
(term: AvailableVisitsTermPresentation) => {
ValuationsRequest(
clinicId = term.clinic.id,
implicit val TermToReservationLocktermRequest:
ObjectConverter[TermExt, ReservationLocktermRequest] =
termExt => {
val term = termExt.term
val additionalData = termExt.additionalData
ReservationLocktermRequest(
date = term.dateTimeFrom.minusHours(2).toString + ":00.000Z",
doctor = term.doctor,
doctorId = term.doctor.id,
payerDetailsList = term.payerDetailsList,
referralRequiredByService = term.referralRequiredByService,
facilityId = term.clinicId,
impedimentText = term.impedimentText,
isAdditional = term.isAdditional,
isImpediment = term.isImpediment,
isPreparationRequired = additionalData.isPreparationRequired,
isTelemedicine = term.isTelemedicine,
preparationItems = additionalData.preparationItems,
roomId = term.roomId,
serviceId = term.serviceId,
startDateTime = term.visitDate.startDateTime
scheduleId = term.scheduleId,
serviceVariantId = term.serviceId,
timeFrom = term.dateTimeFrom.toLocalTime.toString,
timeTo = term.dateTimeTo.toLocalTime.toString
)
}
implicit val HistoryToIdNameConverter: ObjectConverter[History, IdName] =
(history: History) => IdName(history.id, history.name)
}
@@ -124,6 +147,8 @@ package object util {
}
object DateTimeUtil {
val Zone: ZoneId = ZoneId.of("Europe/Warsaw")
private val DateFormat: Locale => DateTimeFormatter = locale => DateTimeFormatter.ofPattern("dd MMM yyyy", locale)
private val DateShortFormat = DateTimeFormatter.ofPattern("dd-MM")
@@ -132,21 +157,25 @@ package object util {
private val DateTimeFormat: Locale => DateTimeFormatter = locale => DateTimeFormatter.ofPattern("EEE',' dd MMM yyyy',' HH:mm", locale)
def formatDate(date: LocalDateTime, locale: Locale): String = date.format(DateFormat(locale))
def formatDate(date: ZonedDateTime, locale: Locale): String = date.format(DateFormat(locale))
def formatDateShort(date: ZonedDateTime): String = date.format(DateShortFormat)
def formatDateShort(date: LocalDateTime): String = date.format(DateShortFormat)
def formatTime(time: LocalTime): String = time.format(TimeFormat)
def formatDateTime(date: ZonedDateTime, locale: Locale): String = date.format(DateTimeFormat(locale))
private val EpochMinutesTillBeginOf2018: Long = epochMinutes(ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()))
def formatDateTime(date: LocalDateTime, locale: Locale): String = date.format(DateTimeFormat(locale))
def epochMinutes(time: ZonedDateTime): Long = time.toInstant.getEpochSecond / 60
private val EpochMinutesTillBeginOf2022: Long = epochMinutes(LocalDateTime.of(2022, 1, 1, 0, 0, 0, 0))
def minutesSinceBeginOf2018(time: ZonedDateTime): Long = epochMinutes(time) - EpochMinutesTillBeginOf2018
def epochMinutes(time: LocalDateTime): Long = time.toInstant(ZonedDateTime.now().getOffset).getEpochSecond / 60
def applyDayMonth(dayMonthStr: String, date: ZonedDateTime): ZonedDateTime = {
def minutesSinceBeginOf2018(time: LocalDateTime): Long = epochMinutes(time) - EpochMinutesTillBeginOf2022
def applyDayMonth(dayMonthStr: String, date: LocalDateTime): LocalDateTime = {
val dayMonth = MonthDay.parse(dayMonthStr, DateShortFormat)
val newDate = date.withDayOfMonth(dayMonth.getDayOfMonth).withMonth(dayMonth.getMonthValue)

View File

@@ -1,9 +1,6 @@
package com.lbs.server.repository
import java.time.ZonedDateTime
import com.lbs.server.repository.model.{CityHistory, ClinicHistory, Credentials, DoctorHistory, ServiceHistory}
import javax.persistence.EntityManager
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -13,6 +10,9 @@ import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.test.context.junit4.SpringRunner
import java.time.ZonedDateTime
import javax.persistence.EntityManager
object DataRepositorySpec {
@TestConfiguration