Keep open tachograph intervals without synthetic tails
This commit is contained in:
parent
7209a73d30
commit
9e6f8efb26
|
|
@ -31,7 +31,7 @@ public record ResolvedVehicleUsageInterval(
|
||||||
intervalId,
|
intervalId,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
Duration.between(from, to).getSeconds(),
|
to == null ? 0L : Duration.between(from, to).getSeconds(),
|
||||||
odometerBeginKm,
|
odometerBeginKm,
|
||||||
odometerEndKm,
|
odometerEndKm,
|
||||||
registrationKey,
|
registrationKey,
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,14 @@ public class DriverCardXmlExtractionService {
|
||||||
String path = "/DriverCard/VehiclesUsed/cardVehiclesUsed/cardVehicleRecords[" + (i + 1) + "]";
|
String path = "/DriverCard/VehiclesUsed/cardVehiclesUsed/cardVehicleRecords[" + (i + 1) + "]";
|
||||||
OffsetDateTime from = offsetDateTime(childText(record, "vehicleFirstUse"));
|
OffsetDateTime from = offsetDateTime(childText(record, "vehicleFirstUse"));
|
||||||
OffsetDateTime to = offsetDateTime(childText(record, "vehicleLastUse"));
|
OffsetDateTime to = offsetDateTime(childText(record, "vehicleLastUse"));
|
||||||
|
if (from == null) {
|
||||||
|
warnings.add(new ExtractionWarning("MISSING_VEHICLE_FIRST_USE", "Driver-card vehicle record is missing vehicleFirstUse.", path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (to != null && to.isBefore(from)) {
|
||||||
|
warnings.add(new ExtractionWarning("INVALID_VEHICLE_USE_INTERVAL", "Driver-card vehicle record has an invalid first/last use range.", path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Element vehicleRegistration = child(record, "vehicleRegistration");
|
Element vehicleRegistration = child(record, "vehicleRegistration");
|
||||||
String registrationNation = childText(vehicleRegistration, "vehicleRegistrationNation");
|
String registrationNation = childText(vehicleRegistration, "vehicleRegistrationNation");
|
||||||
String registrationNumber = childText(child(vehicleRegistration, "vehicleRegistrationNumber"), "vehicleRegNumber");
|
String registrationNumber = childText(child(vehicleRegistration, "vehicleRegistrationNumber"), "vehicleRegNumber");
|
||||||
|
|
@ -192,9 +200,8 @@ public class DriverCardXmlExtractionService {
|
||||||
new ExtractedVehicle(vehicleKey, vehicleKeyFactory.createSourceVehicleId(vehicleKey), vin)
|
new ExtractedVehicle(vehicleKey, vehicleKeyFactory.createSourceVehicleId(vehicleKey), vin)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (from == null || to == null) {
|
if (to == null) {
|
||||||
warnings.add(new ExtractionWarning("INCOMPLETE_VEHICLE_USAGE", "Vehicle usage interval is missing start or end timestamp.", path));
|
warnings.add(new ExtractionWarning("OPEN_VEHICLE_USAGE", "Driver-card vehicle record has no vehicleLastUse and was kept open-ended.", path));
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
intervals.add(new ExtractedCardVehicleUsageInterval(
|
intervals.add(new ExtractedCardVehicleUsageInterval(
|
||||||
"CVU-" + (i + 1),
|
"CVU-" + (i + 1),
|
||||||
|
|
@ -244,11 +251,9 @@ public class DriverCardXmlExtractionService {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
parsedChanges.sort(Comparator.comparing(ActivityChange::from));
|
parsedChanges.sort(Comparator.comparing(ActivityChange::from));
|
||||||
for (int i = 0; i < parsedChanges.size(); i++) {
|
for (int i = 0; i + 1 < parsedChanges.size(); i++) {
|
||||||
ActivityChange current = parsedChanges.get(i);
|
ActivityChange current = parsedChanges.get(i);
|
||||||
OffsetDateTime to = i + 1 < parsedChanges.size()
|
OffsetDateTime to = parsedChanges.get(i + 1).from();
|
||||||
? parsedChanges.get(i + 1).from()
|
|
||||||
: date.plusDays(1).atStartOfDay().atOffset(ZoneOffset.UTC);
|
|
||||||
if (!current.from().isBefore(to)) {
|
if (!current.from().isBefore(to)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -279,7 +284,7 @@ public class DriverCardXmlExtractionService {
|
||||||
int usageStartIndex = 0;
|
int usageStartIndex = 0;
|
||||||
for (ExtractedCardActivityInterval interval : activityIntervals) {
|
for (ExtractedCardActivityInterval interval : activityIntervals) {
|
||||||
while (usageStartIndex < vehicleUsageIntervals.size()
|
while (usageStartIndex < vehicleUsageIntervals.size()
|
||||||
&& !endExclusive(vehicleUsageIntervals.get(usageStartIndex).to()).isAfter(interval.from())) {
|
&& !usageEndExclusive(vehicleUsageIntervals.get(usageStartIndex), interval.to()).isAfter(interval.from())) {
|
||||||
usageStartIndex++;
|
usageStartIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,7 +314,7 @@ public class DriverCardXmlExtractionService {
|
||||||
if (usage.from().isAfter(interval.from()) && usage.from().isBefore(interval.to())) {
|
if (usage.from().isAfter(interval.from()) && usage.from().isBefore(interval.to())) {
|
||||||
cutPoints.add(usage.from());
|
cutPoints.add(usage.from());
|
||||||
}
|
}
|
||||||
OffsetDateTime usageEndExclusive = endExclusive(usage.to());
|
OffsetDateTime usageEndExclusive = usageEndExclusive(usage, interval.to());
|
||||||
if (usageEndExclusive.isAfter(interval.from()) && usageEndExclusive.isBefore(interval.to())) {
|
if (usageEndExclusive.isAfter(interval.from()) && usageEndExclusive.isBefore(interval.to())) {
|
||||||
cutPoints.add(usageEndExclusive);
|
cutPoints.add(usageEndExclusive);
|
||||||
}
|
}
|
||||||
|
|
@ -326,7 +331,7 @@ public class DriverCardXmlExtractionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
while (coverageIndex < overlappingUsages.size()
|
while (coverageIndex < overlappingUsages.size()
|
||||||
&& !endExclusive(overlappingUsages.get(coverageIndex).to()).isAfter(segmentFrom)) {
|
&& !usageEndExclusive(overlappingUsages.get(coverageIndex), interval.to()).isAfter(segmentFrom)) {
|
||||||
coverageIndex++;
|
coverageIndex++;
|
||||||
}
|
}
|
||||||
ExtractedCardVehicleUsageInterval covering = coverageIndex < overlappingUsages.size()
|
ExtractedCardVehicleUsageInterval covering = coverageIndex < overlappingUsages.size()
|
||||||
|
|
@ -351,11 +356,14 @@ public class DriverCardXmlExtractionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean covers(ExtractedCardVehicleUsageInterval usage, OffsetDateTime timestamp) {
|
private boolean covers(ExtractedCardVehicleUsageInterval usage, OffsetDateTime timestamp) {
|
||||||
return !usage.from().isAfter(timestamp) && timestamp.isBefore(endExclusive(usage.to()));
|
return !usage.from().isAfter(timestamp) && timestamp.isBefore(usageEndExclusive(usage, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private OffsetDateTime endExclusive(OffsetDateTime timestamp) {
|
private OffsetDateTime usageEndExclusive(ExtractedCardVehicleUsageInterval usage, OffsetDateTime fallbackExclusiveEnd) {
|
||||||
return timestamp.plusSeconds(1);
|
if (usage.to() == null) {
|
||||||
|
return fallbackExclusiveEnd == null ? OffsetDateTime.MAX : fallbackExclusiveEnd;
|
||||||
|
}
|
||||||
|
return usage.to().plusSeconds(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Element child(Element parent, String name) {
|
private Element child(Element parent, String name) {
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ public class DriverTimelineBuilder {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
List<ResolvedVehicleUsageInterval> sorted = rawIntervals.stream()
|
List<ResolvedVehicleUsageInterval> sorted = rawIntervals.stream()
|
||||||
.filter(interval -> interval.from() != null && interval.to() != null && interval.to().isAfter(interval.from()))
|
.filter(interval -> interval.from() != null && (interval.to() == null || interval.to().isAfter(interval.from())))
|
||||||
.map(interval -> ResolvedVehicleUsageInterval.resolved(
|
.map(interval -> ResolvedVehicleUsageInterval.resolved(
|
||||||
interval.intervalId(),
|
interval.intervalId(),
|
||||||
interval.from(),
|
interval.from(),
|
||||||
|
|
@ -65,7 +65,7 @@ public class DriverTimelineBuilder {
|
||||||
List.of(interval.intervalId())
|
List.of(interval.intervalId())
|
||||||
))
|
))
|
||||||
.sorted(Comparator.comparing(ResolvedVehicleUsageInterval::from)
|
.sorted(Comparator.comparing(ResolvedVehicleUsageInterval::from)
|
||||||
.thenComparing(ResolvedVehicleUsageInterval::to))
|
.thenComparing(ResolvedVehicleUsageInterval::to, Comparator.nullsLast(Comparator.naturalOrder())))
|
||||||
.toList();
|
.toList();
|
||||||
if (sorted.isEmpty()) {
|
if (sorted.isEmpty()) {
|
||||||
return List.of();
|
return List.of();
|
||||||
|
|
@ -81,7 +81,7 @@ public class DriverTimelineBuilder {
|
||||||
current = ResolvedVehicleUsageInterval.resolved(
|
current = ResolvedVehicleUsageInterval.resolved(
|
||||||
current.intervalId() + "+" + next.intervalId(),
|
current.intervalId() + "+" + next.intervalId(),
|
||||||
current.from(),
|
current.from(),
|
||||||
max(current.to(), next.to()),
|
mergedTo(current.to(), next.to()),
|
||||||
current.odometerBeginKm(),
|
current.odometerBeginKm(),
|
||||||
next.odometerEndKm() != null ? next.odometerEndKm() : current.odometerEndKm(),
|
next.odometerEndKm() != null ? next.odometerEndKm() : current.odometerEndKm(),
|
||||||
current.registrationKey(),
|
current.registrationKey(),
|
||||||
|
|
@ -102,7 +102,7 @@ public class DriverTimelineBuilder {
|
||||||
private boolean canMerge(ResolvedVehicleUsageInterval left, ResolvedVehicleUsageInterval right) {
|
private boolean canMerge(ResolvedVehicleUsageInterval left, ResolvedVehicleUsageInterval right) {
|
||||||
return Objects.equals(left.registrationKey(), right.registrationKey())
|
return Objects.equals(left.registrationKey(), right.registrationKey())
|
||||||
&& Objects.equals(left.vehicleKey(), right.vehicleKey())
|
&& Objects.equals(left.vehicleKey(), right.vehicleKey())
|
||||||
&& !right.from().isAfter(left.to().plusSeconds(1));
|
&& !right.from().isAfter(mergeBoundary(left.to()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ResolvedActivityInterval> resolveActivities(
|
private List<ResolvedActivityInterval> resolveActivities(
|
||||||
|
|
@ -209,4 +209,15 @@ public class DriverTimelineBuilder {
|
||||||
}
|
}
|
||||||
return left.isAfter(right) ? left : right;
|
return left.isAfter(right) ? left : right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OffsetDateTime mergedTo(OffsetDateTime left, OffsetDateTime right) {
|
||||||
|
if (left == null || right == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return max(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OffsetDateTime mergeBoundary(OffsetDateTime endInclusive) {
|
||||||
|
return endInclusive == null ? OffsetDateTime.MAX : endInclusive.plusSeconds(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,15 +103,7 @@ public class VehicleUnitXmlExtractionService {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
OffsetDateTime to = offsetDateTime(text(record, "cardWithdrawalTime"));
|
OffsetDateTime to = offsetDateTime(text(record, "cardWithdrawalTime"));
|
||||||
if (to == null) {
|
if (to != null && to.isBefore(from)) {
|
||||||
to = vehicleContext.defaultOpenIntervalEnd();
|
|
||||||
sessionWarnings.add(new ExtractionWarning(
|
|
||||||
"OPEN_VU_CARD_INTERVAL",
|
|
||||||
"Vehicle-unit insertion/withdrawal record has no withdrawal time; interval was closed using the VU downloadable-period end.",
|
|
||||||
path
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if (to == null || to.isBefore(from)) {
|
|
||||||
sessionWarnings.add(new ExtractionWarning(
|
sessionWarnings.add(new ExtractionWarning(
|
||||||
"INVALID_VU_CARD_INTERVAL",
|
"INVALID_VU_CARD_INTERVAL",
|
||||||
"Vehicle-unit insertion/withdrawal record has an invalid interval range.",
|
"Vehicle-unit insertion/withdrawal record has an invalid interval range.",
|
||||||
|
|
@ -288,11 +280,9 @@ public class VehicleUnitXmlExtractionService {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
parsedChanges.sort(Comparator.comparing(ActivityChange::from));
|
parsedChanges.sort(Comparator.comparing(ActivityChange::from));
|
||||||
for (int i = 0; i < parsedChanges.size(); i++) {
|
for (int i = 0; i + 1 < parsedChanges.size(); i++) {
|
||||||
ActivityChange current = parsedChanges.get(i);
|
ActivityChange current = parsedChanges.get(i);
|
||||||
OffsetDateTime to = i + 1 < parsedChanges.size()
|
OffsetDateTime to = parsedChanges.get(i + 1).from();
|
||||||
? parsedChanges.get(i + 1).from()
|
|
||||||
: date.plusDays(1).atStartOfDay().atOffset(ZoneOffset.UTC);
|
|
||||||
if (!current.from().isBefore(to)) {
|
if (!current.from().isBefore(to)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -898,7 +888,7 @@ public class VehicleUnitXmlExtractionService {
|
||||||
String rawRecordPath
|
String rawRecordPath
|
||||||
) {
|
) {
|
||||||
private OffsetDateTime endExclusive() {
|
private OffsetDateTime endExclusive() {
|
||||||
return to.plusSeconds(1);
|
return to == null ? OffsetDateTime.MAX : to.plusSeconds(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean covers(OffsetDateTime timestamp) {
|
private boolean covers(OffsetDateTime timestamp) {
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,10 @@ class DriverCardXmlExtractionServiceTest {
|
||||||
assertThat(driver.vehicleRegistrations()).hasSize(2);
|
assertThat(driver.vehicleRegistrations()).hasSize(2);
|
||||||
assertThat(driver.vehicles()).hasSize(2);
|
assertThat(driver.vehicles()).hasSize(2);
|
||||||
assertThat(driver.cardVehicleUsageIntervals()).hasSize(2);
|
assertThat(driver.cardVehicleUsageIntervals()).hasSize(2);
|
||||||
assertThat(driver.cardActivityIntervals()).hasSize(5);
|
assertThat(driver.cardActivityIntervals()).hasSize(3);
|
||||||
assertThat(driver.cardActivityIntervals().get(0).registrationKey()).isEqualTo("12:W-12345A");
|
assertThat(driver.cardActivityIntervals().get(0).registrationKey()).isEqualTo("12:W-12345A");
|
||||||
assertThat(driver.cardActivityIntervals().get(1).to()).isEqualTo(driver.cardVehicleUsageIntervals().get(0).to().plusSeconds(1));
|
assertThat(driver.cardActivityIntervals().get(1).to()).isEqualTo(driver.cardVehicleUsageIntervals().get(0).to().plusSeconds(1));
|
||||||
assertThat(driver.cardActivityIntervals().get(2).registrationKey()).isEqualTo("12:W-54321B");
|
assertThat(driver.cardActivityIntervals().get(2).registrationKey()).isEqualTo("12:W-54321B");
|
||||||
assertThat(driver.cardActivityIntervals().get(3).registrationKey()).isEqualTo("12:W-54321B");
|
|
||||||
assertThat(driver.cardActivityIntervals().get(4).registrationKey()).isNull();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -74,9 +72,36 @@ class DriverCardXmlExtractionServiceTest {
|
||||||
);
|
);
|
||||||
|
|
||||||
DriverExtractionSession driver = session.driversByKey().values().iterator().next();
|
DriverExtractionSession driver = session.driversByKey().values().iterator().next();
|
||||||
assertThat(driver.cardActivityIntervals()).hasSize(5);
|
assertThat(driver.cardActivityIntervals()).hasSize(3);
|
||||||
assertThat(driver.cardActivityIntervals())
|
assertThat(driver.cardActivityIntervals())
|
||||||
.extracting(interval -> interval.from().toString())
|
.extracting(interval -> interval.from().toString())
|
||||||
.contains("2026-04-01T08:00Z", "2026-04-01T12:30Z");
|
.contains("2026-04-01T08:00Z", "2026-04-01T12:00Z");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void keepsLastOpenVehicleUseRecordWithoutSynthesizingActivityTail() {
|
||||||
|
TachographFileSession session = service.extract(
|
||||||
|
parser.parse(DriverCardXmlSamples.driverCardXmlWithOpenVehicleUseRecord()),
|
||||||
|
new TachographFileSessionMetadata(
|
||||||
|
"default",
|
||||||
|
"legalrequirements-drivercard",
|
||||||
|
"sample",
|
||||||
|
"sample.ddd",
|
||||||
|
"abc",
|
||||||
|
10,
|
||||||
|
"42",
|
||||||
|
"def",
|
||||||
|
true,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
Instant.now(),
|
||||||
|
Instant.now().plus(4, ChronoUnit.HOURS)
|
||||||
|
);
|
||||||
|
|
||||||
|
DriverExtractionSession driver = session.driversByKey().values().iterator().next();
|
||||||
|
assertThat(driver.cardVehicleUsageIntervals()).hasSize(2);
|
||||||
|
assertThat(driver.cardVehicleUsageIntervals().get(1).to()).isNull();
|
||||||
|
assertThat(driver.cardActivityIntervals()).hasSize(3);
|
||||||
|
assertThat(driver.cardActivityIntervals().get(2).registrationKey()).isEqualTo("12:W-54321B");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,4 +119,9 @@ final class DriverCardXmlSamples {
|
||||||
.replace("<timeOfChange>09:00:00Z</timeOfChange>", "<timeOfChange>09:00:00</timeOfChange>")
|
.replace("<timeOfChange>09:00:00Z</timeOfChange>", "<timeOfChange>09:00:00</timeOfChange>")
|
||||||
.replace("<timeOfChange>12:30:00Z</timeOfChange>", "<timeOfChange>12:30:00</timeOfChange>");
|
.replace("<timeOfChange>12:30:00Z</timeOfChange>", "<timeOfChange>12:30:00</timeOfChange>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String driverCardXmlWithOpenVehicleUseRecord() {
|
||||||
|
return validDriverCardXml()
|
||||||
|
.replace("<vehicleLastUse>2026-04-01T18:00:00Z</vehicleLastUse>\n", "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,4 +108,70 @@ class DriverTimelineBuilderTest {
|
||||||
assertThat(timeline.loadedTo()).isEqualTo(OffsetDateTime.parse("2026-05-02T08:00:00Z"));
|
assertThat(timeline.loadedTo()).isEqualTo(OffsetDateTime.parse("2026-05-02T08:00:00Z"));
|
||||||
assertThat(timeline.warnings()).extracting(ExtractionWarning::code).containsExactly("W2", "W1");
|
assertThat(timeline.warnings()).extracting(ExtractionWarning::code).containsExactly("W2", "W1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mergesTouchingOpenEndedVehicleUsageInterval() {
|
||||||
|
DriverExtractionSession driver = new DriverExtractionSession(
|
||||||
|
"12:123",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(
|
||||||
|
new ExtractedCardVehicleUsageInterval(
|
||||||
|
"CVU-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T00:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T23:59:59Z"),
|
||||||
|
100L,
|
||||||
|
200L,
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"a"
|
||||||
|
),
|
||||||
|
new ExtractedCardVehicleUsageInterval(
|
||||||
|
"CVU-2",
|
||||||
|
OffsetDateTime.parse("2026-05-02T00:00:00Z"),
|
||||||
|
null,
|
||||||
|
201L,
|
||||||
|
null,
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"b"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
List.of(
|
||||||
|
new ExtractedCardActivityInterval(
|
||||||
|
"ACT-1",
|
||||||
|
OffsetDateTime.parse("2026-05-02T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-02T09:00:00Z"),
|
||||||
|
"WORK",
|
||||||
|
"DRIVER",
|
||||||
|
"INSERTED",
|
||||||
|
"SINGLE",
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"c"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
List.of(),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
TachographFileSession session = new TachographFileSession(
|
||||||
|
UUID.randomUUID(),
|
||||||
|
new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 3, "42", "b", true, null),
|
||||||
|
Map.of(driver.driverKey(), driver),
|
||||||
|
new ExtractionStats(1, 1, 2, 1, 1, 0),
|
||||||
|
List.of(),
|
||||||
|
Instant.now(),
|
||||||
|
Instant.now().plus(4, ChronoUnit.HOURS)
|
||||||
|
);
|
||||||
|
|
||||||
|
ResolvedDriverTimeline timeline = builder.build(session, driver);
|
||||||
|
|
||||||
|
assertThat(timeline.vehicleUsageIntervals()).hasSize(1);
|
||||||
|
assertThat(timeline.vehicleUsageIntervals().get(0).from()).isEqualTo(OffsetDateTime.parse("2026-05-01T00:00:00Z"));
|
||||||
|
assertThat(timeline.vehicleUsageIntervals().get(0).to()).isNull();
|
||||||
|
assertThat(timeline.vehicleUsageIntervals().get(0).sourceIntervalIds()).containsExactly("CVU-1", "CVU-2");
|
||||||
|
assertThat(timeline.loadedTo()).isEqualTo(OffsetDateTime.parse("2026-05-02T09:00:00Z"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,9 @@ class VehicleUnitXmlExtractionServiceTest {
|
||||||
assertThat(firstDriver.cardVehicleUsageIntervals()).hasSize(1);
|
assertThat(firstDriver.cardVehicleUsageIntervals()).hasSize(1);
|
||||||
assertThat(firstDriver.cardVehicleUsageIntervals().get(0).from().toString()).isEqualTo("2026-04-01T08:00Z");
|
assertThat(firstDriver.cardVehicleUsageIntervals().get(0).from().toString()).isEqualTo("2026-04-01T08:00Z");
|
||||||
assertThat(firstDriver.cardVehicleUsageIntervals().get(0).to().toString()).isEqualTo("2026-04-01T11:00Z");
|
assertThat(firstDriver.cardVehicleUsageIntervals().get(0).to().toString()).isEqualTo("2026-04-01T11:00Z");
|
||||||
assertThat(firstDriver.cardActivityIntervals()).hasSize(3);
|
assertThat(firstDriver.cardActivityIntervals()).hasSize(2);
|
||||||
assertThat(firstDriver.cardActivityIntervals().get(0).activityType()).isEqualTo("WORK");
|
assertThat(firstDriver.cardActivityIntervals().get(0).activityType()).isEqualTo("WORK");
|
||||||
assertThat(firstDriver.cardActivityIntervals().get(1).activityType()).isEqualTo("DRIVE");
|
assertThat(firstDriver.cardActivityIntervals().get(1).activityType()).isEqualTo("DRIVE");
|
||||||
assertThat(firstDriver.cardActivityIntervals().get(2).to().toString()).isEqualTo("2026-04-01T11:00:01Z");
|
|
||||||
assertThat(firstDriver.supportEvents()).hasSize(1);
|
assertThat(firstDriver.supportEvents()).hasSize(1);
|
||||||
assertThat(firstDriver.supportEvents().get(0).eventDomain()).isEqualTo("PLACE");
|
assertThat(firstDriver.supportEvents().get(0).eventDomain()).isEqualTo("PLACE");
|
||||||
assertThat(firstDriver.supportEvents().get(0).eventType()).isEqualTo("BEGIN_DAILY_WORK_PERIOD");
|
assertThat(firstDriver.supportEvents().get(0).eventType()).isEqualTo("BEGIN_DAILY_WORK_PERIOD");
|
||||||
|
|
@ -63,17 +62,15 @@ class VehicleUnitXmlExtractionServiceTest {
|
||||||
assertThat(firstDriver.supportEvents().get(0).latitude()).isNotNull();
|
assertThat(firstDriver.supportEvents().get(0).latitude()).isNotNull();
|
||||||
|
|
||||||
assertThat(secondDriver.cardVehicleUsageIntervals()).hasSize(1);
|
assertThat(secondDriver.cardVehicleUsageIntervals()).hasSize(1);
|
||||||
assertThat(secondDriver.cardVehicleUsageIntervals().get(0).to().toString()).isEqualTo("2026-04-02T10:00Z");
|
assertThat(secondDriver.cardVehicleUsageIntervals().get(0).to()).isNull();
|
||||||
assertThat(secondDriver.cardActivityIntervals()).hasSize(2);
|
assertThat(secondDriver.cardActivityIntervals()).hasSize(1);
|
||||||
assertThat(secondDriver.cardActivityIntervals().get(0).from().toString()).isEqualTo("2026-04-02T07:30Z");
|
assertThat(secondDriver.cardActivityIntervals().get(0).from().toString()).isEqualTo("2026-04-02T07:30Z");
|
||||||
assertThat(secondDriver.cardActivityIntervals().get(1).to().toString()).isEqualTo("2026-04-02T10:00:01Z");
|
|
||||||
assertThat(secondDriver.supportEvents()).hasSize(2);
|
assertThat(secondDriver.supportEvents()).hasSize(2);
|
||||||
assertThat(secondDriver.supportEvents()).extracting("eventDomain").containsExactly("POSITION", "SPECIFIC_CONDITION");
|
assertThat(secondDriver.supportEvents()).extracting("eventDomain").containsExactly("POSITION", "SPECIFIC_CONDITION");
|
||||||
assertThat(secondDriver.supportEvents().get(0).latitude()).isNotNull();
|
assertThat(secondDriver.supportEvents().get(0).latitude()).isNotNull();
|
||||||
assertThat(secondDriver.supportEvents().get(1).code()).isEqualTo("1");
|
assertThat(secondDriver.supportEvents().get(1).code()).isEqualTo("1");
|
||||||
|
|
||||||
assertThat(session.warnings()).extracting("code")
|
assertThat(session.warnings()).isEmpty();
|
||||||
.contains("OPEN_VU_CARD_INTERVAL", "VU_ACTIVITY_UNASSIGNED");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -102,6 +99,32 @@ class VehicleUnitXmlExtractionServiceTest {
|
||||||
assertThat(secondDriver.cardActivityIntervals().get(0).from().toString()).isEqualTo("2026-04-02T07:30Z");
|
assertThat(secondDriver.cardActivityIntervals().get(0).from().toString()).isEqualTo("2026-04-02T07:30Z");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void keepsOpenVuCardUsageRecordWithoutClosingItAtDownloadPeriodEnd() throws Exception {
|
||||||
|
TachographFileSession session = service.extract(
|
||||||
|
new TachographXmlParser.ParsedTachographXml(document(VehicleUnitXmlSamples.vehicleUnitXml()), "VehicleUnit"),
|
||||||
|
new TachographFileSessionMetadata(
|
||||||
|
"default",
|
||||||
|
"legalrequirements-vehicleunit",
|
||||||
|
"sample-vu",
|
||||||
|
"sample-vu.ddd",
|
||||||
|
"abc",
|
||||||
|
10,
|
||||||
|
"42",
|
||||||
|
"def",
|
||||||
|
false,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
Instant.now(),
|
||||||
|
Instant.now().plus(4, ChronoUnit.HOURS)
|
||||||
|
);
|
||||||
|
|
||||||
|
DriverExtractionSession secondDriver = session.driversByKey().get("12:99999999999911");
|
||||||
|
assertThat(secondDriver.cardVehicleUsageIntervals()).hasSize(1);
|
||||||
|
assertThat(secondDriver.cardVehicleUsageIntervals().get(0).from().toString()).isEqualTo("2026-04-02T07:30Z");
|
||||||
|
assertThat(secondDriver.cardVehicleUsageIntervals().get(0).to()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
private Document document(String xml) throws Exception {
|
private Document document(String xml) throws Exception {
|
||||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
factory.setNamespaceAware(false);
|
factory.setNamespaceAware(false);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue