Skip to content

Commit 10fe56b

Browse files
committed
fix/qg-266: на страницу списка клиентов добавлена кнопка добавления записи в журнал
1 parent cb4aea8 commit 10fe56b

21 files changed

Lines changed: 283 additions & 189 deletions

File tree

app/src/main/kotlin/pro/qyoga/app/therapist/clients/ClientPageModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import pro.qyoga.core.clients.cards.toDto
99
enum class ClientPageTab {
1010
JOURNAL,
1111
CARD,
12-
FILES
12+
FILES,
13+
ADD_JOURNAL_ENTRY
1314
}
1415

1516
fun clientPageModel(

app/src/main/kotlin/pro/qyoga/app/therapist/clients/cards/ClientPageModel.kt renamed to app/src/main/kotlin/pro/qyoga/app/therapist/clients/cards/EditClientPageModel.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import pro.qyoga.core.clients.cards.dtos.ClientCardDto
55

66

77
fun editClientFormWithValidationError(clientCardDto: ClientCardDto) =
8-
modelAndView("therapist/clients/client-create") {
9-
"client" bindTo clientCardDto
10-
"duplicatedPhone" bindTo true
11-
}
8+
modelAndView(
9+
"therapist/clients/client-create", mapOf(
10+
"client" to clientCardDto,
11+
"duplicatedPhone" to true
12+
)
13+
)

app/src/main/kotlin/pro/qyoga/app/therapist/clients/journal/edit_entry/create/CreateJournalEntryPageController.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import org.springframework.web.bind.annotation.PostMapping
1010
import org.springframework.web.servlet.ModelAndView
1111
import pro.azhidkov.platform.kotlin.isFailureOf
1212
import pro.azhidkov.platform.spring.http.hxRedirect
13-
import pro.azhidkov.platform.spring.sdj.ergo.hydration.ref
1413
import pro.qyoga.app.platform.notFound
14+
import pro.qyoga.app.therapist.clients.ClientPageTab
15+
import pro.qyoga.app.therapist.clients.clientPageModel
1516
import pro.qyoga.app.therapist.clients.journal.edit_entry.edit.EditJournalEntryPageModel
1617
import pro.qyoga.app.therapist.clients.journal.edit_entry.shared.JOURNAL_ENTRY_FROM
1718
import pro.qyoga.core.clients.cards.ClientsRepo
@@ -35,8 +36,22 @@ class CreateJournalEntryPageController(
3536
val client = clientsRepo.findByIdOrNull(clientId)
3637
?: return notFound
3738

39+
return clientPageModel(
40+
client,
41+
ClientPageTab.ADD_JOURNAL_ENTRY,
42+
CreateJournalEntryPageModel(client, LocalDate.now()).modelMap
43+
)
44+
}
45+
46+
@GetMapping(CREATE_JOURNAL_PAGE_URL, headers = ["HX-Request"])
47+
fun handleGetCreateJournalEntryFragment(
48+
@PathVariable clientId: UUID
49+
): ModelAndView {
50+
val client = clientsRepo.findByIdOrNull(clientId)
51+
?: return notFound
52+
3853
return CreateJournalEntryPageModel(
39-
client.ref(),
54+
client,
4055
LocalDate.now(),
4156
)
4257
}

app/src/main/kotlin/pro/qyoga/app/therapist/clients/journal/edit_entry/create/CreateJournalEntryPageModel.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,25 @@ package pro.qyoga.app.therapist.clients.journal.edit_entry.create
22

33
import org.springframework.web.servlet.ModelAndView
44
import pro.azhidkov.platform.spring.mvc.viewId
5+
import pro.azhidkov.platform.spring.sdj.ergo.hydration.ref
56
import pro.qyoga.app.therapist.clients.journal.edit_entry.create.CreateJournalEntryPageController.Companion.CREATE_JOURNAL_PAGE_URL
67
import pro.qyoga.app.therapist.clients.journal.edit_entry.shared.JOURNAL_ENTRY_VIEW_NAME
8+
import pro.qyoga.core.clients.cards.model.Client
79
import pro.qyoga.core.clients.cards.model.ClientRef
810
import java.time.LocalDate
911

1012

1113
data class CreateJournalEntryPageModel(
12-
val clientRef: ClientRef,
13-
val entryDate: LocalDate,
14-
val fragment: String? = null,
15-
val duplicatedDate: Boolean = false
14+
private val client: Client,
15+
private val entryDate: LocalDate,
16+
private val fragment: String? = null,
17+
private val duplicatedDate: Boolean = false
1618
) : ModelAndView(
1719
viewId(JOURNAL_ENTRY_VIEW_NAME, fragment), mapOf(
18-
"client" to clientRef,
20+
"client" to client,
1921
"entryDate" to entryDate,
2022
"duplicatedDate" to duplicatedDate,
21-
"formAction" to createFormAction(clientRef)
23+
"formAction" to createFormAction(client.ref())
2224
)
2325
)
2426

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
package pro.qyoga.app.therapist.clients.journal.edit_entry.shared
22

3-
const val JOURNAL_ENTRY_VIEW_NAME = "therapist/clients/journal-entry"
3+
const val JOURNAL_ENTRY_VIEW_NAME = "therapist/clients/clients-add-journal-entry-fragment"
44
const val JOURNAL_ENTRY_FROM = "journalEntryFrom"

app/src/main/resources/templates/therapist/clients/journal-entry.html renamed to app/src/main/resources/templates/therapist/clients/client-add_journal_entry-fragment.html

File renamed without changes.

app/src/main/resources/templates/therapist/clients/client-edit.html

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@
55
<title th:text="${client?.fullName() ?: 'ФИО'}"></title>
66
<link href="/styles/therapist/clients/style.css" rel="stylesheet">
77
<script defer src="/js/form-drafts.js"></script>
8-
<style>
9-
.pill {
10-
padding: 0.5rem 0;
11-
}
12-
</style>
138
</head>
149

1510
<body class="sb-nav-fixed">
@@ -58,10 +53,11 @@ <h1 class="mt-4 mb-3" th:text="${client?.fullName() ?: 'ФИО'}">Новый к
5853

5954
<div class="row d-flex d-sm-none pb-3 mb-4 border-bottom">
6055
<div class="btn-group">
61-
<button aria-expanded="false" class="btn btn-outline-primary dropdown-toggle nav-drop"
56+
<button aria-expanded="false"
57+
class="btn btn-outline-primary dropdown-toggle nav-drop" id="clientPageTab"
6258
data-bs-toggle="dropdown"
6359
type="button">
64-
<span th:if="${activeTab.name.equalsIgnoreCase('journal')}">Журнал</span>
60+
<span th:if="${activeTab.name.toLowerCase().contains('journal')}">Журнал</span>
6561
<span th:if="${activeTab.name.equalsIgnoreCase('card')}">Карточка</span>
6662
<span th:if="${activeTab.name.equalsIgnoreCase('files')}">Файлы</span>
6763
</button>

app/src/main/resources/templates/therapist/clients/clients-list.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ <h1 class="mt-4 pb-2 mb-3 border-bottom">Клиенты</h1>
9292
th:text="${client.lastJournalEntryDateLabel(today)}">12.04</span>
9393
</small>
9494
</td>
95-
<td class="text-end">
95+
<td class="text-end w-auto text-nowrap">
96+
<a class="addJournalEntryLink btn btn-outline-success me-2"
97+
th:attr="href=@{/therapist/clients/{id}/journal/create (id=${client.id})}">
98+
<i class="fas fa-file-edit"></i>
99+
</a>
96100
<a class="deleteClientLink btn btn-outline-danger text"
97101
hx-swap="outerHTML swap:0.2s"
98102
hx-target="closest tr"

app/src/test/kotlin/pro/qyoga/tests/cases/app/therapist/clients/ClientViewPageTest.kt

Lines changed: 0 additions & 44 deletions
This file was deleted.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package pro.qyoga.tests.cases.app.therapist.clients.journal
2+
3+
import io.kotest.inspectors.forAny
4+
import io.kotest.matchers.shouldBe
5+
import org.junit.jupiter.api.DisplayName
6+
import org.junit.jupiter.api.Test
7+
import org.springframework.http.HttpStatus
8+
import pro.qyoga.app.therapist.clients.journal.list.JournalPageController
9+
import pro.qyoga.l10n.russianDateFormat
10+
import pro.qyoga.tests.assertions.shouldBeComponent
11+
import pro.qyoga.tests.assertions.shouldBePage
12+
import pro.qyoga.tests.assertions.shouldHaveElement
13+
import pro.qyoga.tests.assertions.shouldMatch
14+
import pro.qyoga.tests.clients.TherapistClient
15+
import pro.qyoga.tests.fixture.object_mothers.clients.ClientsObjectMother
16+
import pro.qyoga.tests.fixture.object_mothers.clients.JournalEntriesObjectMother
17+
import pro.qyoga.tests.fixture.object_mothers.therapists.THE_THERAPIST_ID
18+
import pro.qyoga.tests.fixture.object_mothers.therapists.theTherapistUserDetails
19+
import pro.qyoga.tests.infra.web.QYogaAppIntegrationBaseTest
20+
import pro.qyoga.tests.pages.publc.GenericErrorPage
21+
import pro.qyoga.tests.pages.publc.NotFoundErrorPage
22+
import pro.qyoga.tests.pages.therapist.clients.journal.entry.CreateJournalEntryForm
23+
import pro.qyoga.tests.pages.therapist.clients.journal.entry.CreateJournalEntryFragment
24+
import pro.qyoga.tests.pages.therapist.clients.journal.entry.JournalEntryFrom
25+
import java.time.LocalDate
26+
27+
28+
@DisplayName("Фрагмент создания записи журнала страницы клиента")
29+
class CreateJournalEntryFragmentTest : QYogaAppIntegrationBaseTest() {
30+
31+
@Test
32+
fun `должна рендерится корректно`() {
33+
// Сетап
34+
val therapist = TherapistClient.loginAsTheTherapist()
35+
val client = backgrounds.clients.createClients(1, THE_THERAPIST_ID).first()
36+
37+
// Действие
38+
val document = therapist.clientJournal.getCreateJournalEntryFragment(client.id)
39+
40+
// Проверка
41+
document shouldBePage CreateJournalEntryFragment(client.id, LocalDate.now())
42+
}
43+
44+
@Test
45+
fun `должна добавлять запись в журнал`() {
46+
// Сетап
47+
val therapist = TherapistClient.loginAsTheTherapist()
48+
val client = backgrounds.clients.createClients(1, THE_THERAPIST_ID).first()
49+
val createJournalEntryRequest = JournalEntriesObjectMother.journalEntry()
50+
51+
// Действие
52+
therapist.clientJournal.createJournalEntry(client.id, createJournalEntryRequest)
53+
54+
// И действие
55+
val modelAndView = getBean<JournalPageController>().handleGetJournalPage(client.id)
56+
57+
// Проверка
58+
val journal = JournalPageController.getJournal(modelAndView.model).content
59+
journal.forAny { it shouldMatch createJournalEntryRequest }
60+
}
61+
62+
@Test
63+
fun `должа отображать ошибку валидации, при попытки создать запись за дату, для которой уже есть запись`() {
64+
// Сетап
65+
val entryDate = LocalDate.now()
66+
val therapist = TherapistClient.loginAsTheTherapist()
67+
val client = backgrounds.clients.createClients(1, THE_THERAPIST_ID).first()
68+
val createJournalEntryRequest = JournalEntriesObjectMother.journalEntry(date = entryDate)
69+
backgrounds.clientJournal.createJournalEntry(client.id, createJournalEntryRequest, theTherapistUserDetails)
70+
71+
// Действие
72+
val document = therapist.clientJournal.createJournalEntryForError(client.id, createJournalEntryRequest)
73+
74+
// Проверка
75+
document.select("body form").single() shouldBeComponent CreateJournalEntryForm
76+
CreateJournalEntryForm.dateInput.value(document) shouldBe russianDateFormat.format(LocalDate.now())
77+
CreateJournalEntryForm.therapeuticTaskNameInput.value(document) shouldBe createJournalEntryRequest.therapeuticTaskName
78+
CreateJournalEntryForm.entryTextInput.value(document) shouldBe createJournalEntryRequest.journalEntryText
79+
document shouldHaveElement JournalEntryFrom.DUPLICATED_DATE_MESSAGE
80+
}
81+
82+
@Test
83+
fun `должна отображтаь страницу ошибки 404 при запросе страницы создания записи журнала для несуществующего клиента`() {
84+
// Сетап
85+
val therapist = TherapistClient.loginAsTheTherapist()
86+
val notExistingClientId = ClientsObjectMother.randomId()
87+
88+
// Действие
89+
val document = therapist.clientJournal.getCreateJournalEntryFragment(
90+
notExistingClientId,
91+
expectedStatus = HttpStatus.NOT_FOUND
92+
)
93+
94+
// Проверка
95+
document shouldBePage NotFoundErrorPage
96+
}
97+
98+
@Test
99+
fun `должна отображтаь страницу ошибки 500 при запросе создания записи журнала для несуществующего клиента`() {
100+
// Сетап
101+
val therapist = TherapistClient.loginAsTheTherapist()
102+
val notExistingClientId = ClientsObjectMother.randomId()
103+
val anyJournalEntry = JournalEntriesObjectMother.journalEntry()
104+
105+
// Действие
106+
val document = therapist.clientJournal.createJournalEntryForError(
107+
notExistingClientId,
108+
anyJournalEntry,
109+
expectedStatus = HttpStatus.INTERNAL_SERVER_ERROR
110+
)
111+
112+
// Проверка
113+
document shouldBePage GenericErrorPage
114+
}
115+
116+
}

0 commit comments

Comments
 (0)