Add tachograph card and border crossing extractors
This commit is contained in:
parent
866e275691
commit
d27be66d0d
12
README.md
12
README.md
|
|
@ -814,14 +814,22 @@ The first concrete extractor is `JdbcTachographExtractionBatchExecutor`. It is e
|
||||||
Currently implemented extraction definitions:
|
Currently implemented extraction definitions:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
CARD_ACTIVITY -> DRIVER_ACTIVITY / DRIVER_CARD / CardActivity
|
CARD_ACTIVITY -> DRIVER_ACTIVITY / DRIVER_CARD / CardActivity
|
||||||
VU_ACTIVITY -> DRIVER_ACTIVITY / VEHICLE_UNIT / VUActivity
|
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:
|
SQL resources:
|
||||||
|
|
||||||
```text
|
```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/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
|
src/main/resources/sql/tachograph/vu-activity.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package at.procon.eventhub.service;
|
||||||
|
|
||||||
import at.procon.eventhub.dto.CardSlot;
|
import at.procon.eventhub.dto.CardSlot;
|
||||||
import at.procon.eventhub.dto.CardStatus;
|
import at.procon.eventhub.dto.CardStatus;
|
||||||
|
import at.procon.eventhub.dto.DriverCardRefDto;
|
||||||
import at.procon.eventhub.dto.DrivingStatus;
|
import at.procon.eventhub.dto.DrivingStatus;
|
||||||
import at.procon.eventhub.dto.EventDetailsDto;
|
import at.procon.eventhub.dto.EventDetailsDto;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
@ -29,9 +30,17 @@ public class EventDetailsFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventDetailsDto driverCard(CardSlot cardSlot, CardStatus cardStatus) {
|
public EventDetailsDto driverCard(CardSlot cardSlot, CardStatus cardStatus) {
|
||||||
|
return driverCard(cardSlot, cardStatus, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventDetailsDto driverCard(CardSlot cardSlot, CardStatus cardStatus, DriverCardRefDto driverCard) {
|
||||||
Map<String, Object> attributes = new LinkedHashMap<>();
|
Map<String, Object> attributes = new LinkedHashMap<>();
|
||||||
put(attributes, "cardSlot", cardSlot);
|
put(attributes, "cardSlot", cardSlot);
|
||||||
put(attributes, "cardStatus", cardStatus);
|
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));
|
return new EventDetailsDto("DRIVER_CARD", objectMapper.valueToTree(attributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<TachographImportRequest> {
|
||||||
|
|
||||||
|
private final EventDetailsFactory detailsFactory;
|
||||||
|
|
||||||
|
protected AbstractTachographBorderCrossingRowMapper(EventDetailsFactory detailsFactory) {
|
||||||
|
this.detailsFactory = detailsFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventHubEventDto map(ResultSet rs, int rowNum, ExtractionContext<TachographImportRequest> 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<String, Object> 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<String, Object> payload(ResultSet rs) throws SQLException {
|
||||||
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
|
put(raw, "sourceRowId", string(rs, "source_row_id"));
|
||||||
|
raw.putAll(sourceSpecificPayload(rs));
|
||||||
|
return Map.of("raw", raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultExternalSourceEventId(
|
||||||
|
ExtractionContext<TachographImportRequest> 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<String, Object> target, String key, Object value) {
|
||||||
|
if (value != null) {
|
||||||
|
target.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<TachographImportRequest> {
|
||||||
|
|
||||||
|
private final EventDetailsFactory detailsFactory;
|
||||||
|
|
||||||
|
protected AbstractTachographCardEventRowMapper(EventDetailsFactory detailsFactory) {
|
||||||
|
this.detailsFactory = detailsFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventHubEventDto map(ResultSet rs, int rowNum, ExtractionContext<TachographImportRequest> 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<String, Object> 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<String, Object> payload(ResultSet rs) throws SQLException {
|
||||||
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
|
put(raw, "sourceRowId", string(rs, "source_row_id"));
|
||||||
|
raw.putAll(sourceSpecificPayload(rs));
|
||||||
|
return Map.of("raw", raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultExternalSourceEventId(
|
||||||
|
ExtractionContext<TachographImportRequest> 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 extends Enum<T>> T parseEnum(Class<T> 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<String, Object> target, String key, Object value) {
|
||||||
|
if (value != null) {
|
||||||
|
target.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,11 @@ public class TachographExtractionDefinitionRegistry extends ExtractionDefinition
|
||||||
|
|
||||||
public TachographExtractionDefinitionRegistry(
|
public TachographExtractionDefinitionRegistry(
|
||||||
CardActivityRowMapper cardActivityRowMapper,
|
CardActivityRowMapper cardActivityRowMapper,
|
||||||
VuActivityRowMapper vuActivityRowMapper
|
VuActivityRowMapper vuActivityRowMapper,
|
||||||
|
IwCycleCardEventRowMapper iwCycleCardEventRowMapper,
|
||||||
|
CardVehiclesUsedCardEventRowMapper cardVehiclesUsedCardEventRowMapper,
|
||||||
|
VuBorderCrossingRowMapper vuBorderCrossingRowMapper,
|
||||||
|
CardBorderCrossingRowMapper cardBorderCrossingRowMapper
|
||||||
) {
|
) {
|
||||||
super(List.of(
|
super(List.of(
|
||||||
new ExtractionDefinition<>(
|
new ExtractionDefinition<>(
|
||||||
|
|
@ -30,6 +34,38 @@ public class TachographExtractionDefinitionRegistry extends ExtractionDefinition
|
||||||
"VEHICLE",
|
"VEHICLE",
|
||||||
"classpath:sql/tachograph/vu-activity.sql",
|
"classpath:sql/tachograph/vu-activity.sql",
|
||||||
vuActivityRowMapper
|
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
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package at.procon.eventhub.tachograph.service;
|
||||||
import at.procon.eventhub.importing.masterdata.MasterDataRefreshResult;
|
import at.procon.eventhub.importing.masterdata.MasterDataRefreshResult;
|
||||||
import at.procon.eventhub.importing.masterdata.SourceMasterEntityUpsert;
|
import at.procon.eventhub.importing.masterdata.SourceMasterEntityUpsert;
|
||||||
import at.procon.eventhub.importing.masterdata.SourceMasterRelationUpsert;
|
import at.procon.eventhub.importing.masterdata.SourceMasterRelationUpsert;
|
||||||
|
import at.procon.eventhub.dto.EventSourceDto;
|
||||||
import at.procon.eventhub.persistence.EventSourceRepository;
|
import at.procon.eventhub.persistence.EventSourceRepository;
|
||||||
import at.procon.eventhub.persistence.SourceMasterDataRepository;
|
import at.procon.eventhub.persistence.SourceMasterDataRepository;
|
||||||
import at.procon.eventhub.persistence.VehicleIdentityRepository;
|
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 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<NamedParameterJdbcTemplate> tachographJdbcTemplateProvider;
|
private final ObjectProvider<NamedParameterJdbcTemplate> tachographJdbcTemplateProvider;
|
||||||
private final SourceMasterDataRepository sourceMasterDataRepository;
|
private final SourceMasterDataRepository sourceMasterDataRepository;
|
||||||
|
|
@ -80,7 +83,8 @@ public class TachographMasterDataRefreshService {
|
||||||
}
|
}
|
||||||
|
|
||||||
String tenantKey = request.tenantKey() == null || request.tenantKey().isBlank() ? "default" : request.tenantKey().trim();
|
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;
|
int entities = 0;
|
||||||
for (String sqlResource : ENTITY_SQL_RESOURCES) {
|
for (String sqlResource : ENTITY_SQL_RESOURCES) {
|
||||||
|
|
@ -102,10 +106,21 @@ public class TachographMasterDataRefreshService {
|
||||||
|
|
||||||
MasterDataRefreshResult result = new MasterDataRefreshResult(entities, relationCount);
|
MasterDataRefreshResult result = new MasterDataRefreshResult(entities, relationCount);
|
||||||
log.info("Refreshed tachograph source master data tenant={} source={} entities={} relations={} reconciledVehicles={}",
|
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;
|
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 {
|
private SourceMasterEntityUpsert entity(ResultSet rs) throws SQLException {
|
||||||
String entityType = string(rs, "entity_type");
|
String entityType = string(rs, "entity_type");
|
||||||
String sourceEntityId = string(rs, "source_entity_id");
|
String sourceEntityId = string(rs, "source_entity_id");
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -20,19 +20,25 @@ class TachographImportPlanServiceTest {
|
||||||
private final EventDetailsFactory detailsFactory = new EventDetailsFactory(new ObjectMapper());
|
private final EventDetailsFactory detailsFactory = new EventDetailsFactory(new ObjectMapper());
|
||||||
private final TachographExtractionDefinitionRegistry definitionRegistry = new TachographExtractionDefinitionRegistry(
|
private final TachographExtractionDefinitionRegistry definitionRegistry = new TachographExtractionDefinitionRegistry(
|
||||||
new CardActivityRowMapper(detailsFactory),
|
new CardActivityRowMapper(detailsFactory),
|
||||||
new VuActivityRowMapper(detailsFactory)
|
new VuActivityRowMapper(detailsFactory),
|
||||||
|
new IwCycleCardEventRowMapper(detailsFactory),
|
||||||
|
new CardVehiclesUsedCardEventRowMapper(detailsFactory),
|
||||||
|
new VuBorderCrossingRowMapper(detailsFactory),
|
||||||
|
new CardBorderCrossingRowMapper(detailsFactory)
|
||||||
);
|
);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rejectsUnsupportedEventFamiliesWhenJdbcExtractionIsEnabled() {
|
void rejectsUnsupportedEventFamiliesWhenJdbcExtractionIsEnabled() {
|
||||||
TachographImportPlanService service = serviceWithJdbcExtractor();
|
TachographImportPlanService service = serviceWithJdbcExtractor();
|
||||||
TachographImportRequest request = requestForFamilies(EventFamily.DRIVER_CARD);
|
TachographImportRequest request = requestForFamilies(EventFamily.POSITION);
|
||||||
|
|
||||||
assertThatThrownBy(() -> service.createPlan(request))
|
assertThatThrownBy(() -> service.createPlan(request))
|
||||||
.isInstanceOf(UnsupportedTachographExtractionException.class)
|
.isInstanceOf(UnsupportedTachographExtractionException.class)
|
||||||
.hasMessageContaining("IW_CYCLE")
|
.hasMessageContaining("VU_POSITION")
|
||||||
.hasMessageContaining("Supported JDBC extraction codes")
|
.hasMessageContaining("Supported JDBC extraction codes")
|
||||||
.hasMessageContaining("DRIVER_ACTIVITY");
|
.hasMessageContaining("DRIVER_ACTIVITY")
|
||||||
|
.hasMessageContaining("DRIVER_CARD")
|
||||||
|
.hasMessageContaining("BORDER_CROSSING");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -47,6 +53,30 @@ class TachographImportPlanServiceTest {
|
||||||
.containsExactlyInAnyOrder("VU_ACTIVITY", "CARD_ACTIVITY");
|
.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() {
|
private TachographImportPlanService serviceWithJdbcExtractor() {
|
||||||
EventHubProperties properties = new EventHubProperties();
|
EventHubProperties properties = new EventHubProperties();
|
||||||
properties.getTachograph().getDatasource().setJdbcUrl("jdbc:sqlserver://tachograph-db");
|
properties.getTachograph().getDatasource().setJdbcUrl("jdbc:sqlserver://tachograph-db");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue