diff --git a/README.md b/README.md index db8e700..1d805b7 100644 --- a/README.md +++ b/README.md @@ -814,14 +814,22 @@ The first concrete extractor is `JdbcTachographExtractionBatchExecutor`. It is e Currently implemented extraction definitions: ```text -CARD_ACTIVITY -> DRIVER_ACTIVITY / DRIVER_CARD / CardActivity -VU_ACTIVITY -> DRIVER_ACTIVITY / VEHICLE_UNIT / VUActivity +CARD_ACTIVITY -> DRIVER_ACTIVITY / DRIVER_CARD / CardActivity +VU_ACTIVITY -> DRIVER_ACTIVITY / VEHICLE_UNIT / VUActivity +CARD_VEHICLES_USED -> DRIVER_CARD / DRIVER_CARD / CardVehiclesUsed +IW_CYCLE -> DRIVER_CARD / VEHICLE_UNIT / IWCycle +CARD_BORDER_CROSSING -> BORDER_CROSSING / DRIVER_CARD / CardBorderCrossing +VU_BORDER_CROSSING -> BORDER_CROSSING / VEHICLE_UNIT / VUBorderCrossing ``` SQL resources: ```text +src/main/resources/sql/tachograph/card-border-crossing.sql +src/main/resources/sql/tachograph/card-vehicles-used.sql src/main/resources/sql/tachograph/card-activity.sql +src/main/resources/sql/tachograph/iw-cycle.sql +src/main/resources/sql/tachograph/vu-border-crossing.sql src/main/resources/sql/tachograph/vu-activity.sql ``` diff --git a/src/main/java/at/procon/eventhub/service/EventDetailsFactory.java b/src/main/java/at/procon/eventhub/service/EventDetailsFactory.java index 0f96b3c..a67f03b 100644 --- a/src/main/java/at/procon/eventhub/service/EventDetailsFactory.java +++ b/src/main/java/at/procon/eventhub/service/EventDetailsFactory.java @@ -2,6 +2,7 @@ package at.procon.eventhub.service; import at.procon.eventhub.dto.CardSlot; import at.procon.eventhub.dto.CardStatus; +import at.procon.eventhub.dto.DriverCardRefDto; import at.procon.eventhub.dto.DrivingStatus; import at.procon.eventhub.dto.EventDetailsDto; import com.fasterxml.jackson.databind.JsonNode; @@ -29,9 +30,17 @@ public class EventDetailsFactory { } public EventDetailsDto driverCard(CardSlot cardSlot, CardStatus cardStatus) { + return driverCard(cardSlot, cardStatus, null); + } + + public EventDetailsDto driverCard(CardSlot cardSlot, CardStatus cardStatus, DriverCardRefDto driverCard) { Map attributes = new LinkedHashMap<>(); put(attributes, "cardSlot", cardSlot); put(attributes, "cardStatus", cardStatus); + if (driverCard != null && driverCard.hasValue()) { + put(attributes, "cardNation", driverCard.nation()); + put(attributes, "cardNumber", driverCard.number()); + } return new EventDetailsDto("DRIVER_CARD", objectMapper.valueToTree(attributes)); } diff --git a/src/main/java/at/procon/eventhub/tachograph/service/AbstractTachographBorderCrossingRowMapper.java b/src/main/java/at/procon/eventhub/tachograph/service/AbstractTachographBorderCrossingRowMapper.java new file mode 100644 index 0000000..daed7ed --- /dev/null +++ b/src/main/java/at/procon/eventhub/tachograph/service/AbstractTachographBorderCrossingRowMapper.java @@ -0,0 +1,200 @@ +package at.procon.eventhub.tachograph.service; + +import at.procon.eventhub.dto.DriverCardRefDto; +import at.procon.eventhub.dto.DriverRefDto; +import at.procon.eventhub.dto.EventDomain; +import at.procon.eventhub.dto.EventHubEventDto; +import at.procon.eventhub.dto.EventLifecycle; +import at.procon.eventhub.dto.EventType; +import at.procon.eventhub.dto.GeoPointDto; +import at.procon.eventhub.dto.SourcePackageRefDto; +import at.procon.eventhub.dto.VehicleRefDto; +import at.procon.eventhub.dto.VehicleRegistrationRefDto; +import at.procon.eventhub.importing.extraction.ExtractionContext; +import at.procon.eventhub.importing.extraction.ExtractionRowMapper; +import at.procon.eventhub.service.EventDetailsFactory; +import at.procon.eventhub.tachograph.dto.TachographImportRequest; +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; + +abstract class AbstractTachographBorderCrossingRowMapper implements ExtractionRowMapper { + + private final EventDetailsFactory detailsFactory; + + protected AbstractTachographBorderCrossingRowMapper(EventDetailsFactory detailsFactory) { + this.detailsFactory = detailsFactory; + } + + @Override + public EventHubEventDto map(ResultSet rs, int rowNum, ExtractionContext context) throws SQLException { + OffsetDateTime occurredAt = offsetDateTime(rs, "occurred_at"); + SourcePackageRefDto sourcePackageRef = sourcePackageRef(rs); + DriverRefDto driverRef = driverRef(rs); + VehicleRefDto vehicleRef = vehicleRef(rs); + + String externalSourceEventId = string(rs, "external_source_event_id"); + if (externalSourceEventId == null) { + externalSourceEventId = defaultExternalSourceEventId(context, rowNum, occurredAt, sourcePackageRef, driverRef, vehicleRef); + } + + return new EventHubEventDto( + UUID.randomUUID(), + externalSourceEventId, + driverRef, + vehicleRef, + occurredAt, + offsetDateTime(rs, "received_partner_at"), + OffsetDateTime.now(), + EventDomain.BORDER_CROSSING, + EventType.BORDER_OUTBOUND, + EventLifecycle.OUTBOUND, + longValue(rs, "odometer_m"), + position(rs), + detailsFactory.borderCrossing(string(rs, "country_from"), string(rs, "country_to")), + sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null, + detailsFactory.payloadFromMap(payload(rs)), + false, + context.packageInfo() + ); + } + + protected Map sourceSpecificPayload(ResultSet rs) throws SQLException { + return Map.of(); + } + + private DriverRefDto driverRef(ResultSet rs) throws SQLException { + DriverCardRefDto driverCard = null; + String cardNumber = string(rs, "driver_card_number"); + if (cardNumber != null) { + driverCard = new DriverCardRefDto(string(rs, "driver_card_nation"), cardNumber); + } + DriverRefDto driverRef = new DriverRefDto(string(rs, "driver_source_entity_id"), driverCard); + return driverRef.hasAnyReference() ? driverRef : null; + } + + private VehicleRefDto vehicleRef(ResultSet rs) throws SQLException { + VehicleRegistrationRefDto registration = null; + String registrationNumber = string(rs, "vehicle_registration_number"); + if (registrationNumber != null) { + registration = new VehicleRegistrationRefDto(string(rs, "vehicle_registration_nation"), registrationNumber); + } + VehicleRefDto vehicleRef = new VehicleRefDto( + string(rs, "vehicle_source_entity_id"), + string(rs, "vehicle_vin"), + string(rs, "vehicle_registration_source_entity_id"), + registration + ); + return vehicleRef.hasAnyReference() ? vehicleRef : null; + } + + private SourcePackageRefDto sourcePackageRef(ResultSet rs) throws SQLException { + return new SourcePackageRefDto( + string(rs, "source_package_kind"), + string(rs, "source_package_id"), + string(rs, "source_package_entity_id"), + offsetDateTime(rs, "source_package_period_from"), + offsetDateTime(rs, "source_package_period_to"), + offsetDateTime(rs, "source_package_imported_at") + ); + } + + private GeoPointDto position(ResultSet rs) throws SQLException { + BigDecimal latitude = decimal(rs, "latitude"); + BigDecimal longitude = decimal(rs, "longitude"); + return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude); + } + + private Map payload(ResultSet rs) throws SQLException { + Map raw = new LinkedHashMap<>(); + put(raw, "sourceRowId", string(rs, "source_row_id")); + raw.putAll(sourceSpecificPayload(rs)); + return Map.of("raw", raw); + } + + private String defaultExternalSourceEventId( + ExtractionContext context, + int rowNum, + OffsetDateTime occurredAt, + SourcePackageRefDto sourcePackageRef, + DriverRefDto driverRef, + VehicleRefDto vehicleRef + ) { + String sourcePackageId = sourcePackageRef == null || sourcePackageRef.sourcePackageId() == null + ? "NO_SOURCE_PACKAGE" + : sourcePackageRef.sourcePackageId(); + String subject = driverRef != null && driverRef.hasAnyReference() + ? driverRef.stableKey() + : vehicleRef == null ? "NO_SUBJECT" : vehicleRef.stableKey(); + return "TACHOGRAPH:" + context.planItem().extractionCode() + + ":" + sourcePackageId + + ":" + occurredAt + + ":" + subject + + ":ROW-" + rowNum; + } + + protected String string(ResultSet rs, String column) throws SQLException { + String value = rs.getString(column); + return value == null || value.isBlank() ? null : value.trim(); + } + + protected OffsetDateTime offsetDateTime(ResultSet rs, String column) throws SQLException { + Object value = rs.getObject(column); + if (value == null) { + return null; + } + if (value instanceof OffsetDateTime offsetDateTime) { + return offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC); + } + if (value instanceof Timestamp timestamp) { + return timestamp.toLocalDateTime().atOffset(ZoneOffset.UTC); + } + if (value instanceof LocalDateTime localDateTime) { + return localDateTime.atOffset(ZoneOffset.UTC); + } + String text = value.toString(); + try { + return OffsetDateTime.parse(text).withOffsetSameInstant(ZoneOffset.UTC); + } catch (RuntimeException ignored) { + return LocalDateTime.parse(text).atOffset(ZoneOffset.UTC); + } + } + + private Long longValue(ResultSet rs, String column) throws SQLException { + Object value = rs.getObject(column); + if (value == null) { + return null; + } + if (value instanceof Number number) { + return number.longValue(); + } + return Long.parseLong(value.toString()); + } + + private BigDecimal decimal(ResultSet rs, String column) throws SQLException { + Object value = rs.getObject(column); + if (value == null) { + return null; + } + if (value instanceof BigDecimal bigDecimal) { + return bigDecimal; + } + if (value instanceof Number number) { + return BigDecimal.valueOf(number.doubleValue()); + } + return new BigDecimal(value.toString()); + } + + protected void put(Map target, String key, Object value) { + if (value != null) { + target.put(key, value); + } + } +} diff --git a/src/main/java/at/procon/eventhub/tachograph/service/AbstractTachographCardEventRowMapper.java b/src/main/java/at/procon/eventhub/tachograph/service/AbstractTachographCardEventRowMapper.java new file mode 100644 index 0000000..53ae000 --- /dev/null +++ b/src/main/java/at/procon/eventhub/tachograph/service/AbstractTachographCardEventRowMapper.java @@ -0,0 +1,246 @@ +package at.procon.eventhub.tachograph.service; + +import at.procon.eventhub.dto.CardSlot; +import at.procon.eventhub.dto.CardStatus; +import at.procon.eventhub.dto.DriverCardRefDto; +import at.procon.eventhub.dto.DriverRefDto; +import at.procon.eventhub.dto.EventDomain; +import at.procon.eventhub.dto.EventHubEventDto; +import at.procon.eventhub.dto.EventLifecycle; +import at.procon.eventhub.dto.EventType; +import at.procon.eventhub.dto.SourcePackageRefDto; +import at.procon.eventhub.dto.VehicleRefDto; +import at.procon.eventhub.dto.VehicleRegistrationRefDto; +import at.procon.eventhub.importing.extraction.ExtractionContext; +import at.procon.eventhub.importing.extraction.ExtractionRowMapper; +import at.procon.eventhub.service.EventDetailsFactory; +import at.procon.eventhub.tachograph.dto.TachographImportRequest; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +abstract class AbstractTachographCardEventRowMapper implements ExtractionRowMapper { + + private final EventDetailsFactory detailsFactory; + + protected AbstractTachographCardEventRowMapper(EventDetailsFactory detailsFactory) { + this.detailsFactory = detailsFactory; + } + + @Override + public EventHubEventDto map(ResultSet rs, int rowNum, ExtractionContext context) throws SQLException { + OffsetDateTime occurredAt = offsetDateTime(rs, "occurred_at"); + SourcePackageRefDto sourcePackageRef = sourcePackageRef(rs); + DriverRefDto driverRef = driverRef(rs); + VehicleRefDto vehicleRef = vehicleRef(rs); + DriverCardRefDto driverCard = driverRef == null ? null : driverRef.driverCard(); + EventType eventType = eventType(rs); + EventLifecycle lifecycle = lifecycle(rs); + CardStatus cardStatus = cardStatus(lifecycle); + + String externalSourceEventId = string(rs, "external_source_event_id"); + if (externalSourceEventId == null) { + externalSourceEventId = defaultExternalSourceEventId(context, rowNum, occurredAt, sourcePackageRef, driverRef, vehicleRef, lifecycle); + } + + return new EventHubEventDto( + UUID.randomUUID(), + externalSourceEventId, + driverRef, + vehicleRef, + occurredAt, + offsetDateTime(rs, "received_partner_at"), + OffsetDateTime.now(), + EventDomain.DRIVER_CARD, + eventType, + lifecycle, + longValue(rs, "odometer_m"), + null, + detailsFactory.driverCard(cardSlot(rs), cardStatus, driverCard), + sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null, + detailsFactory.payloadFromMap(payload(rs)), + false, + context.packageInfo() + ); + } + + protected Map sourceSpecificPayload(ResultSet rs) throws SQLException { + return Map.of(); + } + + private DriverRefDto driverRef(ResultSet rs) throws SQLException { + DriverCardRefDto driverCard = null; + String cardNumber = string(rs, "driver_card_number"); + if (cardNumber != null) { + driverCard = new DriverCardRefDto(string(rs, "driver_card_nation"), cardNumber); + } + DriverRefDto driverRef = new DriverRefDto(string(rs, "driver_source_entity_id"), driverCard); + return driverRef.hasAnyReference() ? driverRef : null; + } + + private VehicleRefDto vehicleRef(ResultSet rs) throws SQLException { + VehicleRegistrationRefDto registration = null; + String registrationNumber = string(rs, "vehicle_registration_number"); + if (registrationNumber != null) { + registration = new VehicleRegistrationRefDto(string(rs, "vehicle_registration_nation"), registrationNumber); + } + VehicleRefDto vehicleRef = new VehicleRefDto( + string(rs, "vehicle_source_entity_id"), + string(rs, "vehicle_vin"), + string(rs, "vehicle_registration_source_entity_id"), + registration + ); + return vehicleRef.hasAnyReference() ? vehicleRef : null; + } + + private SourcePackageRefDto sourcePackageRef(ResultSet rs) throws SQLException { + return new SourcePackageRefDto( + string(rs, "source_package_kind"), + string(rs, "source_package_id"), + string(rs, "source_package_entity_id"), + offsetDateTime(rs, "source_package_period_from"), + offsetDateTime(rs, "source_package_period_to"), + offsetDateTime(rs, "source_package_imported_at") + ); + } + + private Map payload(ResultSet rs) throws SQLException { + Map raw = new LinkedHashMap<>(); + put(raw, "sourceRowId", string(rs, "source_row_id")); + raw.putAll(sourceSpecificPayload(rs)); + return Map.of("raw", raw); + } + + private String defaultExternalSourceEventId( + ExtractionContext context, + int rowNum, + OffsetDateTime occurredAt, + SourcePackageRefDto sourcePackageRef, + DriverRefDto driverRef, + VehicleRefDto vehicleRef, + EventLifecycle lifecycle + ) { + String sourcePackageId = sourcePackageRef == null || sourcePackageRef.sourcePackageId() == null + ? "NO_SOURCE_PACKAGE" + : sourcePackageRef.sourcePackageId(); + String subject = driverRef != null && driverRef.hasAnyReference() + ? driverRef.stableKey() + : vehicleRef == null ? "NO_SUBJECT" : vehicleRef.stableKey(); + return "TACHOGRAPH:" + context.planItem().extractionCode() + + ":" + sourcePackageId + + ":" + lifecycle + + ":" + occurredAt + + ":" + subject + + ":ROW-" + rowNum; + } + + protected String string(ResultSet rs, String column) throws SQLException { + String value = rs.getString(column); + return value == null || value.isBlank() ? null : value.trim(); + } + + protected Object object(ResultSet rs, String column) throws SQLException { + return rs.getObject(column); + } + + protected OffsetDateTime offsetDateTime(ResultSet rs, String column) throws SQLException { + Object value = rs.getObject(column); + if (value == null) { + return null; + } + if (value instanceof OffsetDateTime offsetDateTime) { + return offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC); + } + if (value instanceof Timestamp timestamp) { + return timestamp.toLocalDateTime().atOffset(ZoneOffset.UTC); + } + if (value instanceof LocalDateTime localDateTime) { + return localDateTime.atOffset(ZoneOffset.UTC); + } + String text = value.toString(); + try { + return OffsetDateTime.parse(text).withOffsetSameInstant(ZoneOffset.UTC); + } catch (RuntimeException ignored) { + return LocalDateTime.parse(text).atOffset(ZoneOffset.UTC); + } + } + + private Long longValue(ResultSet rs, String column) throws SQLException { + Object value = rs.getObject(column); + if (value == null) { + return null; + } + if (value instanceof Number number) { + return number.longValue(); + } + return Long.parseLong(value.toString()); + } + + private CardSlot cardSlot(ResultSet rs) throws SQLException { + Object value = object(rs, "card_slot"); + if (value instanceof Number number) { + return switch (number.intValue()) { + case 0 -> CardSlot.DRIVER; + case 1 -> CardSlot.CO_DRIVER; + default -> null; + }; + } + String text = value == null ? null : value.toString(); + Integer parsed = parseInteger(text); + if (parsed != null) { + return parsed == 0 ? CardSlot.DRIVER : parsed == 1 ? CardSlot.CO_DRIVER : null; + } + return parseEnum(CardSlot.class, text, null); + } + + private EventType eventType(ResultSet rs) throws SQLException { + return parseEnum(EventType.class, string(rs, "event_type"), EventType.UNKNOWN_EVENT); + } + + private EventLifecycle lifecycle(ResultSet rs) throws SQLException { + return parseEnum(EventLifecycle.class, string(rs, "lifecycle"), EventLifecycle.SNAPSHOT); + } + + private CardStatus cardStatus(EventLifecycle lifecycle) { + return lifecycle == EventLifecycle.INSERT ? CardStatus.INSERTED : lifecycle == EventLifecycle.WITHDRAW ? CardStatus.NOT_INSERTED : null; + } + + private > T parseEnum(Class type, String value, T fallback) { + if (value == null) { + return fallback; + } + String normalized = value.trim().toUpperCase(Locale.ROOT).replace('-', '_').replace(' ', '_'); + if ("CODRIVER".equals(normalized)) { + normalized = "CO_DRIVER"; + } + try { + return Enum.valueOf(type, normalized); + } catch (IllegalArgumentException ignored) { + return fallback; + } + } + + private Integer parseInteger(String value) { + if (value == null || value.isBlank()) { + return null; + } + try { + return Integer.parseInt(value.trim()); + } catch (NumberFormatException ignored) { + return null; + } + } + + protected void put(Map target, String key, Object value) { + if (value != null) { + target.put(key, value); + } + } +} diff --git a/src/main/java/at/procon/eventhub/tachograph/service/CardBorderCrossingRowMapper.java b/src/main/java/at/procon/eventhub/tachograph/service/CardBorderCrossingRowMapper.java new file mode 100644 index 0000000..f5ec4dc --- /dev/null +++ b/src/main/java/at/procon/eventhub/tachograph/service/CardBorderCrossingRowMapper.java @@ -0,0 +1,12 @@ +package at.procon.eventhub.tachograph.service; + +import at.procon.eventhub.service.EventDetailsFactory; +import org.springframework.stereotype.Component; + +@Component +public class CardBorderCrossingRowMapper extends AbstractTachographBorderCrossingRowMapper { + + public CardBorderCrossingRowMapper(EventDetailsFactory detailsFactory) { + super(detailsFactory); + } +} diff --git a/src/main/java/at/procon/eventhub/tachograph/service/CardVehiclesUsedCardEventRowMapper.java b/src/main/java/at/procon/eventhub/tachograph/service/CardVehiclesUsedCardEventRowMapper.java new file mode 100644 index 0000000..73177c9 --- /dev/null +++ b/src/main/java/at/procon/eventhub/tachograph/service/CardVehiclesUsedCardEventRowMapper.java @@ -0,0 +1,12 @@ +package at.procon.eventhub.tachograph.service; + +import at.procon.eventhub.service.EventDetailsFactory; +import org.springframework.stereotype.Component; + +@Component +public class CardVehiclesUsedCardEventRowMapper extends AbstractTachographCardEventRowMapper { + + public CardVehiclesUsedCardEventRowMapper(EventDetailsFactory detailsFactory) { + super(detailsFactory); + } +} diff --git a/src/main/java/at/procon/eventhub/tachograph/service/IwCycleCardEventRowMapper.java b/src/main/java/at/procon/eventhub/tachograph/service/IwCycleCardEventRowMapper.java new file mode 100644 index 0000000..eec7a1f --- /dev/null +++ b/src/main/java/at/procon/eventhub/tachograph/service/IwCycleCardEventRowMapper.java @@ -0,0 +1,12 @@ +package at.procon.eventhub.tachograph.service; + +import at.procon.eventhub.service.EventDetailsFactory; +import org.springframework.stereotype.Component; + +@Component +public class IwCycleCardEventRowMapper extends AbstractTachographCardEventRowMapper { + + public IwCycleCardEventRowMapper(EventDetailsFactory detailsFactory) { + super(detailsFactory); + } +} diff --git a/src/main/java/at/procon/eventhub/tachograph/service/TachographExtractionDefinitionRegistry.java b/src/main/java/at/procon/eventhub/tachograph/service/TachographExtractionDefinitionRegistry.java index 10cb896..773e31b 100644 --- a/src/main/java/at/procon/eventhub/tachograph/service/TachographExtractionDefinitionRegistry.java +++ b/src/main/java/at/procon/eventhub/tachograph/service/TachographExtractionDefinitionRegistry.java @@ -12,7 +12,11 @@ public class TachographExtractionDefinitionRegistry extends ExtractionDefinition public TachographExtractionDefinitionRegistry( CardActivityRowMapper cardActivityRowMapper, - VuActivityRowMapper vuActivityRowMapper + VuActivityRowMapper vuActivityRowMapper, + IwCycleCardEventRowMapper iwCycleCardEventRowMapper, + CardVehiclesUsedCardEventRowMapper cardVehiclesUsedCardEventRowMapper, + VuBorderCrossingRowMapper vuBorderCrossingRowMapper, + CardBorderCrossingRowMapper cardBorderCrossingRowMapper ) { super(List.of( new ExtractionDefinition<>( @@ -30,6 +34,38 @@ public class TachographExtractionDefinitionRegistry extends ExtractionDefinition "VEHICLE", "classpath:sql/tachograph/vu-activity.sql", vuActivityRowMapper + ), + new ExtractionDefinition<>( + "IW_CYCLE", + EventFamily.DRIVER_CARD, + "VEHICLE_UNIT", + "BOTH", + "classpath:sql/tachograph/iw-cycle.sql", + iwCycleCardEventRowMapper + ), + new ExtractionDefinition<>( + "CARD_VEHICLES_USED", + EventFamily.DRIVER_CARD, + "DRIVER_CARD", + "DRIVER", + "classpath:sql/tachograph/card-vehicles-used.sql", + cardVehiclesUsedCardEventRowMapper + ), + new ExtractionDefinition<>( + "VU_BORDER_CROSSING", + EventFamily.BORDER_CROSSING, + "VEHICLE_UNIT", + "VEHICLE", + "classpath:sql/tachograph/vu-border-crossing.sql", + vuBorderCrossingRowMapper + ), + new ExtractionDefinition<>( + "CARD_BORDER_CROSSING", + EventFamily.BORDER_CROSSING, + "DRIVER_CARD", + "DRIVER", + "classpath:sql/tachograph/card-border-crossing.sql", + cardBorderCrossingRowMapper ) )); } diff --git a/src/main/java/at/procon/eventhub/tachograph/service/TachographMasterDataRefreshService.java b/src/main/java/at/procon/eventhub/tachograph/service/TachographMasterDataRefreshService.java index 83dc5ff..5e6c342 100644 --- a/src/main/java/at/procon/eventhub/tachograph/service/TachographMasterDataRefreshService.java +++ b/src/main/java/at/procon/eventhub/tachograph/service/TachographMasterDataRefreshService.java @@ -3,6 +3,7 @@ package at.procon.eventhub.tachograph.service; import at.procon.eventhub.importing.masterdata.MasterDataRefreshResult; import at.procon.eventhub.importing.masterdata.SourceMasterEntityUpsert; import at.procon.eventhub.importing.masterdata.SourceMasterRelationUpsert; +import at.procon.eventhub.dto.EventSourceDto; import at.procon.eventhub.persistence.EventSourceRepository; import at.procon.eventhub.persistence.SourceMasterDataRepository; import at.procon.eventhub.persistence.VehicleIdentityRepository; @@ -43,6 +44,8 @@ public class TachographMasterDataRefreshService { ); private static final String RELATIONS_SQL_RESOURCE = "classpath:sql/tachograph/master-data/relations.sql"; + private static final String MASTER_DATA_SOURCE_KIND = "MASTER_DATA"; + private static final String MASTER_DATA_SOURCE_KEY = "TACHOGRAPH_MASTER_DATA"; private final ObjectProvider tachographJdbcTemplateProvider; private final SourceMasterDataRepository sourceMasterDataRepository; @@ -80,7 +83,8 @@ public class TachographMasterDataRefreshService { } String tenantKey = request.tenantKey() == null || request.tenantKey().isBlank() ? "default" : request.tenantKey().trim(); - int eventSourceId = eventSourceRepository.resolveSourceId(tenantKey, request.eventSource()); + EventSourceDto masterDataSource = masterDataSourceFor(request.eventSource()); + int eventSourceId = eventSourceRepository.resolveSourceId(tenantKey, masterDataSource); int entities = 0; for (String sqlResource : ENTITY_SQL_RESOURCES) { @@ -102,10 +106,21 @@ public class TachographMasterDataRefreshService { MasterDataRefreshResult result = new MasterDataRefreshResult(entities, relationCount); log.info("Refreshed tachograph source master data tenant={} source={} entities={} relations={} reconciledVehicles={}", - tenantKey, request.eventSource().stableKey(), result.entitiesUpserted(), result.relationsUpserted(), reconciledVehicles); + tenantKey, masterDataSource.stableKey(), result.entitiesUpserted(), result.relationsUpserted(), reconciledVehicles); return result; } + private EventSourceDto masterDataSourceFor(EventSourceDto source) { + return new EventSourceDto( + source.providerKey(), + MASTER_DATA_SOURCE_KIND, + MASTER_DATA_SOURCE_KEY, + source.sourceInstanceKey(), + source.tenantProviderSettingKey(), + source.externalFleetKey() + ); + } + private SourceMasterEntityUpsert entity(ResultSet rs) throws SQLException { String entityType = string(rs, "entity_type"); String sourceEntityId = string(rs, "source_entity_id"); diff --git a/src/main/java/at/procon/eventhub/tachograph/service/VuBorderCrossingRowMapper.java b/src/main/java/at/procon/eventhub/tachograph/service/VuBorderCrossingRowMapper.java new file mode 100644 index 0000000..b1a986f --- /dev/null +++ b/src/main/java/at/procon/eventhub/tachograph/service/VuBorderCrossingRowMapper.java @@ -0,0 +1,12 @@ +package at.procon.eventhub.tachograph.service; + +import at.procon.eventhub.service.EventDetailsFactory; +import org.springframework.stereotype.Component; + +@Component +public class VuBorderCrossingRowMapper extends AbstractTachographBorderCrossingRowMapper { + + public VuBorderCrossingRowMapper(EventDetailsFactory detailsFactory) { + super(detailsFactory); + } +} diff --git a/src/main/resources/sql/tachograph/card-border-crossing.sql b/src/main/resources/sql/tachograph/card-border-crossing.sql new file mode 100644 index 0000000..2c27c1b --- /dev/null +++ b/src/main/resources/sql/tachograph/card-border-crossing.sql @@ -0,0 +1,96 @@ +/* + * CardBorderCrossing BORDER_CROSSING extraction for the bytebar tachograph schema. + */ +with OrgTree as ( + select org.I_90021_OID + from dbo.GetOrganisationTree(null, :organisationId, 0, null) org + where :organisationId is not null +) +, +Base as ( + select + border.ID, + border.Timestamp as occurred_at, + border.Odo, + border.ID_FileLog, + c.ID as card_id, + c.ID_Driver as driver_id, + cn.AlphaCode as driver_card_nation, + c.CardNumber as driver_card_number, + leftNation.AlphaCode as country_from, + enteredNation.AlphaCode as country_to, + gnss.Latitude as latitude, + gnss.Longitude as longitude, + v.ID as vehicle_registration_id, + v.ID_VehicleIdentification as vehicle_identification_id, + vi.VIN as vehicle_vin, + vehicleNation.AlphaCode as vehicle_registration_nation, + v.VRN as vehicle_registration_number, + coalesce(fl.DownloadDate, fl.OriginalDownloadDate, fl.TStamp, fl.CreationDate) as received_partner_at, + coalesce(fl.ID, border.ID_FileLog, border.ID) as source_package_id_raw, + coalesce(fl.ID_Card, border.ID_Card) as source_package_entity_id_raw, + coalesce(fl.DownloadFrom, border.Timestamp) as source_package_period_from, + coalesce(fl.DownloadTo, border.Timestamp) as source_package_period_to, + coalesce(fl.CreationDate, fl.TStamp) as source_package_imported_at + from dbo.CardBorderCrossing border + join dbo.Card c on c.ID = border.ID_Card + left join dbo.Nation cn on cn.ID = c.ID_Nation + left join dbo.Nation leftNation on leftNation.ID = border.ID_NationLeft + left join dbo.Nation enteredNation on enteredNation.ID = border.ID_NationEntered + left join dbo.GnssPlace gnss on gnss.ID = border.ID_GnssPlace + left join dbo.FileLog fl on fl.ID = border.ID_FileLog + outer apply ( + select top 1 used.ID_Vehicle + from dbo.CardVehiclesUsed used + where used.ID_Card = border.ID_Card + and (used.FirstUse is null or used.FirstUse <= border.Timestamp) + and (used.LastUse is null or used.LastUse >= border.Timestamp) + order by + used.FirstUse desc, + used.ID desc + ) cvu + left join dbo.Vehicle v on v.ID = cvu.ID_Vehicle + left join dbo.VehicleIdentification vi on vi.ID = v.ID_VehicleIdentification + left join dbo.Nation vehicleNation on vehicleNation.ID = v.ID_Nation + where (:occurredFrom is null or border.Timestamp >= :occurredFrom) + and (:occurredTo is null or border.Timestamp < :occurredTo) + and ( + :organisationId is null + or exists ( + select 1 + from dbo.Driver_I_90021 rel + join OrgTree on OrgTree.I_90021_OID = rel.ID_I_90021 + where rel.ID_Driver = c.ID_Driver + and rel.GILT_BIS is null + ) + ) +) +select + cast(base.ID as varchar(128)) as source_row_id, + concat('TACHOGRAPH:CARD_BORDER_CROSSING:', base.ID) as external_source_event_id, + + base.occurred_at, + base.received_partner_at, + cast(base.Odo as bigint) * 1000 as odometer_m, + base.latitude, + base.longitude, + base.country_from, + base.country_to, + + cast(base.driver_id as varchar(128)) as driver_source_entity_id, + base.driver_card_nation, + base.driver_card_number, + + cast(base.vehicle_identification_id as varchar(128)) as vehicle_source_entity_id, + base.vehicle_vin, + cast(base.vehicle_registration_id as varchar(128)) as vehicle_registration_source_entity_id, + base.vehicle_registration_nation, + base.vehicle_registration_number, + + 'DRIVER_CARD' as source_package_kind, + cast(base.source_package_id_raw as varchar(128)) as source_package_id, + cast(base.source_package_entity_id_raw as varchar(128)) as source_package_entity_id, + base.source_package_period_from, + base.source_package_period_to, + base.source_package_imported_at +from Base base diff --git a/src/main/resources/sql/tachograph/card-vehicles-used.sql b/src/main/resources/sql/tachograph/card-vehicles-used.sql new file mode 100644 index 0000000..8e650f4 --- /dev/null +++ b/src/main/resources/sql/tachograph/card-vehicles-used.sql @@ -0,0 +1,114 @@ +/* + * CardVehiclesUsed DRIVER_CARD extraction for the bytebar tachograph schema. + * + * Each row is emitted as INSERT at FirstUse and WITHDRAW at LastUse when those + * timestamps are available. + */ +with OrgTree as ( + select org.I_90021_OID + from dbo.GetOrganisationTree(null, :organisationId, 0, null) org + where :organisationId is not null +) +, +CandidateUse as ( + select + used.ID, + used.ID_Card, + used.ID_Vehicle, + used.OdoBegin, + used.OdoEnd, + used.FirstUse, + used.LastUse, + used.ID_FileLog, + used.VIN as used_vin, + c.ID_Driver as driver_id, + cn.AlphaCode as driver_card_nation, + c.CardNumber as driver_card_number, + v.ID_VehicleIdentification, + vi.ID as vehicle_identification_id, + coalesce(used.VIN, vi.VIN) as vehicle_vin, + vn.AlphaCode as vehicle_registration_nation, + v.VRN as vehicle_registration_number + from dbo.CardVehiclesUsed used + join dbo.Card c on c.ID = used.ID_Card + left join dbo.Nation cn on cn.ID = c.ID_Nation + left join dbo.Vehicle v on v.ID = used.ID_Vehicle + left join dbo.VehicleIdentification vi on vi.ID = v.ID_VehicleIdentification + left join dbo.Nation vn on vn.ID = v.ID_Nation + where (:occurredTo is null or used.FirstUse < :occurredTo or used.LastUse < :occurredTo) + and (:occurredFrom is null or used.FirstUse >= :occurredFrom or used.LastUse >= :occurredFrom) + and ( + :organisationId is null + or exists ( + select 1 + from dbo.Driver_I_90021 rel + join OrgTree on OrgTree.I_90021_OID = rel.ID_I_90021 + where rel.ID_Driver = c.ID_Driver + and rel.GILT_BIS is null + ) + ) +) +, +Base as ( + select + used.ID, + used.ID_Card, + used.ID_Vehicle, + used.driver_id, + used.driver_card_nation, + used.driver_card_number, + used.vehicle_identification_id, + used.vehicle_vin, + used.vehicle_registration_nation, + used.vehicle_registration_number, + evt.lifecycle, + evt.occurred_at, + evt.odometer_m, + coalesce(fl.DownloadDate, fl.OriginalDownloadDate, fl.TStamp, fl.CreationDate) as received_partner_at, + coalesce(fl.ID, used.ID_FileLog, used.ID) as source_package_id_raw, + coalesce(fl.ID_Card, used.ID_Card) as source_package_entity_id_raw, + coalesce(fl.DownloadFrom, used.FirstUse) as source_package_period_from, + coalesce(fl.DownloadTo, used.LastUse, used.FirstUse) as source_package_period_to, + coalesce(fl.CreationDate, fl.TStamp) as source_package_imported_at + from CandidateUse used + cross apply (values + ('INSERT', used.FirstUse, cast(used.OdoBegin as bigint) * 1000), + ('WITHDRAW', used.LastUse, cast(used.OdoEnd as bigint) * 1000) + ) evt(lifecycle, occurred_at, odometer_m) + left join dbo.FileLog fl on fl.ID = used.ID_FileLog + where evt.occurred_at is not null + and (:occurredFrom is null or evt.occurred_at >= :occurredFrom) + and (:occurredTo is null or evt.occurred_at < :occurredTo) +) +select + cast(base.ID as varchar(128)) as source_row_id, + concat('TACHOGRAPH:CARD_VEHICLES_USED:', base.ID, ':', base.lifecycle) as external_source_event_id, + + base.occurred_at, + base.received_partner_at, + case base.lifecycle + when 'INSERT' then 'CARD_INSERTED' + when 'WITHDRAW' then 'CARD_WITHDRAWN' + else 'UNKNOWN_EVENT' + end as event_type, + base.lifecycle, + cast(null as varchar(32)) as card_slot, + base.odometer_m, + + cast(base.driver_id as varchar(128)) as driver_source_entity_id, + base.driver_card_nation, + base.driver_card_number, + + cast(base.vehicle_identification_id as varchar(128)) as vehicle_source_entity_id, + base.vehicle_vin, + cast(base.ID_Vehicle as varchar(128)) as vehicle_registration_source_entity_id, + base.vehicle_registration_nation, + base.vehicle_registration_number, + + 'DRIVER_CARD' as source_package_kind, + cast(base.source_package_id_raw as varchar(128)) as source_package_id, + cast(base.source_package_entity_id_raw as varchar(128)) as source_package_entity_id, + base.source_package_period_from, + base.source_package_period_to, + base.source_package_imported_at +from Base base diff --git a/src/main/resources/sql/tachograph/iw-cycle.sql b/src/main/resources/sql/tachograph/iw-cycle.sql new file mode 100644 index 0000000..96c5a39 --- /dev/null +++ b/src/main/resources/sql/tachograph/iw-cycle.sql @@ -0,0 +1,128 @@ +/* + * IWCycle DRIVER_CARD extraction for the bytebar tachograph schema. + * + * Each IWCycle row describes a card inserted into a vehicle unit. It is emitted + * as two point events when timestamps are available: INSERT at BeginTime and + * WITHDRAW at EndTime. + */ +with OrgTree as ( + select org.I_90021_OID + from dbo.GetOrganisationTree(null, :organisationId, 0, null) org + where :organisationId is not null +) +, +CandidateCycle as ( + select + iw.ID, + iw.BeginTime, + iw.EndTime, + iw.OdoBegin, + iw.OdoEnd, + iw.Slot, + iw.ID_FileLog, + iw.ID_Card, + iw.ID_VUInstallation, + vui.ID_VehicleIdentification, + vui.ID_FileLog as vui_filelog_id, + vi.ID as vehicle_identification_id, + vi.VIN as vehicle_vin, + c.ID_Driver as driver_id, + cn.AlphaCode as driver_card_nation, + c.CardNumber as driver_card_number, + coalesce(iw.ID_FileLog, vui.ID_FileLog) as file_log_id + from dbo.IWCycle iw + join dbo.VUInstallation vui on vui.ID = iw.ID_VUInstallation + join dbo.VehicleIdentification vi on vi.ID = vui.ID_VehicleIdentification + left join dbo.Card c on c.ID = iw.ID_Card + left join dbo.Nation cn on cn.ID = c.ID_Nation + where (:occurredTo is null or iw.BeginTime < :occurredTo or iw.EndTime < :occurredTo) + and (:occurredFrom is null or iw.BeginTime >= :occurredFrom or iw.EndTime >= :occurredFrom) +) +, +Base as ( + select + cycle.ID, + cycle.Slot, + cycle.ID_FileLog, + cycle.vui_filelog_id, + cycle.ID_Card, + cycle.ID_VUInstallation, + cycle.vehicle_identification_id, + cycle.vehicle_vin, + cycle.driver_id, + cycle.driver_card_nation, + cycle.driver_card_number, + evt.lifecycle, + evt.occurred_at, + evt.odometer_m, + coalesce(fl.DownloadDate, fl.OriginalDownloadDate, fl.TStamp, fl.CreationDate) as received_partner_at, + coalesce(fl.ID, cycle.ID_FileLog, cycle.vui_filelog_id, cycle.ID) as source_package_id_raw, + coalesce(fl.ID_VehicleIdentification, cycle.ID_VehicleIdentification) as source_package_entity_id_raw, + coalesce(fl.DownloadFrom, cycle.BeginTime) as source_package_period_from, + coalesce(fl.DownloadTo, cycle.EndTime, cycle.BeginTime) as source_package_period_to, + coalesce(fl.CreationDate, fl.TStamp) as source_package_imported_at + from CandidateCycle cycle + cross apply (values + ('INSERT', cycle.BeginTime, cast(cycle.OdoBegin as bigint) * 1000), + ('WITHDRAW', cycle.EndTime, cast(cycle.OdoEnd as bigint) * 1000) + ) evt(lifecycle, occurred_at, odometer_m) + left join dbo.FileLog fl on fl.ID = cycle.file_log_id + where evt.occurred_at is not null + and (:occurredFrom is null or evt.occurred_at >= :occurredFrom) + and (:occurredTo is null or evt.occurred_at < :occurredTo) +) +select + cast(base.ID as varchar(128)) as source_row_id, + concat('TACHOGRAPH:IW_CYCLE:', base.ID, ':', base.lifecycle) as external_source_event_id, + + base.occurred_at, + base.received_partner_at, + case base.lifecycle + when 'INSERT' then 'CARD_INSERTED' + when 'WITHDRAW' then 'CARD_WITHDRAWN' + else 'UNKNOWN_EVENT' + end as event_type, + base.lifecycle, + base.Slot as card_slot, + base.odometer_m, + + cast(base.driver_id as varchar(128)) as driver_source_entity_id, + base.driver_card_nation, + base.driver_card_number, + + cast(base.vehicle_identification_id as varchar(128)) as vehicle_source_entity_id, + base.vehicle_vin, + cast(v.ID as varchar(128)) as vehicle_registration_source_entity_id, + vn.AlphaCode as vehicle_registration_nation, + v.VRN as vehicle_registration_number, + + 'VEHICLE_UNIT' as source_package_kind, + cast(base.source_package_id_raw as varchar(128)) as source_package_id, + cast(base.source_package_entity_id_raw as varchar(128)) as source_package_entity_id, + base.source_package_period_from, + base.source_package_period_to, + base.source_package_imported_at +from Base base +outer apply ( + select top 1 vehicle.ID, + vehicle.VRN, + vehicle.ID_Nation + from dbo.Vehicle vehicle + where vehicle.ID_VehicleIdentification = base.vehicle_identification_id + and (vehicle.ValidFrom is null or vehicle.ValidFrom <= base.occurred_at) + and (vehicle.ValidTo is null or vehicle.ValidTo > base.occurred_at) + order by + vehicle.ValidFrom desc, + vehicle.ID desc +) v +left join dbo.Nation vn on vn.ID = v.ID_Nation +where ( + :organisationId is null + or exists ( + select 1 + from dbo.Vehicle_I_90021 rel + join OrgTree on OrgTree.I_90021_OID = rel.ID_I_90021 + where rel.ID_Vehicle = v.ID + and rel.GILT_BIS is null + ) + ) diff --git a/src/main/resources/sql/tachograph/vu-border-crossing.sql b/src/main/resources/sql/tachograph/vu-border-crossing.sql new file mode 100644 index 0000000..584768c --- /dev/null +++ b/src/main/resources/sql/tachograph/vu-border-crossing.sql @@ -0,0 +1,100 @@ +/* + * VUBorderCrossing BORDER_CROSSING extraction for the bytebar tachograph schema. + */ +with OrgTree as ( + select org.I_90021_OID + from dbo.GetOrganisationTree(null, :organisationId, 0, null) org + where :organisationId is not null +) +, +Base as ( + select + border.ID, + border.Timestamp as occurred_at, + border.Odo, + border.ID_FileLog, + border.ID_VUInstallation, + coalesce(driverCard.ID, coDriverCard.ID) as card_id, + coalesce(driverCard.ID_Driver, coDriverCard.ID_Driver) as driver_id, + coalesce(driverNation.AlphaCode, coDriverNation.AlphaCode) as driver_card_nation, + coalesce(driverCard.CardNumber, coDriverCard.CardNumber) as driver_card_number, + leftNation.AlphaCode as country_from, + enteredNation.AlphaCode as country_to, + gnss.Latitude as latitude, + gnss.Longitude as longitude, + vui.ID_FileLog as vui_filelog_id, + vui.ID_VehicleIdentification, + vi.ID as vehicle_identification_id, + vi.VIN as vehicle_vin, + coalesce(fl.DownloadDate, fl.OriginalDownloadDate, fl.TStamp, fl.CreationDate) as received_partner_at, + coalesce(fl.ID, border.ID_FileLog, vui.ID_FileLog, border.ID) as source_package_id_raw, + coalesce(fl.ID_VehicleIdentification, vui.ID_VehicleIdentification) as source_package_entity_id_raw, + coalesce(fl.DownloadFrom, border.Timestamp) as source_package_period_from, + coalesce(fl.DownloadTo, border.Timestamp) as source_package_period_to, + coalesce(fl.CreationDate, fl.TStamp) as source_package_imported_at + from dbo.VUBorderCrossing border + join dbo.VUInstallation vui on vui.ID = border.ID_VUInstallation + join dbo.VehicleIdentification vi on vi.ID = vui.ID_VehicleIdentification + left join dbo.Card driverCard on driverCard.ID = border.ID_DriverCard + left join dbo.Nation driverNation on driverNation.ID = driverCard.ID_Nation + left join dbo.Card coDriverCard on coDriverCard.ID = border.ID_CoDriverCard + left join dbo.Nation coDriverNation on coDriverNation.ID = coDriverCard.ID_Nation + left join dbo.Nation leftNation on leftNation.ID = border.ID_NationLeft + left join dbo.Nation enteredNation on enteredNation.ID = border.ID_NationEntered + left join dbo.GnssPlace gnss on gnss.ID = border.ID_GnssPlace + left join dbo.FileLog fl on fl.ID = coalesce(border.ID_FileLog, vui.ID_FileLog) + where (:occurredFrom is null or border.Timestamp >= :occurredFrom) + and (:occurredTo is null or border.Timestamp < :occurredTo) +) +select + cast(base.ID as varchar(128)) as source_row_id, + concat('TACHOGRAPH:VU_BORDER_CROSSING:', base.ID) as external_source_event_id, + + base.occurred_at, + base.received_partner_at, + cast(base.Odo as bigint) * 1000 as odometer_m, + base.latitude, + base.longitude, + base.country_from, + base.country_to, + + cast(base.driver_id as varchar(128)) as driver_source_entity_id, + base.driver_card_nation, + base.driver_card_number, + + cast(base.vehicle_identification_id as varchar(128)) as vehicle_source_entity_id, + base.vehicle_vin, + cast(v.ID as varchar(128)) as vehicle_registration_source_entity_id, + vn.AlphaCode as vehicle_registration_nation, + v.VRN as vehicle_registration_number, + + 'VEHICLE_UNIT' as source_package_kind, + cast(base.source_package_id_raw as varchar(128)) as source_package_id, + cast(base.source_package_entity_id_raw as varchar(128)) as source_package_entity_id, + base.source_package_period_from, + base.source_package_period_to, + base.source_package_imported_at +from Base base +outer apply ( + select top 1 vehicle.ID, + vehicle.VRN, + vehicle.ID_Nation + from dbo.Vehicle vehicle + where vehicle.ID_VehicleIdentification = base.vehicle_identification_id + and (vehicle.ValidFrom is null or vehicle.ValidFrom <= base.occurred_at) + and (vehicle.ValidTo is null or vehicle.ValidTo > base.occurred_at) + order by + vehicle.ValidFrom desc, + vehicle.ID desc +) v +left join dbo.Nation vn on vn.ID = v.ID_Nation +where ( + :organisationId is null + or exists ( + select 1 + from dbo.Vehicle_I_90021 rel + join OrgTree on OrgTree.I_90021_OID = rel.ID_I_90021 + where rel.ID_Vehicle = v.ID + and rel.GILT_BIS is null + ) + ) diff --git a/src/test/java/at/procon/eventhub/tachograph/service/TachographImportPlanServiceTest.java b/src/test/java/at/procon/eventhub/tachograph/service/TachographImportPlanServiceTest.java index 5a49121..078af6f 100644 --- a/src/test/java/at/procon/eventhub/tachograph/service/TachographImportPlanServiceTest.java +++ b/src/test/java/at/procon/eventhub/tachograph/service/TachographImportPlanServiceTest.java @@ -20,19 +20,25 @@ class TachographImportPlanServiceTest { private final EventDetailsFactory detailsFactory = new EventDetailsFactory(new ObjectMapper()); private final TachographExtractionDefinitionRegistry definitionRegistry = new TachographExtractionDefinitionRegistry( new CardActivityRowMapper(detailsFactory), - new VuActivityRowMapper(detailsFactory) + new VuActivityRowMapper(detailsFactory), + new IwCycleCardEventRowMapper(detailsFactory), + new CardVehiclesUsedCardEventRowMapper(detailsFactory), + new VuBorderCrossingRowMapper(detailsFactory), + new CardBorderCrossingRowMapper(detailsFactory) ); @Test void rejectsUnsupportedEventFamiliesWhenJdbcExtractionIsEnabled() { TachographImportPlanService service = serviceWithJdbcExtractor(); - TachographImportRequest request = requestForFamilies(EventFamily.DRIVER_CARD); + TachographImportRequest request = requestForFamilies(EventFamily.POSITION); assertThatThrownBy(() -> service.createPlan(request)) .isInstanceOf(UnsupportedTachographExtractionException.class) - .hasMessageContaining("IW_CYCLE") + .hasMessageContaining("VU_POSITION") .hasMessageContaining("Supported JDBC extraction codes") - .hasMessageContaining("DRIVER_ACTIVITY"); + .hasMessageContaining("DRIVER_ACTIVITY") + .hasMessageContaining("DRIVER_CARD") + .hasMessageContaining("BORDER_CROSSING"); } @Test @@ -47,6 +53,30 @@ class TachographImportPlanServiceTest { .containsExactlyInAnyOrder("VU_ACTIVITY", "CARD_ACTIVITY"); } + @Test + void allowsSupportedDriverCardFamilyWhenJdbcExtractionIsEnabled() { + TachographImportPlanService service = serviceWithJdbcExtractor(); + TachographImportRequest request = requestForFamilies(EventFamily.DRIVER_CARD); + + var plan = service.createPlan(request); + + assertThat(plan.items()) + .extracting(item -> item.extractionCode()) + .containsExactlyInAnyOrder("IW_CYCLE", "CARD_VEHICLES_USED"); + } + + @Test + void allowsSupportedBorderCrossingFamilyWhenJdbcExtractionIsEnabled() { + TachographImportPlanService service = serviceWithJdbcExtractor(); + TachographImportRequest request = requestForFamilies(EventFamily.BORDER_CROSSING); + + var plan = service.createPlan(request); + + assertThat(plan.items()) + .extracting(item -> item.extractionCode()) + .containsExactlyInAnyOrder("VU_BORDER_CROSSING", "CARD_BORDER_CROSSING"); + } + private TachographImportPlanService serviceWithJdbcExtractor() { EventHubProperties properties = new EventHubProperties(); properties.getTachograph().getDatasource().setJdbcUrl("jdbc:sqlserver://tachograph-db");