Use scalafmt

This commit is contained in:
Michał Siatkowski
2022-08-10 00:04:19 +02:00
committed by Yevhen Zadyra
parent 327bdfe6d7
commit e870e404e1
105 changed files with 2255 additions and 1582 deletions

View File

@@ -13,7 +13,7 @@ steps:
GRADLE_USER_HOME: ~/.gradle
command: [ /bin/bash ]
commands:
- ./gradlew test
- ./gradlew checkScalafmtAll test
- ./gradlew prepare
- echo -n "1.0.$DRONE_BUILD_NUMBER,latest" > .tags
- echo -n "1.0.$DRONE_BUILD_NUMBER" > ./docker/version

11
.scalafmt.conf Normal file
View File

@@ -0,0 +1,11 @@
version = 3.5.8
runner.dialect = scala213
preset = IntelliJ
maxColumn = 120
project.git = true
align.preset = some
docstrings.style = SpaceAsterisk
docstrings.oneline = fold
docstrings.wrap = no
docstrings.blankFirstLine = yes

View File

@@ -32,7 +32,7 @@ to the same folder and rename it to **secrets.env**
### Develop run
1. Run `docker-compose up` to launch PostgreSQL database
2. Set env `TELEGRAM_TOKEN="YOUR_TOKEN"`
2. Set env `TELEGRAM_TOKEN=YOUR_TOKEN`
3. Run `Boot.scala` app

View File

@@ -1,4 +1,3 @@
package com.lbs.api
import com.lbs.api.http.Session
@@ -7,9 +6,11 @@ import scalaj.http.{BaseHttp, HttpRequest}
import java.net.HttpCookie
object ApiHttp extends BaseHttp(
userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
)
object ApiHttp
extends BaseHttp(
userAgent =
"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
)
trait ApiBase {
private val CommonHeaders =
@@ -22,13 +23,11 @@ trait ApiBase {
`Accept-Language` -> "pl;q=1.0, pl;q=0.9, en;q=0.8"
)
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)

View File

@@ -1,30 +1,28 @@
package com.lbs.api
import cats.implicits.toFunctorOps
import com.lbs.api.http._
import com.lbs.api.http.headers._
import com.lbs.api.json.JsonSerializer.extensions._
import com.lbs.api.json.model.{EventsResponse, TermsIndexResponse, _}
import com.lbs.api.json.model._
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 {
class LuxmedApi[F[_]: ThrowableMonad] extends ApiBase {
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[HttpResponse[LoginResponse]] = {
val request = httpUnauthorized("token").
header(`Content-Type`, "application/x-www-form-urlencoded").
header(`x-api-client-identifier`, clientId).
param("client_id", clientId).
param("grant_type", "password").
param("password", password).
param("username", username)
val request = httpUnauthorized("token")
.header(`Content-Type`, "application/x-www-form-urlencoded")
.header(`x-api-client-identifier`, clientId)
.param("client_id", clientId)
.param("grant_type", "password")
.param("password", password)
.param("username", username)
post[LoginResponse](request)
}
@@ -39,102 +37,139 @@ class LuxmedApi[F[_] : ThrowableMonad] extends ApiBase {
get[ForgeryTokenResponse](request)
}
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").
param("filter.filterDateFrom", dateFormatEvents.format(fromDate)).
param("filter.filterDateTo", dateFormatEvents.format(toDate))
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")
.param("filter.filterDateFrom", dateFormatEvents.format(fromDate))
.param("filter.filterDateTo", dateFormatEvents.format(toDate))
get[EventsResponse](request).map(_.body)
}
def dictionaryCities(session: Session): F[List[DictionaryCity]] = {
val request = httpNewApi("NewPortal/Dictionary/cities", session).
header(`Content-Type`, "application/json")
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")
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").
param("cityId", cityId.map(_.toString)).
param("serviceVariantId", serviceVariantId.map(_.toString))
def dictionaryFacilitiesAndDoctors(
session: Session,
cityId: Option[Long],
serviceVariantId: Option[Long]
): F[FacilitiesAndDoctors] = {
val request = httpNewApi("NewPortal/Dictionary/facilitiesAndDoctors", session)
.header(`Content-Type`, "application/json")
.param("cityId", cityId.map(_.toString))
.param("serviceVariantId", serviceVariantId.map(_.toString))
get[FacilitiesAndDoctors](request).map(_.body)
}
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").
param("cityId", cityId.toString).
param("serviceVariantId", serviceId.toString).
param("languageId", languageId.toString).
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)
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")
.param("cityId", cityId.toString)
.param("serviceVariantId", serviceId.toString)
.param("languageId", languageId.toString)
.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 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")
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(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")
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")
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")
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")
val request = http(s"events/Visit/$reservationId", session).header(`Content-Type`, "application/json")
delete(request)
}
private def get[T <: SerializableJsonObject](request: HttpRequest)(implicit mf: scala.reflect.Manifest[T]): F[HttpResponse[T]] = {
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]))
}
private def getList[T <: SerializableJsonObject](request: HttpRequest)(implicit mf: scala.reflect.Manifest[T]): F[HttpResponse[List[T]]] = {
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]))
}
private def getVoid[T <: SerializableJsonObject](request: HttpRequest)(implicit mf: scala.reflect.Manifest[T]): F[HttpResponse[Unit]] = {
private def getVoid[T <: SerializableJsonObject](
request: HttpRequest
)(implicit mf: scala.reflect.Manifest[T]): F[HttpResponse[Unit]] = {
request.invoke.map(r => r.copy(body = {}))
}
private def post[T <: SerializableJsonObject](request: HttpRequest, bodyOpt: Option[SerializableJsonObject] = None)(implicit mf: scala.reflect.Manifest[T]): F[HttpResponse[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
case None => request.postForm
}
postRequest.invoke.map(r => r.copy(body = r.body.as[T]))
}
@@ -142,7 +177,7 @@ class LuxmedApi[F[_] : ThrowableMonad] extends ApiBase {
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
case None => request.postForm
}
postRequest.invoke.void
}

View File

@@ -1,4 +1,3 @@
package com.lbs.api.exception
abstract class ApiException(message: String) extends Exception(s"Luxmed API exception: $message")

View File

