Skip to content

Commit 2a3834c

Browse files
committed
✨ Add time part to dueDate
- add new dueTime field in the backend and in the API - change the dueDate input field to a datetime-local input field - review the display of dates in the cases list view
1 parent a54551a commit 2a3834c

File tree

14 files changed

+439
-331
lines changed

14 files changed

+439
-331
lines changed

backend/src/main/kotlin/de/obqo/causalist/Case.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import dev.forkhandles.values.between
99
import dev.forkhandles.values.regex
1010
import java.time.Instant
1111
import java.time.LocalDate
12+
import java.time.LocalTime
1213
import java.util.UUID
1314

1415
enum class Type {
@@ -40,27 +41,27 @@ open class ZeroPaddedIntValueFactory<DOMAIN : Value<Int>>(
4041

4142
// Spruchkörper
4243
class RefEntity private constructor(value: Int) : IntValue(value) {
43-
fun idPart() = RefEntity.show(this)
44+
fun idPart() = show(this)
4445

4546
companion object : ZeroPaddedIntValueFactory<RefEntity>(::RefEntity, 1, 5)
4647
}
4748

4849
// Registerzeichen
4950
class RefRegister private constructor(value: String) : StringValue(value) {
50-
fun idPart() = RefRegister.show(this)
51+
fun idPart() = show(this)
5152

5253
companion object : StringValueFactory<RefRegister>(::RefRegister, "O|OH|S|T".regex)
5354
}
5455

5556
class RefNumber private constructor(value: Int) : IntValue(value) {
56-
fun idPart() = RefNumber.show(this)
57+
fun idPart() = show(this)
5758

5859
companion object : ZeroPaddedIntValueFactory<RefNumber>(::RefNumber, 1, 5)
5960
}
6061

6162
class RefYear private constructor(value: Int) : IntValue(value) {
62-
fun idPart() = RefYear.show(this)
63-
fun show() = RefYear.show(this)
63+
fun idPart() = show(this)
64+
fun show() = show(this)
6465

6566
companion object : ZeroPaddedIntValueFactory<RefYear>(::RefYear, 0, 2)
6667
}
@@ -119,8 +120,9 @@ data class Case(
119120
val receivedOn: LocalDate,
120121
val settledOn: LocalDate?,
121122
val dueDate: LocalDate?,
123+
val dueTime: LocalTime?,
122124
val todoDate: LocalDate?,
123-
val hasDocuments: Boolean, // denormalized value, will be true if there are any attached CaseDocuments
125+
val hasDocuments: Boolean, // denormalized value: will be true if there are any attached CaseDocuments
124126
val updatedAt: Instant = Instant.now()
125127
)
126128

backend/src/main/kotlin/de/obqo/causalist/api/Api.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ object Spec {
104104
private val referenceSample = reference.toResource()
105105
private val getCaseSample = CaseResource(
106106
reference.toId(), referenceSample, Type.SINGLE.name, "", "", "", Status.SESSION.name, "",
107-
"red", LocalDate.now(), null, LocalDate.now().plusDays(7), null, null, false, Instant.now()
107+
"red", LocalDate.now(), null, LocalDate.now().plusDays(7), null, null, null, false, Instant.now()
108108
)
109109
private val postCaseSample = getCaseSample.copy(id = null, updatedAt = null)
110110
private val casesSample = CasesResource(listOf(getCaseSample))

backend/src/main/kotlin/de/obqo/causalist/api/CaseRtfImporter.kt

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import se.ansman.kotshi.JsonSerializable
1717
import java.io.InputStream
1818
import java.time.DayOfWeek
1919
import java.time.LocalDate
20+
import java.time.LocalTime
2021
import java.time.format.DateTimeFormatter
2122
import java.time.temporal.ChronoUnit
2223
import java.util.UUID
@@ -32,6 +33,7 @@ class CellsLens<FINAL>(private val getFn: (Cells) -> FINAL) {
3233

3334
companion object {
3435
private val localDateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
36+
private val localTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
3537

3638
private fun base(): Spec<String> = Spec(Get { index, target -> target[index] })
3739
private fun nullIfEmpty() = base().map { it.ifEmpty { null } }
@@ -53,6 +55,14 @@ class CellsLens<FINAL>(private val getFn: (Cells) -> FINAL) {
5355
}
5456
}
5557

58+
fun localTime() = nullIfEmpty().map { str ->
59+
runCatching {
60+
LocalTime.parse(str, localTimeFormatter)
61+
}.getOrElse {
62+
throw CellsLensException("Unerkannte Uhrzeit: $str")
63+
}
64+
}
65+
5666
fun type() = base().map { str ->
5767
when (str) {
5868
"Berichterstatter" -> CHAMBER
@@ -158,6 +168,7 @@ abstract class AbstractStrategy(
158168
receivedOn: LocalDate = LocalDate.now(),
159169
settledOn: LocalDate? = null,
160170
dueDate: LocalDate? = null,
171+
dueTime: LocalTime? = null,
161172
) = CaseResource(
162173
ref = ref,
163174
type = type,
@@ -170,6 +181,7 @@ abstract class AbstractStrategy(
170181
receivedOn = receivedOn,
171182
settledOn = settledOn,
172183
dueDate = dueDate,
184+
dueTime = dueTime,
173185
todoDate = null
174186
)
175187
}
@@ -322,7 +334,8 @@ class UpdateDueDatesImporter : AbstractStrategy(ImportType.UPDATED_DUE_DATES, ta
322334

323335
private val refLens = CellsLens.reference().required(1)
324336
private val statusLens = CellsLens.dueDateStatus().required(3)
325-
private val dueLens = CellsLens.localDate().required(4)
337+
private val dueDateLens = CellsLens.localDate().required(4)
338+
private val dueTimeLens = CellsLens.localTime().required(5)
326339

327340
override fun processCells(
328341
cells: Cells,
@@ -334,7 +347,8 @@ class UpdateDueDatesImporter : AbstractStrategy(ImportType.UPDATED_DUE_DATES, ta
334347
val caseResource = case(
335348
ref = refLens(cells),
336349
status = statusLens(cells).name,
337-
dueDate = dueLens(cells),
350+
dueDate = dueDateLens(cells),
351+
dueTime = dueTimeLens(cells),
338352
)
339353

340354
val importedCase = caseResource.toEntity(currentUserId, secretKey)
@@ -348,7 +362,9 @@ class UpdateDueDatesImporter : AbstractStrategy(ImportType.UPDATED_DUE_DATES, ta
348362
else -> null // will not happen
349363
}
350364

351-
if (persistedCase.dueDate != importedCase.dueDate || persistedCase.status != importedCase.status) {
365+
if (persistedCase.dueDate != importedCase.dueDate ||
366+
persistedCase.dueTime != importedCase.dueTime ||
367+
persistedCase.status != importedCase.status) {
352368
// prevent updates of todoDate when dueDate and status haven't changed
353369
val todoDate = preparationDays?.let { importedCase.dueDate?.minus(it, ChronoUnit.DAYS) }
354370
?.let {
@@ -362,6 +378,7 @@ class UpdateDueDatesImporter : AbstractStrategy(ImportType.UPDATED_DUE_DATES, ta
362378
persistedCase.copy(
363379
status = importedCase.status,
364380
dueDate = importedCase.dueDate,
381+
dueTime = importedCase.dueTime,
365382
todoDate = todoDate
366383
)
367384
)

backend/src/main/kotlin/de/obqo/causalist/api/Resources.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import de.obqo.causalist.Type
1414
import se.ansman.kotshi.JsonSerializable
1515
import java.time.Instant
1616
import java.time.LocalDate
17+
import java.time.LocalTime
1718
import java.time.temporal.IsoFields
1819
import java.util.UUID
1920
import javax.crypto.SecretKey
@@ -41,6 +42,7 @@ data class CaseResource(
4142
val receivedOn: LocalDate,
4243
val settledOn: LocalDate?,
4344
val dueDate: LocalDate?,
45+
val dueTime: LocalTime?,
4446
val todoDate: LocalDate?,
4547
val todoWeekOfYear: Int? = todoDate?.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR),
4648
val hasDocuments: Boolean? = null,
@@ -84,6 +86,7 @@ fun Case.toResource(encryptionKey: SecretKey) = CaseResource(
8486
receivedOn = receivedOn,
8587
settledOn = settledOn,
8688
dueDate = dueDate,
89+
dueTime = dueTime,
8790
todoDate = todoDate,
8891
hasDocuments = hasDocuments,
8992
updatedAt = updatedAt
@@ -102,6 +105,7 @@ fun CaseResource.toEntity(ownerId: UUID, encryptionKey: SecretKey) = Case(
102105
receivedOn = receivedOn,
103106
settledOn = settledOn,
104107
dueDate = dueDate,
108+
dueTime = dueTime,
105109
todoDate = todoDate,
106110
hasDocuments = false // will not be set from the API
107111
// updatedAt will be automatically set

backend/src/main/kotlin/de/obqo/causalist/dynamo/DynamoCaseRepository.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ private val markerColorAttr = Attribute.string().optional("markerColor", ignoreN
3232
private val receivedOnAttr = Attribute.localDate().required("receivedOn")
3333
private val settledOnAttr = Attribute.localDate().optional("settledOn", ignoreNull = true)
3434
private val dueDateAttr = Attribute.localDate().optional("dueDate", ignoreNull = true)
35+
private val dueTimeAttr = Attribute.localTime().optional("dueTime", ignoreNull = true)
3536
private val todoDateAttr = Attribute.localDate().optional("todoDate", ignoreNull = true)
3637
private val hasDocumentsAttr = Attribute.boolean().defaulted("hasDocuments", false)
3738
private val updatedAtAttr = Attribute.instant().required("updatedAt")
@@ -54,6 +55,7 @@ private val caseLens = BiDiLens<Item, Case>(
5455
receivedOn = receivedOnAttr(item),
5556
settledOn = settledOnAttr(item),
5657
dueDate = dueDateAttr(item),
58+
dueTime = dueTimeAttr(item),
5759
todoDate = todoDateAttr(item),
5860
hasDocuments = hasDocumentsAttr(item),
5961
updatedAt = updatedAtAttr(item)
@@ -75,6 +77,7 @@ private val caseLens = BiDiLens<Item, Case>(
7577
receivedOnAttr of case.receivedOn,
7678
settledOnAttr of case.settledOn, // SettledIndex sort key
7779
dueDateAttr of case.dueDate,
80+
dueTimeAttr of case.dueTime,
7881
todoDateAttr of case.todoDate,
7982
hasDocumentsAttr of case.hasDocuments,
8083
updatedAtAttr of case.updatedAt

backend/src/test/kotlin/de/obqo/causalist/CaseBuilder.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package de.obqo.causalist
22

33
import java.time.Instant
44
import java.time.LocalDate
5+
import java.time.LocalTime
56
import java.util.UUID
67

78
fun aReference() = Reference.parseValue("123 O 1/23")
@@ -27,6 +28,7 @@ fun aCase() = Case(
2728
receivedOn = LocalDate.now(),
2829
settledOn = null,
2930
dueDate = null,
31+
dueTime = null,
3032
todoDate = null,
3133
hasDocuments = false
3234
)
@@ -45,6 +47,7 @@ fun Case.withMarkerColor(color: String) = copy(markerColor = color)
4547
fun Case.withReceivedOn(receivedOn: LocalDate) = copy(receivedOn = receivedOn)
4648
fun Case.withSettledOn(settledOn: LocalDate) = copy(settledOn = settledOn)
4749
fun Case.withDueDate(dueDate: LocalDate) = copy(dueDate = dueDate)
50+
fun Case.withDueTime(dueTime: LocalTime) = copy(dueTime = dueTime)
4851
fun Case.withTodoDate(todoDate: LocalDate) = copy(todoDate = todoDate)
4952
fun Case.withHasDocuments(hasDocuments: Boolean) = copy(hasDocuments = hasDocuments)
5053
fun Case.withUpdatedAt(instant: Instant) = copy(updatedAt = instant)

backend/src/test/kotlin/de/obqo/causalist/api/CaseRtfImporterTest.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import de.obqo.causalist.aCase
1111
import de.obqo.causalist.caseService
1212
import de.obqo.causalist.fakeCaseRepository
1313
import de.obqo.causalist.withDueDate
14+
import de.obqo.causalist.withDueTime
1415
import de.obqo.causalist.withOwnerId
1516
import de.obqo.causalist.withParties
1617
import de.obqo.causalist.withReceivedOn
@@ -30,6 +31,7 @@ import io.kotest.matchers.nulls.shouldNotBeNull
3031
import io.kotest.matchers.shouldBe
3132
import io.mockk.mockk
3233
import java.time.LocalDate
34+
import java.time.LocalTime
3335
import java.util.UUID
3436

3537
class CaseRtfImporterTest : DescribeSpec({
@@ -257,6 +259,7 @@ class CaseRtfImporterTest : DescribeSpec({
257259
aCase().withOwnerId(userId).withRef(refNotUpdated).withType(SINGLE)
258260
.withStatus(Status.DECISION)
259261
.withDueDate(LocalDate.of(2024, 1, 19))
262+
.withDueTime(LocalTime.of(13, 50))
260263
.withTodoDate(LocalDate.of(2024, 1, 15))
261264
)
262265

@@ -276,32 +279,38 @@ class CaseRtfImporterTest : DescribeSpec({
276279
importResult.unknownCaseRefs.shouldContainExactly("123 O 1/23")
277280
importResult.errors.shouldContainExactly(
278281
"Unerkanntes Aktenzeichen: 123 Z 46/20",
279-
"Unerkanntes Datum: 2024-01-12"
282+
"Unerkanntes Datum: 2024-01-12",
283+
"Unerkannte Uhrzeit: 8 Uhr",
280284
)
281285

282286
caseService.get(userId, refChamber1.toId()).shouldNotBeNull().apply {
283287
status shouldBe Status.SESSION
284288
dueDate shouldBe LocalDate.of(2024, 1, 9)
289+
dueTime shouldBe LocalTime.of(9, 30)
285290
todoDate shouldBe LocalDate.of(2024, 1, 2)
286291
}
287292
caseService.get(userId, refSingle1.toId()).shouldNotBeNull().apply {
288293
status shouldBe Status.SESSION
289294
dueDate shouldBe LocalDate.of(2024, 1, 10)
295+
dueTime shouldBe LocalTime.of(11, 0)
290296
todoDate shouldBe LocalDate.of(2024, 1, 9)
291297
}
292298
caseService.get(userId, refChamber2.toId()).shouldNotBeNull().apply {
293299
status shouldBe Status.DECISION
294300
dueDate shouldBe LocalDate.of(2024, 1, 12)
301+
dueTime shouldBe LocalTime.of(9, 50)
295302
todoDate shouldBe LocalDate.of(2024, 1, 5)
296303
}
297304
caseService.get(userId, refSingle2.toId()).shouldNotBeNull().apply {
298305
status shouldBe Status.DECISION
299306
dueDate shouldBe LocalDate.of(2024, 1, 15)
307+
dueTime shouldBe LocalTime.of(15, 15)
300308
todoDate shouldBe LocalDate.of(2024, 1, 12) // moved to Friday
301309
}
302310
caseService.get(userId, refNotUpdated.toId()).shouldNotBeNull().apply {
303311
status shouldBe Status.DECISION
304312
dueDate shouldBe LocalDate.of(2024, 1, 19)
313+
dueTime shouldBe LocalTime.of(13, 50)
305314
todoDate shouldBe LocalDate.of(2024, 1, 15) // unchanged
306315
}
307316

backend/src/test/kotlin/de/obqo/causalist/api/ResourcesTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import de.obqo.causalist.Type
88
import de.obqo.causalist.aCase
99
import de.obqo.causalist.withArea
1010
import de.obqo.causalist.withDueDate
11+
import de.obqo.causalist.withDueTime
1112
import de.obqo.causalist.withHasDocuments
1213
import de.obqo.causalist.withMarkerColor
1314
import de.obqo.causalist.withMemo
@@ -22,6 +23,7 @@ import de.obqo.causalist.withUpdatedAt
2223
import io.kotest.core.spec.style.DescribeSpec
2324
import java.time.Instant
2425
import java.time.LocalDate
26+
import java.time.LocalTime
2527

2628
class ResourcesTest : DescribeSpec({
2729

@@ -60,6 +62,7 @@ class ResourcesTest : DescribeSpec({
6062
.withMarkerColor("blue")
6163
.withReceivedOn(LocalDate.of(2023, 12, 10))
6264
.withDueDate(LocalDate.of(2024, 2, 5))
65+
.withDueTime(LocalTime.of(17, 30))
6366
.withTodoDate(LocalDate.of(2024, 1, 29))
6467
.withHasDocuments(true)
6568
.withUpdatedAt(Instant.parse("2024-01-18T13:08:39.123Z"))
@@ -88,6 +91,7 @@ class ResourcesTest : DescribeSpec({
8891
"markerColor": "blue",
8992
"receivedOn": "2023-12-10",
9093
"dueDate": "2024-02-05",
94+
"dueTime": "17:30:00",
9195
"todoDate": "2024-01-29",
9296
"todoWeekOfYear": 5,
9397
"hasDocuments": true,

backend/src/test/kotlin/de/obqo/causalist/dynamo/DynamoCaseRepositoryTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,31 @@ import de.obqo.causalist.Status
44
import de.obqo.causalist.Type
55
import de.obqo.causalist.aCase
66
import de.obqo.causalist.mutableReference
7+
import de.obqo.causalist.withArea
8+
import de.obqo.causalist.withDueDate
9+
import de.obqo.causalist.withDueTime
10+
import de.obqo.causalist.withHasDocuments
11+
import de.obqo.causalist.withMarkerColor
12+
import de.obqo.causalist.withMemo
713
import de.obqo.causalist.withOwnerId
14+
import de.obqo.causalist.withParties
815
import de.obqo.causalist.withRef
916
import de.obqo.causalist.withRefId
1017
import de.obqo.causalist.withSettledOn
1118
import de.obqo.causalist.withStatus
19+
import de.obqo.causalist.withStatusNote
20+
import de.obqo.causalist.withTodoDate
1221
import de.obqo.causalist.withType
22+
import de.obqo.causalist.withUpdatedAt
1323
import io.kotest.core.spec.style.DescribeSpec
1424
import io.kotest.matchers.sequences.shouldBeEmpty
1525
import io.kotest.matchers.sequences.shouldContainExactly
1626
import io.kotest.matchers.shouldBe
1727
import org.http4k.connect.amazon.dynamodb.FakeDynamoDb
1828
import org.http4k.connect.amazon.dynamodb.model.TableName
29+
import java.time.Instant
1930
import java.time.LocalDate
31+
import java.time.LocalTime
2032
import java.util.UUID
2133

2234
class DynamoCaseRepositoryTest : DescribeSpec({
@@ -40,6 +52,9 @@ class DynamoCaseRepositoryTest : DescribeSpec({
4052
val reference = mutableReference()
4153
val case11 = aCase().withOwnerId(ownerId1).withRef(reference.next()).withStatus(Status.UNKNOWN)
4254
val case12 = aCase().withOwnerId(ownerId1).withRef(reference.next()).withStatus(Status.SESSION)
55+
.withParties("A vs B").withArea("area").withStatusNote("session note").withMemo("area memo")
56+
.withMarkerColor("red").withDueDate(LocalDate.of(2023, 10, 1)).withDueTime(LocalTime.of(10, 0))
57+
.withTodoDate(LocalDate.of(2023, 9, 15)).withHasDocuments(true).withUpdatedAt(Instant.now())
4358
val case21 = aCase().withOwnerId(ownerId2).withRef(reference.next()).withStatus(Status.SETTLED)
4459

4560
// when

0 commit comments

Comments
 (0)