@@ -1,4 +1,3 @@
package com.lbs.api.exception
case class GenericException(code: Int, message: String) extends ApiException(message) {

View File

@@ -1,4 +1,3 @@
package com.lbs.api.exception
class InvalidLoginOrPasswordException extends ApiException("Invalid login or password")

View File

@@ -1,4 +1,3 @@
package com.lbs.api.exception
class SessionExpiredException extends ApiException("Session expired")

View File

@@ -1,4 +1,3 @@
package com.lbs.api
import cats.MonadError
@@ -33,7 +32,7 @@ package object http extends StrictLogging {
private val SensitiveHeaders = List("passw", "access_token", "refresh_token", "authorization")
implicit class ExtendedHttpRequest[F[_] : ThrowableMonad](httpRequest: HttpRequest) {
implicit class ExtendedHttpRequest[F[_]: ThrowableMonad](httpRequest: HttpRequest) {
def invoke: F[HttpResponse[String]] = {
val me = MonadError[F, Throwable]
logger.debug(s"Sending request:\n${hideSensitive(httpRequest)}")
@@ -75,7 +74,9 @@ package object http extends StrictLogging {
code match {
case HttpURLConnection.HTTP_MOVED_TEMP if httpResponse.header("Location").exists(_.contains("/LogOn")) =>
Some(new SessionExpiredException)
case HttpURLConnection.HTTP_CONFLICT if lowercasedBody.contains("nieprawidłowy login lub hasło") || lowercasedBody.contains("invalid login or password") =>
case HttpURLConnection.HTTP_CONFLICT
if lowercasedBody
.contains("nieprawidłowy login lub hasło") || lowercasedBody.contains("invalid login or password") =>
Some(new InvalidLoginOrPasswordException)
case _ =>
Try(body.as[LuxmedErrorsMap])

View File

@@ -1,4 +1,3 @@
package com.lbs.api.json
import com.lbs.api.json.model.{LuxmedFunnyDateTime, SerializableJsonObject}
@@ -10,43 +9,58 @@ import java.time.format.DateTimeFormatter
import java.time.{LocalDateTime, LocalTime, ZonedDateTime}
import scala.util.Try
object JsonSerializer extends StrictLogging {
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 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 val localTimeSerializer = new CustomSerializer[LocalTime](_ => ( {
case JString(str) => LocalTime.parse(str)
}, {
case localTime: LocalTime => JString(localTime.toString)
}
))
private val localTimeSerializer = new CustomSerializer[LocalTime](_ =>
(
{ case JString(str) =>
LocalTime.parse(str)
},
{ case localTime: LocalTime =>
JString(localTime.toString)
}
)
)
private val localDateTimeSerializer = new CustomSerializer[LocalDateTime](_ => ( {
case JString(str) => LocalDateTime.parse(str)
}, {
case localTime: LocalDateTime => JString(localTime.toString)
}
))
private val localDateTimeSerializer = new CustomSerializer[LocalDateTime](_ =>
(
{ case JString(str) =>
LocalDateTime.parse(str)
},
{ case localTime: LocalDateTime =>
JString(localTime.toString)
}
)
)
private val luxmedFunnyDateTimeSerializer = new CustomSerializer[LuxmedFunnyDateTime](_ => ( {
case JString(str) =>
Try(LocalDateTime.parse(str))
.map(v => LuxmedFunnyDateTime(dateTimeLocal = Some(v)))
.recoverWith{case _ => Try(ZonedDateTime.parse(str)).map(v => LuxmedFunnyDateTime(dateTimeTz = Some(v)))}
.getOrElse(sys.error(s"can't parse date $str"))
}, {
case time: LocalDateTime => JString(time.toString)
case time: ZonedDateTime => JString(time.toString)
}
))
private val luxmedFunnyDateTimeSerializer = new CustomSerializer[LuxmedFunnyDateTime](_ =>
(
{ case JString(str) =>
Try(LocalDateTime.parse(str))
.map(v => LuxmedFunnyDateTime(dateTimeLocal = Some(v)))
.recoverWith { case _ => Try(ZonedDateTime.parse(str)).map(v => LuxmedFunnyDateTime(dateTimeTz = Some(v))) }
.getOrElse(sys.error(s"can't parse date $str"))
},
{
case time: LocalDateTime => JString(time.toString)
case time: ZonedDateTime => JString(time.toString)
}
)
)
private implicit val formats: Formats = DefaultFormats.withStrictArrayExtraction + zonedDateTimeSerializer + localTimeSerializer + localDateTimeSerializer + luxmedFunnyDateTimeSerializer
private implicit val formats: Formats =
DefaultFormats.withStrictArrayExtraction + zonedDateTimeSerializer + localTimeSerializer + localDateTimeSerializer + luxmedFunnyDateTimeSerializer
def extract[T](jsonString: String)(implicit mf: scala.reflect.Manifest[T]): T = {
parse(jsonString).camelizeKeys.extract[T]

View File

@@ -1,21 +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
* [
* {
* "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

@@ -1,175 +1,181 @@
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)
* [
* {
* "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

@@ -1,19 +1,26 @@
package com.lbs.api.json.model
/**
* {
"academicTitle": "dr n. med.",
"facilityGroupIds": [
78
],
"firstName": "TARAS",
"id": 11111,
"isEnglishSpeaker": true,
"lastName": "SHEVCHENKO"
}
*/
* {
* "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 {
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

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

View File

@@ -1,108 +1,114 @@
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"
*}
* {
* "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 Event(
date: ZonedDateTime,
clinic: Option[EventClinic],
doctor: EventDoctor,
eventId: Long,
status: String,
title: String
)
case class EventClinic(address: String, city: String)

View File

@@ -1,42 +1,40 @@
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"
}
]
}
*
*/
* {
* "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

@@ -1,4 +1,3 @@
package com.lbs.api.json.model
/**

View File

@@ -1,4 +1,3 @@
package com.lbs.api.json.model
/**
@@ -9,4 +8,5 @@ package com.lbs.api.json.model
* "token_type": "bearer"
* }
*/
case class LoginResponse(accessToken: String, expiresIn: Int, refreshToken: String, tokenType: String) extends SerializableJsonObject
case class LoginResponse(accessToken: String, expiresIn: Int, refreshToken: String, tokenType: String)
extends SerializableJsonObject

View File

@@ -1,4 +1,3 @@
package com.lbs.api.json.model
trait LuxmedBaseError {

View File

@@ -1,4 +1,3 @@
package com.lbs.api.json.model
case class LuxmedError(message: String) extends SerializableJsonObject with LuxmedBaseError

View File

@@ -1,4 +1,3 @@
package com.lbs.api.json.model
case class LuxmedErrorsMap(errors: Map[String, List[String]]) extends SerializableJsonObject with LuxmedBaseError {

View File

@@ -1,43 +1,51 @@
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
*}
*}
* {
* "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
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

@@ -1,38 +1,45 @@
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
*}
* {
* "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
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

@@ -1,25 +1,34 @@
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": []
}
* {
* "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 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
case class ReservationConfirmValue(
canSelfConfirm: Boolean,
eventType: Long,
isTelemedicine: Boolean,
npsToken: String,
reservationId: Long,
serviceInstanceId: Long
) extends SerializableJsonObject

View File

@@ -1,42 +1,54 @@
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"
*}
* {
* "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
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

@@ -1,74 +1,88 @@
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": []
*}
* {
* "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 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 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)
case class RelatedVisit(
doctor: Doctor,
facilityName: String,
isTelemedicine: Boolean,
reservationId: Long,
timeFrom: LocalTime,
timeTo: LocalTime
)

View File

@@ -1,4 +1,3 @@
package com.lbs.api.json.model
trait SerializableJsonObject

View File

@@ -1,124 +1,122 @@
package com.lbs.api.json.model
import java.time.{LocalDateTime, ZonedDateTime}
/**
*
* {
* "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", or 2021-05-21T18:45:00+02:00 sometimes!!!!
*"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
*}
*]
*}
*}
*
*/
* {
* "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", or 2021-05-21T18:45:00+02:00 sometimes!!!!
* "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 TermsForService(additionalData: AdditionalData, termsForDays: List[TermsForDay])
extends SerializableJsonObject
case class PreparationItem(header: Option[String], text: Option[String])
@@ -126,13 +124,23 @@ case class AdditionalData(isPreparationRequired: Boolean, preparationItems: List
case class TermsForDay(day: LuxmedFunnyDateTime, terms: List[Term]) extends SerializableJsonObject
case class Term(clinic: String, clinicId: Long, dateTimeFrom: LuxmedFunnyDateTime, dateTimeTo: LuxmedFunnyDateTime, doctor: Doctor,
impedimentText: String, isAdditional: Boolean, isImpediment: Boolean, isTelemedicine: Boolean, roomId: Long,
scheduleId: Long, serviceId: Long) extends SerializableJsonObject
case class Term(
clinic: String,
clinicId: Long,
dateTimeFrom: LuxmedFunnyDateTime,
dateTimeTo: LuxmedFunnyDateTime,
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
case class TermExt(additionalData: AdditionalData, term: Term) extends SerializableJsonObject
case class LuxmedFunnyDateTime(dateTimeTz: Option[ZonedDateTime] = None, dateTimeLocal: Option[LocalDateTime] = None) {
def get: LocalDateTime = dateTimeLocal.getOrElse(dateTimeTz.map(_.toLocalDateTime).get)
}

View File

@@ -1,23 +1,31 @@
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
}
*/
* {
* "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: Option[Long], productId: Long,
productInContractId: Long, requireReferralForPP: Boolean, valuationType: Long) {
}
case class Valuation(
alternativePrice: Option[String],
contractId: Long,
isExternalReferralAllowed: Boolean,
isReferralRequired: Boolean,
payerId: Long,
price: Double,
productElementId: Option[Long],
productId: Long,
productInContractId: Long,
requireReferralForPP: Boolean,
valuationType: Long
) {}

View File

@@ -1,4 +1,3 @@
package com.lbs
import cats.MonadError

View File

@@ -27,11 +27,17 @@ class ExtendedHttpRequestSpec extends AnyFunSuite with Matchers with MockitoSuga
}
test("error response") {
val errorResponse = HttpResponse("""{"Errors":{"ToDate.Date":["'To Date. Date' must be greater than or equal to '06/04/2018 00:00:00'."]}}""", 200, Map())
val errorResponse = HttpResponse(
"""{"Errors":{"ToDate.Date":["'To Date. Date' must be greater than or equal to '06/04/2018 00:00:00'."]}}""",
200,
Map()
)
when(request.asString).thenReturn(errorResponse)
val result = invoke(request)
assert(result == Left(GenericException(200, "'To Date. Date' must be greater than or equal to '06/04/2018 00:00:00'.")))
assert(
result == Left(GenericException(200, "'To Date. Date' must be greater than or equal to '06/04/2018 00:00:00'."))
)
}
private def invoke(request: HttpRequest) = {

View File

@@ -1,11 +1,10 @@
package com.lbs.bot
import com.lbs.bot.model._
import com.lbs.bot.telegram.TelegramBot
import com.typesafe.scalalogging.LazyLogging
class Bot(telegram: TelegramBot /* other bots */) extends LazyLogging {
class Bot(telegram: TelegramBot /* other bots */ ) extends LazyLogging {
def sendMessage(source: MessageSource, text: String): Unit =
resolveAdapter(source).sendMessage(source.chatId, text)
@@ -15,7 +14,12 @@ class Bot(telegram: TelegramBot /* other bots */) extends LazyLogging {
def sendEditMessage(source: MessageSource, messageId: String, inlineKeyboard: Option[InlineKeyboard]): Unit =
resolveAdapter(source).sendEditMessage(source.chatId, messageId, inlineKeyboard)
def sendEditMessage(source: MessageSource, messageId: String, text: String, inlineKeyboard: Option[InlineKeyboard] = None): Unit =
def sendEditMessage(
source: MessageSource,
messageId: String,
text: String,
inlineKeyboard: Option[InlineKeyboard] = None
): Unit =
resolveAdapter(source).sendEditMessage(source.chatId, messageId, text, inlineKeyboard)
def sendFile(source: MessageSource, filename: String, contents: Array[Byte], caption: Option[String] = None): Unit =

View File

@@ -1,4 +1,3 @@
package com.lbs.bot
import com.lbs.bot.model.{Event, InlineKeyboard}

View File

@@ -1,4 +1,3 @@
package com.lbs.bot
import com.lbs.bot.model.Event

View File

@@ -1,4 +1,3 @@
package com.lbs.bot.model
object Button {

View File

@@ -1,4 +1,3 @@
package com.lbs.bot.model
case class Message(messageId: String, text: Option[String] = None)

View File

@@ -1,4 +1,3 @@
package com.lbs.bot.model
trait Event

View File

@@ -1,4 +1,3 @@
package com.lbs.bot.model
case class InlineKeyboard(buttons: Seq[Seq[Button]])

View File

@@ -1,4 +1,3 @@
package com.lbs.bot.model
case class MessageSource(sourceSystem: MessageSourceSystem, chatId: String)

View File

@@ -1,4 +1,3 @@
package com.lbs.bot.model
trait MessageSourceSystem {
@@ -10,10 +9,7 @@ trait MessageSourceSystem {
}
object MessageSourceSystem {
val MessageSourceSystems: Seq[MessageSourceSystem] = Seq(
TelegramMessageSourceSystem,
FacebookMessageSourceSystem
)
val MessageSourceSystems: Seq[MessageSourceSystem] = Seq(TelegramMessageSourceSystem, FacebookMessageSourceSystem)
private val MessageSourceSystemsMap = MessageSourceSystems.map(e => e.id -> e).toMap

View File

@@ -1,4 +1,3 @@
package com.lbs
import com.lbs.bot.model.{Button, InlineKeyboard}

View File

@@ -1,4 +1,3 @@
package com.lbs.bot.telegram
import com.bot4s.telegram.models.InlineKeyboardMarkup
@@ -18,10 +17,19 @@ class TelegramBot(onCommand: Command => Unit, botToken: String) extends PollBot[
telegramBot.sendMessage(chatId.toLong, text, replyMarkup = buttons.map(_.mapTo[InlineKeyboardMarkup]))
def sendEditMessage(chatId: String, messageId: String, buttons: Option[InlineKeyboard]): Unit =
telegramBot.sendEditMessage(chatId.toLong, messageId.toInt, replyMarkup = buttons.map(_.mapTo[InlineKeyboardMarkup]))
telegramBot.sendEditMessage(
chatId.toLong,
messageId.toInt,
replyMarkup = buttons.map(_.mapTo[InlineKeyboardMarkup])
)
def sendEditMessage(chatId: String, messageId: String, text: String, buttons: Option[InlineKeyboard] = None): Unit =
telegramBot.sendEditMessage(chatId.toLong, messageId.toInt, text, replyMarkup = buttons.map(_.mapTo[InlineKeyboardMarkup]))
telegramBot.sendEditMessage(
chatId.toLong,
messageId.toInt,
text,
replyMarkup = buttons.map(_.mapTo[InlineKeyboardMarkup])
)
def sendFile(chatId: String, filename: String, contents: Array[Byte], caption: Option[String] = None): Unit =
telegramBot.sendFile(chatId.toLong, filename, contents, caption)

View File

@@ -1,4 +1,3 @@
package com.lbs.bot.telegram
import cats.implicits.toFunctorOps
@@ -12,7 +11,13 @@ import com.typesafe.scalalogging.StrictLogging
import scala.concurrent.Future
class TelegramClient(onReceive: TelegramEvent => Unit, botToken: String) extends AkkaTelegramBot with TelegramBoT with Polling with Commands[Future] with Callbacks[Future] with StrictLogging {
class TelegramClient(onReceive: TelegramEvent => Unit, botToken: String)
extends AkkaTelegramBot
with TelegramBoT
with Polling
with Commands[Future]
with Callbacks[Future]
with StrictLogging {
override val client: RequestHandler[Future] = new AkkaHttpClient(botToken)
@@ -22,11 +27,28 @@ class TelegramClient(onReceive: TelegramEvent => Unit, botToken: String) extends
def sendMessage(chatId: Long, text: String, replyMarkup: Option[InlineKeyboardMarkup] = None): Future[Message] =
loggingRequest(SendMessage(chatId, text, parseMode = Some(ParseMode.HTML), replyMarkup = replyMarkup))
def sendEditMessage(chatId: Long, messageId: Int, replyMarkup: Option[InlineKeyboardMarkup]): Future[Either[Boolean, Message]] =
def sendEditMessage(
chatId: Long,
messageId: Int,
replyMarkup: Option[InlineKeyboardMarkup]
): Future[Either[Boolean, Message]] =
loggingRequest(EditMessageReplyMarkup(Some(chatId), Some(messageId), replyMarkup = replyMarkup))
def sendEditMessage(chatId: Long, messageId: Int, text: String, replyMarkup: Option[InlineKeyboardMarkup] = None): Future[Either[Boolean, Message]] =
loggingRequest(EditMessageText(Some(chatId), Some(messageId), text = text, parseMode = Some(ParseMode.HTML), replyMarkup = replyMarkup))
def sendEditMessage(
chatId: Long,
messageId: Int,
text: String,
replyMarkup: Option[InlineKeyboardMarkup] = None
): Future[Either[Boolean, Message]] =
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 = caption))
@@ -36,7 +58,6 @@ class TelegramClient(onReceive: TelegramEvent => Unit, botToken: String) extends
request(req)
}
override def receiveMessage(msg: Message): Future[Unit] = {
logger.debug(s"Received telegram message: $msg")
Future.successful(onReceive(TelegramEvent(msg, None)))

View File

@@ -1,4 +1,3 @@
package com.lbs.bot.telegram
import com.bot4s.telegram.models.Message

View File

@@ -1,4 +1,3 @@
package com.lbs.bot
import com.bot4s.telegram.models.{InlineKeyboardButton, InlineKeyboardMarkup, Message => BMessage}
@@ -10,8 +9,7 @@ package object telegram {
protected[bot] val TagPrefix = "callback"
object TelegramModelConverters extends ModelConverters {
implicit val TelegramCommandToCommandConverter:
ObjectConverter[TelegramEvent, Command] =
implicit val TelegramCommandToCommandConverter: ObjectConverter[TelegramEvent, Command] =
(data: TelegramEvent) => {
Command(
source = MessageSource(TelegramMessageSourceSystem, data.msg.chat.id.toString),
@@ -20,14 +18,12 @@ package object telegram {
)
}
implicit val TelegramMessageToMessageConverter:
ObjectConverter[BMessage, Message] =
implicit val TelegramMessageToMessageConverter: ObjectConverter[BMessage, Message] =
(data: BMessage) => {
Message(data.messageId.toString, data.text)
}
implicit val InlineKeyboardToInlineKeyboardMarkup:
ObjectConverter[InlineKeyboard, InlineKeyboardMarkup] =
implicit val InlineKeyboardToInlineKeyboardMarkup: ObjectConverter[InlineKeyboard, InlineKeyboardMarkup] =
(inlineKeyboard: InlineKeyboard) => {
val buttons = inlineKeyboard.buttons.map { row =>
row.map(createInlineKeyboardButton)
@@ -37,7 +33,7 @@ package object telegram {
private def createInlineKeyboardButton(button: Button) = {
button match {
case b: TaggedButton => InlineKeyboardButton.callbackData(b.label, tag(b.tag))
case b: TaggedButton => InlineKeyboardButton.callbackData(b.label, tag(b.tag))
case b: LabeledButton => InlineKeyboardButton.callbackData(b.label, b.label)
}
}

View File

@@ -1,6 +1,7 @@
plugins {
id "com.github.maiflai.scalatest" version "0.32"
id "org.springframework.boot" version "2.7.2"
id "cz.alenkacz.gradle.scalafmt" version "1.16.2"
}
allprojects {
@@ -17,7 +18,7 @@ allprojects {
compileOnly "org.scala-lang:scala-library:$scala.$scala_minor"
testImplementation "org.scalatest:scalatest_$scala:3.2.11"
testRuntimeOnly 'com.vladsch.flexmark:flexmark-all:0.62.2' // version depends on scalatest dependency
testRuntimeOnly "com.vladsch.flexmark:flexmark-all:0.62.2" // version depends on scalatest dependency
testImplementation "org.scalatestplus:mockito-4-2_$scala:3.2.11.0"
testImplementation "org.pegdown:pegdown:1.6.0"

View File

@@ -1,4 +1,3 @@
package com.lbs.common
import java.util.Optional

View File

@@ -1,7 +1,6 @@
package com.lbs.common
import scala.collection.{BuildFrom, IterableOps}
import scala.collection.IterableOps
import scala.language.implicitConversions
trait ModelConverters {
@@ -14,8 +13,9 @@ trait ModelConverters {
def mapTo[To](implicit converter: ObjectConverter[From, To]): To = converter.convert(anyRef)
}
implicit def sequenceConverters[From, To, Col[+X] <: IterableOps[X, Col, Col[X]]]
(implicit objectConverter: ObjectConverter[From, To]): ObjectConverter[Col[From], Col[To]] =
implicit def sequenceConverters[From, To, Col[+X] <: IterableOps[X, Col, Col[X]]](implicit
objectConverter: ObjectConverter[From, To]
): ObjectConverter[Col[From], Col[To]] =
(col: Col[From]) => col.map(objectConverter.convert)
}

View File

@@ -1,4 +1,3 @@
package com.lbs.common
import java.util.concurrent.ConcurrentHashMap

View File

@@ -1,4 +1,3 @@
package com.lbs.common
import com.typesafe.scalalogging.LazyLogging
@@ -18,13 +17,12 @@ class Scheduler(poolSize: Int) extends LazyLogging {
scheduledThreadPool.scheduleAtFixedRate(silentFn(fn), delay.length, period.length, period.unit)
}
private def silentFn(fn: => Unit): Runnable = {
() =>
try {
fn
} catch {
case ex: Exception =>
logger.error(s"Unable to execute scheduler task", ex)
}
private def silentFn(fn: => Unit): Runnable = { () =>
try {
fn
} catch {
case ex: Exception =>
logger.error(s"Unable to execute scheduler task", ex)
}
}
}

View File

@@ -1,11 +1,9 @@
package com.lbs.server
import com.typesafe.scalalogging.StrictLogging
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
@SpringBootApplication
class Boot

View File

@@ -1,4 +1,3 @@
package com.lbs.server
import akka.actor.ActorSystem
@@ -44,23 +43,45 @@ class BootConfig {
}
@Bean
def authFactory: MessageSourceTo[Auth] = source => new Auth(source,
dataService, unauthorizedHelpFactory, loginFactory, chatFactory)(actorSystem)
def authFactory: MessageSourceTo[Auth] = source =>
new Auth(source, dataService, unauthorizedHelpFactory, loginFactory, chatFactory)(actorSystem)
@Bean
def loginFactory: MessageSourceWithOriginatorTo[Login] = (source, originator) => new Login(source, bot,
dataService, apiService, textEncryptor, localization, originator)(actorSystem)
def loginFactory: MessageSourceWithOriginatorTo[Login] = (source, originator) =>
new Login(source, bot, dataService, apiService, textEncryptor, localization, originator)(actorSystem)
@Bean
def bookFactory: UserIdTo[Book] = userId => new Book(userId, bot, apiService, dataService,
monitoringService, localization, datePickerFactory, timePickerFactory, staticDataFactory, termsPagerFactory)(actorSystem)
def bookFactory: UserIdTo[Book] = userId =>
new Book(
userId,
bot,
apiService,
dataService,
monitoringService,
localization,
datePickerFactory,
timePickerFactory,
staticDataFactory,
termsPagerFactory
)(actorSystem)
@Bean
def bookWithTemplateFactory: UserIdTo[BookWithTemplate] = userId => new BookWithTemplate(userId, bot, apiService, dataService,
monitoringService, localization, datePickerFactory, timePickerFactory, termsPagerFactory)(actorSystem)
def bookWithTemplateFactory: UserIdTo[BookWithTemplate] = userId =>
new BookWithTemplate(
userId,
bot,
apiService,
dataService,
monitoringService,
localization,
datePickerFactory,
timePickerFactory,
termsPagerFactory
)(actorSystem)
@Bean
def unauthorizedHelpFactory: MessageSourceTo[UnauthorizedHelp] = source => new UnauthorizedHelp(source, bot)(actorSystem)
def unauthorizedHelpFactory: MessageSourceTo[UnauthorizedHelp] = source =>
new UnauthorizedHelp(source, bot)(actorSystem)
@Bean
def helpFactory: UserIdTo[Help] = userId => new Help(userId, bot, localization)(actorSystem)
@@ -71,7 +92,15 @@ class BootConfig {
@Bean
def monitoringsHistoryFactory: UserIdTo[MonitoringsHistory] =
userId => new MonitoringsHistory(userId, bot, monitoringService, localization, monitoringsHistoryPagerFactory, bookWithTemplateFactory)(actorSystem)
userId =>
new MonitoringsHistory(
userId,
bot,
monitoringService,
localization,
monitoringsHistoryPagerFactory,
bookWithTemplateFactory
)(actorSystem)
@Bean
def historyFactory: UserIdTo[HistoryViewer] =
@@ -91,8 +120,20 @@ class BootConfig {
@Bean
def chatFactory: UserIdTo[Chat] =
userId => new Chat(userId, dataService, monitoringService, bookFactory, helpFactory,
monitoringsFactory, monitoringsHistoryFactory, historyFactory, reservedVisitsFactory, settingsFactory, accountFactory)(actorSystem)
userId =>
new Chat(
userId,
dataService,
monitoringService,
bookFactory,
helpFactory,
monitoringsFactory,
monitoringsHistoryFactory,
historyFactory,
reservedVisitsFactory,
settingsFactory,
accountFactory
)(actorSystem)
@Bean
def datePickerFactory: UserIdWithOriginatorTo[DatePicker] = (userId, originator) =>
@@ -108,39 +149,63 @@ class BootConfig {
@Bean
def termsPagerFactory: UserIdWithOriginatorTo[Pager[TermExt]] = (userId, originator) =>
new Pager[TermExt](userId, bot,
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)
Some("book"),
localization,
originator
)(actorSystem)
@Bean
def reservedVisitsPagerFactory: UserIdWithOriginatorTo[Pager[Event]] = (userId, originator) =>
new Pager[Event](userId, bot,
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)
Some("cancel"),
localization,
originator
)(actorSystem)
@Bean
def historyPagerFactory: UserIdWithOriginatorTo[Pager[Event]] = (userId, originator) =>
new Pager[Event](userId, bot,
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)
None,
localization,
originator
)(actorSystem)
@Bean
def monitoringsPagerFactory: UserIdWithOriginatorTo[Pager[Monitoring]] = (userId, originator) =>
new Pager[Monitoring](userId, bot,
new Pager[Monitoring](
userId,
bot,
(monitoring: Monitoring, page: Int, index: Int) => lang(userId).monitoringEntry(monitoring, page, index),
(page: Int, pages: Int) => lang(userId).monitoringsHeader(page, pages),
Some("cancel"), localization, originator)(actorSystem)
Some("cancel"),
localization,
originator
)(actorSystem)
@Bean
def monitoringsHistoryPagerFactory: UserIdWithOriginatorTo[Pager[Monitoring]] = (userId, originator) =>
new Pager[Monitoring](userId, bot,
new Pager[Monitoring](
userId,
bot,
(monitoring: Monitoring, page: Int, index: Int) => lang(userId).monitoringHistoryEntry(monitoring, page, index),
(page: Int, pages: Int) => lang(userId).monitoringsHistoryHeader(page, pages),
Some("repeat"), localization, originator)(actorSystem)
Some("repeat"),
localization,
originator
)(actorSystem)
@Bean
def router: Router = new Router(authFactory)(actorSystem)

View File

@@ -1,9 +1,8 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
import com.lbs.bot._
import com.lbs.bot.model.Button
import com.lbs.bot.{Bot, _}
import com.lbs.server.conversation.Account._
import com.lbs.server.conversation.Login._
import com.lbs.server.conversation.base.Conversation
@@ -11,27 +10,37 @@ import com.lbs.server.lang.{Localizable, Localization}
import com.lbs.server.service.DataService
import com.lbs.server.util.MessageExtractors.CallbackCommand
class Account(val userId: UserId, bot: Bot, dataService: DataService, val localization: Localization, router: Router)(val actorSystem: ActorSystem) extends Conversation[Unit] with Localizable {
class Account(val userId: UserId, bot: Bot, dataService: DataService, val localization: Localization, router: Router)(
val actorSystem: ActorSystem
) extends Conversation[Unit]
with Localizable {
entryPoint(askAction)
def askAction: Step =
ask { _ =>
val credentials = dataService.getUserCredentials(userId.userId)
val currentAccount = credentials.find(c => c.accountId == userId.accountId).getOrElse(sys.error("Can't determine current account"))
val buttons = Seq(Button(lang.addAccount, Tags.AddAccount), Button(lang.deleteAccount, Tags.DeleteAccount)) ++ credentials.map(c => Button(s"🔐️ ${c.username}", c.accountId))
bot.sendMessage(userId.source, lang.pleaseChooseAccount(currentAccount.username), inlineKeyboard = createInlineKeyboard(buttons, columns = 1))
} onReply {
case Msg(cmd@CallbackCommand(action), _) =>
action match {
case Tags.AddAccount =>
router ! cmd.copy(message = cmd.message.copy(text = Some("/login")), callbackData = None)
case Tags.DeleteAccount =>
bot.sendMessage(userId.source, "Not implemented yet")
case accountId =>
switchAccount(accountId.toLong)
}
end()
val currentAccount =
credentials.find(c => c.accountId == userId.accountId).getOrElse(sys.error("Can't determine current account"))
val buttons = Seq(
Button(lang.addAccount, Tags.AddAccount),
Button(lang.deleteAccount, Tags.DeleteAccount)
) ++ credentials.map(c => Button(s"🔐️ ${c.username}", c.accountId))
bot.sendMessage(
userId.source,
lang.pleaseChooseAccount(currentAccount.username),
inlineKeyboard = createInlineKeyboard(buttons, columns = 1)
)
} onReply { case Msg(cmd @ CallbackCommand(action), _) =>
action match {
case Tags.AddAccount =>
router ! cmd.copy(message = cmd.message.copy(text = Some("/login")), callbackData = None)
case Tags.DeleteAccount =>
bot.sendMessage(userId.source, "Not implemented yet")
case accountId =>
switchAccount(accountId.toLong)
}
end()
}
private def switchAccount(accountId: Long): Unit = {

View File

@@ -1,4 +1,3 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
@@ -9,8 +8,15 @@ import com.lbs.server.service.DataService
import com.lbs.server.util.MessageExtractors._
import com.typesafe.scalalogging.StrictLogging
class Auth(val source: MessageSource, dataService: DataService, unauthorizedHelpFactory: MessageSourceTo[UnauthorizedHelp],
loginFactory: MessageSourceWithOriginatorTo[Login], chatFactory: UserIdTo[Chat])(val actorSystem: ActorSystem) extends Conversation[Unit] with StrictLogging {
class Auth(
val source: MessageSource,
dataService: DataService,
unauthorizedHelpFactory: MessageSourceTo[UnauthorizedHelp],
loginFactory: MessageSourceWithOriginatorTo[Login],
chatFactory: UserIdTo[Chat]
)(val actorSystem: ActorSystem)
extends Conversation[Unit]
with StrictLogging {
private val login = loginFactory(source, self)
private val unauthorizedHelp = unauthorizedHelpFactory(source)
@@ -22,13 +28,13 @@ class Auth(val source: MessageSource, dataService: DataService, unauthorizedHelp
private def processIncoming =
monologue {
case Msg(cmd@TextCommand("/help"), _) if userId.isEmpty =>
case Msg(cmd @ TextCommand("/help"), _) if userId.isEmpty =>
unauthorizedHelp ! cmd
stay()
case Msg(cmd@TextCommand("/start"), _) if userId.isEmpty =>
case Msg(cmd @ TextCommand("/start"), _) if userId.isEmpty =>
unauthorizedHelp ! cmd
stay()
case Msg(cmd@TextCommand("/login"), _) =>
case Msg(cmd @ TextCommand("/login"), _) =>
userId = None
login.restart()
login ! cmd

View File

@@ -1,4 +1,3 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
@@ -20,9 +19,21 @@ 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[TermExt]])(implicit val actorSystem: ActorSystem) extends Conversation[BookingData] with StaticDataForBooking with Localizable {
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[TermExt]]
)(implicit val actorSystem: ActorSystem)
extends Conversation[BookingData]
with StaticDataForBooking
with Localizable {
private val datePicker = datePickerFactory(userId, self)
private val timePicker = timePickerFactory(userId, self)
@@ -36,7 +47,8 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
withFunctions[DictionaryCity](
latestOptions = dataService.getLatestCities(userId.accountId),
staticOptions = apiService.getAllCities(userId.accountId),
applyId = id => bd.copy(cityId = id.toIdName))
applyId = id => bd.copy(cityId = id.toIdName)
)
}(requestNext = askService)
private def askService: Step =
@@ -44,7 +56,8 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
withFunctions[DictionaryServiceVariants](
latestOptions = dataService.getLatestServicesByCityIdAndClinicId(userId.accountId, bd.cityId.id, None),
staticOptions = apiService.getAllServices(userId.accountId),
applyId = id => bd.copy(serviceId = id.toIdName))
applyId = id => bd.copy(serviceId = id.toIdName)
)
}(requestNext = askClinic)
private def askClinic: Step =
@@ -52,17 +65,27 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
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))
applyId = id => bd.copy(clinicId = id)
)
}(requestNext = askDoctor)
private def askDoctor: Step =
staticData(doctorConfig) { bd: BookingData =>
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.serviceId.id)
.map(_.filterNot(_.facilityGroupIds.exists(z => bd.clinicId == null || z.contains(bd.clinicId.id)))
.map(_.toIdName)),
applyId = id => bd.copy(doctorId = id.toIdName))
latestOptions = dataService.getLatestDoctorsByCityIdAndClinicIdAndServiceId(
userId.accountId,
bd.cityId.id,
bd.clinicId.optionalId,
bd.serviceId.id
),
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 =
@@ -120,24 +143,35 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
private def requestAction: Step =
ask { bookingData =>
dataService.storeAppointment(userId.accountId, bookingData)
bot.sendMessage(userId.source,
bot.sendMessage(
userId.source,
lang.bookingSummary(bookingData),
inlineKeyboard = createInlineKeyboard(
Seq(Button(lang.findTerms, Tags.FindTerms), Button(lang.modifyDate, Tags.ModifyDate))
))
inlineKeyboard =
createInlineKeyboard(Seq(Button(lang.findTerms, Tags.FindTerms), Button(lang.modifyDate, Tags.ModifyDate)))
)
} onReply {
case Msg(CallbackCommand(Tags.FindTerms), _) =>
goto(requestTerm)
case Msg(CallbackCommand(Tags.ModifyDate), bookingData) =>
goto(requestDateFrom) using bookingData.copy(dateFrom = LocalDateTime.now(),
dateTo = LocalDateTime.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.cityId.id,
bookingData.clinicId.optionalId, bookingData.serviceId.id, bookingData.doctorId.optionalId,
bookingData.dateFrom, bookingData.dateTo, timeFrom = bookingData.timeFrom, timeTo = bookingData.timeTo)
val availableTerms = apiService.getAvailableTerms(
userId.accountId,
bookingData.cityId.id,
bookingData.clinicId.optionalId,
bookingData.serviceId.id,
bookingData.doctorId.optionalId,
bookingData.dateFrom,
bookingData.dateTo,
timeFrom = bookingData.timeFrom,
timeTo = bookingData.timeTo
)
termsPager.restart()
termsPager ! availableTerms.map(new SimpleItemsProvider(_))
} onReply {
@@ -147,7 +181,11 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
case Msg(term: TermExt, bookingData) =>
val response = for {
xsrfToken <- apiService.getXsrfToken(userId.accountId)
lockTermResponse <- apiService.reservationLockterm(userId.accountId, xsrfToken, term.mapTo[ReservationLocktermRequest])
lockTermResponse <- apiService.reservationLockterm(
userId.accountId,
xsrfToken,
term.mapTo[ReservationLocktermRequest]
)
} yield (lockTermResponse, xsrfToken)
response match {
case Left(ex) =>
@@ -157,13 +195,28 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
case Right((reservationLocktermResponse, xsrfToken)) =>
if (reservationLocktermResponse.value.changeTermAvailable) {
logger.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))
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))
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, _) =>
@@ -172,17 +225,24 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
private def askNoTermsAction: Step =
ask { _ =>
bot.sendMessage(userId.source, lang.noTermsFound, inlineKeyboard =
createInlineKeyboard(Seq(Button(lang.modifyDate, Tags.ModifyDate), Button(lang.createMonitoring, Tags.CreateMonitoring))))
bot.sendMessage(
userId.source,
lang.noTermsFound,
inlineKeyboard = 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 = LocalDateTime.now(),
dateTo = LocalDateTime.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 {
case Some(settings) => (settings.defaultOffset, settings.alwaysAskOffset)
case None => (0, false)
case None => (0, false)
}
val newData = bookingData.copy(offset = defaultOffset)
if (askOffset) goto(askMonitoringOffsetOption) using newData
@@ -192,8 +252,11 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
private def awaitRebookDecision: Step =
monologue {
case Msg(CallbackCommand(Tags.Yes), bookingData: BookingData) =>
apiService.reservationChangeTerm(userId.accountId, bookingData.xsrfToken.get,
(bookingData.reservationLocktermResponse.get, bookingData.term.get).mapTo[ReservationChangetermRequest]) match {
apiService.reservationChangeTerm(
userId.accountId,
bookingData.xsrfToken.get,
(bookingData.reservationLocktermResponse.get, bookingData.term.get).mapTo[ReservationChangetermRequest]
) match {
case Right(success) =>
logger.debug(s"Successfully confirmed: $success")
bot.sendMessage(userId.source, lang.appointmentIsConfirmed)
@@ -210,7 +273,11 @@ 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.xsrfToken.get, bookingData.reservationLocktermResponse.get.value.temporaryReservationId)
apiService.deleteTemporaryReservation(
userId.accountId,
bookingData.xsrfToken.get,
bookingData.reservationLocktermResponse.get.value.temporaryReservationId
)
stay()
case Msg(CallbackCommand(Tags.Book), bookingData: BookingData) =>
makeReservation(bookingData)
@@ -239,8 +306,11 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
private def askMonitoringOffsetOption: Step =
ask { _ =>
bot.sendMessage(userId.source, lang.pleaseSpecifyOffset,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No))))
bot.sendMessage(
userId.source,
lang.pleaseSpecifyOffset,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No)))
)
} onReply {
case Msg(TextCommand(IntString(offset)), bookingData: BookingData) =>
goto(askMonitoringAutobookOption) using bookingData.copy(offset = offset)
@@ -250,22 +320,29 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da
private def askMonitoringAutobookOption: Step =
ask { _ =>
bot.sendMessage(userId.source, lang.chooseTypeOfMonitoring,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.bookByApplication, Tags.BookByApplication), Button(lang.bookManually, Tags.BookManually)), columns = 1))
} onReply {
case Msg(CallbackCommand(BooleanString(autobook)), bookingData: BookingData) =>
val data = bookingData.copy(autobook = autobook)
if (autobook) goto(askMonitoringRebookOption) using data
else goto(createMonitoring) using data
bot.sendMessage(
userId.source,
lang.chooseTypeOfMonitoring,
inlineKeyboard = createInlineKeyboard(
Seq(Button(lang.bookByApplication, Tags.BookByApplication), Button(lang.bookManually, Tags.BookManually)),
columns = 1
)
)
} onReply { case Msg(CallbackCommand(BooleanString(autobook)), bookingData: BookingData) =>
val data = bookingData.copy(autobook = autobook)
if (autobook) goto(askMonitoringRebookOption) using data
else goto(createMonitoring) using data
}
private def askMonitoringRebookOption: Step =
ask { _ =>
bot.sendMessage(userId.source, lang.rebookIfExists,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes))))
} onReply {
case Msg(CallbackCommand(BooleanString(rebookIfExists)), bookingData: BookingData) =>
goto(createMonitoring) using bookingData.copy(rebookIfExists = rebookIfExists)
bot.sendMessage(
userId.source,
lang.rebookIfExists,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes)))
)
} onReply { case Msg(CallbackCommand(BooleanString(rebookIfExists)), bookingData: BookingData) =>
goto(createMonitoring) using bookingData.copy(rebookIfExists = rebookIfExists)
}
private def createMonitoring: Step =
@@ -300,12 +377,24 @@ 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: 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[TermExt] = None, reservationLocktermResponse: Option[ReservationLocktermResponse] = None,
offset: Int = 0, payerId: Long = 0, payers: Seq[IdName] = Seq(), xsrfToken: Option[XsrfToken] = None)
case class BookingData(
cityId: IdName = null,
clinicId: IdName = null,
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[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,4 +1,3 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
@@ -19,9 +18,19 @@ 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[TermExt]])(implicit val actorSystem: ActorSystem) extends Conversation[BookingData] with Localizable {
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[TermExt]]
)(implicit val actorSystem: ActorSystem)
extends Conversation[BookingData]
with Localizable {
private val datePicker = datePickerFactory(userId, self)
private val timePicker = timePickerFactory(userId, self)
@@ -30,18 +39,18 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
entryPoint(awaitMonitoring)
private def awaitMonitoring: Step =
monologue {
case Msg(monitoring: Monitoring, _) =>
val bookingData = BookingData(
cityId = IdName.from(monitoring.cityId, monitoring.cityName),
clinicId = IdName.from(monitoring.clinicId, monitoring.clinicName),
serviceId = IdName.from(monitoring.serviceId, monitoring.serviceName),
doctorId = IdName.from(monitoring.doctorId, monitoring.doctorName),
dateFrom = monitoring.dateFrom.toLocalDateTime,
dateTo = monitoring.dateTo.toLocalDateTime,
timeFrom = monitoring.timeFrom,
timeTo = monitoring.timeTo)
goto(requestDateFrom) using bookingData
monologue { case Msg(monitoring: Monitoring, _) =>
val bookingData = BookingData(
cityId = IdName.from(monitoring.cityId, monitoring.cityName),
clinicId = IdName.from(monitoring.clinicId, monitoring.clinicName),
serviceId = IdName.from(monitoring.serviceId, monitoring.serviceName),
doctorId = IdName.from(monitoring.doctorId, monitoring.doctorName),
dateFrom = monitoring.dateFrom.toLocalDateTime,
dateTo = monitoring.dateTo.toLocalDateTime,
timeFrom = monitoring.timeFrom,
timeTo = monitoring.timeTo
)
goto(requestDateFrom) using bookingData
}
private def requestDateFrom: Step =
@@ -99,24 +108,35 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
private def requestAction: Step =
ask { bookingData =>
dataService.storeAppointment(userId.accountId, bookingData)
bot.sendMessage(userId.source,
bot.sendMessage(
userId.source,
lang.bookingSummary(bookingData),
inlineKeyboard = createInlineKeyboard(
Seq(Button(lang.findTerms, Tags.FindTerms), Button(lang.modifyDate, Tags.ModifyDate))
))
inlineKeyboard =
createInlineKeyboard(Seq(Button(lang.findTerms, Tags.FindTerms), Button(lang.modifyDate, Tags.ModifyDate)))
)
} onReply {
case Msg(CallbackCommand(Tags.FindTerms), _) =>
goto(requestTerm)
case Msg(CallbackCommand(Tags.ModifyDate), bookingData) =>
goto(requestDateFrom) using bookingData.copy(dateFrom = LocalDateTime.now(),
dateTo = LocalDateTime.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.cityId.id,
bookingData.clinicId.optionalId, bookingData.serviceId.id, bookingData.doctorId.optionalId,
bookingData.dateFrom, bookingData.dateTo, timeFrom = bookingData.timeFrom, timeTo = bookingData.timeTo)
val availableTerms = apiService.getAvailableTerms(
userId.accountId,
bookingData.cityId.id,
bookingData.clinicId.optionalId,
bookingData.serviceId.id,
bookingData.doctorId.optionalId,
bookingData.dateFrom,
bookingData.dateTo,
timeFrom = bookingData.timeFrom,
timeTo = bookingData.timeTo
)
termsPager.restart()
termsPager ! availableTerms.map(new SimpleItemsProvider(_))
} onReply {
@@ -126,18 +146,32 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
case Msg(term: TermExt, bookingData) =>
val response = for {
xsrfToken <- apiService.getXsrfToken(userId.accountId)
lockTermResponse <- apiService.reservationLockterm(userId.accountId, xsrfToken, term.mapTo[ReservationLocktermRequest])
lockTermResponse <- apiService.reservationLockterm(
userId.accountId,
xsrfToken,
term.mapTo[ReservationLocktermRequest]
)
} yield (lockTermResponse, xsrfToken)
response match {
case Left(ex) =>
logger.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))))
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((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), xsrfToken = Some(xsrfToken), reservationLocktermResponse = Some(reservationLocktermResponse))
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)
@@ -145,17 +179,24 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
private def askNoTermsAction: Step =
ask { _ =>
bot.sendMessage(userId.source, lang.noTermsFound, inlineKeyboard =
createInlineKeyboard(Seq(Button(lang.modifyDate, Tags.ModifyDate), Button(lang.createMonitoring, Tags.CreateMonitoring))))
bot.sendMessage(
userId.source,
lang.noTermsFound,
inlineKeyboard = 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 = LocalDateTime.now(),
dateTo = LocalDateTime.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 {
case Some(settings) => (settings.defaultOffset, settings.alwaysAskOffset)
case None => (0, false)
case None => (0, false)
}
val newData = bookingData.copy(offset = defaultOffset)
if (askOffset) goto(askMonitoringOffsetOption) using newData
@@ -165,7 +206,11 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
private def awaitRebookDecision: Step =
monologue {
case Msg(CallbackCommand(Tags.Yes), bookingData: BookingData) =>
apiService.reservationChangeTerm(userId.accountId, bookingData.xsrfToken.get, (bookingData.reservationLocktermResponse.get, bookingData.term.get).mapTo[ReservationChangetermRequest]) match {
apiService.reservationChangeTerm(
userId.accountId,
bookingData.xsrfToken.get,
(bookingData.reservationLocktermResponse.get, bookingData.term.get).mapTo[ReservationChangetermRequest]
) match {
case Right(success) =>
logger.debug(s"Successfully confirmed: $success")
bot.sendMessage(userId.source, lang.appointmentIsConfirmed)
@@ -182,7 +227,11 @@ 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.xsrfToken.get, bookingData.reservationLocktermResponse.get.value.temporaryReservationId)
apiService.deleteTemporaryReservation(
userId.accountId,
bookingData.xsrfToken.get,
bookingData.reservationLocktermResponse.get.value.temporaryReservationId
)
stay()
case Msg(CallbackCommand(Tags.Book), bookingData: BookingData) =>
makeReservation(bookingData)
@@ -211,8 +260,11 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
private def askMonitoringOffsetOption: Step =
ask { _ =>
bot.sendMessage(userId.source, lang.pleaseSpecifyOffset,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No))))
bot.sendMessage(
userId.source,
lang.pleaseSpecifyOffset,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No)))
)
} onReply {
case Msg(TextCommand(IntString(offset)), bookingData: BookingData) =>
goto(askMonitoringAutobookOption) using bookingData.copy(offset = offset)
@@ -222,22 +274,29 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
private def askMonitoringAutobookOption: Step =
ask { _ =>
bot.sendMessage(userId.source, lang.chooseTypeOfMonitoring,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.bookByApplication, Tags.BookByApplication), Button(lang.bookManually, Tags.BookManually)), columns = 1))
} onReply {
case Msg(CallbackCommand(BooleanString(autobook)), bookingData: BookingData) =>
val data = bookingData.copy(autobook = autobook)
if (autobook) goto(askMonitoringRebookOption) using data
else goto(createMonitoring) using data
bot.sendMessage(
userId.source,
lang.chooseTypeOfMonitoring,
inlineKeyboard = createInlineKeyboard(
Seq(Button(lang.bookByApplication, Tags.BookByApplication), Button(lang.bookManually, Tags.BookManually)),
columns = 1
)
)
} onReply { case Msg(CallbackCommand(BooleanString(autobook)), bookingData: BookingData) =>
val data = bookingData.copy(autobook = autobook)
if (autobook) goto(askMonitoringRebookOption) using data
else goto(createMonitoring) using data
}
private def askMonitoringRebookOption: Step =
ask { _ =>
bot.sendMessage(userId.source, lang.rebookIfExists,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes))))
} onReply {
case Msg(CallbackCommand(BooleanString(rebookIfExists)), bookingData: BookingData) =>
goto(createMonitoring) using bookingData.copy(rebookIfExists = rebookIfExists)
bot.sendMessage(
userId.source,
lang.rebookIfExists,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes)))
)
} onReply { case Msg(CallbackCommand(BooleanString(rebookIfExists)), bookingData: BookingData) =>
goto(createMonitoring) using bookingData.copy(rebookIfExists = rebookIfExists)
}
private def createMonitoring: Step =
@@ -260,5 +319,3 @@ class BookWithTemplate(val userId: UserId, bot: Bot, apiService: ApiService, dat
timePicker.destroy()
}
}

View File

@@ -1,4 +1,3 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
@@ -12,9 +11,21 @@ import com.typesafe.scalalogging.StrictLogging
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[HistoryViewer],
visitsFactory: UserIdTo[ReservedVisitsViewer], settingsFactory: UserIdTo[Settings], accountFactory: UserIdTo[Account])(val actorSystem: ActorSystem) extends Conversation[Unit] with StrictLogging {
class Chat(
val userId: UserId,
dataService: DataService,
monitoringService: MonitoringService,
bookingFactory: UserIdTo[Book],
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 StrictLogging {
private val book = bookingFactory(userId)
private val help = helpFactory(userId)
@@ -29,99 +40,91 @@ class Chat(val userId: UserId, dataService: DataService, monitoringService: Moni
private def helpChat: Step =
dialogue(help) {
case Msg(cmd@TextCommand("/help"), _) =>
case Msg(cmd @ TextCommand("/help"), _) =>
help ! cmd
stay()
case Msg(cmd@TextCommand("/start"), _) =>
case Msg(cmd @ TextCommand("/start"), _) =>
help ! cmd
stay()
}
private def bookChat: Step =
dialogue(book) {
case Msg(TextCommand("/book"), _) =>
book.restart()
stay()
dialogue(book) { case Msg(TextCommand("/book"), _) =>
book.restart()
stay()
}
private def historyChat: Step =
dialogue(history) {
case Msg(TextCommand("/history"), _) =>
history.restart()
stay()
dialogue(history) { case Msg(TextCommand("/history"), _) =>
history.restart()
stay()
}
private def visitsChat: Step =
dialogue(visits) {
case Msg(TextCommand("/reserved"), _) =>
visits.restart()
stay()
dialogue(visits) { case Msg(TextCommand("/reserved"), _) =>
visits.restart()
stay()
}
private def monitoringsChat: Step =
dialogue(monitorings) {
case Msg(TextCommand("/monitorings"), _) =>
monitorings.restart()
stay()
dialogue(monitorings) { case Msg(TextCommand("/monitorings"), _) =>
monitorings.restart()
stay()
}
private def monitoringsHistoryChat: Step =
dialogue(monitoringsHistory) {
case Msg(TextCommand("/monitorings_history"), _) =>
monitoringsHistory.restart()
stay()
dialogue(monitoringsHistory) { case Msg(TextCommand("/monitorings_history"), _) =>
monitoringsHistory.restart()
stay()
}
private def settingsChat: Step =
dialogue(settings) {
case Msg(TextCommand("/settings"), _) =>
settings.restart()
stay()
dialogue(settings) { case Msg(TextCommand("/settings"), _) =>
settings.restart()
stay()
}
private def accountChat: Step =
dialogue(account) {
case Msg(TextCommand("/accounts"), _) =>
account.restart()
stay()
dialogue(account) { case Msg(TextCommand("/accounts"), _) =>
account.restart()
stay()
}
private def dialogue(interactional: Interactional)(mainMessageProcessor: MessageProcessorFn): Step =
monologue {
case event: Msg =>
if (mainMessageProcessor.isDefinedAt(event)) mainMessageProcessor(event)
else {
val defaultMessageProcessor = secondaryState(interactional)
defaultMessageProcessor(event)
}
monologue { case event: Msg =>
if (mainMessageProcessor.isDefinedAt(event)) mainMessageProcessor(event)
else {
val defaultMessageProcessor = secondaryState(interactional)
defaultMessageProcessor(event)
}
}
private def secondaryState(interactional: Interactional): MessageProcessorFn = {
case Msg(cmd@TextCommand("/help"), _) =>
case Msg(cmd @ TextCommand("/help"), _) =>
self ! cmd
goto(helpChat)
case Msg(cmd@TextCommand("/start"), _) =>
case Msg(cmd @ TextCommand("/start"), _) =>
self ! cmd
goto(helpChat)
case Msg(cmd@TextCommand("/book"), _) =>
case Msg(cmd @ TextCommand("/book"), _) =>
self ! cmd
goto(bookChat)
case Msg(cmd@TextCommand("/monitorings"), _) =>
case Msg(cmd @ TextCommand("/monitorings"), _) =>
self ! cmd
goto(monitoringsChat)
case Msg(cmd@TextCommand("/monitorings_history"), _) =>
case Msg(cmd @ TextCommand("/monitorings_history"), _) =>
self ! cmd
goto(monitoringsHistoryChat)
case Msg(cmd@TextCommand("/history"), _) =>
case Msg(cmd @ TextCommand("/history"), _) =>
self ! cmd
goto(historyChat)
case Msg(cmd@TextCommand("/reserved"), _) =>
case Msg(cmd @ TextCommand("/reserved"), _) =>
self ! cmd
goto(visitsChat)
case Msg(cmd@TextCommand("/settings"), _) =>
case Msg(cmd @ TextCommand("/settings"), _) =>
self ! cmd
goto(settingsChat)
case Msg(cmd@TextCommand("/accounts"), _) =>
case Msg(cmd @ TextCommand("/accounts"), _) =>
self ! cmd
goto(accountChat)
case Msg(TextCommand(ReserveTerm(monitoringIdStr, scheduleIdStr, timeStr)), _) =>

View File

@@ -1,9 +1,8 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
import com.lbs.bot._
import com.lbs.bot.model.Button
import com.lbs.bot.{Bot, _}
import com.lbs.server.conversation.DatePicker._
import com.lbs.server.conversation.Login.UserId
import com.lbs.server.conversation.base.{Conversation, Interactional}
@@ -16,15 +15,16 @@ import java.time.{LocalDateTime, LocalTime}
import scala.util.control.NonFatal
/**
* 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[LocalDateTime] with Localizable {
* 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[LocalDateTime]
with Localizable {
private var mode: Mode = DateFromMode
@@ -43,11 +43,11 @@ class DatePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
ask { initialDate =>
val message = mode match {
case DateFromMode => lang.chooseDateFrom(initialDate)
case DateToMode => lang.chooseDateTo(initialDate)
case DateToMode => lang.chooseDateTo(initialDate)
}
bot.sendMessage(userId.source, message, inlineKeyboard = dateButtons(initialDate))
} onReply {
case Msg(cmd@CallbackCommand(Tags.Done), finalDate) =>
case Msg(cmd @ CallbackCommand(Tags.Done), finalDate) =>
val (message, updatedDate) = mode match {
case DateFromMode =>
val startOfTheDay = finalDate.`with`(LocalTime.MIN)
@@ -78,7 +78,7 @@ class DatePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
bot.sendMessage(userId.source, "Incorrect date. Please use format dd-MM")
goto(requestDate)
}
case Msg(cmd@CallbackCommand(tag), date) =>
case Msg(cmd @ CallbackCommand(tag), date) =>
val modifiedDate = modifyDate(date, tag)
bot.sendEditMessage(userId.source, cmd.message.messageId, inlineKeyboard = dateButtons(modifiedDate))
stay() using modifiedDate
@@ -86,12 +86,12 @@ class DatePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
private def modifyDate(date: LocalDateTime, tag: String) = {
val dateModifier = tag match {
case Tags.DayInc => date.plusDays _
case Tags.DayInc => date.plusDays _
case Tags.MonthInc => date.plusMonths _
case Tags.YearInc => date.plusYears _
case Tags.DayDec => date.minusDays _
case Tags.YearInc => date.plusYears _
case Tags.DayDec => date.minusDays _
case Tags.MonthDec => date.minusMonths _
case Tags.YearDec => date.minusYears _
case Tags.YearDec => date.minusYears _
}
dateModifier(1)
}
@@ -102,12 +102,14 @@ class DatePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
val month = date.getMonth.getDisplayName(TextStyle.SHORT, lang.locale)
val year = date.getYear.toString
createInlineKeyboard(Seq(
Seq(Button("⬆", Tags.DayInc), Button("⬆", Tags.MonthInc), Button("⬆", Tags.YearInc)),
Seq(Button(s"$day ($dayOfWeek)"), Button(month), Button(year)),
Seq(Button("⬇", Tags.DayDec), Button("⬇", Tags.MonthDec), Button("⬇", Tags.YearDec)),
Seq(Button("Done", Tags.Done))
))
createInlineKeyboard(
Seq(
Seq(Button("⬆", Tags.DayInc), Button("⬆", Tags.MonthInc), Button("⬆", Tags.YearInc)),
Seq(Button(s"$day ($dayOfWeek)"), Button(month), Button(year)),
Seq(Button("⬇", Tags.DayDec), Button("⬇", Tags.MonthDec), Button("⬇", Tags.YearDec)),
Seq(Button("Done", Tags.Done))
)
)
}
}

View File

@@ -1,4 +1,3 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
@@ -8,14 +7,15 @@ import com.lbs.server.conversation.Login.UserId
import com.lbs.server.conversation.base.Conversation
import com.lbs.server.lang.{Localizable, Localization}
class Help(val userId: UserId, bot: Bot, val localization: Localization)(val actorSystem: ActorSystem) extends Conversation[Unit] with Localizable {
class Help(val userId: UserId, bot: Bot, val localization: Localization)(val actorSystem: ActorSystem)
extends Conversation[Unit]
with Localizable {
entryPoint(displayHelp)
def displayHelp: Step =
monologue {
case Msg(_: Command, _) =>
bot.sendMessage(userId.source, lang.help)
stay()
monologue { case Msg(_: Command, _) =>
bot.sendMessage(userId.source, lang.help)
stay()
}
}

View File

@@ -1,4 +1,3 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
@@ -11,8 +10,15 @@ import com.lbs.server.conversation.base.Conversation
import com.lbs.server.lang.{Localizable, Localization}
import com.lbs.server.service.ApiService
class HistoryViewer(val userId: UserId, bot: Bot, apiService: ApiService, val localization: Localization,
historyPagerFactory: UserIdWithOriginatorTo[Pager[Event]])(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)

View File

@@ -1,4 +1,3 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
@@ -11,8 +10,17 @@ import com.lbs.server.service.{ApiService, DataService}
import com.lbs.server.util.MessageExtractors
import org.jasypt.util.text.TextEncryptor
class Login(source: MessageSource, bot: Bot, dataService: DataService, apiService: ApiService, textEncryptor: TextEncryptor,
val localization: Localization, originator: Interactional)(val actorSystem: ActorSystem) extends Conversation[String] with Localizable {
class Login(
source: MessageSource,
bot: Bot,
dataService: DataService,
apiService: ApiService,
textEncryptor: TextEncryptor,
val localization: Localization,
originator: Interactional
)(val actorSystem: ActorSystem)
extends Conversation[String]
with Localizable {
protected var userId: UserId = _
@@ -21,38 +29,35 @@ class Login(source: MessageSource, bot: Bot, dataService: DataService, apiServic
private var forwardCommand: ForwardCommand = _
def logIn: Step =
monologue {
case Msg(cmd: Command, _) =>
forwardCommand = ForwardCommand(cmd)
goto(requestUsername)
monologue { case Msg(cmd: Command, _) =>
forwardCommand = ForwardCommand(cmd)
goto(requestUsername)
}
def requestUsername: Step =
ask { _ =>
bot.sendMessage(source, lang.provideUsername)
} onReply {
case Msg(MessageExtractors.TextCommand(username), _) =>
goto(requestPassword) using username
} onReply { case Msg(MessageExtractors.TextCommand(username), _) =>
goto(requestPassword) using username
}
def requestPassword: Step =
ask { _ =>
bot.sendMessage(source, lang.providePassword)
} onReply {
case Msg(MessageExtractors.TextCommand(plainPassword), username) =>
val password = textEncryptor.encrypt(plainPassword)
apiService.fullLogin(username, password) match {
case Left(error) =>
bot.sendMessage(source, error.getMessage)
goto(requestUsername)
case Right(session) =>
val credentials = dataService.saveCredentials(source, username, password)
userId = UserId(credentials.userId, credentials.accountId, source)
apiService.addSession(credentials.accountId, session)
bot.sendMessage(source, lang.loginAndPasswordAreOk)
originator ! LoggedIn(forwardCommand, credentials.userId, credentials.accountId)
end()
}
} onReply { case Msg(MessageExtractors.TextCommand(plainPassword), username) =>
val password = textEncryptor.encrypt(plainPassword)
apiService.fullLogin(username, password) match {
case Left(error) =>
bot.sendMessage(source, error.getMessage)
goto(requestUsername)
case Right(session) =>
val credentials = dataService.saveCredentials(source, username, password)
userId = UserId(credentials.userId, credentials.accountId, source)
apiService.addSession(credentials.accountId, session)
bot.sendMessage(source, lang.loginAndPasswordAreOk)
originator ! LoggedIn(forwardCommand, credentials.userId, credentials.accountId)
end()
}
}
}

View File

@@ -1,4 +1,3 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
@@ -12,7 +11,15 @@ import com.lbs.server.lang.{Localizable, Localization}
import com.lbs.server.repository.model.Monitoring
import com.lbs.server.service.MonitoringService
class Monitorings(val userId: UserId, bot: Bot, monitoringService: MonitoringService, val localization: Localization, monitoringsPagerFactory: UserIdWithOriginatorTo[Pager[Monitoring]])(val actorSystem: ActorSystem) extends Conversation[Monitoring] with Localizable {
class Monitorings(
val userId: UserId,
bot: Bot,
monitoringService: MonitoringService,
val localization: Localization,
monitoringsPagerFactory: UserIdWithOriginatorTo[Pager[Monitoring]]
)(val actorSystem: ActorSystem)
extends Conversation[Monitoring]
with Localizable {
private val monitoringsPager = monitoringsPagerFactory(userId, self)
@@ -40,8 +47,11 @@ class Monitorings(val userId: UserId, bot: Bot, monitoringService: MonitoringSer
def askToDeactivateMonitoring: Step =
ask { monitoring =>
bot.sendMessage(userId.source, lang.deactivateMonitoring(monitoring), inlineKeyboard =
createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes))))
bot.sendMessage(
userId.source,
lang.deactivateMonitoring(monitoring),
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes)))
)
} onReply {
case Msg(Command(_, _, Some(Tags.No)), _) =>
bot.sendMessage(userId.source, lang.monitoringWasNotDeactivated)

View File

@@ -1,4 +1,3 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
@@ -11,7 +10,16 @@ import com.lbs.server.lang.{Localizable, Localization}
import com.lbs.server.repository.model.Monitoring
import com.lbs.server.service.MonitoringService
class MonitoringsHistory(val userId: UserId, bot: Bot, monitoringService: MonitoringService, val localization: Localization, monitoringsPagerFactory: UserIdWithOriginatorTo[Pager[Monitoring]], bookWithTemplateFactory: UserIdTo[BookWithTemplate])(val actorSystem: ActorSystem) extends Conversation[Monitoring] with Localizable {
class MonitoringsHistory(
val userId: UserId,
bot: Bot,
monitoringService: MonitoringService,
val localization: Localization,
monitoringsPagerFactory: UserIdWithOriginatorTo[Pager[Monitoring]],
bookWithTemplateFactory: UserIdTo[BookWithTemplate]
)(val actorSystem: ActorSystem)
extends Conversation[Monitoring]
with Localizable {
private val monitoringsPager = monitoringsPagerFactory(userId, self)
private val bookWithTemplate = bookWithTemplateFactory(userId)
@@ -42,10 +50,9 @@ class MonitoringsHistory(val userId: UserId, bot: Bot, monitoringService: Monito
ask { monitoring =>
bookWithTemplate.restart()
bookWithTemplate ! monitoring
} onReply {
case Msg(cmd: Command, _) =>
bookWithTemplate ! cmd
stay()
} onReply { case Msg(cmd: Command, _) =>
bookWithTemplate ! cmd
stay()
}
beforeDestroy {
@@ -62,7 +69,8 @@ class MonitoringsHistory(val userId: UserId, bot: Bot, monitoringService: Monito
override def previous(): Unit = index -= 1
override def items: Seq[Monitoring] = monitoringService.getMonitoringsPage(userId.accountId, index * Pager.PageSize, Pager.PageSize)
override def items: Seq[Monitoring] =
monitoringService.getMonitoringsPage(userId.accountId, index * Pager.PageSize, Pager.PageSize)
override def currentPage: Int = index
@@ -70,4 +78,3 @@ class MonitoringsHistory(val userId: UserId, bot: Bot, monitoringService: Monito
}
}

View File

@@ -1,9 +1,8 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
import com.lbs.bot.model.{Button, Command}
import com.lbs.bot._
import com.lbs.bot.model.{Button, Command}
import com.lbs.server.conversation.Login.UserId
import com.lbs.server.conversation.Pager._
import com.lbs.server.conversation.base.{Conversation, Interactional}
@@ -11,10 +10,18 @@ import com.lbs.server.lang.{Localizable, Localization}
import com.lbs.server.util.MessageExtractors
import com.typesafe.scalalogging.StrictLogging
class Pager[Data](val userId: UserId, bot: Bot, makeMessage: (Data, Int, Int) => String,
makeHeader: (Int, Int) => String, selectionPrefix: Option[String],
val localization: Localization, originator: Interactional)(val actorSystem: ActorSystem)
extends Conversation[(ItemsProvider[Data], Option[String])] with Localizable with StrictLogging {
class Pager[Data](
val userId: UserId,
bot: Bot,
makeMessage: (Data, Int, Int) => String,
makeHeader: (Int, Int) => String,
selectionPrefix: Option[String],
val localization: Localization,
originator: Interactional
)(val actorSystem: ActorSystem)
extends Conversation[(ItemsProvider[Data], Option[String])]
with Localizable
with StrictLogging {
private val Selection = s"/${selectionPrefix.getOrElse("")}_(\\d+)".r
@@ -49,10 +56,17 @@ class Pager[Data](val userId: UserId, bot: Bot, makeMessage: (Data, Int, Int) =>
}
private def sendPage(itemsProvider: ItemsProvider[Data], messageId: Option[String] = None): Unit = {
val message = makeHeader(itemsProvider.currentPage, itemsProvider.pages) + "\n\n" + itemsProvider.items.zipWithIndex.map { case (d, index) => makeMessage(d, itemsProvider.currentPage, index) }.mkString
val message =
makeHeader(itemsProvider.currentPage, itemsProvider.pages) + "\n\n" + itemsProvider.items.zipWithIndex.map {
case (d, index) =>
makeMessage(d, itemsProvider.currentPage, index)
}.mkString
val previousButton = if (itemsProvider.currentPage > 0) Some(Button(lang.previous, Tags.Previous)) else None
val nextButton = if (itemsProvider.currentPage >= 0 && itemsProvider.currentPage < itemsProvider.pages - 1) Some(Button(lang.next, Tags.Next)) else None
val nextButton =
if (itemsProvider.currentPage >= 0 && itemsProvider.currentPage < itemsProvider.pages - 1)
Some(Button(lang.next, Tags.Next))
else None
val buttons = previousButton.toSeq ++ nextButton.toSeq
messageId match {

View File

@@ -1,10 +1,9 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
import com.lbs.api.json.model.Event
import com.lbs.bot._
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.ReservedVisitsViewer.Tags
@@ -12,8 +11,15 @@ import com.lbs.server.conversation.base.Conversation
import com.lbs.server.lang.{Localizable, Localization}
import com.lbs.server.service.ApiService
class ReservedVisitsViewer(val userId: UserId, bot: Bot, apiService: ApiService, val localization: Localization,
visitsPagerFactory: UserIdWithOriginatorTo[Pager[Event]])(val actorSystem: ActorSystem) extends Conversation[Event] 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)
@@ -41,8 +47,11 @@ class ReservedVisitsViewer(val userId: UserId, bot: Bot, apiService: ApiService,
def askToCancelVisit: Step =
ask { visit =>
bot.sendMessage(userId.source, lang.areYouSureToCancelAppointment(visit),
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes))))
bot.sendMessage(
userId.source,
lang.areYouSureToCancelAppointment(visit),
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.no, Tags.No), Button(lang.yes, Tags.Yes)))
)
} onReply {
case Msg(Command(_, _, Some(Tags.No)), _) =>
bot.sendMessage(userId.source, lang.appointmentWasNotCancelled)

View File

@@ -1,4 +1,3 @@
package com.lbs.server.conversation
import akka.actor.{ActorSystem, Cancellable}
@@ -11,7 +10,9 @@ import scala.collection.mutable
import scala.concurrent.ExecutionContextExecutor
import scala.concurrent.duration.DurationLong
class Router(authFactory: MessageSourceTo[Auth])(val actorSystem: ActorSystem) extends Conversation[Unit] with StrictLogging {
class Router(authFactory: MessageSourceTo[Auth])(val actorSystem: ActorSystem)
extends Conversation[Unit]
with StrictLogging {
private case class DestroyChat(source: MessageSource)
@@ -27,7 +28,7 @@ class Router(authFactory: MessageSourceTo[Auth])(val actorSystem: ActorSystem) e
private def routeMessage: Step =
monologue {
case Msg(cmd@Command(source, _, _), _) =>
case Msg(cmd @ Command(source, _, _), _) =>
val chat = instantiateChatOrGet(source)
chat ! cmd
stay()

View File

@@ -1,9 +1,8 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
import com.lbs.bot._
import com.lbs.bot.model.{Button, Command}
import com.lbs.bot.{Bot, _}
import com.lbs.server.conversation.Login.UserId
import com.lbs.server.conversation.Settings._
import com.lbs.server.conversation.base.Conversation
@@ -12,15 +11,21 @@ import com.lbs.server.repository.model
import com.lbs.server.service.DataService
import com.lbs.server.util.MessageExtractors.{CallbackCommand, IntString, TextCommand}
class Settings(val userId: UserId, bot: Bot, dataService: DataService, val localization: Localization)(val actorSystem: ActorSystem) extends Conversation[Unit] with Localizable {
class Settings(val userId: UserId, bot: Bot, dataService: DataService, val localization: Localization)(
val actorSystem: ActorSystem
) extends Conversation[Unit]
with Localizable {
entryPoint(askForAction)
def askForAction: Step =
ask { _ =>
bot.sendMessage(userId.source, lang.settingsHeader, inlineKeyboard =
createInlineKeyboard(Seq(Button(lang.language, Tags.Language),
Button(lang.offset, Tags.Offset)), columns = 1))
bot.sendMessage(
userId.source,
lang.settingsHeader,
inlineKeyboard =
createInlineKeyboard(Seq(Button(lang.language, Tags.Language), Button(lang.offset, Tags.Offset)), columns = 1)
)
} onReply {
case Msg(Command(_, _, Some(Tags.Language)), _) =>
goto(askLanguage)
@@ -30,29 +35,47 @@ class Settings(val userId: UserId, bot: Bot, dataService: DataService, val local
def askLanguage: Step =
ask { _ =>
bot.sendMessage(userId.source, lang.chooseLanguage,
inlineKeyboard = createInlineKeyboard(Lang.Langs.map(l => Button(l.label, l.id)), columns = 1))
} onReply {
case Msg(CallbackCommand(IntString(langId)), _) =>
localization.updateLanguage(userId.userId, Lang(langId))
bot.sendMessage(userId.source, lang.languageUpdated)
end()
bot.sendMessage(
userId.source,
lang.chooseLanguage,
inlineKeyboard = createInlineKeyboard(Lang.Langs.map(l => Button(l.label, l.id)), columns = 1)
)
} onReply { case Msg(CallbackCommand(IntString(langId)), _) =>
localization.updateLanguage(userId.userId, Lang(langId))
bot.sendMessage(userId.source, lang.languageUpdated)
end()
}
def showOffsetOptions: Step = {
ask { _ =>
val settings = getSettings
bot.sendMessage(userId.source, lang.configureOffset,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.alwaysAskOffset(settings.alwaysAskOffset), Tags.ToggleAskOffsetOnOff),
Button(lang.changeDefaultOffset(settings.defaultOffset), Tags.ChangeDefaultOffset)), columns = 1))
bot.sendMessage(
userId.source,
lang.configureOffset,
inlineKeyboard = createInlineKeyboard(
Seq(
Button(lang.alwaysAskOffset(settings.alwaysAskOffset), Tags.ToggleAskOffsetOnOff),
Button(lang.changeDefaultOffset(settings.defaultOffset), Tags.ChangeDefaultOffset)
),
columns = 1
)
)
} onReply {
case Msg(cmd@CallbackCommand(Tags.ToggleAskOffsetOnOff), _) =>
case Msg(cmd @ CallbackCommand(Tags.ToggleAskOffsetOnOff), _) =>
val settings = getSettings
settings.alwaysAskOffset = !settings.alwaysAskOffset
dataService.saveSettings(settings)
bot.sendEditMessage(userId.source, cmd.message.messageId,
inlineKeyboard = createInlineKeyboard(Seq(Button(lang.alwaysAskOffset(settings.alwaysAskOffset), Tags.ToggleAskOffsetOnOff),
Button(lang.changeDefaultOffset(settings.defaultOffset), Tags.ChangeDefaultOffset)), columns = 1))
bot.sendEditMessage(
userId.source,
cmd.message.messageId,
inlineKeyboard = createInlineKeyboard(
Seq(
Button(lang.alwaysAskOffset(settings.alwaysAskOffset), Tags.ToggleAskOffsetOnOff),
Button(lang.changeDefaultOffset(settings.defaultOffset), Tags.ChangeDefaultOffset)
),
columns = 1
)
)
stay()
case Msg(CallbackCommand(Tags.ChangeDefaultOffset), _) =>
goto(askDefaultOffset)
@@ -63,17 +86,18 @@ class Settings(val userId: UserId, bot: Bot, dataService: DataService, val local
ask { _ =>
val settings = getSettings
bot.sendMessage(userId.source, lang.pleaseEnterOffset(settings.defaultOffset))
} onReply {
case Msg(TextCommand(IntString(offset)), _) =>
val settings = getSettings
settings.defaultOffset = offset
dataService.saveSettings(settings)
goto(showOffsetOptions)
} onReply { case Msg(TextCommand(IntString(offset)), _) =>
val settings = getSettings
settings.defaultOffset = offset
dataService.saveSettings(settings)
goto(showOffsetOptions)
}
}
private def getSettings = {
dataService.findSettings(userId.userId).getOrElse(model.Settings(userId.userId, lang.id, 0, alwaysAskOffset = false))
dataService
.findSettings(userId.userId)
.getOrElse(model.Settings(userId.userId, lang.id, 0, alwaysAskOffset = false))
}
}

View File

@@ -1,17 +1,19 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
import com.lbs.api.json.model.{IdName, Identified}
import com.lbs.bot._
import com.lbs.bot.model.{Button, Command, TaggedButton}
import com.lbs.bot.{Bot, _}
import com.lbs.server.ThrowableOr
import com.lbs.server.conversation.Login.UserId
import com.lbs.server.conversation.StaticData._
import com.lbs.server.conversation.base.{Conversation, Interactional}
import com.lbs.server.lang.{Localizable, Localization}
class StaticData(val userId: UserId, bot: Bot, val localization: Localization, originator: Interactional)(val actorSystem: ActorSystem) extends Conversation[List[TaggedButton]] with Localizable {
class StaticData(val userId: UserId, bot: Bot, val localization: Localization, originator: Interactional)(
val actorSystem: ActorSystem
) extends Conversation[List[TaggedButton]]
with Localizable {
private def anySelectOption: List[TaggedButton] = if (config.isAnyAllowed) List(Button(lang.any, -1L)) else List()
@@ -20,10 +22,9 @@ class StaticData(val userId: UserId, bot: Bot, val localization: Localization, o
entryPoint(AwaitConfig)
def AwaitConfig: Step =
monologue {
case Msg(newConfig: StaticDataConfig, _) =>
config = newConfig
goto(askForLatestOption)
monologue { case Msg(newConfig: StaticDataConfig, _) =>
config = newConfig
goto(askForLatestOption)
}
def askForLatestOption: Step =
@@ -40,12 +41,16 @@ class StaticData(val userId: UserId, bot: Bot, val localization: Localization, o
def askForUserInput: Step =
ask { callbackTags =>
bot.sendMessage(userId.source, lang.pleaseEnterStaticDataNameOrPrevious(config),
inlineKeyboard = createInlineKeyboard(callbackTags, columns = 1))
bot.sendMessage(
userId.source,
lang.pleaseEnterStaticDataNameOrPrevious(config),
inlineKeyboard = createInlineKeyboard(callbackTags, columns = 1)
)
} onReply {
case Msg(Command(_, msg, Some(tag)), callbackTags) =>
val id = tag.toLong
val label = callbackTags.find(_.tag == tag).map(_.label).getOrElse(sys.error("Unable to get callback tag label"))
val label =
callbackTags.find(_.tag == tag).map(_.label).getOrElse(sys.error("Unable to get callback tag label"))
bot.sendEditMessage(userId.source, msg.messageId, lang.staticDataIs(config, label))
originator ! IdName(id, label)
end()

View File

@@ -1,4 +1,3 @@
package com.lbs.server.conversation
import com.lbs.api.json.model.{IdName, Identified}
@@ -12,8 +11,12 @@ trait StaticDataForBooking extends Conversation[BookingData] {
private[conversation] def staticData: StaticData
protected def withFunctions[T <: Identified](latestOptions: => Seq[IdName], staticOptions: => ThrowableOr[List[T]], applyId: IdName => BookingData): Step => MessageProcessorFn = {
nextStep: Step => {
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
stay()
@@ -28,14 +31,15 @@ trait StaticDataForBooking extends Conversation[BookingData] {
}
}
protected def staticData(staticDataConfig: => StaticDataConfig)(functions: BookingData => Step => MessageProcessorFn)(requestNext: Step)(implicit functionName: sourcecode.Name): Step = {
protected def staticData(staticDataConfig: => StaticDataConfig)(
functions: BookingData => Step => MessageProcessorFn
)(requestNext: Step)(implicit functionName: sourcecode.Name): Step = {
ask { _ =>
staticData.restart()
staticData ! staticDataConfig
} onReply {
case msg@Msg(_, bookingData: BookingData) =>
val fn = functions(bookingData)(requestNext)
fn(msg)
} onReply { case msg @ Msg(_, bookingData: BookingData) =>
val fn = functions(bookingData)(requestNext)
fn(msg)
}
}

View File

@@ -1,9 +1,8 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
import com.lbs.bot._
import com.lbs.bot.model.Button
import com.lbs.bot.{Bot, _}
import com.lbs.server.conversation.Login.UserId
import com.lbs.server.conversation.TimePicker.{Mode, Tags, TimeFromMode, TimeToMode}
import com.lbs.server.conversation.base.{Conversation, Interactional}
@@ -20,9 +19,11 @@ import scala.util.control.NonFatal
* ⬆ ⬆
* HH mm
* ⬇ ⬇
*
*/
class TimePicker(val userId: UserId, val bot: Bot, val localization: Localization, originator: Interactional)(val actorSystem: ActorSystem) extends Conversation[LocalTime] with Localizable {
class TimePicker(val userId: UserId, val bot: Bot, val localization: Localization, originator: Interactional)(
val actorSystem: ActorSystem
) extends Conversation[LocalTime]
with Localizable {
private var mode: Mode = TimeFromMode
@@ -41,11 +42,11 @@ class TimePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
ask { initialTime =>
val message = mode match {
case TimeFromMode => lang.chooseTimeFrom(initialTime)
case TimeToMode => lang.chooseTimeTo(initialTime)
case TimeToMode => lang.chooseTimeTo(initialTime)
}
bot.sendMessage(userId.source, message, inlineKeyboard = timeButtons(initialTime))
} onReply {
case Msg(cmd@CallbackCommand(Tags.Done), selectedTime) =>
case Msg(cmd @ CallbackCommand(Tags.Done), selectedTime) =>
val message = mode match {
case TimeFromMode =>
lang.timeFromIs(selectedTime)
@@ -73,7 +74,7 @@ class TimePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
bot.sendMessage(userId.source, "Incorrect time. Please use format HH:mm")
goto(requestTime)
}
case Msg(cmd@CallbackCommand(tag), time) =>
case Msg(cmd @ CallbackCommand(tag), time) =>
val modifiedTime = modifyTime(time, tag)
bot.sendEditMessage(userId.source, cmd.message.messageId, inlineKeyboard = timeButtons(modifiedTime))
stay() using modifiedTime
@@ -81,9 +82,9 @@ class TimePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
private def modifyTime(time: LocalTime, tag: String) = {
tag match {
case Tags.HourInc => time.plusHours(1)
case Tags.HourInc => time.plusHours(1)
case Tags.MinuteInc => time.plusMinutes(30)
case Tags.HourDec => time.minusHours(1)
case Tags.HourDec => time.minusHours(1)
case Tags.MinuteDec => time.minusMinutes(30)
}
}
@@ -92,12 +93,14 @@ class TimePicker(val userId: UserId, val bot: Bot, val localization: Localizatio
val hour = f"${time.getHour}%02d"
val minute = f"${time.getMinute}%02d"
createInlineKeyboard(Seq(
Seq(Button("⬆", Tags.HourInc), Button("⬆", Tags.MinuteInc)),
Seq(Button(hour), Button(minute)),
Seq(Button("⬇", Tags.HourDec), Button("⬇", Tags.MinuteDec)),
Seq(Button("Done", Tags.Done))
))
createInlineKeyboard(
Seq(
Seq(Button("⬆", Tags.HourInc), Button("⬆", Tags.MinuteInc)),
Seq(Button(hour), Button(minute)),
Seq(Button("⬇", Tags.HourDec), Button("⬇", Tags.MinuteDec)),
Seq(Button("Done", Tags.Done))
)
)
}
}
@@ -118,4 +121,3 @@ object TimePicker {
}
}

View File

@@ -1,4 +1,3 @@
package com.lbs.server.conversation
import akka.actor.ActorSystem
@@ -11,9 +10,8 @@ class UnauthorizedHelp(source: MessageSource, bot: Bot)(val actorSystem: ActorSy
entryPoint(displayHelp)
def displayHelp: Step =
monologue {
case Msg(_: Command, _) =>
bot.sendMessage(source, En.help)
stay()
monologue { case Msg(_: Command, _) =>
bot.sendMessage(source, En.help)
stay()
}
}

View File

@@ -14,10 +14,9 @@ trait Conversation[D] extends Domain[D] with Interactional with StrictLogging {
private var initialStep: Step = _
private val defaultMsgHandler: MessageProcessorFn = {
case Msg(any, data) =>
logger.warn(s"Unhandled message received in step '${currentStep.name}'. Message: [$any]. Data: [$data]")
NextStep(currentStep, Some(data))
private val defaultMsgHandler: MessageProcessorFn = { case Msg(any, data) =>
logger.warn(s"Unhandled message received in step '${currentStep.name}'. Message: [$any]. Data: [$data]")
NextStep(currentStep, Some(data))
}
private var msgHandler: MessageProcessorFn = defaultMsgHandler
@@ -29,7 +28,7 @@ trait Conversation[D] extends Domain[D] with Interactional with StrictLogging {
case Process(_, fn) =>
val nextStep = fn(currentData)
moveToNextStep(nextStep)
case _ => //do nothing
case _ => // do nothing
}
} catch {
case NonFatal(ex) => logger.error("Step execution failed", ex)
@@ -53,7 +52,7 @@ trait Conversation[D] extends Domain[D] with Interactional with StrictLogging {
case Monologue(_, fn) =>
val fact = Msg(any, currentData)
handle(fact, fn, msgHandler)
case _ => //do nothing
case _ => // do nothing
}
}
@@ -71,12 +70,13 @@ trait Conversation[D] extends Domain[D] with Interactional with StrictLogging {
currentData = initialData
}
protected def monologue(answerFn: MessageProcessorFn)(implicit functionName: sourcecode.Name): Monologue = Monologue(functionName.value, answerFn)
protected def monologue(answerFn: MessageProcessorFn)(implicit functionName: sourcecode.Name): Monologue =
Monologue(functionName.value, answerFn)
protected def ask(askFn: D => Unit): Ask = Ask(askFn)
protected def process(processFn: ProcessFn)(implicit functionName: sourcecode.Name): Process = Process(functionName.value, processFn)
protected def process(processFn: ProcessFn)(implicit functionName: sourcecode.Name): Process =
Process(functionName.value, processFn)
protected def end(): NextStep = NextStep(End)
@@ -106,4 +106,3 @@ trait Conversation[D] extends Domain[D] with Interactional with StrictLogging {
entryPoint(step, null.asInstanceOf[D])
}
}

View File

@@ -29,9 +29,9 @@ trait Interactional extends StrictLogging {
private implicit val dispatcher: ExecutionContextExecutor = context.system.dispatcher
override def receive: Receive = {
case InitConversation => initializeConversation()
case InitConversation => initializeConversation()
case StartConversation | ContinueConversation => executeCurrentStep()
case any => makeStepTransition(any)
case any => makeStepTransition(any)
}
override def preStart(): Unit = {
@@ -71,4 +71,3 @@ trait Interactional extends StrictLogging {
ref ! PoisonPill
}
}

View File

@@ -1,4 +1,3 @@
package com.lbs.server
import com.lbs.bot.model.MessageSource

View File

@@ -1,4 +1,3 @@
package com.lbs.server.exception
case class UserNotFoundException(chatId: Long) extends Exception(s"Luxmed username for chat with id $chatId")

View File

@@ -1,4 +1,3 @@
package com.lbs.server.lang
import com.lbs.api.json.model.{Event, TermExt}
@@ -40,14 +39,18 @@ object En extends Lang {
s"""<b>➡</b> Are you sure want to cancel appointment?
|
|⏱ <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")}
|${capitalize(doctor)}: ${capitalize(event.doctor.name)} ${capitalize(event.doctor.lastname)}
|${capitalize(service)}: ${event.title}
|${capitalize(clinic)}: ${event.clinic
.map(c => s"${capitalize(c.city)} - ${capitalize(c.address)}")
.getOrElse("Telemedicine")}
|""".stripMargin
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 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: LocalDateTime): 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"
@@ -74,12 +77,11 @@ object En extends Lang {
override def book: String = "Book"
override def confirmAppointment(term: TermExt): String =
s"""<b>➡</b> Would you like to confirm your appointment booking?
|
|⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}""".stripMargin
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(clinic)}: ${term.term.clinic}""".stripMargin
override def appointmentIsConfirmed: String = "👍 Your appointment has been confirmed!"
@@ -97,7 +99,8 @@ object En extends Lang {
override def pleaseSpecifyOffset: String = "<b>➡</b> Please send me offset in hours or press No button"
override def visitAlreadyExists: String = "<b>➡</b> The same service is already booked. Do you want to update the term?"
override def visitAlreadyExists: String =
"<b>➡</b> The same service is already booked. Do you want to update the term?"
override def city: String = "city"
@@ -118,9 +121,9 @@ object En extends Lang {
|
|📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}""".stripMargin
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}""".stripMargin
override def deactivated: String = "👍 Deactivated! List of active /monitorings"
@@ -132,7 +135,8 @@ object En extends Lang {
withAnyVariant(
s"""<b>➡</b> Please enter a partial ${config.name} name
|For example: <b>${config.partialExample}</b> if you are looking for <b>${config.example}</b>""".stripMargin,
config.isAnyAllowed)
config.isAnyAllowed
)
override def pleaseEnterStaticDataNameOrPrevious(config: StaticDataConfig): String =
s"""<b>➡</b> Please enter a partial ${config.name} name
@@ -141,7 +145,7 @@ object En extends Lang {
|or choose a ${config.name} from previous searches""".stripMargin
override def staticDataIs(config: StaticDataConfig, label: String): String =
s"<b>✅</b> ${capitalizeFirstLetter(config.name)} is <b>$label</b>"
s"<b>✅</b> ${capitalize(config.name)} is <b>$label</b>"
override def pleaseChooseStaticDataNameOrAny(config: StaticDataConfig): String =
withAnyVariant(s"<b>➡</b> Please choose a ${config.name}", config.isAnyAllowed)
@@ -149,7 +153,9 @@ object En extends Lang {
override def staticNotFound(config: StaticDataConfig): String =
withAnyVariant(
s"""<b>➡</b> Nothing was found 😔
|Please enter a ${config.name} name again""", config.isAnyAllowed)
|Please enter a ${config.name} name again""",
config.isAnyAllowed
)
override def loginAndPasswordAreOk: String =
s"""✅ Congrats! Login and password are OK!
@@ -186,8 +192,8 @@ object En extends Lang {
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}
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(clinic)}: ${term.term.clinic}
|<b>➡</b> /book_$index
|
|""".stripMargin
@@ -197,9 +203,11 @@ object En extends Lang {
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")}
|${capitalize(doctor)}: ${capitalize(event.doctor.name)} ${capitalize(event.doctor.lastname)}
|${capitalize(service)}: ${event.title}
|${capitalize(clinic)}: ${event.clinic
.map(c => s"${capitalize(c.city)} - ${capitalize(c.address)}")
.getOrElse("Telemedicine")}
|
|""".stripMargin
@@ -208,9 +216,11 @@ object En extends Lang {
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")}
|${capitalize(doctor)}: ${capitalize(event.doctor.name)} ${capitalize(event.doctor.lastname)}
|${capitalize(service)}: ${event.title}
|${capitalize(clinic)}: ${event.clinic
.map(c => s"${capitalize(c.city)} - ${capitalize(c.address)}")
.getOrElse("Telemedicine")}
|<b>➡</b> /cancel_$index
|
|""".stripMargin
@@ -224,10 +234,10 @@ object En extends Lang {
override def monitoringEntry(monitoring: Monitoring, page: Int, index: Int): String =
s"""📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}
|${capitalize(city)}: ${monitoring.cityName}
|Type: ${if (monitoring.autobook) "Auto" else "Manual"}
|<b>➡</b> /cancel_$index
|
@@ -236,10 +246,10 @@ object En extends Lang {
override def monitoringHistoryEntry(monitoring: Monitoring, page: Int, index: Int): String =
s"""📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}
|${capitalize(city)}: ${monitoring.cityName}
|Type: ${if (monitoring.autobook) "Auto" else "Manual"}
|<b>➡</b> /repeat_$index
|
@@ -258,10 +268,10 @@ object En extends Lang {
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.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${term.term.clinic}
|${capitalize(city)}: ${monitoring.cityName}
|/reserve_${monitoring.recordId}_${term.term.scheduleId}_${minutesSinceBeginOf2018(term.term.dateTimeFrom.get)}
|
|""".stripMargin
@@ -276,10 +286,10 @@ object En extends Lang {
|
|📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}
|${capitalize(city)}: ${monitoring.cityName}
|
|<b>➡</b> Create new monitoring /book""".stripMargin
@@ -287,10 +297,10 @@ object En extends Lang {
s"""👍 We just booked an appointment for you!
|
|⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}""".stripMargin
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${term.term.clinic}
|${capitalize(city)}: ${monitoring.cityName}""".stripMargin
override def maximumMonitoringsLimitExceeded: String = "Maximum monitorings per user is 10"
@@ -315,7 +325,8 @@ object En extends Lang {
override def configureOffset: String = "<b>➡</b> Please specify offset options"
override def pleaseEnterOffset(current: Int): String = s"<b>➡</b> Please enter default offset. Current: <b>$current</b>"
override def pleaseEnterOffset(current: Int): String =
s"<b>➡</b> Please enter default offset. Current: <b>$current</b>"
override def alwaysAskOffset(enabled: Boolean): String = s"${if (enabled) "✅ " else ""}Always ask offset"
@@ -353,9 +364,11 @@ object En extends Lang {
override def moreParameters: String = "🛠 More parameters"
override def chooseTimeFrom(exampleTime: LocalTime): String = s"<b>➡</b> Please choose time from or write time using format HH:mm, e.g. ${formatTime(exampleTime)}"
override def chooseTimeFrom(exampleTime: LocalTime): String =
s"<b>➡</b> Please choose time from or write time using format HH:mm, e.g. ${formatTime(exampleTime)}"
override def chooseTimeTo(exampleTime: LocalTime): String = s"<b>➡</b> Please choose time to or write time using format HH:mm, e.g. ${formatTime(exampleTime)}"
override def chooseTimeTo(exampleTime: LocalTime): String =
s"<b>➡</b> Please choose time to or write time using format HH:mm, e.g. ${formatTime(exampleTime)}"
override def timeFromIs(timeFrom: LocalTime): String = s"⏱ Time from is ${formatTime(timeFrom)}"

View File

@@ -1,4 +1,3 @@
package com.lbs.server.lang
import com.lbs.api.json.model.{Event, TermExt}
@@ -32,7 +31,7 @@ trait Lang {
def label: String
protected def capitalizeFirstLetter(str: String): String = {
protected def capitalize(str: String): String = {
if (str != null && str != "") {
val fistCapitalLetter = str.head.toTitleCase
s"$fistCapitalLetter${str.tail.toLowerCase}"

View File

@@ -1,4 +1,3 @@
package com.lbs.server.lang
import com.lbs.server.conversation.Login.UserId

View File

@@ -1,4 +1,3 @@
package com.lbs.server.lang
import com.lbs.server.repository.model
@@ -17,10 +16,13 @@ class Localization {
private val cachedLangs = new ConcurrentHashMap[Long, Lang]
def lang(userId: Long): Lang = {
cachedLangs.computeIfAbsent(userId, _ => {
val settings = dataService.findSettings(userId)
settings.map(s => Lang(s.lang)).getOrElse(En)
})
cachedLangs.computeIfAbsent(
userId,
_ => {
val settings = dataService.findSettings(userId)
settings.map(s => Lang(s.lang)).getOrElse(En)
}
)
}

View File

@@ -1,4 +1,3 @@
package com.lbs.server.lang
import com.lbs.api.json.model.{Event, TermExt}
@@ -40,14 +39,18 @@ object Pl extends Lang {
s"""<b>➡</b> Czy na pewno chcesz anulować wizytę?
|
|⏱ <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")}
|${capitalize(doctor)}: ${capitalize(event.doctor.name)} ${capitalize(event.doctor.lastname)}
|${capitalize(service)}: ${event.title}
|${capitalize(clinic)}: ${event.clinic
.map(c => s"${capitalize(c.city)} - ${capitalize(c.address)}")
.getOrElse("Telemedicine")}
|""".stripMargin
override def chooseDateFrom(exampleDate: LocalDateTime): 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: LocalDateTime): 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"
@@ -74,18 +77,19 @@ object Pl extends Lang {
override def book: String = "Zarezerwuj"
override def confirmAppointment(term: TermExt): String =
s"""<b>➡</b> Czy potwierdzasz wizytę?
|
|⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}""".stripMargin
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(clinic)}: ${term.term.clinic}""".stripMargin
override def appointmentIsConfirmed: String = "👍 Twoja wizyta została potwierdzona!"
override def monitoringHasBeenCreated: String = "👍 Stworzono monitoring! Sprawdź aktywne monitoringi przez /monitorings"
override def monitoringHasBeenCreated: String =
"👍 Stworzono monitoring! Sprawdź aktywne monitoringi przez /monitorings"
override def unableToCreateMonitoring(reason: String): String = s"👎 Nie udało się stworzyć monitoringu. Powód: $reason."
override def unableToCreateMonitoring(reason: String): String =
s"👎 Nie udało się stworzyć monitoringu. Powód: $reason."
override def chooseTypeOfMonitoring: String = "<b>➡</b> Wybierz typ monitoringu"
@@ -97,7 +101,8 @@ object Pl extends Lang {
override def pleaseSpecifyOffset: String = "<b>➡</b> Podaj offset w godzinach albo kliknij Nie"
override def visitAlreadyExists: String = "<b>➡</b> Wizyta została juz zarezerwowana. Czy chcesz zaktualizować jej termin?"
override def visitAlreadyExists: String =
"<b>➡</b> Wizyta została juz zarezerwowana. Czy chcesz zaktualizować jej termin?"
override def city: String = "miasto"
@@ -118,9 +123,9 @@ object Pl extends Lang {
|
|📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}""".stripMargin
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}""".stripMargin
override def deactivated: String = "👍 Wyłączony! Sprawdź aktywne monitoringi przez /monitorings"
@@ -132,7 +137,8 @@ object Pl extends Lang {
withAnyVariant(
s"""<b>➡</b> Podaj fragment nazwy ${config.name}
|Na przykład: <b>${config.partialExample}</b> jeśli szukasz <b>${config.example}</b>""".stripMargin,
config.isAnyAllowed)
config.isAnyAllowed
)
override def pleaseEnterStaticDataNameOrPrevious(config: StaticDataConfig): String =
s"""<b>➡</b> Podaj fragment nazwy ${config.name}
@@ -141,7 +147,7 @@ object Pl extends Lang {
|lub wybierz ${config.name} z poprzednich wyszukiwań""".stripMargin
override def staticDataIs(config: StaticDataConfig, label: String): String =
s"<b>✅</b> ${capitalizeFirstLetter(config.name)} jest <b>$label</b>"
s"<b>✅</b> ${capitalize(config.name)} jest <b>$label</b>"
override def pleaseChooseStaticDataNameOrAny(config: StaticDataConfig): String =
withAnyVariant(s"<b>➡</b> Wybierz ${config.name}", config.isAnyAllowed)
@@ -149,7 +155,9 @@ object Pl extends Lang {
override def staticNotFound(config: StaticDataConfig): String =
withAnyVariant(
s"""<b>➡</b> Brak wyników 😔
|Proszę podaj nazwę ${config.name} jeszcze raz""", config.isAnyAllowed)
|Proszę podaj nazwę ${config.name} jeszcze raz""",
config.isAnyAllowed
)
override def loginAndPasswordAreOk: String =
s"""✅ Brawo! Login i hasło są OK!
@@ -186,8 +194,8 @@ object Pl extends Lang {
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}
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(clinic)}: ${term.term.clinic}
|<b>➡</b> /book_$index
|
|""".stripMargin
@@ -197,9 +205,11 @@ object Pl extends Lang {
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")}
|${capitalize(doctor)}: ${capitalize(event.doctor.name)} ${capitalize(event.doctor.lastname)}
|${capitalize(service)}: ${event.title}
|${capitalize(clinic)}: ${event.clinic
.map(c => s"${capitalize(c.city)} - ${capitalize(c.address)}")
.getOrElse("Telemedicine")}
|
|""".stripMargin
@@ -208,9 +218,11 @@ object Pl extends Lang {
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")}
|${capitalize(doctor)}: ${capitalize(event.doctor.name)} ${capitalize(event.doctor.lastname)}
|${capitalize(service)}: ${event.title}
|${capitalize(clinic)}: ${event.clinic
.map(c => s"${capitalize(c.city)} - ${capitalize(c.address)}")
.getOrElse("Telemedicine")}
|<b>➡</b> /cancel_$index
|
|""".stripMargin
@@ -224,10 +236,10 @@ object Pl extends Lang {
override def monitoringEntry(monitoring: Monitoring, page: Int, index: Int): String =
s"""📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}
|${capitalize(city)}: ${monitoring.cityName}
|Sposób rejestracji: ${if (monitoring.autobook) "Automatyczny" else "Ręczny"}
|<b>➡</b> /cancel_$index
|
@@ -236,10 +248,10 @@ object Pl extends Lang {
override def monitoringHistoryEntry(monitoring: Monitoring, page: Int, index: Int): String =
s"""📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}
|${capitalize(city)}: ${monitoring.cityName}
|Sposób rejestracji: ${if (monitoring.autobook) "Automatyczny" else "Ręczny"}
|<b>➡</b> /repeat_$index
|
@@ -258,10 +270,10 @@ object Pl extends Lang {
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.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${term.term.clinic}
|${capitalize(city)}: ${monitoring.cityName}
|/reserve_${monitoring.recordId}_${term.term.scheduleId}_${minutesSinceBeginOf2018(term.term.dateTimeFrom.get)}
|
|""".stripMargin
@@ -276,10 +288,10 @@ object Pl extends Lang {
|
|📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}
|${capitalize(city)}: ${monitoring.cityName}
|
|<b>➡</b> Stwórz nowy monitoring przez /book""".stripMargin
@@ -287,10 +299,10 @@ object Pl extends Lang {
s"""👍 Zarezerwowaliśmy za Ciebie termin!
|
|⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}""".stripMargin
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${term.term.clinic}
|${capitalize(city)}: ${monitoring.cityName}""".stripMargin
override def maximumMonitoringsLimitExceeded: String = "Maksymalna liczba monitoringów uzytkownika to 10"
@@ -315,7 +327,8 @@ object Pl extends Lang {
override def configureOffset: String = "<b>➡</b> Wybierz opcje offsetu"
override def pleaseEnterOffset(current: Int): String = s"<b>➡</b> Podaj domyślny offset. Obecny offset: <b>$current</b>"
override def pleaseEnterOffset(current: Int): String =
s"<b>➡</b> Podaj domyślny offset. Obecny offset: <b>$current</b>"
override def alwaysAskOffset(enabled: Boolean): String = s"${if (enabled) "✅ " else ""}Zawsze pytaj o offset"
@@ -353,9 +366,11 @@ object Pl extends Lang {
override def moreParameters: String = "🛠 Więcej opcji"
override def chooseTimeFrom(exampleTime: LocalTime): String = s"<b>➡</b> Wybierz godzinę OD albo zapisz w formacie HH:mm, np. ${formatTime(exampleTime)}"
override def chooseTimeFrom(exampleTime: LocalTime): String =
s"<b>➡</b> Wybierz godzinę OD albo zapisz w formacie HH:mm, np. ${formatTime(exampleTime)}"
override def chooseTimeTo(exampleTime: LocalTime): String = s"<b>➡</b> Wybierz godzinę DO albo zapisz w formacie HH:mm ${formatTime(exampleTime)}"
override def chooseTimeTo(exampleTime: LocalTime): String =
s"<b>➡</b> Wybierz godzinę DO albo zapisz w formacie HH:mm ${formatTime(exampleTime)}"
override def timeFromIs(timeFrom: LocalTime): String = s"⏱ Godzina OD: ${formatTime(timeFrom)}"

View File

@@ -1,4 +1,3 @@
package com.lbs.server.lang
import com.lbs.api.json.model.{Event, TermExt}
@@ -40,14 +39,18 @@ object Ua extends Lang {
s"""<b>➡</b> Ви впевнені, що хочете скасувати візит?
|
|⏱ <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")}
|${capitalize(doctor)}: ${capitalize(event.doctor.name)} ${capitalize(event.doctor.lastname)}
|${capitalize(service)}: ${event.title}
|${capitalize(clinic)}: ${event.clinic
.map(c => s"${capitalize(c.city)} - ${capitalize(c.address)}")
.getOrElse("Telemedicine")}
|""".stripMargin
override def chooseDateFrom(exampleDate: LocalDateTime): 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: LocalDateTime): 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 = "🔍 Знайти терміни"
@@ -74,18 +77,18 @@ object Ua extends Lang {
override def book: String = "Зарезервувати"
override def confirmAppointment(term: TermExt): String =
s"""<b>➡</b> Ви хотіли б підтвердити резервацію візиту?
|
|⏱ <b>${formatDateTime(term.term.dateTimeFrom, locale)}</b>
|${capitalizeFirstLetter(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalizeFirstLetter(clinic)}: ${term.term.clinic}""".stripMargin
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(clinic)}: ${term.term.clinic}""".stripMargin
override def appointmentIsConfirmed: String = "👍 Ваш візит було підтверджено!"
override def monitoringHasBeenCreated: String = "👍 Моніторинг був створений! Список активних /monitorings"
override def unableToCreateMonitoring(reason: String): String = s"👎 Не вдається створити моніторинг. Причина: $reason."
override def unableToCreateMonitoring(reason: String): String =
s"👎 Не вдається створити моніторинг. Причина: $reason."
override def chooseTypeOfMonitoring: String = "<b>➡</b> Будь ласка, виберіть тип моніторингу"
@@ -97,7 +100,8 @@ object Ua extends Lang {
override def pleaseSpecifyOffset: String = "<b>➡</b> Будь ласка, надішліть мені зміщення в годинах, або натисніть Ні"
override def visitAlreadyExists: String = "<b>➡</b> Резервація для такого сервісу вже існує. Чі хотіли би ви змінити термін?"
override def visitAlreadyExists: String =
"<b>➡</b> Резервація для такого сервісу вже існує. Чі хотіли би ви змінити термін?"
override def city: String = "місто"
@@ -118,9 +122,9 @@ object Ua extends Lang {
|
|📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}""".stripMargin
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}""".stripMargin
override def deactivated: String = "👍 Деактивовано! Список активних /monitorings"
@@ -132,7 +136,8 @@ object Ua extends Lang {
withAnyVariant(
s"""<b>➡</b> Будь ласка, введіть частково ${config.name}
|Наприклад: <b>${config.partialExample}</b> якщо ви шукаете <b>${config.example}""".stripMargin,
config.isAnyAllowed)
config.isAnyAllowed
)
override def pleaseEnterStaticDataNameOrPrevious(config: StaticDataConfig): String =
s"""<b>➡</b> Будь ласка, введіть частково ${config.name}
@@ -141,7 +146,7 @@ object Ua extends Lang {
|або оберіть ${config.name} з попередніх пошуків""".stripMargin
override def staticDataIs(config: StaticDataConfig, label: String): String =
s"<b>✅</b> ${capitalizeFirstLetter(config.name)} <b>$label</b>"
s"<b>✅</b> ${capitalize(config.name)} <b>$label</b>"
override def pleaseChooseStaticDataNameOrAny(config: StaticDataConfig): String =
withAnyVariant(s"<b>➡</b> Будь ласка, виберіть ${config.name}", config.isAnyAllowed)
@@ -149,7 +154,9 @@ object Ua extends Lang {
override def staticNotFound(config: StaticDataConfig): String =
withAnyVariant(
s"""<b>➡</b> Нічого не знайдено 😔
|Будь ласка, введіть ${config.name} знову""".stripMargin, config.isAnyAllowed)
|Будь ласка, введіть ${config.name} знову""".stripMargin,
config.isAnyAllowed
)
override def loginAndPasswordAreOk: String =
s"""✅ Супер! Логін і пароль збережено
@@ -185,8 +192,8 @@ object Ua extends Lang {
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}
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(clinic)}: ${term.term.clinic}
|<b>➡</b> /book_$index
|
|""".stripMargin
@@ -196,9 +203,11 @@ object Ua extends Lang {
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")}
|${capitalize(doctor)}: ${capitalize(event.doctor.name)} ${capitalize(event.doctor.lastname)}
|${capitalize(service)}: ${event.title}
|${capitalize(clinic)}: ${event.clinic
.map(c => s"${capitalize(c.city)} - ${capitalize(c.address)}")
.getOrElse("Telemedicine")}
|
|""".stripMargin
@@ -207,9 +216,11 @@ object Ua extends Lang {
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")}
|${capitalize(doctor)}: ${capitalize(event.doctor.name)} ${capitalize(event.doctor.lastname)}
|${capitalize(service)}: ${event.title}
|${capitalize(clinic)}: ${event.clinic
.map(c => s"${capitalize(c.city)} - ${capitalize(c.address)}")
.getOrElse("Telemedicine")}
|<b>➡</b> /cancel_$index
|
|""".stripMargin
@@ -223,10 +234,10 @@ object Ua extends Lang {
override def monitoringEntry(monitoring: Monitoring, page: Int, index: Int): String =
s"""📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}
|${capitalize(city)}: ${monitoring.cityName}
|Тип: ${if (monitoring.autobook) "Автоматичний" else "Ручний"}
|<b>➡</b> /cancel_$index
|
@@ -235,10 +246,10 @@ object Ua extends Lang {
override def monitoringHistoryEntry(monitoring: Monitoring, page: Int, index: Int): String =
s"""📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}
|${capitalize(city)}: ${monitoring.cityName}
|Тип: ${if (monitoring.autobook) "Автоматичний" else "Ручний"}
|<b>➡</b> /repeat_$index
|
@@ -257,10 +268,10 @@ object Ua extends Lang {
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.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${term.term.clinic}
|${capitalize(city)}: ${monitoring.cityName}
|/reserve_${monitoring.recordId}_${term.term.scheduleId}_${minutesSinceBeginOf2018(term.term.dateTimeFrom.get)}
|
|""".stripMargin
@@ -275,10 +286,10 @@ object Ua extends Lang {
|
|📅 <b>${formatDate(monitoring.dateFrom, locale)}</b> -> <b>${formatDate(monitoring.dateTo, locale)}</b>
|⏱ <b>${formatTime(monitoring.timeFrom)}</b> -> <b>${formatTime(monitoring.timeTo)}</b>
|${capitalizeFirstLetter(doctor)}: ${monitoring.doctorName}
|${capitalizeFirstLetter(service)}: ${monitoring.serviceName}
|${capitalizeFirstLetter(clinic)}: ${monitoring.clinicName}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}
|${capitalize(doctor)}: ${monitoring.doctorName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${monitoring.clinicName}
|${capitalize(city)}: ${monitoring.cityName}
|
|<b>➡</b> Створити новий моніторінг /book""".stripMargin
@@ -286,10 +297,10 @@ object Ua extends Lang {
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.term.clinic}
|${capitalizeFirstLetter(city)}: ${monitoring.cityName}""".stripMargin
|${capitalize(doctor)}: ${term.term.doctor.firstName} ${term.term.doctor.lastName}
|${capitalize(service)}: ${monitoring.serviceName}
|${capitalize(clinic)}: ${term.term.clinic}
|${capitalize(city)}: ${monitoring.cityName}""".stripMargin
override def maximumMonitoringsLimitExceeded: String = "Максимальна кількість моніторінгів 10"
@@ -314,7 +325,8 @@ object Ua extends Lang {
override def configureOffset: String = "<b>➡</b> Будь ласка, сконфігуруйте зміщення"
override def pleaseEnterOffset(current: Int): String = s"<b>➡</b> Будь ласка, введіть зміщення за замовчуванням. Поточне: <b>$current</b>"
override def pleaseEnterOffset(current: Int): String =
s"<b>➡</b> Будь ласка, введіть зміщення за замовчуванням. Поточне: <b>$current</b>"
override def alwaysAskOffset(enabled: Boolean): String = s"${if (enabled) "✅ " else ""}Завжди питати зміщення"
@@ -352,9 +364,11 @@ object Ua extends Lang {
override def moreParameters: String = "🛠 Більше налаштувань"
override def chooseTimeFrom(exampleTime: LocalTime): String = s"<b>➡</b> Будь ласка, виберіть початковий час або введіть час, використовуючи формат HH:mm, наприклад ${formatTime(exampleTime)}"
override def chooseTimeFrom(exampleTime: LocalTime): String =
s"<b>➡</b> Будь ласка, виберіть початковий час або введіть час, використовуючи формат HH:mm, наприклад ${formatTime(exampleTime)}"
override def chooseTimeTo(exampleTime: LocalTime): String = s"<b>➡</b> Будь ласка, виберіть кінцевий час або введіть час, використовуючи формат HH:mm, наприклад ${formatTime(exampleTime)}"
override def chooseTimeTo(exampleTime: LocalTime): String =
s"<b>➡</b> Будь ласка, виберіть кінцевий час або введіть час, використовуючи формат HH:mm, наприклад ${formatTime(exampleTime)}"
override def timeFromIs(timeFrom: LocalTime): String = s"⏱ Початковий час ${formatTime(timeFrom)}"
@@ -362,5 +376,6 @@ object Ua extends Lang {
override def canNotDetectPayer(error: String): String = s"Не можу визначити платника. Причина: $error"
override def pleaseChoosePayer: String = "<b>➡</b> Не можу визначити платника за замовчуванням. Будь ласка, виберіть платника"
override def pleaseChoosePayer: String =
"<b>➡</b> Не можу визначити платника за замовчуванням. Будь ласка, виберіть платника"
}

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository
import com.lbs.server.repository.model.{CityHistory, ClinicHistory, Credentials, DoctorHistory, JLong, Monitoring, ServiceHistory, Settings, Source, SystemUser}
@@ -18,29 +17,40 @@ class DataRepository(@Autowired em: EntityManager) {
em.createQuery(
"""select city from CityHistory city where city.recordId in
| (select max(c.recordId) from CityHistory c where c.accountId = :accountId group by c.name order by MAX(c.time) desc)
| order by city.time desc""".stripMargin, classOf[CityHistory])
.setParameter("accountId", accountId)
| order by city.time desc""".stripMargin,
classOf[CityHistory]
).setParameter("accountId", accountId)
.setMaxResults(maxHistory)
.getResultList.asScala.toSeq
.getResultList
.asScala
.toSeq
}
def getClinicHistory(accountId: Long, cityId: Long): Seq[ClinicHistory] = {
em.createQuery(
"""select clinic from ClinicHistory clinic where clinic.recordId in
| (select max(c.recordId) from ClinicHistory c where c.accountId = :accountId and c.cityId = :cityId group by c.name order by MAX(c.time) desc)
| order by clinic.time desc""".stripMargin, classOf[ClinicHistory])
.setParameter("accountId", accountId)
| order by clinic.time desc""".stripMargin,
classOf[ClinicHistory]
).setParameter("accountId", accountId)
.setParameter("cityId", cityId)
.setMaxResults(maxHistory)
.getResultList.asScala.toSeq
.getResultList
.asScala
.toSeq
}
def getServiceHistory(accountId: Long, cityId: Long, clinicId: Option[Long]): Seq[ServiceHistory] = {
val query = em.createQuery(
s"""select service from ServiceHistory service where service.recordId in
val query = em
.createQuery(
s"""select service from ServiceHistory service where service.recordId in
| (select max(s.recordId) from ServiceHistory s where s.accountId = :accountId and s.cityId = :cityId
| and s.clinicId ${clinicId.map(_ => "= :clinicId").getOrElse("IS NULL")} group by s.name order by MAX(s.time) desc)
| order by service.time desc""".stripMargin, classOf[ServiceHistory])
| and s.clinicId ${clinicId
.map(_ => "= :clinicId")
.getOrElse("IS NULL")} group by s.name order by MAX(s.time) desc)
| order by service.time desc""".stripMargin,
classOf[ServiceHistory]
)
.setParameter("accountId", accountId)
.setParameter("cityId", cityId)
.setMaxResults(maxHistory)
@@ -49,12 +59,15 @@ class DataRepository(@Autowired em: EntityManager) {
}
def getDoctorHistory(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): Seq[DoctorHistory] = {
val query = em.createQuery(
s"""select doctor from DoctorHistory doctor where doctor.recordId in
val query = em
.createQuery(
s"""select doctor from DoctorHistory doctor where doctor.recordId in
| (select max(d.recordId) from DoctorHistory d where d.accountId = :accountId
| and d.cityId = :cityId and d.clinicId ${clinicId.map(_ => "= :clinicId").getOrElse("IS NULL")}
| and d.serviceId = :serviceId group by d.name order by MAX(d.time) desc)
| order by doctor.time desc""".stripMargin, classOf[DoctorHistory])
| order by doctor.time desc""".stripMargin,
classOf[DoctorHistory]
)
.setParameter("accountId", accountId)
.setParameter("cityId", cityId)
.setParameter("serviceId", serviceId)
@@ -65,158 +78,204 @@ class DataRepository(@Autowired em: EntityManager) {
def findCredentials(accountId: Long): Option[Credentials] = {
em.createQuery(
"select credentials from Credentials credentials where credentials.accountId = :accountId", classOf[Credentials])
.setParameter("accountId", accountId)
.getResultList.asScala.headOption
"select credentials from Credentials credentials where credentials.accountId = :accountId",
classOf[Credentials]
).setParameter("accountId", accountId)
.getResultList
.asScala
.headOption
}
def getActiveMonitorings: Seq[Monitoring] = {
em.createQuery(
"""select monitoring from Monitoring monitoring where monitoring.active = true""".stripMargin, classOf[Monitoring])
.getResultList.asScala.toSeq
"""select monitoring from Monitoring monitoring where monitoring.active = true""".stripMargin,
classOf[Monitoring]
).getResultList
.asScala
.toSeq
}
def getActiveMonitoringsCount(accountId: Long): JLong = {
em.createQuery(
"""select count(monitoring) from Monitoring monitoring where monitoring.active = true
| and monitoring.accountId = :accountId""".stripMargin, classOf[JLong])
.setParameter("accountId", accountId)
| and monitoring.accountId = :accountId""".stripMargin,
classOf[JLong]
).setParameter("accountId", accountId)
.getSingleResult
}
def getActiveMonitorings(accountId: Long): Seq[Monitoring] = {
em.createQuery(
"""select monitoring from Monitoring monitoring where monitoring.active = true
| and monitoring.accountId = :accountId order by monitoring.dateTo asc""".stripMargin, classOf[Monitoring])
.setParameter("accountId", accountId)
.getResultList.asScala.toSeq
| and monitoring.accountId = :accountId order by monitoring.dateTo asc""".stripMargin,
classOf[Monitoring]
).setParameter("accountId", accountId)
.getResultList
.asScala
.toSeq
}
def getAllMonitoringsCount(accountId: Long): JLong = {
em.createQuery(
"""select count(monitoring) from Monitoring monitoring where
| monitoring.accountId = :accountId""".stripMargin, classOf[JLong])
.setParameter("accountId", accountId)
| monitoring.accountId = :accountId""".stripMargin,
classOf[JLong]
).setParameter("accountId", accountId)
.getSingleResult
}
def getMonitoringsPage(accountId: Long, start: Int, count: Int): Seq[Monitoring] = {
em.createQuery(
"""select monitoring from Monitoring monitoring where monitoring.accountId = :accountId
| order by monitoring.created desc""".stripMargin, classOf[Monitoring])
.setParameter("accountId", accountId)
| order by monitoring.created desc""".stripMargin,
classOf[Monitoring]
).setParameter("accountId", accountId)
.setFirstResult(start)
.setMaxResults(count)
.getResultList.asScala.toSeq
.getResultList
.asScala
.toSeq
}
def findActiveMonitoring(accountId: Long, cityId: Long, serviceId: Long, doctorId: Long): Option[Monitoring] = {
em.createQuery(
"""select monitoring from Monitoring monitoring where monitoring.active = true
| and monitoring.accountId = :accountId
| and monitoring.cityId = :cityId
| and monitoring.serviceId = :serviceId
| and monitoring.doctorId = :doctorId""".stripMargin, classOf[Monitoring])
.setParameter("accountId", accountId)
| and monitoring.doctorId = :doctorId""".stripMargin,
classOf[Monitoring]
).setParameter("accountId", accountId)
.setParameter("cityId", cityId)
.setParameter("serviceId", serviceId)
.setParameter("doctorId", doctorId)
.getResultList.asScala.headOption
.getResultList
.asScala
.headOption
}
def getActiveMonitoringsSince(since: ZonedDateTime): Seq[Monitoring] = {
em.createQuery(
"""select monitoring from Monitoring monitoring where monitoring.active = true
| and monitoring.created > :since""".stripMargin, classOf[Monitoring])
.setParameter("since", since)
.getResultList.asScala.toSeq
| and monitoring.created > :since""".stripMargin,
classOf[Monitoring]
).setParameter("since", since)
.getResultList
.asScala
.toSeq
}
def findMonitoring(accountId: Long, monitoringId: Long): Option[Monitoring] = {
em.createQuery(
"""select monitoring from Monitoring monitoring where monitoring.accountId = :accountId
| and monitoring.recordId = :monitoringId""".stripMargin, classOf[Monitoring])
.setParameter("accountId", accountId)
| and monitoring.recordId = :monitoringId""".stripMargin,
classOf[Monitoring]
).setParameter("accountId", accountId)
.setParameter("monitoringId", monitoringId)
.getResultList.asScala.headOption
.getResultList
.asScala
.headOption
}
def findSettings(userId: Long): Option[Settings] = {
em.createQuery(
"select settings from Settings settings where settings.userId = :userId", classOf[Settings])
em.createQuery("select settings from Settings settings where settings.userId = :userId", classOf[Settings])
.setParameter("userId", userId)
.getResultList.asScala.headOption
.getResultList
.asScala
.headOption
}
def findUserId(chatId: String, sourceSystemId: Long): Option[JLong] = {
em.createQuery(
"select source.userId from Source source where source.chatId = :chatId" +
" and source.sourceSystemId = :sourceSystemId", classOf[JLong])
.setParameter("chatId", chatId)
" and source.sourceSystemId = :sourceSystemId",
classOf[JLong]
).setParameter("chatId", chatId)
.setParameter("sourceSystemId", sourceSystemId)
.getResultList.asScala.headOption
.getResultList
.asScala
.headOption
}
def findCredentialsByUsername(username: String, userId: Long): Option[Credentials] = {
em.createQuery(
"select credentials from Credentials credentials where credentials.username = :username" +
" and credentials.userId = :userId", classOf[Credentials])
.setParameter("username", username)
" and credentials.userId = :userId",
classOf[Credentials]
).setParameter("username", username)
.setParameter("userId", userId)
.getResultList.asScala.headOption
.getResultList
.asScala
.headOption
}
def findSource(chatId: String, sourceSystemId: Long, userId: Long): Option[Source] = {
em.createQuery(
"select source from Source source where source.chatId = :chatId" +
" and source.sourceSystemId = :sourceSystemId" +
" and userId = :userId", classOf[Source])
.setParameter("chatId", chatId)
" and userId = :userId",
classOf[Source]
).setParameter("chatId", chatId)
.setParameter("sourceSystemId", sourceSystemId)
.setParameter("userId", userId)
.getResultList.asScala.headOption
.getResultList
.asScala
.headOption
}
def findUserIdBySource(chatId: String, sourceSystemId: Long): Option[JLong] = {
em.createQuery(
"select source.userId from Source source where source.chatId = :chatId" +
" and source.sourceSystemId = :sourceSystemId", classOf[JLong])
.setParameter("chatId", chatId)
" and source.sourceSystemId = :sourceSystemId",
classOf[JLong]
).setParameter("chatId", chatId)
.setParameter("sourceSystemId", sourceSystemId)
.getResultList.asScala.headOption
.getResultList
.asScala
.headOption
}
def findAccountId(userId: Long): Option[JLong] = {
em.createQuery(
"select systemUser.activeAccountId from SystemUser systemUser where systemUser.recordId = :recordId", classOf[JLong])
.setParameter("recordId", userId)
.getResultList.asScala.headOption
"select systemUser.activeAccountId from SystemUser systemUser where systemUser.recordId = :recordId",
classOf[JLong]
).setParameter("recordId", userId)
.getResultList
.asScala
.headOption
}
def findUser(userId: Long): Option[SystemUser] = {
em.createQuery(
"select systemUser from SystemUser systemUser where systemUser.recordId = :recordId", classOf[SystemUser])
.setParameter("recordId", userId)
.getResultList.asScala.headOption
"select systemUser from SystemUser systemUser where systemUser.recordId = :recordId",
classOf[SystemUser]
).setParameter("recordId", userId)
.getResultList
.asScala
.headOption
}
def getUserCredentials(userId: Long): Seq[Credentials] = {
em.createQuery(
"select credentials from Credentials credentials where credentials.userId = :userId", classOf[Credentials])
.setParameter("userId", userId)
.getResultList.asScala.toSeq
"select credentials from Credentials credentials where credentials.userId = :userId",
classOf[Credentials]
).setParameter("userId", userId)
.getResultList
.asScala
.toSeq
}
def findUserCredentialsByUserIdAndAccountId(userId: Long, accountId: Long): Option[Credentials] = {
em.createQuery(
"""select credentials from Credentials credentials where credentials.userId = :userId
| and credentials.accountId = :accountId
""".stripMargin, classOf[Credentials])
.setParameter("userId", userId)
""".stripMargin,
classOf[Credentials]
).setParameter("userId", userId)
.setParameter("accountId", accountId)
.getResultList.asScala.headOption
.getResultList
.asScala
.headOption
}
def saveEntity[T](entity: T): T = {

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository.model
import javax.persistence._

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository.model
import java.time.ZonedDateTime

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository.model
import java.time.ZonedDateTime

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository.model
import javax.persistence._

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository.model
import java.time.ZonedDateTime
@@ -38,7 +37,15 @@ class DoctorHistory extends History with RecordId {
}
object DoctorHistory {
def apply(accountId: Long, id: Long, name: String, cityId: Long, clinicId: Option[Long], serviceId: Long, time: ZonedDateTime): DoctorHistory = {
def apply(
accountId: Long,
id: Long,
name: String,
cityId: Long,
clinicId: Option[Long],
serviceId: Long,
time: ZonedDateTime
): DoctorHistory = {
val doctor = new DoctorHistory
doctor.accountId = accountId
doctor.id = id

View File

@@ -1,7 +1,5 @@
package com.lbs.server.repository.model
trait History {
def id: JLong

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository.model
import java.time.{LocalTime, ZonedDateTime}
@@ -98,10 +97,30 @@ class Monitoring extends RecordId {
}
object Monitoring {
def apply(userId: Long, accountId: Long, chatId: String, sourceSystemId: Long, payerId: Long, cityId: Long, cityName: String, clinicId: Option[Long], clinicName: String,
serviceId: Long, serviceName: String, doctorId: Option[Long], doctorName: String, dateFrom: ZonedDateTime,
dateTo: ZonedDateTime, autobook: Boolean = false, rebookIfExists: Boolean = false, created: ZonedDateTime = ZonedDateTime.now(), timeFrom: LocalTime, timeTo: LocalTime,
active: Boolean = true, offset: Int): Monitoring = {
def apply(
userId: Long,
accountId: Long,
chatId: String,
sourceSystemId: Long,
payerId: Long,
cityId: Long,
cityName: String,
clinicId: Option[Long],
clinicName: String,
serviceId: Long,
serviceName: String,
doctorId: Option[Long],
doctorName: String,
dateFrom: ZonedDateTime,
dateTo: ZonedDateTime,
autobook: Boolean = false,
rebookIfExists: Boolean = false,
created: ZonedDateTime = ZonedDateTime.now(),
timeFrom: LocalTime,
timeTo: LocalTime,
active: Boolean = true,
offset: Int
): Monitoring = {
val monitoring = new Monitoring
monitoring.userId = userId
monitoring.accountId = accountId

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository.model
import javax.persistence._

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository.model
import java.time.ZonedDateTime
@@ -34,7 +33,14 @@ class ServiceHistory extends History with RecordId {
}
object ServiceHistory {
def apply(accountId: Long, id: Long, name: String, cityId: Long, clinicId: Option[Long], time: ZonedDateTime): ServiceHistory = {
def apply(
accountId: Long,
id: Long,
name: String,
cityId: Long,
clinicId: Option[Long],
time: ZonedDateTime
): ServiceHistory = {
val service = new ServiceHistory
service.accountId = accountId
service.id = id

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository.model
import javax.persistence.{Access, AccessType, Column, Entity}
@@ -13,7 +12,7 @@ class Settings extends RecordId {
@BeanProperty
@Column(nullable = false)
var lang: Int = 0 //En by default
var lang: Int = 0 // En by default
@BeanProperty
@Column(nullable = false)

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository.model
import javax.persistence._

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository.model
import javax.persistence._

View File

@@ -1,4 +1,3 @@
package com.lbs.server.repository
import scala.language.implicitConversions

View File

@@ -1,4 +1,3 @@
package com.lbs.server.service
import cats.instances.either._
@@ -32,7 +31,9 @@ class ApiService extends SessionSupport {
def getAllFacilities(accountId: Long, cityId: Long, serviceVariantId: Long): ThrowableOr[List[IdName]] =
withSession(accountId) { session =>
luxmedApi.dictionaryFacilitiesAndDoctors(session, cityId = Some(cityId), serviceVariantId = Some(serviceVariantId)).map(_.facilities)
luxmedApi
.dictionaryFacilitiesAndDoctors(session, cityId = Some(cityId), serviceVariantId = Some(serviceVariantId))
.map(_.facilities)
}
def getAllServices(accountId: Long): ThrowableOr[List[DictionaryServiceVariants]] =
@@ -42,60 +43,98 @@ class ApiService extends SessionSupport {
def getAllDoctors(accountId: Long, cityId: Long, serviceVariantId: Long): ThrowableOr[List[Doctor]] =
withSession(accountId) { session =>
luxmedApi.dictionaryFacilitiesAndDoctors(session, cityId = Some(cityId), serviceVariantId = Some(serviceVariantId)).map(_.doctors)
luxmedApi
.dictionaryFacilitiesAndDoctors(session, cityId = Some(cityId), serviceVariantId = Some(serviceVariantId))
.map(_.doctors)
}
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]] =
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 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)))
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.term.dateTimeFrom.get.toLocalTime
val date = term.term.dateTimeFrom.get
(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)))
(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 reservationLockterm(accountId: Long, xsrfToken: XsrfToken, reservationLocktermRequest: ReservationLocktermRequest): ThrowableOr[ReservationLocktermResponse] =
def reservationLockterm(
accountId: Long,
xsrfToken: XsrfToken,
reservationLocktermRequest: ReservationLocktermRequest
): ThrowableOr[ReservationLocktermResponse] =
withSession(accountId) { session =>
luxmedApi.reservationLockterm(session, xsrfToken, reservationLocktermRequest)
}
def deleteTemporaryReservation(accountId: Long, xsrfToken: XsrfToken, temporaryReservationId: Long): ThrowableOr[Unit] =
def deleteTemporaryReservation(
accountId: Long,
xsrfToken: XsrfToken,
temporaryReservationId: Long
): ThrowableOr[Unit] =
withSession(accountId) { session =>
luxmedApi.deleteTemporaryReservation(session, xsrfToken, temporaryReservationId)
}
def reservationConfirm(accountId: Long, xsrfToken: XsrfToken, reservationConfirmRequest: ReservationConfirmRequest): ThrowableOr[ReservationConfirmResponse] =
def reservationConfirm(
accountId: Long,
xsrfToken: XsrfToken,
reservationConfirmRequest: ReservationConfirmRequest
): ThrowableOr[ReservationConfirmResponse] =
withSession(accountId) { session =>
luxmedApi.reservationConfirm(session, xsrfToken, reservationConfirmRequest)
}
def reservationChangeTerm(accountId: Long, xsrfToken: XsrfToken, reservationChangetermRequest: ReservationChangetermRequest): ThrowableOr[ReservationConfirmResponse] =
def reservationChangeTerm(
accountId: Long,
xsrfToken: XsrfToken,
reservationChangetermRequest: ReservationChangetermRequest
): ThrowableOr[ReservationConfirmResponse] =
withSession(accountId) { session =>
luxmedApi.reservationChangeTerm(session, xsrfToken, reservationChangetermRequest)
}
def history(accountId: Long, fromDate: LocalDateTime = LocalDateTime.now().minusYears(1),
toDate: LocalDateTime = LocalDateTime.now()): ThrowableOr[List[Event]] =
def history(
accountId: Long,
fromDate: LocalDateTime = LocalDateTime.now().minusYears(1),
toDate: LocalDateTime = LocalDateTime.now()
): ThrowableOr[List[Event]] =
withSession(accountId) { session =>
luxmedApi.events(session, fromDate.atZone(DateTimeUtil.Zone), toDate.atZone(DateTimeUtil.Zone)).map(_.events.filter(_.status == "Realized"))
luxmedApi
.events(session, fromDate.atZone(DateTimeUtil.Zone), toDate.atZone(DateTimeUtil.Zone))
.map(_.events.filter(_.status == "Realized"))
}
def reserved(accountId: Long, fromDate: LocalDateTime = LocalDateTime.now(),
toDate: LocalDateTime = LocalDateTime.now().plusMonths(3)): ThrowableOr[List[Event]] =
def reserved(
accountId: Long,
fromDate: LocalDateTime = LocalDateTime.now(),
toDate: LocalDateTime = LocalDateTime.now().plusMonths(3)
): ThrowableOr[List[Event]] =
withSession(accountId) { session =>
luxmedApi.events(session, fromDate.atZone(DateTimeUtil.Zone), toDate.atZone(DateTimeUtil.Zone)).map(_.events.filter(_.status == "Reserved"))
luxmedApi
.events(session, fromDate.atZone(DateTimeUtil.Zone), toDate.atZone(DateTimeUtil.Zone))
.map(_.events.filter(_.status == "Reserved"))
}
def deleteReservation(accountId: Long, reservationId: Long): ThrowableOr[HttpResponse[String]] =

View File

@@ -1,4 +1,3 @@
package com.lbs.server.service
import com.lbs.api.json.model.IdName
@@ -31,7 +30,12 @@ class DataService {
dataRepository.getServiceHistory(userId, cityId, clinicId).mapTo[Seq[IdName]]
}
def getLatestDoctorsByCityIdAndClinicIdAndServiceId(userId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): Seq[IdName] = {
def getLatestDoctorsByCityIdAndClinicIdAndServiceId(
userId: Long,
cityId: Long,
clinicId: Option[Long],
serviceId: Long
): Seq[IdName] = {
dataRepository.getDoctorHistory(userId, cityId, clinicId, serviceId).mapTo[Seq[IdName]]
}
@@ -78,7 +82,9 @@ class DataService {
def findUserAndAccountIdBySource(source: MessageSource): Option[(Long, Long)] = {
val userIdMaybe = dataRepository.findUserId(source.chatId, source.sourceSystem.id).map(_.toLong)
userIdMaybe.flatMap(userId => dataRepository.findAccountId(userId).map(_.toLong).map(accountId => userId -> accountId))
userIdMaybe.flatMap(userId =>
dataRepository.findAccountId(userId).map(_.toLong).map(accountId => userId -> accountId)
)
}
def findCredentialsByUsername(username: String, userId: Long): Option[Credentials] = {
@@ -109,18 +115,18 @@ class DataService {
@Transactional
def saveCredentials(source: MessageSource, username: String, password: String): Credentials = {
val userMaybe = dataRepository.findUserIdBySource(source.chatId, source.sourceSystem.id).flatMap {
userId => dataRepository.findUser(userId).map(_ -> userId)
val userMaybe = dataRepository.findUserIdBySource(source.chatId, source.sourceSystem.id).flatMap { userId =>
dataRepository.findUser(userId).map(_ -> userId)
}
userMaybe match {
case Some((user, userId)) =>
val credentialsMaybe = findCredentialsByUsername(username, userId)
credentialsMaybe match {
case Some(credentials) => //user already exists
case Some(credentials) => // user already exists
val sourceMaybe = dataRepository.findSource(source.chatId, source.sourceSystem.id, credentials.userId)
sourceMaybe match {
case Some(_) => //source already exists. Just update credentials
case None => //add new source
case Some(_) => // source already exists. Just update credentials
case None => // add new source
val src = Source(source.chatId, source.sourceSystem.id, credentials.userId)
dataRepository.saveEntity(src)
}
@@ -135,8 +141,8 @@ class DataService {
dataRepository.saveEntity(user)
val sourceMaybe = dataRepository.findSource(source.chatId, source.sourceSystem.id, user.recordId)
sourceMaybe match {
case Some(_) => //source already exists. Just save credentials
case None => //add new source
case Some(_) => // source already exists. Just save credentials
case None => // add new source
val src = Source(source.chatId, source.sourceSystem.id, user.recordId)
dataRepository.saveEntity(src)
}
@@ -144,7 +150,7 @@ class DataService {
dataRepository.saveEntity(credentials)
}
case None => //everything is new
case None => // everything is new
val account = dataRepository.saveEntity(new Account)
val user = dataRepository.saveEntity(SystemUser(account.recordId))
val src = Source(source.chatId, source.sourceSystem.id, user.recordId)
@@ -171,7 +177,9 @@ class DataService {
val service = ServiceHistory(accountId, serviceId.id, serviceId.name, cityId.id, clinicId.optionalId, time)
dataRepository.saveEntity(service)
val doctorMaybe = doctorId.optionalId.map(id => DoctorHistory(accountId, id, doctorId.name, cityId.id, clinicId.optionalId, serviceId.id, time))
val doctorMaybe = doctorId.optionalId.map(id =>
DoctorHistory(accountId, id, doctorId.name, cityId.id, clinicId.optionalId, serviceId.id, time)
)
doctorMaybe.foreach(dataRepository.saveEntity)
}
}

View File

@@ -1,4 +1,3 @@
package com.lbs.server.service
import com.lbs.api.exception.InvalidLoginOrPasswordException
@@ -54,7 +53,7 @@ class MonitoringService extends StrictLogging {
def notifyUserAboutTerms(terms: Seq[TermExt], monitoring: Monitoring): Unit = {
deactivateMonitoring(monitoring.accountId, monitoring.recordId)
val fiveTerms = terms.take(5).zipWithIndex //send only 5 closest terms
val fiveTerms = terms.take(5).zipWithIndex // send only 5 closest terms
val messages = lang(monitoring.userId)
val message = messages.availableTermsHeader(terms.length) + "\n\n" +
@@ -68,8 +67,17 @@ class MonitoringService extends StrictLogging {
private def monitor(monitoring: Monitoring): Unit = {
logger.debug(s"Looking for available terms. Monitoring [#${monitoring.recordId}]")
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)
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) {
@@ -105,7 +113,9 @@ class MonitoringService extends StrictLogging {
val delaySnapshot = delay
val periodSnapshot = period
val future = monitoringExecutor.schedule(monitor(monitoring), delaySnapshot, periodSnapshot)
logger.debug(s"Scheduled monitoring: [#${monitoring.recordId}] with delay: $delaySnapshot and period: $periodSnapshot")
logger.debug(
s"Scheduled monitoring: [#${monitoring.recordId}] with delay: $delaySnapshot and period: $periodSnapshot"
)
activeMonitorings += (monitoring.recordId -> (monitoring -> future))
}
}
@@ -127,8 +137,9 @@ class MonitoringService extends StrictLogging {
private def disableOutdated(): Unit = {
val now = ZonedDateTime.now()
val toDisable = activeMonitorings.collect { case (id, (monitoring, _)) if monitoring.dateTo.isBefore(now) =>
id -> monitoring
val toDisable = activeMonitorings.collect {
case (id, (monitoring, _)) if monitoring.dateTo.isBefore(now) =>
id -> monitoring
}
toDisable.foreach { case (id, monitoring) =>
@@ -150,14 +161,37 @@ class MonitoringService extends StrictLogging {
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])
reservationLocktermResponse <- apiService.reservationLockterm(
monitoring.accountId,
xsrfToken,
term.mapTo[ReservationLocktermRequest]
)
temporaryReservationId = reservationLocktermResponse.value.temporaryReservationId
response <- if (reservationLocktermResponse.value.changeTermAvailable && rebookIfExists) {
logger.info(s"Service [${monitoring.serviceName}] is already booked. Trying to update term")
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]))
}
response <-
if (reservationLocktermResponse.value.changeTermAvailable && rebookIfExists) {
logger.info(s"Service [${monitoring.serviceName}] is already booked. Trying to update term")
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(_) =>
@@ -168,9 +202,14 @@ class MonitoringService extends StrictLogging {
}
}
private def bookOrUnlockTerm[T](accountId: Long, xsrfToken: XsrfToken, temporaryReservationId: Long, fn: (Long) => Either[Throwable, T]): Either[Throwable, T] = {
private def bookOrUnlockTerm[T](
accountId: Long,
xsrfToken: XsrfToken,
temporaryReservationId: Long,
fn: (Long) => Either[Throwable, T]
): Either[Throwable, T] = {
fn(accountId) match {
case r@Left(_) =>
case r @ Left(_) =>
apiService.deleteTemporaryReservation(accountId, xsrfToken, temporaryReservationId)
r
case r => r
@@ -218,11 +257,22 @@ class MonitoringService extends StrictLogging {
val monitoringMaybe = dataService.findMonitoring(accountId, monitoringId)
monitoringMaybe match {
case Some(monitoring) =>
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)
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.term.scheduleId == scheduleId && minutesSinceBeginOf2018(term.term.dateTimeFrom.get) == time)
val termMaybe = terms.find(term =>
term.term.scheduleId == scheduleId && minutesSinceBeginOf2018(term.term.dateTimeFrom.get) == time
)
termMaybe match {
case Some(term) =>
bookAppointment(term, monitoring, rebookIfExists = true)
@@ -232,7 +282,8 @@ class MonitoringService extends StrictLogging {
case Left(ex: InvalidLoginOrPasswordException) =>
logger.error(s"User entered invalid name or password. Monitoring will be disabled", ex)
bot.sendMessage(monitoring.source, lang(monitoring.userId).loginHasChangedOrWrong)
case Left(ex) => logger.error(s"Error occurred during receiving terms for monitoring [#${monitoring.recordId}]", ex)
case Left(ex) =>
logger.error(s"Error occurred during receiving terms for monitoring [#${monitoring.recordId}]", ex)
}
case None =>
logger.debug(s"Monitoring [#$monitoringId] not found in db")
@@ -240,9 +291,7 @@ class MonitoringService extends StrictLogging {
}
implicit class MonitoringAsSource(monitoring: Monitoring) {
def source: MessageSource = MessageSource(
MessageSourceSystem(monitoring.sourceSystemId), monitoring.chatId
)
def source: MessageSource = MessageSource(MessageSourceSystem(monitoring.sourceSystemId), monitoring.chatId)
}
private def lang(userId: Long) = localization.lang(userId)

View File

@@ -1,4 +1,3 @@
package com.lbs.server.service
import com.lbs.api.exception.SessionExpiredException
@@ -27,7 +26,7 @@ trait SessionSupport extends StrictLogging {
val credentialsMaybe = dataService.getCredentials(accountId)
credentialsMaybe match {
case Some(credentials) => fullLogin(credentials.username, credentials.password)
case None => Left(UserNotFoundException(accountId))
case None => Left(UserNotFoundException(accountId))
}
}

Some files were not shown because too many files have changed in this diff Show More