diff --git a/alfa-client/pom.xml b/alfa-client/pom.xml
index 155340560e7db527d6e0d0de3b99f320f13bbfc8..5626b5b6cc955de23b9dabf78f36a2d0c784306f 100644
--- a/alfa-client/pom.xml
+++ b/alfa-client/pom.xml
@@ -29,7 +29,7 @@
 	<parent>
 		<groupId>de.ozgcloud.alfa</groupId>
 		<artifactId>alfa</artifactId>
-		<version>2.3.0-SNAPSHOT</version>
+		<version>2.4.0-SNAPSHOT</version>
 	</parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/alfa-server/pom.xml b/alfa-server/pom.xml
index cd18f5ebb3a482794fb46b30ed44cec42fa44b6d..c8036d51f5c68fe7e96c57ecf9785fae98dcdeea 100644
--- a/alfa-server/pom.xml
+++ b/alfa-server/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>de.ozgcloud.alfa</groupId>
 		<artifactId>alfa</artifactId>
-		<version>2.3.0-SNAPSHOT</version>
+		<version>2.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>alfa-server</artifactId>
diff --git a/alfa-service/pom.xml b/alfa-service/pom.xml
index 380cea0dadb30e4e4978d8ba87606a467fcfb083..82c3e8b0f47164aadd2ec13fb4daf47aba8d10cd 100644
--- a/alfa-service/pom.xml
+++ b/alfa-service/pom.xml
@@ -31,7 +31,7 @@
 	<parent>
 		<groupId>de.ozgcloud.alfa</groupId>
 		<artifactId>alfa</artifactId>
-		<version>2.3.0-SNAPSHOT</version>
+		<version>2.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>alfa-service</artifactId>
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/historie/AktenzeichenChangeHistoryBuilder.java b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/AktenzeichenChangeHistoryBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..9af4e5c41d71d45c297dc5bec47ccfcb40896401
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/AktenzeichenChangeHistoryBuilder.java
@@ -0,0 +1,48 @@
+package de.ozgcloud.alfa.historie;
+
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+class AktenzeichenChangeHistoryBuilder extends ChangeHistoryBuilder<AktenzeichenChangeHistoryBuilder> {
+
+	static final String BODY_PROPERTY_AKTENZEICHEN = "aktenzeichen";
+
+	public static AktenzeichenChangeHistoryBuilder builder() {
+		return new AktenzeichenChangeHistoryBuilder();
+	}
+
+	@Override
+	boolean isRelevant(Command command) {
+		return command.getOrder().equals(CommandOrder.SET_AKTENZEICHEN);
+	}
+
+	@Override
+	CommandWithChangeValues toCommandWithChangeValues(CommandWithPrevious commandWithPrevious) {
+		return new CommandWithChangeValues(
+				commandWithPrevious.getCommand(),
+				getAktenzeichenBeforeChange(commandWithPrevious),
+				getAktenzeichenAfterChange(commandWithPrevious)
+		);
+	}
+
+	String getAktenzeichenBeforeChange(CommandWithPrevious commandWithPrevious) {
+		return Optional.ofNullable(commandWithPrevious.getPrevious())
+				.map(this::getAktenzeichen)
+				.orElse(StringUtils.EMPTY);
+	}
+
+	String getAktenzeichenAfterChange(CommandWithPrevious commandWithPrevious) {
+		return getAktenzeichen(commandWithPrevious.getCommand());
+	}
+
+	String getAktenzeichen(Command command) {
+		return getValueFromCommandBody(BODY_PROPERTY_AKTENZEICHEN, command);
+	}
+}
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/historie/AssignedUserChangeHistoryBuilder.java b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/AssignedUserChangeHistoryBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..b48b74cc1c04e70cf1d5a3e0cbd117bcb114c3d0
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/AssignedUserChangeHistoryBuilder.java
@@ -0,0 +1,56 @@
+package de.ozgcloud.alfa.historie;
+
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import de.ozgcloud.alfa.common.user.UserId;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+class AssignedUserChangeHistoryBuilder extends ChangeHistoryBuilder<AssignedUserChangeHistoryBuilder> {
+
+	static final String BODY_PROPERTY_ASSIGNED_USER = "assignedTo";
+
+	private UserProfileCache userProfileCache;
+
+	public static AssignedUserChangeHistoryBuilder builder() {
+		return new AssignedUserChangeHistoryBuilder();
+	}
+
+	public AssignedUserChangeHistoryBuilder withUserProfileCache(UserProfileCache userProfileCache) {
+		this.userProfileCache = userProfileCache;
+		return this;
+	}
+
+	@Override
+	boolean isRelevant(Command command) {
+		return command.getOrder().equals(CommandOrder.ASSIGN_USER);
+	}
+
+	@Override
+	CommandWithChangeValues toCommandWithChangeValues(CommandWithPrevious commandWithPrevious) {
+		return new CommandWithChangeValues(
+				commandWithPrevious.getCommand(),
+				getAssignedUserBeforeChange(commandWithPrevious),
+				getAssignedUserAfterChange(commandWithPrevious));
+	}
+
+	String getAssignedUserBeforeChange(CommandWithPrevious commandWithPrevious) {
+		return Optional.ofNullable(commandWithPrevious.getPrevious())
+				.map(this::getAssignedUserFullNameFromCommand)
+				.orElse(StringUtils.EMPTY);
+	}
+
+	String getAssignedUserAfterChange(CommandWithPrevious commandWithPrevious) {
+		return getAssignedUserFullNameFromCommand(commandWithPrevious.getCommand());
+	}
+
+	String getAssignedUserFullNameFromCommand(Command command) {
+		var assignedUserId = UserId.from(getValueFromCommandBody(BODY_PROPERTY_ASSIGNED_USER, command));
+		return userProfileCache.getUserProfile(assignedUserId).getFullName();
+	}
+}
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/historie/ChangeHistoryBuilder.java b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/ChangeHistoryBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..886ceafa109be774f0f07c071438264041679132
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/ChangeHistoryBuilder.java
@@ -0,0 +1,89 @@
+package de.ozgcloud.alfa.historie;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.historie.CommandWithPrevious.CommandWithPreviousBuilder;
+
+abstract class ChangeHistoryBuilder<T extends ChangeHistoryBuilder<T>> {
+
+	List<Command> commands;
+	String organisationseinheitenID;
+
+	@SuppressWarnings("unchecked")
+	public T withCommands(List<Command> commands) {
+		this.commands = commands;
+		return (T) this;
+	}
+
+	@SuppressWarnings("unchecked")
+	public T withOrganisationseinheitenID(String organisationseinheitenID) {
+		this.organisationseinheitenID = organisationseinheitenID;
+		return (T) this;
+	}
+
+	public Stream<VorgangChange> build() {
+		return Stream.of(retrieveRelevantCommands())
+				.map(this::inChronologicalOrder)
+				.map(this::addPreviousCommand)
+				.map(this::toCommandsWithChangeValues)
+				.flatMap(this::toVorgangChanges);
+	}
+
+	Stream<Command> retrieveRelevantCommands() {
+		return commands.stream().filter(this::isRelevant);
+	}
+
+	abstract boolean isRelevant(Command command);
+
+	Stream<Command> inChronologicalOrder(Stream<Command> commands) {
+		return commands.sorted(Comparator.comparing(Command::getFinishedAt));
+	}
+
+	Stream<CommandWithPrevious> addPreviousCommand(Stream<Command> commands) {
+		var commandsList = commands.toList();
+		var result = new ArrayList<CommandWithPrevious>(commandsList.size());
+		for (int i = 0; i < commandsList.size(); i++) {
+			CommandWithPreviousBuilder builder = new CommandWithPreviousBuilder().command(commandsList.get(i));
+			if (i > 0) {
+				builder.previous(commandsList.get(i - 1));
+			}
+			result.add(builder.build());
+		}
+		return result.stream();
+	}
+
+	Stream<CommandWithChangeValues> toCommandsWithChangeValues(Stream<CommandWithPrevious> commands) {
+		return commands.map(this::toCommandWithChangeValues);
+	}
+
+	abstract CommandWithChangeValues toCommandWithChangeValues(CommandWithPrevious commandWithPrevious);
+
+	Stream<VorgangChange> toVorgangChanges(Stream<CommandWithChangeValues> commandsWithChangeValues) {
+		return commandsWithChangeValues.map(this::toVorgangChange);
+	}
+
+	VorgangChange toVorgangChange(CommandWithChangeValues commandChangeValues) {
+		return VorgangChange.builder()
+				.valueBeforeChange(commandChangeValues.valueBeforeChange())
+				.valueAfterChange(commandChangeValues.valueAfterChange())
+				.authorFullName(commandChangeValues.command().getCreatedByName())
+				.finishedAt(commandChangeValues.command().getFinishedAt())
+				.order(commandChangeValues.command().getOrder())
+				.organisationseinheitenID(organisationseinheitenID)
+				.build();
+	}
+
+	String getValueFromCommandBody(String propertyName, Command command) {
+		return Optional.ofNullable(command.getBody()).map(body -> body.get(propertyName)).map(Object::toString).orElse(StringUtils.EMPTY);
+	}
+
+	record CommandWithChangeValues(Command command, String valueBeforeChange, String valueAfterChange) {
+	}
+}
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/historie/CommandWithPrevious.java b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/CommandWithPrevious.java
new file mode 100644
index 0000000000000000000000000000000000000000..07b785475ecdae9c63dc39353cb2bccc4479200d
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/CommandWithPrevious.java
@@ -0,0 +1,18 @@
+package de.ozgcloud.alfa.historie;
+
+import de.ozgcloud.alfa.common.command.Command;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Builder(toBuilder = true)
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+class CommandWithPrevious {
+
+	private Command command;
+	private Command previous;
+}
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/historie/HistorieService.java b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/HistorieService.java
index 6fd5dbfd9ccb84d5982961afa222bfc09ed23164..a6c233ae6b62d7643defae7f2dd0e0e5731a3b4b 100644
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/historie/HistorieService.java
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/HistorieService.java
@@ -41,7 +41,6 @@ class HistorieService {
 
 	@Autowired
 	private CommandService commandService;
-
 	@Autowired
 	private HistorieCommandHandler historieCommandHandler;
 
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/historie/StatusChangeHistoryBuilder.java b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/StatusChangeHistoryBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..35d9f2883c2e69931e93e5014491d27c11fc5208
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/StatusChangeHistoryBuilder.java
@@ -0,0 +1,78 @@
+package de.ozgcloud.alfa.historie;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import de.ozgcloud.alfa.vorgang.Vorgang.VorgangStatus;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StatusChangeHistoryBuilder extends ChangeHistoryBuilder<StatusChangeHistoryBuilder> {
+
+	static final String GELOESCHT_NAME = "Gelöscht";
+	static final Map<VorgangStatus, String> VORGANG_STATUS_TO_NAME = Map.of(
+			VorgangStatus.NEU, "Neu",
+			VorgangStatus.ANGENOMMEN, "Angenommen",
+			VorgangStatus.VERWORFEN, "Verworfen",
+			VorgangStatus.IN_BEARBEITUNG, "In Bearbeitung",
+			VorgangStatus.BESCHIEDEN, "Beschieden",
+			VorgangStatus.ABGESCHLOSSEN, "Abgeschlossen",
+			VorgangStatus.WEITERGELEITET, "Weitergeleitet",
+			VorgangStatus.ZU_LOESCHEN, "Zu Löschen"
+	);
+	static final Set<CommandOrder> STATUS_CHANGE_COMMAND_ORDER = Set.of(
+			CommandOrder.VORGANG_ANNEHMEN,
+			CommandOrder.VORGANG_VERWERFEN,
+			CommandOrder.VORGANG_ZURUECKHOLEN,
+			CommandOrder.VORGANG_BEARBEITEN,
+			CommandOrder.VORGANG_BESCHEIDEN,
+			CommandOrder.VORGANG_ABSCHLIESSEN,
+			CommandOrder.VORGANG_ZUM_LOESCHEN_MARKIEREN,
+			CommandOrder.VORGANG_WIEDEREROEFFNEN,
+			CommandOrder.VORGANG_LOESCHEN,
+			CommandOrder.VORGANG_ZURUECKSTELLEN);
+
+	public static StatusChangeHistoryBuilder builder() {
+		return new StatusChangeHistoryBuilder();
+	}
+
+	@Override
+	boolean isRelevant(Command command) {
+		return STATUS_CHANGE_COMMAND_ORDER.contains(command.getOrder());
+	}
+
+	@Override
+	CommandWithChangeValues toCommandWithChangeValues(CommandWithPrevious commandWithPrevious) {
+		return new CommandWithChangeValues(commandWithPrevious.getCommand(),
+				getStatusBeforeChange(commandWithPrevious),
+				getStatusAfterChange(commandWithPrevious));
+	}
+
+	String getStatusBeforeChange(CommandWithPrevious commandWithPrevious) {
+		return Optional.ofNullable(commandWithPrevious.getPrevious())
+				.map(this::getStatus)
+				.orElse(VORGANG_STATUS_TO_NAME.get(VorgangStatus.NEU));
+	}
+
+	String getStatusAfterChange(CommandWithPrevious commandWithPrevious) {
+		return getStatus(commandWithPrevious.getCommand());
+	}
+
+	String getStatus(Command command) {
+		return switch (command.getOrder()) {
+			case VORGANG_ANNEHMEN, VORGANG_ZURUECKSTELLEN -> VORGANG_STATUS_TO_NAME.get(VorgangStatus.ANGENOMMEN);
+			case VORGANG_VERWERFEN -> VORGANG_STATUS_TO_NAME.get(VorgangStatus.VERWORFEN);
+			case VORGANG_ZURUECKHOLEN -> VORGANG_STATUS_TO_NAME.get(VorgangStatus.NEU);
+			case VORGANG_BEARBEITEN, VORGANG_WIEDEREROEFFNEN -> VORGANG_STATUS_TO_NAME.get(VorgangStatus.IN_BEARBEITUNG);
+			case VORGANG_BESCHEIDEN -> VORGANG_STATUS_TO_NAME.get(VorgangStatus.BESCHIEDEN);
+			case VORGANG_ABSCHLIESSEN -> VORGANG_STATUS_TO_NAME.get(VorgangStatus.ABGESCHLOSSEN);
+			case VORGANG_ZUM_LOESCHEN_MARKIEREN -> VORGANG_STATUS_TO_NAME.get(VorgangStatus.ZU_LOESCHEN);
+			case VORGANG_LOESCHEN -> GELOESCHT_NAME;
+			default -> throw new IllegalArgumentException("Unexpected or unknown command order " + command.getOrder());
+		};
+	}
+}
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/historie/UserProfileCache.java b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/UserProfileCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..70cb3ebf4fed00ea537ceeeb4ae9f97985e48067
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/UserProfileCache.java
@@ -0,0 +1,38 @@
+package de.ozgcloud.alfa.historie;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+import de.ozgcloud.alfa.common.user.UserId;
+import de.ozgcloud.alfa.common.user.UserProfile;
+import de.ozgcloud.common.errorhandling.TechnicalException;
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+class UserProfileCache {
+
+	private final Function<UserId, UserProfile> findUserProfileForUserId;
+
+	private final Map<UserId, UserProfile> cache = new HashMap<>();
+
+	public static UserProfileCache create(Function<UserId, UserProfile> findUserProfileForUserId) {
+		return new UserProfileCache(findUserProfileForUserId);
+	}
+
+	public UserProfile getUserProfile(UserId userId) {
+		if (!cache.containsKey(userId)) {
+			findAndAddToCache(userId);
+		}
+		return cache.get(userId);
+	}
+
+	private void findAndAddToCache(UserId userId) {
+		var userProfile = findUserProfileForUserId.apply(userId);
+		if (userProfile == null) {
+			throw new TechnicalException("UserProfile not found for user with ID " + userId);
+		}
+		cache.put(userId, userProfile);
+	}
+}
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/historie/VorgangChange.java b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/VorgangChange.java
new file mode 100644
index 0000000000000000000000000000000000000000..cff5cac6fe5ccb7b08ebf9fdcf201a7cb5061b48
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/VorgangChange.java
@@ -0,0 +1,24 @@
+package de.ozgcloud.alfa.historie;
+
+import java.time.ZonedDateTime;
+
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Builder(toBuilder = true)
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class VorgangChange {
+
+	private String valueBeforeChange;
+	private String valueAfterChange;
+	private String authorFullName;
+	private ZonedDateTime finishedAt;
+	private CommandOrder order;
+	private String organisationseinheitenID;
+}
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/historie/VorgangChangeHistory.java b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/VorgangChangeHistory.java
new file mode 100644
index 0000000000000000000000000000000000000000..2fef260aa22d04cad7226090c43cdee68885ca13
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/VorgangChangeHistory.java
@@ -0,0 +1,20 @@
+package de.ozgcloud.alfa.historie;
+
+import java.util.List;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Builder(toBuilder = true)
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public class VorgangChangeHistory {
+
+	private List<VorgangChange> statusChangeHistory;
+	private List<VorgangChange> aktenzeichenChangeHistory;
+	private List<VorgangChange> assignedUserChangeHistory;
+}
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryService.java b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryService.java
new file mode 100644
index 0000000000000000000000000000000000000000..bcc71eaf47a9f1d83bcd198df36b3dbe908587ed
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryService.java
@@ -0,0 +1,60 @@
+package de.ozgcloud.alfa.historie;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.springframework.stereotype.Service;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandService;
+import de.ozgcloud.alfa.common.user.UserService;
+import de.ozgcloud.alfa.vorgang.Eingang;
+import de.ozgcloud.alfa.vorgang.VorgangWithEingang;
+import de.ozgcloud.alfa.vorgang.ZustaendigeStelle;
+import lombok.RequiredArgsConstructor;
+
+@Service
+@RequiredArgsConstructor
+public class VorgangChangeHistoryService {
+
+	private final HistorieService historieService;
+	private final UserService userService;
+	private final CommandService commandService;
+
+	public VorgangChangeHistory createVorgangChangeHistory(VorgangWithEingang vorgang) {
+		var commands = commandService.findFinishedCommands(vorgang.getId()).toList();
+		return VorgangChangeHistory.builder()
+				.statusChangeHistory(createStatusChangeHistory(vorgang, commands).toList())
+				.aktenzeichenChangeHistory(createAktenzeichenChangeHistory(vorgang, commands).toList())
+				.assignedUserChangeHistory(createAssignedUserChangeHistory(vorgang, commands).toList())
+				.build();
+	}
+
+	Stream<VorgangChange> createStatusChangeHistory(VorgangWithEingang vorgang, List<Command> commands) {
+		return StatusChangeHistoryBuilder.builder()
+				.withCommands(commands)
+				.withOrganisationseinheitenID(getOrganisationseinheitenID(vorgang))
+				.build();
+	}
+
+	Stream<VorgangChange> createAktenzeichenChangeHistory(VorgangWithEingang vorgang, List<Command> commands) {
+		return AktenzeichenChangeHistoryBuilder.builder()
+				.withCommands(commands)
+				.withOrganisationseinheitenID(getOrganisationseinheitenID(vorgang))
+				.build();
+	}
+
+	Stream<VorgangChange> createAssignedUserChangeHistory(VorgangWithEingang vorgang, List<Command> commands) {
+		return AssignedUserChangeHistoryBuilder.builder()
+				.withCommands(commands)
+				.withOrganisationseinheitenID(getOrganisationseinheitenID(vorgang))
+				.withUserProfileCache(UserProfileCache.create(userService::getById))
+				.build();
+	}
+
+	String getOrganisationseinheitenID(VorgangWithEingang vorgang) {
+		return Optional.ofNullable(vorgang.getEingang()).map(Eingang::getZustaendigeStelle).map(ZustaendigeStelle::getOrganisationseinheitenId)
+				.orElse(null);
+	}
+}
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/CountVorgangResponse.java b/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/CountVorgangResponse.java
deleted file mode 100644
index 219546eb83065256a476c14426ebbdb93e01a87b..0000000000000000000000000000000000000000
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/CountVorgangResponse.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// TODO sebo: diese Datei soll später komplett gelöscht werden
-package de.ozgcloud.alfa.statistic;
-
-import java.util.Map;
-
-import lombok.Builder;
-import lombok.Getter;
-
-@Getter
-@Builder
-class CountVorgangResponse {
-
-	private ByStatus byStatus;
-	private Map<String, Integer> pathCountResult;
-}
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticController.java b/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticController.java
index d3d08b9d3ee2215ead724fddd2e9fbda20ea9c91..9e8f79b4c0c54b059dad310b1835a164b687666a 100644
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticController.java
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticController.java
@@ -10,20 +10,6 @@ public class StatisticController {
 	private StatisticService statisticService;
 
 	public Statistic getVorgaengeStatistic() {
-		var countStatistic = statisticService.countVorgang();
-		var statistic = statisticService.getVorgaengeStatistic();
-		return Statistic.builder()
-				.existsWiedervorlageOverdue(statistic.isExistsWiedervorlageOverdue())
-				.wiedervorlagen(countStatistic.getWiedervorlagen())
-				.byStatus(ByStatus.builder()
-						.verworfen(countStatistic.getByStatus().getVerworfen())
-						.inBearbeitung(countStatistic.getByStatus().getInBearbeitung())
-						.angenommen(countStatistic.getByStatus().getAngenommen())
-						.beschieden(countStatistic.getByStatus().getBeschieden())
-						.neu(countStatistic.getByStatus().getNeu())
-						.abgeschlossen(countStatistic.getByStatus().getAbgeschlossen())
-						.zuLoeschen(statistic.getByStatus().getZuLoeschen())
-						.build())
-				.build();
+		return statisticService.getVorgaengeStatistic();
 	}
 }
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticMapper.java b/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticMapper.java
index 3be93cd822823c832f3e0aa3f6c26f4cb716b838..cb23df8599c51b4f7cbdf3b99d8ab0d19120466f 100644
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticMapper.java
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticMapper.java
@@ -1,17 +1,11 @@
 package de.ozgcloud.alfa.statistic;
 
-import java.util.Map;
-import java.util.stream.Collectors;
-
 import org.mapstruct.CollectionMappingStrategy;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.NullValueCheckStrategy;
 import org.mapstruct.ReportingPolicy;
 
-import de.ozgcloud.vorgang.statistic.GrpcByStatusResult;
-import de.ozgcloud.vorgang.statistic.GrpcPathCountResult;
-import de.ozgcloud.vorgang.statistic.GrpcVorgangCountResponse;
 import de.ozgcloud.vorgang.statistic.GrpcVorgangStatisticResult;
 
 @Mapper(unmappedTargetPolicy = ReportingPolicy.WARN,
@@ -23,16 +17,4 @@ interface StatisticMapper {
 	@Mapping(target = "boolValue", source = "resultBoolValue")
 	@Mapping(target = "intValue", source = "resultIntValue")
 	StatisticResult toResult(GrpcVorgangStatisticResult grpcVorgangStatisticResult);
-
-	// TODO sebo: der Code muss später gelöscht werden
-	@Mapping(target = "pathCountResult", expression = "java(toMap(response))")
-	CountVorgangResponse fromCountVorgangResponse(GrpcVorgangCountResponse response);
-
-	ByStatus fromByStatus(GrpcByStatusResult byStatus);
-
-	default Map<String, Integer> toMap(GrpcVorgangCountResponse response) {
-		return response.getPathCountResultList().stream()
-				.collect(Collectors.toMap(GrpcPathCountResult::getName, pathCountResult -> Integer.valueOf(pathCountResult.getValue())));
-	}
-	// END: code löschen
 }
\ No newline at end of file
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticRemoteService.java b/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticRemoteService.java
index b97fcadbc07f1dc8d1d4c8ce2eaa3803fac49a90..cd508d6198827251fd4f661a7e0a4815570fa898 100644
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticRemoteService.java
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticRemoteService.java
@@ -11,7 +11,6 @@ import org.springframework.stereotype.Service;
 import de.ozgcloud.alfa.AlfaProperties;
 import de.ozgcloud.alfa.common.GrpcUtil;
 import de.ozgcloud.alfa.vorgang.Vorgang.VorgangStatus;
-import de.ozgcloud.vorgang.statistic.GrpcVorgangCountRequest;
 import de.ozgcloud.vorgang.statistic.GrpcVorgangStatisticQuery;
 import de.ozgcloud.vorgang.statistic.GrpcVorgangStatisticQuery.GroupMethod;
 import de.ozgcloud.vorgang.statistic.GrpcVorgangStatisticQuery.Operator;
@@ -41,8 +40,9 @@ class StatisticRemoteService {
 
 	public Map<String, StatisticResult> getVorgaengeStatistics(List<VorgangStatus> countByVorgangStatus) {
 		var grpcRequest = GrpcVorgangStatisticRequest.newBuilder()
-				.addQuery(buildExistsWiedervorlageOverdueQuery())
 				.addAllQuery(buildCountByStatusQueries(countByVorgangStatus))
+				.addQuery(buildCountWiedervorlageNextFristQuery())
+				.addQuery(buildExistsWiedervorlageOverdueQuery())
 				.build();
 
 		var grpcResponse = statisticServiceStub.getVorgangStatistic(grpcRequest);
@@ -64,6 +64,15 @@ class StatisticRemoteService {
 				.build();
 	}
 
+	GrpcVorgangStatisticQuery buildCountWiedervorlageNextFristQuery() {
+		return GrpcVorgangStatisticQuery.newBuilder()
+				.setResultName(COUNT_WIEDERVORLAGE_NEXT_FRIST_RESULT_NAME)
+				.setPath(String.format(WIEDERVORLAGE_NEXT_FRIST_PATH_TEMPLATE, alfaProperties.getApplicationName()))
+				.setGroupMethod(GroupMethod.COUNT)
+				.setOperator(Operator.UNEQUAL)
+				.build();
+	}
+
 	GrpcVorgangStatisticQuery buildExistsWiedervorlageOverdueQuery() {
 		return GrpcVorgangStatisticQuery.newBuilder()
 				.setResultName(EXISTS_WIEDERVORLAGE_OVERDUE_RESULT_NAME)
@@ -84,16 +93,4 @@ class StatisticRemoteService {
 				.map(mapper::toResult)
 				.collect(Collectors.toMap(StatisticResult::getName, Function.identity()));
 	}
-
-	// TODO sebo: der Code muss später gelöscht werden
-	public CountVorgangResponse countVorgang(List<String> countByPath) {
-		var response = statisticServiceStub.countVorgang(buildVorgangCountRequest(countByPath));
-
-		return mapper.fromCountVorgangResponse(response);
-	}
-
-	GrpcVorgangCountRequest buildVorgangCountRequest(List<String> countByPath) {
-		return GrpcVorgangCountRequest.newBuilder().addAllCountByPath(countByPath).build();
-	}
-	// END: code löschen
 }
\ No newline at end of file
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticService.java b/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticService.java
index b868bf8f05d5ff708e6284556e3a80ec92ea5b4f..68b9ab06c4d73f5b6810035b9b1312a6d3edbd2c 100644
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticService.java
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/statistic/StatisticService.java
@@ -8,7 +8,6 @@ import java.util.Optional;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import de.ozgcloud.alfa.AlfaProperties;
 import de.ozgcloud.alfa.common.user.CurrentUserService;
 import de.ozgcloud.alfa.common.user.UserRole;
 import de.ozgcloud.alfa.vorgang.Vorgang.VorgangStatus;
@@ -16,15 +15,10 @@ import de.ozgcloud.alfa.vorgang.Vorgang.VorgangStatus;
 @Service
 class StatisticService {
 
-	// TODO sebo: dieser Code wird später gelöscht
-	static final String COUNT_WIEDERVORLAGE_NEXT_FRIST_KEY = "countWiedervorlage";
-	static final String COUNT_WIEDERVORLAGE_NEXT_FRIST_TEMPLATE = COUNT_WIEDERVORLAGE_NEXT_FRIST_KEY + ":ClientAttribute.%s.nextWiedervorlageFrist";
-	// END: code löschen
-
-	static final List<VorgangStatus> VORGAENGE_STATUS_COUNT_VERWALTUNG_USER = List.of(VorgangStatus.NEU, VorgangStatus.ANGENOMMEN,
+	static final List<VorgangStatus> COUNT_BY_VORGANG_STATUS_VERWALTUNG_USER = List.of(VorgangStatus.NEU, VorgangStatus.ANGENOMMEN,
 			VorgangStatus.IN_BEARBEITUNG, VorgangStatus.BESCHIEDEN, VorgangStatus.ABGESCHLOSSEN, VorgangStatus.VERWORFEN, VorgangStatus.ZU_LOESCHEN);
-	static final List<VorgangStatus> VORGAENGE_STATUS_COUNT_POSTSTELLE_USER = Collections.emptyList();
-	static final List<VorgangStatus> VORGAENGE_STATUS_COUNT_EINHEITLICHER_ANSPRECHPARTNER = Collections.emptyList();
+	static final List<VorgangStatus> COUNT_BY_VORGANG_STATUS_POSTSTELLE_USER = Collections.emptyList();
+	static final List<VorgangStatus> COUNT_BY_VORGANG_EINHEITLICHER_ANSPRECHPARTNER = Collections.emptyList();
 
 	@Autowired
 	private StatisticRemoteService remoteService;
@@ -32,23 +26,20 @@ class StatisticService {
 	@Autowired
 	private CurrentUserService currentUserService;
 
-	@Autowired
-	private AlfaProperties alfaProperties;
-
 	public Statistic getVorgaengeStatistic() {
-		var response = remoteService.getVorgaengeStatistics(List.of(VorgangStatus.ZU_LOESCHEN));
+		var response = remoteService.getVorgaengeStatistics(getCountByVorgangStatusList());
 		return buildGetVorgaengeStatisticResult(response);
 	}
 
 	List<VorgangStatus> getCountByVorgangStatusList() {
 		if (currentUserService.hasRole(UserRole.VERWALTUNG_USER)) {
-			return VORGAENGE_STATUS_COUNT_VERWALTUNG_USER;
+			return COUNT_BY_VORGANG_STATUS_VERWALTUNG_USER;
 		}
 		if (currentUserService.hasRole(UserRole.VERWALTUNG_POSTSTELLE)) {
-			return VORGAENGE_STATUS_COUNT_POSTSTELLE_USER;
+			return COUNT_BY_VORGANG_STATUS_POSTSTELLE_USER;
 		}
 		if (currentUserService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER)) {
-			return VORGAENGE_STATUS_COUNT_EINHEITLICHER_ANSPRECHPARTNER;
+			return COUNT_BY_VORGANG_EINHEITLICHER_ANSPRECHPARTNER;
 		}
 		return Collections.emptyList();
 	}
@@ -76,26 +67,4 @@ class StatisticService {
 	private boolean getBooleanResult(Map<String, StatisticResult> response, String resultName) {
 		return Optional.ofNullable(response.get(resultName)).map(StatisticResult::getBoolValue).orElse(false);
 	}
-
-	// TODO sebo: dieser Code wird später gelöscht
-	public Statistic countVorgang() {
-		var response = remoteService.countVorgang(createCountByPathList());
-
-		return buildStatistic(response);
-	}
-
-	List<String> createCountByPathList() {
-		return List.of(String.format(COUNT_WIEDERVORLAGE_NEXT_FRIST_TEMPLATE, alfaProperties.getApplicationName()));
-	}
-
-	Statistic buildStatistic(CountVorgangResponse response) {
-		return Statistic.builder().byStatus(response.getByStatus()).wiedervorlagen(extractWiedervorlageCount(response)).build();
-	}
-
-	Integer extractWiedervorlageCount(CountVorgangResponse response) {
-		return Optional.ofNullable(response.getPathCountResult())
-				.map(pathResultEntry -> pathResultEntry.get(COUNT_WIEDERVORLAGE_NEXT_FRIST_KEY))
-				.orElse(0);
-	}
-	// END: code löschen
 }
\ No newline at end of file
diff --git a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/common/TestUtils.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/TestUtils.java
similarity index 99%
rename from alfa-xdomea/src/test/java/de/ozgcloud/alfa/common/TestUtils.java
rename to alfa-service/src/test/java/de/ozgcloud/alfa/common/TestUtils.java
index 098eb70e6f0fa46f2fff02c0cd9ab19f95ba0d17..14f1cb54f665838fdc1938fc5da84962ca16d1c8 100644
--- a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/common/TestUtils.java
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/TestUtils.java
@@ -19,4 +19,5 @@ public class TestUtils {
 		when(mockStream.toList()).thenReturn(list);
 		mock.accept(mockStream);
 	}
+
 }
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandTestFactory.java
index f0b608d73e33551e1f6ac87db6200a0d275ae6e7..7a56836a827ce418a16646a969b5bc3f6370856f 100644
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandTestFactory.java
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandTestFactory.java
@@ -52,7 +52,8 @@ public class CommandTestFactory {
 				.relationId(RELATION_ID)
 				.status(STATUS)
 				.order(ORDER)
-				.createdBy(UserProfileTestFactory.ID);
+				.createdBy(UserProfileTestFactory.ID)
+				.createdByName(UserProfileTestFactory.FULLNAME);
 	}
 
 	public static CreateCommand createCreateCommand() {
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/AktenzeichenChangeHistoryBuilderTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/AktenzeichenChangeHistoryBuilderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..578007c995c76a52ecd9ecca1f0ebabc4644f5b1
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/AktenzeichenChangeHistoryBuilderTest.java
@@ -0,0 +1,177 @@
+package de.ozgcloud.alfa.historie;
+
+import static de.ozgcloud.alfa.historie.AktenzeichenChangeHistoryBuilder.*;
+import static de.ozgcloud.alfa.historie.ChangeHistoryBuilderTest.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Spy;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import de.ozgcloud.alfa.common.command.CommandTestFactory;
+
+public class AktenzeichenChangeHistoryBuilderTest {
+
+	private static final String ORGANISATIONSEINHEITEN_ID = UUID.randomUUID().toString();
+
+	private final Command previousCommand = commandFinishedAt(LocalDateTime.of(2023, 5, 1, 12, 0));
+	private final Command command = commandFinishedAt(LocalDateTime.of(2023, 6, 1, 12, 0));
+	private final List<Command> commands = List.of(previousCommand, command);
+
+	@Spy
+	private AktenzeichenChangeHistoryBuilder builder = AktenzeichenChangeHistoryBuilder.builder()
+			.withCommands(commands)
+			.withOrganisationseinheitenID(ORGANISATIONSEINHEITEN_ID);
+
+	@Nested
+	class TestIsRelevant {
+
+		@Test
+		void shouldReturnTrue() {
+			var command = CommandTestFactory.createBuilder()
+					.order(CommandOrder.SET_AKTENZEICHEN)
+					.build();
+
+			var result = builder.isRelevant(command);
+
+			assertThat(result).isTrue();
+		}
+
+		@Test
+		void shouldReturnFalse() {
+			var command = CommandTestFactory.createBuilder()
+					.order(CommandOrder.VORGANG_WIEDEREROEFFNEN)
+					.build();
+
+			var result = builder.isRelevant(command);
+
+			assertThat(result).isFalse();
+		}
+	}
+
+	@Nested
+	class TestToCommandWithChangeValues {
+
+		private static final String EXPECTED_AKTENZEICHEN_BEFORE_CHANGE = LoremIpsum.getInstance().getWords(2);
+		private static final String EXPECTED_AKTENZEICHEN_AFTER_CHANGE = LoremIpsum.getInstance().getWords(2);
+
+		private final CommandWithPrevious commandWithPrevious = CommandWithPreviousTestFactory.createBuilder().command(command).previous(previousCommand).build();
+
+		@BeforeEach
+		void init() {
+			when(builder.getAktenzeichenBeforeChange(commandWithPrevious)).thenReturn(EXPECTED_AKTENZEICHEN_BEFORE_CHANGE);
+			when(builder.getAktenzeichenAfterChange(commandWithPrevious)).thenReturn(EXPECTED_AKTENZEICHEN_AFTER_CHANGE);
+		}
+
+		@Test
+		void shouldSetCommand() {
+			var commandWithChangeValues = callBuilder();
+
+			assertThat(commandWithChangeValues.command()).isEqualTo(command);
+		}
+
+		@Test
+		void shouldSetValueBeforeChange() {
+			var commandWithChangeValues = callBuilder();
+
+			assertThat(commandWithChangeValues.valueBeforeChange()).isEqualTo(EXPECTED_AKTENZEICHEN_BEFORE_CHANGE);
+		}
+
+		@Test
+		void shouldSetValueAfterChange() {
+			var commandWithChangeValues = callBuilder();
+
+			assertThat(commandWithChangeValues.valueAfterChange()).isEqualTo(EXPECTED_AKTENZEICHEN_AFTER_CHANGE);
+		}
+
+		private ChangeHistoryBuilder.CommandWithChangeValues callBuilder() {
+			return builder.toCommandWithChangeValues(commandWithPrevious);
+		}
+	}
+
+	@Nested
+	class TestGetAktenzeichenBeforeChange {
+
+		private final CommandWithPrevious commandWithoutPrevious = CommandWithPreviousTestFactory.createBuilder().command(previousCommand).previous(null).build();
+		private final CommandWithPrevious commandWithPrevious = CommandWithPreviousTestFactory.createBuilder().command(command).previous(previousCommand).build();
+
+		@Test
+		void shouldReturnEmptyIfPreviousIsNull() {
+			var aktenzeichen = builder.getAktenzeichenBeforeChange(commandWithoutPrevious);
+
+			assertThat(aktenzeichen).isEmpty();
+		}
+
+		@Test
+		void shouldGetAktenzeichenFromPreviousCommand() {
+			var expectedValue = LoremIpsum.getInstance().getWords(2);
+			when(builder.getAktenzeichen(previousCommand)).thenReturn(expectedValue);
+
+			var aktenzeichen = builder.getAktenzeichenBeforeChange(commandWithPrevious);
+
+			assertThat(aktenzeichen).isEqualTo(expectedValue);
+		}
+	}
+
+	@Nested
+	class TestGetAktenzeichenAfterChange {
+
+		private static final String EXPECTED_AKTENZEICHEN = LoremIpsum.getInstance().getWords(2);
+
+		private final CommandWithPrevious commandWithPrevious = CommandWithPreviousTestFactory.createBuilder().command(command).previous(previousCommand).build();
+
+		@BeforeEach
+		void init() {
+			doReturn(EXPECTED_AKTENZEICHEN).when(builder).getAktenzeichen(command);
+		}
+
+		@Test
+		void shouldGetAktenzeichenFromCommand() {
+			builder.getAktenzeichenAfterChange(commandWithPrevious);
+
+			verify(builder).getAktenzeichen(command);
+		}
+
+		@Test
+		void shouldReturnAktenzeichen() {
+			var result = builder.getAktenzeichenAfterChange(commandWithPrevious);
+
+			assertThat(result).isEqualTo(EXPECTED_AKTENZEICHEN);
+		}
+	}
+
+	@Nested
+	class TestGetAktenzeichen {
+
+		private static final String AKTENZEICHEN = LoremIpsum.getInstance().getWords(1);
+
+		@BeforeEach
+		void init() {
+			when(builder.getValueFromCommandBody(BODY_PROPERTY_AKTENZEICHEN, previousCommand)).thenReturn(AKTENZEICHEN);
+		}
+
+		@Test
+		void shouldGetValueFromCommandBody() {
+			builder.getAktenzeichen(previousCommand);
+
+			verify(builder).getValueFromCommandBody(BODY_PROPERTY_AKTENZEICHEN, previousCommand);
+		}
+
+		@Test
+		void shouldReturnValueFromCommandBody() {
+			var value = builder.getAktenzeichen(previousCommand);
+
+			assertThat(value).isEqualTo(AKTENZEICHEN);
+		}
+	}
+}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/AssignedUserChangeHistoryBuilderTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/AssignedUserChangeHistoryBuilderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe8e03ecd2ea716f1427f5b56a020e19f054b9d9
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/AssignedUserChangeHistoryBuilderTest.java
@@ -0,0 +1,226 @@
+package de.ozgcloud.alfa.historie;
+
+import static de.ozgcloud.alfa.historie.AssignedUserChangeHistoryBuilder.*;
+import static de.ozgcloud.alfa.historie.ChangeHistoryBuilderTest.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import de.ozgcloud.alfa.common.command.CommandTestFactory;
+import de.ozgcloud.alfa.common.user.UserId;
+import de.ozgcloud.alfa.common.user.UserProfile;
+import de.ozgcloud.alfa.common.user.UserProfileTestFactory;
+
+public class AssignedUserChangeHistoryBuilderTest {
+
+	private static final String ORGANISATIONSEINHEITEN_ID = UUID.randomUUID().toString();
+
+	private final Command previousCommand = commandFinishedAt(LocalDateTime.of(2023, 5, 1, 12, 0));
+	private final Command command = commandFinishedAt(LocalDateTime.of(2023, 6, 1, 12, 0));
+	private final List<Command> commands = List.of(previousCommand, command);
+
+	@Mock
+	private UserProfileCache userProfileCache;
+
+	@Spy
+	@InjectMocks
+	private AssignedUserChangeHistoryBuilder builder = AssignedUserChangeHistoryBuilder.builder()
+			.withCommands(commands)
+			.withOrganisationseinheitenID(ORGANISATIONSEINHEITEN_ID);
+
+	@Nested
+	class TestIsRelevant {
+
+		@Test
+		void shouldReturnTrue() {
+			var command = CommandTestFactory.createBuilder()
+					.order(CommandOrder.ASSIGN_USER)
+					.build();
+
+			var result = builder.isRelevant(command);
+
+			assertThat(result).isTrue();
+		}
+
+		@Test
+		void shouldReturnFalse() {
+			var command = CommandTestFactory.createBuilder()
+					.order(CommandOrder.VORGANG_WIEDEREROEFFNEN)
+					.build();
+
+			var result = builder.isRelevant(command);
+
+			assertThat(result).isFalse();
+		}
+	}
+
+	@Nested
+	class TestToCommandWithChangeValues {
+
+		private static final String ASSIGNED_USER_BEFORE_CHANGE = LoremIpsum.getInstance().getWords(2);
+		private static final String ASSIGNED_USER_AFTER_CHANGE = LoremIpsum.getInstance().getWords(2);
+
+		private final CommandWithPrevious commandWithPrevious = CommandWithPreviousTestFactory.createBuilder().command(command).previous(previousCommand).build();
+
+		@BeforeEach
+		void init() {
+			doReturn(ASSIGNED_USER_BEFORE_CHANGE).when(builder).getAssignedUserBeforeChange(commandWithPrevious);
+			doReturn(ASSIGNED_USER_AFTER_CHANGE).when(builder).getAssignedUserAfterChange(commandWithPrevious);
+		}
+
+		@Test
+		void shouldSetCommand() {
+			var commandWithChangeValues = callBuilder();
+
+			assertThat(commandWithChangeValues.command()).isEqualTo(command);
+		}
+
+		@Test
+		void shouldSetValueBeforeChange() {
+			var commandWithChangeValues = callBuilder();
+
+			assertThat(commandWithChangeValues.valueBeforeChange()).isEqualTo(ASSIGNED_USER_BEFORE_CHANGE);
+		}
+
+		@Test
+		void shouldSetValueAfterChange() {
+			var commandWithChangeValues = callBuilder();
+
+			assertThat(commandWithChangeValues.valueAfterChange()).isEqualTo(ASSIGNED_USER_AFTER_CHANGE);
+		}
+
+		private ChangeHistoryBuilder.CommandWithChangeValues callBuilder() {
+			return builder.toCommandWithChangeValues(commandWithPrevious);
+		}
+	}
+
+	@Nested
+	class TestGetAssignedUserBeforeChange {
+
+		private static final String EXPECTED_ASSIGNED_USER = LoremIpsum.getInstance().getWords(2);
+
+		private final CommandWithPrevious commandWithoutPrevious = CommandWithPreviousTestFactory.createBuilder().command(previousCommand).previous(null).build();
+		private final CommandWithPrevious commandWithPrevious = CommandWithPreviousTestFactory.createBuilder().command(command).previous(previousCommand).build();
+
+		@Test
+		void shouldReturnEmptyStringIfPreviousIsNull() {
+			var assignedUser = builder.getAssignedUserBeforeChange(commandWithoutPrevious);
+
+			assertThat(assignedUser).isEmpty();
+		}
+
+		@Test
+		void shouldGetAssignedUserFromPreviousCommand() {
+			givenAssignedUserFromPreviousCommand();
+
+			callBuilder();
+
+			verify(builder).getAssignedUserFullNameFromCommand(previousCommand);
+		}
+
+		@Test
+		void shouldReturnAssignedUserRetrievedFromPreviousCommand() {
+			givenAssignedUserFromPreviousCommand();
+
+			var assignedUser = callBuilder();
+
+			assertThat(assignedUser).isEqualTo(EXPECTED_ASSIGNED_USER);
+		}
+
+		private void givenAssignedUserFromPreviousCommand() {
+			doReturn(EXPECTED_ASSIGNED_USER).when(builder).getAssignedUserFullNameFromCommand(previousCommand);
+		}
+
+		private String callBuilder() {
+			return builder.getAssignedUserBeforeChange(commandWithPrevious);
+		}
+	}
+
+	@Nested
+	class TestGetAssignedUserAfterChange {
+
+		private static final String EXPECTED_ASSIGNED_USER = LoremIpsum.getInstance().getWords(2);
+
+		private final CommandWithPrevious commandWithPrevious = CommandWithPreviousTestFactory.createBuilder().command(command).previous(previousCommand).build();
+
+		@Test
+		void shouldGetAssignedUserFromCommand() {
+			givenAssignedUserFromCurrentCommand();
+
+			callBuilder();
+
+			verify(builder).getAssignedUserFullNameFromCommand(command);
+		}
+
+		@Test
+		void shouldReturnAssignedUserRetrievedFromCommand() {
+			givenAssignedUserFromCurrentCommand();
+
+			var assignedUser = callBuilder();
+
+			assertThat(assignedUser).isEqualTo(EXPECTED_ASSIGNED_USER);
+		}
+
+		private void givenAssignedUserFromCurrentCommand() {
+			doReturn(EXPECTED_ASSIGNED_USER).when(builder).getAssignedUserFullNameFromCommand(command);
+		}
+
+		private String callBuilder() {
+			return builder.getAssignedUserAfterChange(commandWithPrevious);
+		}
+	}
+
+	@Nested
+	class TestGetAssignedUserFullNameFromCommand {
+
+		private static final UserId USER_ID = UserId.from(UUID.randomUUID());
+		private static final UserProfile USER_PROFILE = UserProfileTestFactory.create();
+		private final Command command = previousCommand;
+
+		@BeforeEach
+		void init() {
+			when(builder.getValueFromCommandBody(BODY_PROPERTY_ASSIGNED_USER, previousCommand)).thenReturn(USER_ID.toString());
+			when(userProfileCache.getUserProfile(USER_ID)).thenReturn(USER_PROFILE);
+		}
+
+		@Test
+		void shouldGetUserIdFromCommand() {
+			callBuilder();
+
+			verify(builder).getValueFromCommandBody(BODY_PROPERTY_ASSIGNED_USER, command);
+		}
+
+		@Test
+		void shouldGetUserProfileForUserId() {
+			callBuilder();
+
+			verify(userProfileCache).getUserProfile(USER_ID);
+		}
+
+		@Test
+		void shouldReturnUserFullName() {
+			var assignedUser = callBuilder();
+
+			assertThat(assignedUser).isEqualTo(UserProfileTestFactory.FULLNAME);
+		}
+
+		private String callBuilder() {
+			return builder.getAssignedUserFullNameFromCommand(command);
+		}
+	}
+}
+
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/ChangeHistoryBuilderTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/ChangeHistoryBuilderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..823baf3713395824cd0902c0ba3e0d45d4a0d903
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/ChangeHistoryBuilderTest.java
@@ -0,0 +1,276 @@
+package de.ozgcloud.alfa.historie;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.testcontainers.shaded.org.apache.commons.lang3.NotImplementedException;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandTestFactory;
+import de.ozgcloud.alfa.common.user.UserProfileTestFactory;
+import de.ozgcloud.alfa.historie.ChangeHistoryBuilder.CommandWithChangeValues;
+
+public class ChangeHistoryBuilderTest {
+
+	private static final String ORGANISATIONSEINHEITEN_ID = UUID.randomUUID().toString();
+
+	private final Command previousCommand = commandFinishedAt(LocalDateTime.of(2023, 5, 1, 12, 0));
+	private final Command command = commandFinishedAt(LocalDateTime.of(2023, 6, 1, 12, 0));
+	private final List<Command> commands = List.of(previousCommand, command);
+
+	@Spy
+	private TestChangeHistoryBuilder builder = new TestChangeHistoryBuilder()
+			.withCommands(commands)
+			.withOrganisationseinheitenID(ORGANISATIONSEINHEITEN_ID);
+
+	@Nested
+	class TestBuild {
+
+		private final CommandWithChangeValues previousCommandWithChangeValues = new CommandWithChangeValues(
+				previousCommand, "a", "b");
+		private final CommandWithChangeValues commandWithChangeValues = new CommandWithChangeValues(
+				command, "b", "c");
+		private final List<CommandWithChangeValues> commandsWithChangeValues = List.of(previousCommandWithChangeValues,
+				commandWithChangeValues);
+
+		@Test
+		void shouldCallInOrder() {
+			var orderVerifier = Mockito.inOrder(builder);
+			doReturn(commands.stream()).when(builder).retrieveRelevantCommands();
+			doReturn(commandsWithChangeValues.stream()).when(builder).toCommandsWithChangeValues(notNull());
+
+			builder.build().toList();
+
+			orderVerifier.verify(builder).retrieveRelevantCommands();
+			orderVerifier.verify(builder).inChronologicalOrder(notNull());
+			orderVerifier.verify(builder).addPreviousCommand(notNull());
+			orderVerifier.verify(builder).toCommandsWithChangeValues(notNull());
+			orderVerifier.verify(builder).toVorgangChanges(notNull());
+		}
+	}
+
+	@Nested
+	class TestRetrieveRelevantCommands {
+
+		@BeforeEach
+		void init() {
+			doReturn(false).when(builder).isRelevant(previousCommand);
+			doReturn(true).when(builder).isRelevant(command);
+		}
+
+		@Test
+		void shouldCallIsRelevant() {
+			builder.retrieveRelevantCommands().toList();
+
+			verify(builder).isRelevant(previousCommand);
+			verify(builder).isRelevant(command);
+		}
+
+		@Test
+		void shouldFilterOutIrrelevantCommands() {
+			var result = builder.retrieveRelevantCommands().toList();
+
+			assertThat(result).containsExactly(command);
+		}
+	}
+
+	@Nested
+	class TestInChronologicalOrder {
+
+		private final Stream<Command> unorderedCommands = Stream.of(command, previousCommand);
+
+		@Test
+		void shouldSortAscendingByFinishedAt() {
+			var orderedCommands = builder.inChronologicalOrder(unorderedCommands);
+
+			assertThat(orderedCommands).containsExactly(previousCommand, command);
+		}
+	}
+
+	@Nested
+	class TestAddPreviousCommand {
+
+		@Test
+		void shouldSetPreviousOfFirstCommandToNull() {
+			var commands = Stream.of(previousCommand);
+
+			var commandsWithPrevious = builder.addPreviousCommand(commands);
+
+			assertThat(commandsWithPrevious).first().extracting("previous").isEqualTo(null);
+		}
+
+		@Test
+		void shouldSetPreviousOfSubsequentCommand() {
+			var commands = Stream.of(previousCommand, command);
+
+			var commandsWithPrevious = builder.addPreviousCommand(commands);
+
+			assertThat(commandsWithPrevious).element(1).extracting("previous").isEqualTo(previousCommand);
+		}
+	}
+
+	@Nested
+	class TestToCommandsWithChangeValues {
+
+		private final CommandWithPrevious commandWithoutPrevious = CommandWithPreviousTestFactory.createBuilder().command(previousCommand).previous(null).build();
+		private final CommandWithPrevious commandWithPrevious = CommandWithPreviousTestFactory.createBuilder().command(command).previous(previousCommand).build();
+		private final List<CommandWithPrevious> commandsWithPrevious = List.of(commandWithoutPrevious, commandWithPrevious);
+
+		private final ChangeHistoryBuilder.CommandWithChangeValues previousCommandWithChangeValues = new ChangeHistoryBuilder.CommandWithChangeValues(
+				previousCommand, "a", "b");
+		private final ChangeHistoryBuilder.CommandWithChangeValues commandWithChangeValues = new ChangeHistoryBuilder.CommandWithChangeValues(
+				command, "b", "c");
+		private final List<ChangeHistoryBuilder.CommandWithChangeValues> commandsWithChangeValues = List.of(previousCommandWithChangeValues,
+				commandWithChangeValues);
+
+		@BeforeEach
+		void init() {
+			doReturn(previousCommandWithChangeValues).when(builder).toCommandWithChangeValues(commandWithoutPrevious);
+			doReturn(commandWithChangeValues).when(builder).toCommandWithChangeValues(commandWithPrevious);
+		}
+
+		@Test
+		void shouldMapToCommandWithChangeValues() {
+			callBuilder().toList();
+
+			verify(builder).toCommandWithChangeValues(commandWithoutPrevious);
+			verify(builder).toCommandWithChangeValues(commandWithPrevious);
+		}
+
+		@Test
+		void shouldReturnMappedCommandWithChangeValues() {
+			var historieEntries = callBuilder().toList();
+
+			assertThat(historieEntries).containsExactly(previousCommandWithChangeValues, commandWithChangeValues);
+		}
+
+		private Stream<CommandWithChangeValues> callBuilder() {
+			return builder.toCommandsWithChangeValues(commandsWithPrevious.stream());
+		}
+	}
+
+	@Nested
+	class TestToVorgangChange {
+
+		private final ChangeHistoryBuilder.CommandWithChangeValues commandWithChangeValues = new ChangeHistoryBuilder.CommandWithChangeValues(
+				previousCommand, "a", "b");
+
+		@Test
+		void shouldSeValueBeforeChange() {
+			var vorgangChange = callBuilder();
+
+			assertThat(vorgangChange.getValueBeforeChange()).isNotBlank().isEqualTo(commandWithChangeValues.valueBeforeChange());
+		}
+
+		@Test
+		void shouldSeValueAfterChange() {
+			var vorgangChange = callBuilder();
+
+			assertThat(vorgangChange.getValueAfterChange()).isNotBlank().isEqualTo(commandWithChangeValues.valueAfterChange());
+		}
+
+		@Test
+		void shouldSetAuthorFullName() {
+			var vorgangChange = callBuilder();
+
+			assertThat(vorgangChange.getAuthorFullName()).isEqualTo(UserProfileTestFactory.FULLNAME);
+		}
+
+		@Test
+		void shouldSetFinishedAt() {
+			var vorgangChange = callBuilder();
+
+			assertThat(vorgangChange.getFinishedAt()).isNotNull().isEqualTo(previousCommand.getFinishedAt());
+		}
+
+		@Test
+		void shouldSetOrder() {
+			var vorgangChange = callBuilder();
+
+			assertThat(vorgangChange.getOrder()).isNotNull().isEqualTo(previousCommand.getOrder());
+		}
+
+		@Test
+		void shouldSetOrganisationseinheitenID() {
+			var vorgangChange = callBuilder();
+
+			assertThat(vorgangChange.getOrganisationseinheitenID()).isEqualTo(ORGANISATIONSEINHEITEN_ID);
+		}
+
+		private VorgangChange callBuilder() {
+			return builder.toVorgangChange(commandWithChangeValues);
+		}
+	}
+
+	@Nested
+	class TestGetValueFromCommandBody {
+
+		private static final String PROPERTY_NAME = LoremIpsum.getInstance().getWords(1);
+
+		@ParameterizedTest
+		@NullAndEmptySource
+		void shouldReturnEmptyIfBodyIsNullOrEmpty(Map<String, ?> body) {
+			var command = previousCommand.toBuilder().body(body).build();
+
+			var value = builder.getValueFromCommandBody(PROPERTY_NAME, command);
+
+			assertThat(value).isEmpty();
+		}
+
+		@Test
+		void shouldReturnEmptyIfPropertyIsNotPresentInBody() {
+			var command = previousCommand.toBuilder().body(Map.of("a", "b")).build();
+
+			var value = builder.getValueFromCommandBody(PROPERTY_NAME, command);
+
+			assertThat(value).isEmpty();
+		}
+
+		@Test
+		void shouldReturnValueFromBody() {
+			var expectedValue = LoremIpsum.getInstance().getWords(2);
+			var command = previousCommand.toBuilder().body(Map.of(PROPERTY_NAME, expectedValue)).build();
+
+			var value = builder.getValueFromCommandBody(PROPERTY_NAME, command);
+
+			assertThat(value).isEqualTo(expectedValue);
+		}
+	}
+
+	
+	static Command commandFinishedAt(LocalDateTime finishedAt) {
+		return CommandTestFactory.createBuilder()
+				.finishedAt(ZonedDateTime.of(finishedAt, ZoneId.of("UTC")))
+				.build();
+	}
+	
+	private static class TestChangeHistoryBuilder extends ChangeHistoryBuilder<TestChangeHistoryBuilder> {
+
+		@Override
+		boolean isRelevant(Command command) {
+			throw new NotImplementedException();
+		}
+
+		@Override
+		CommandWithChangeValues toCommandWithChangeValues(CommandWithPrevious commandWithPrevious) {
+			throw new NotImplementedException();
+		}
+	}
+}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/CommandWithPreviousTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/CommandWithPreviousTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c875f11d9e9de3b3178244fe83c4e149899f2b2
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/CommandWithPreviousTestFactory.java
@@ -0,0 +1,21 @@
+package de.ozgcloud.alfa.historie;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandTestFactory;
+import de.ozgcloud.alfa.historie.CommandWithPrevious.CommandWithPreviousBuilder;
+
+public class CommandWithPreviousTestFactory {
+
+	public static final Command COMMAND = CommandTestFactory.create();
+	public static final Command PREVIOUS_COMMAND = CommandTestFactory.create();
+
+	public static CommandWithPrevious create() {
+		return createBuilder().build();
+	}
+
+	public static CommandWithPreviousBuilder createBuilder() {
+		return new CommandWithPreviousBuilder()
+				.command(COMMAND)
+				.previous(PREVIOUS_COMMAND);
+	}
+}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/HistorieServiceTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/HistorieServiceTest.java
index 3a5be826fa3dd6b2b4b12afa5232d57585482eba..c077ec5d7ac5bec75832117f08d395c119223c12 100644
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/HistorieServiceTest.java
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/HistorieServiceTest.java
@@ -37,6 +37,7 @@ import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.Spy;
 
 import de.ozgcloud.alfa.common.command.Command;
 import de.ozgcloud.alfa.common.command.CommandOrder;
@@ -47,6 +48,7 @@ import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory;
 
 class HistorieServiceTest {
 
+	@Spy
 	@InjectMocks
 	private HistorieService service;
 	@Mock
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/StatusChangeHistoryBuilderTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/StatusChangeHistoryBuilderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ddf9c1e21c5d2ed0d886ae48a21479264c72bbdc
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/StatusChangeHistoryBuilderTest.java
@@ -0,0 +1,214 @@
+package de.ozgcloud.alfa.historie;
+
+import static de.ozgcloud.alfa.historie.ChangeHistoryBuilderTest.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+import org.mockito.Spy;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import de.ozgcloud.alfa.common.command.CommandTestFactory;
+import de.ozgcloud.alfa.vorgang.Vorgang.VorgangStatus;
+
+public class StatusChangeHistoryBuilderTest {
+
+	private static final String ORGANISATIONSEINHEITEN_ID = UUID.randomUUID().toString();
+
+	private final Command previousCommand = commandFinishedAt(LocalDateTime.of(2023, 5, 1, 12, 0));
+	private final Command command = commandFinishedAt(LocalDateTime.of(2023, 6, 1, 12, 0));
+	private final List<Command> commands = List.of(previousCommand, command);
+
+	@Spy
+	private StatusChangeHistoryBuilder builder = StatusChangeHistoryBuilder.builder()
+			.withCommands(commands)
+			.withOrganisationseinheitenID(ORGANISATIONSEINHEITEN_ID);
+
+	@Nested
+	class TestIsRelevant {
+
+		@EnumSource(mode = Mode.INCLUDE, names = { "VORGANG_ANNEHMEN", "VORGANG_VERWERFEN", "VORGANG_ZURUECKHOLEN", "VORGANG_BEARBEITEN",
+				"VORGANG_BESCHEIDEN", "VORGANG_ABSCHLIESSEN", "VORGANG_ZUM_LOESCHEN_MARKIEREN", "VORGANG_LOESCHEN", "VORGANG_ZURUECKSTELLEN",
+				"VORGANG_WIEDEREROEFFNEN" })
+		@ParameterizedTest
+		void shouldReturnTrue(CommandOrder order) {
+			var isStatusChangeCommand = builder.isRelevant(CommandTestFactory.createBuilder().order(order).build());
+
+			assertThat(isStatusChangeCommand).isTrue();
+		}
+
+		@EnumSource(mode = Mode.EXCLUDE, names = { "VORGANG_ANNEHMEN", "VORGANG_VERWERFEN", "VORGANG_ZURUECKHOLEN", "VORGANG_BEARBEITEN",
+				"VORGANG_BESCHEIDEN", "VORGANG_ABSCHLIESSEN", "VORGANG_ZUM_LOESCHEN_MARKIEREN", "VORGANG_LOESCHEN", "VORGANG_ZURUECKSTELLEN",
+				"VORGANG_WIEDEREROEFFNEN" })
+		@ParameterizedTest
+		void shouldReturnFalse(CommandOrder order) {
+			var isStatusChangeCommand = builder.isRelevant(CommandTestFactory.createBuilder().order(order).build());
+
+			assertThat(isStatusChangeCommand).isFalse();
+		}
+	}
+
+	@Nested
+	class TestToCommandWithChangeValues {
+
+		private static final String EXPECTED_STATUS_BEFORE_CHANGE = LoremIpsum.getInstance().getWords(2);
+		private static final String EXPECTED_STATUS_AFTER_CHANGE = LoremIpsum.getInstance().getWords(2);
+
+		private final CommandWithPrevious commandWithPrevious = CommandWithPreviousTestFactory.createBuilder().command(command)
+				.previous(previousCommand).build();
+
+		@BeforeEach
+		void init() {
+			doReturn(EXPECTED_STATUS_BEFORE_CHANGE).when(builder).getStatusBeforeChange(commandWithPrevious);
+			doReturn(EXPECTED_STATUS_AFTER_CHANGE).when(builder).getStatusAfterChange(commandWithPrevious);
+		}
+
+		@Test
+		void shouldSetCommand() {
+			var commandWithChangeValues = callBuilder();
+
+			assertThat(commandWithChangeValues.command()).isEqualTo(command);
+		}
+
+		@Test
+		void shouldSetValueBeforeChange() {
+			var commandWithChangeValues = callBuilder();
+
+			assertThat(commandWithChangeValues.valueBeforeChange()).isEqualTo(EXPECTED_STATUS_BEFORE_CHANGE);
+		}
+
+		@Test
+		void shouldSetValueAfterChange() {
+			var commandWithChangeValues = callBuilder();
+
+			assertThat(commandWithChangeValues.valueAfterChange()).isEqualTo(EXPECTED_STATUS_AFTER_CHANGE);
+		}
+
+		private ChangeHistoryBuilder.CommandWithChangeValues callBuilder() {
+			return builder.toCommandWithChangeValues(commandWithPrevious);
+		}
+	}
+
+	@Nested
+	class TestGetStatusBeforeChange {
+
+		private final CommandWithPrevious commandWithoutPrevious = CommandWithPreviousTestFactory.createBuilder().command(previousCommand)
+				.previous(null).build();
+		private final CommandWithPrevious commandWithPrevious = CommandWithPreviousTestFactory.createBuilder().command(command)
+				.previous(previousCommand).build();
+
+		@Test
+		void shouldReturnStatusBeforeChange() {
+			var status = "Status";
+			doReturn(status).when(builder).getStatus(previousCommand);
+
+			var statusBeforeChange = builder.getStatusBeforeChange(commandWithPrevious);
+
+			assertThat(statusBeforeChange).isEqualTo(status);
+		}
+
+		@Test
+		void shouldReturnDefaultValue() {
+			var status = builder.getStatusBeforeChange(commandWithoutPrevious);
+
+			assertThat(status).isEqualTo(StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.NEU));
+		}
+	}
+
+	@Nested
+	class TestGetStatusAfterChange {
+
+		private final CommandWithPrevious commandWithPrevious = CommandWithPreviousTestFactory.createBuilder().command(command)
+				.previous(previousCommand).build();
+
+		@Test
+		void shouldReturnStatusAfterChange() {
+			var status = "Status";
+			doReturn(status).when(builder).getStatus(command);
+
+			var statusAfterChange = builder.getStatusAfterChange(commandWithPrevious);
+
+			assertThat(statusAfterChange).isEqualTo(status);
+		}
+	}
+
+	@Nested
+	class TestGetStatus {
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.INCLUDE, names = { "VORGANG_ANNEHMEN", "VORGANG_ZURUECKSTELLEN" })
+		void shouldReturnAngenommen(CommandOrder order) {
+			var status = builder.getStatus(CommandTestFactory.createBuilder().order(order).build());
+
+			assertThat(status).isEqualTo(StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ANGENOMMEN));
+		}
+
+		@Test
+		void shouldReturnVerworfen() {
+			var status = builder.getStatus(CommandTestFactory.createBuilder().order(CommandOrder.VORGANG_VERWERFEN).build());
+
+			assertThat(status).isEqualTo(StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.VERWORFEN));
+		}
+
+		@Test
+		void shouldReturnNeu() {
+			var status = builder.getStatus(CommandTestFactory.createBuilder().order(CommandOrder.VORGANG_ZURUECKHOLEN).build());
+
+			assertThat(status).isEqualTo(StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.NEU));
+		}
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.INCLUDE, names = { "VORGANG_BEARBEITEN", "VORGANG_WIEDEREROEFFNEN" })
+		void shouldReturnInBearbeitung(CommandOrder order) {
+			var status = builder.getStatus(CommandTestFactory.createBuilder().order(order).build());
+
+			assertThat(status).isEqualTo(StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.IN_BEARBEITUNG));
+		}
+
+		@Test
+		void shouldReturnBeschieden() {
+			var status = builder.getStatus(CommandTestFactory.createBuilder().order(CommandOrder.VORGANG_BESCHEIDEN).build());
+
+			assertThat(status).isEqualTo(StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.BESCHIEDEN));
+		}
+
+		@Test
+		void shouldReturnAbschliessen() {
+			var status = builder.getStatus(CommandTestFactory.createBuilder().order(CommandOrder.VORGANG_ABSCHLIESSEN).build());
+
+			assertThat(status).isEqualTo(StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ABGESCHLOSSEN));
+		}
+
+		@Test
+		void shouldReturnZuLoeschen() {
+			var status = builder.getStatus(CommandTestFactory.createBuilder().order(CommandOrder.VORGANG_ZUM_LOESCHEN_MARKIEREN).build());
+
+			assertThat(status).isEqualTo(StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ZU_LOESCHEN));
+		}
+
+		@Test
+		void shouldReturnGeloescht() {
+			var status = builder.getStatus(CommandTestFactory.createBuilder().order(CommandOrder.VORGANG_LOESCHEN).build());
+
+			assertThat(status).isEqualTo(StatusChangeHistoryBuilder.GELOESCHT_NAME);
+		}
+
+		@Test
+		void shouldtThrowIllegalArgumentException() {
+			assertThatThrownBy(() -> builder.getStatus(CommandTestFactory.createBuilder().order(CommandOrder.ASSIGN_USER).build())).isInstanceOf(
+					IllegalArgumentException.class);
+		}
+	}
+}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/UserProfileCacheTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/UserProfileCacheTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..001726a186d01137015228abec157856847b1585
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/UserProfileCacheTest.java
@@ -0,0 +1,98 @@
+package de.ozgcloud.alfa.historie;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import de.ozgcloud.alfa.common.user.UserId;
+import de.ozgcloud.alfa.common.user.UserProfile;
+import de.ozgcloud.alfa.common.user.UserProfileTestFactory;
+import de.ozgcloud.common.errorhandling.TechnicalException;
+
+public class UserProfileCacheTest {
+
+	private static final UserId USER_ID = UserProfileTestFactory.ID;
+	private static final UserProfile EXPECTED_USER_PROFILE = UserProfileTestFactory.create();
+
+	@Mock
+	private Function<UserId, UserProfile> findUserProfileForUserId;
+	@InjectMocks
+	private UserProfileCache cache;
+
+	@Test
+	void shouldSearchForUserProfileThatWasNotCached() {
+		givenFindUserProfileForUserIdReturnsUserProfile();
+
+		cache.getUserProfile(USER_ID);
+
+		verify(findUserProfileForUserId).apply(USER_ID);
+	}
+
+	@Test
+	void shouldReturnFoundUserProfile() {
+		givenFindUserProfileForUserIdReturnsUserProfile();
+
+		var userProfile = cache.getUserProfile(USER_ID);
+
+		assertThat(userProfile).isEqualTo(EXPECTED_USER_PROFILE);
+	}
+
+	@Test
+	void shouldAddFoundUserProfileToCache() {
+		givenFindUserProfileForUserIdReturnsUserProfile();
+
+		cache.getUserProfile(USER_ID);
+
+		assertThat(getUserProfileFromCache()).isEqualTo(EXPECTED_USER_PROFILE);
+	}
+
+	@Test
+	void shouldNotSearchForCachedUserProfile() {
+		givenUserProfileIsInCache();
+
+		cache.getUserProfile(USER_ID);
+
+		verifyNoInteractions(findUserProfileForUserId);
+	}
+
+	@Test
+	void shouldReturnUserProfileFromCache() {
+		givenUserProfileIsInCache();
+
+		var userProfile = cache.getUserProfile(USER_ID);
+
+		assertThat(userProfile).isEqualTo(EXPECTED_USER_PROFILE);
+	}
+
+	@Test
+	void shouldThrowExceptionIfUserProfileCannotBeFound() {
+		when(findUserProfileForUserId.apply(USER_ID)).thenReturn(null);
+
+		assertThatExceptionOfType(TechnicalException.class).isThrownBy(() -> cache.getUserProfile(USER_ID))
+				.withMessageStartingWith("UserProfile not found for user with ID " + USER_ID);
+	}
+
+	private void givenFindUserProfileForUserIdReturnsUserProfile() {
+		when(findUserProfileForUserId.apply(USER_ID)).thenReturn(EXPECTED_USER_PROFILE);
+	}
+
+	private void givenUserProfileIsInCache() {
+		var cache = new HashMap<UserId, UserProfile>();
+		cache.put(USER_ID, EXPECTED_USER_PROFILE);
+		ReflectionTestUtils.setField(this.cache, "cache", cache);
+	}
+
+	@SuppressWarnings("unchecked")
+	private UserProfile getUserProfileFromCache() {
+		return ((Map<String, UserProfile>) Objects.requireNonNull(ReflectionTestUtils.getField(cache, "cache"))).get(USER_ID);
+	}
+}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryServiceITCase.java b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryServiceITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..c7adce6f28f185465b435fa45c0f9bbf55ddbb4b
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryServiceITCase.java
@@ -0,0 +1,343 @@
+package de.ozgcloud.alfa.historie;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import de.ozgcloud.alfa.common.command.CommandService;
+import de.ozgcloud.alfa.common.command.CommandTestFactory;
+import de.ozgcloud.alfa.common.user.UserId;
+import de.ozgcloud.alfa.common.user.UserProfile;
+import de.ozgcloud.alfa.common.user.UserProfileTestFactory;
+import de.ozgcloud.alfa.common.user.UserService;
+import de.ozgcloud.alfa.vorgang.Vorgang.VorgangStatus;
+import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory;
+import de.ozgcloud.alfa.vorgang.VorgangWithEingang;
+import de.ozgcloud.alfa.vorgang.VorgangWithEingangTestFactory;
+
+public class VorgangChangeHistoryServiceITCase {
+
+	private static final UserId USER_1_ID = UserId.from("user-1");
+	private static final String USER_1_FIRST_NAME = LoremIpsum.getInstance().getFirstName();
+	private static final String USER_1_LAST_NAME = LoremIpsum.getInstance().getLastName();
+	private static final String USER_1_FULL_NAME = String.format("%s %s", USER_1_FIRST_NAME, USER_1_LAST_NAME);
+
+	private static final UserId USER_2_ID = UserId.from("user-2");
+	private static final String USER_2_FIRST_NAME = LoremIpsum.getInstance().getFirstName();
+	private static final String USER_2_LAST_NAME = LoremIpsum.getInstance().getLastName();
+	private static final String USER_2_FULL_NAME = String.format("%s %s", USER_2_FIRST_NAME, USER_2_LAST_NAME);
+
+	private static final String AKTENZEICHEN_1 = "ABC-1";
+	private static final String AKTENZEICHEN_2 = "ABC-2";
+
+	private final VorgangWithEingang vorgangWithEingang = VorgangWithEingangTestFactory.create();
+
+	@Mock
+	private CommandService commandService;
+	@Mock
+	private UserService userService;
+
+	@InjectMocks
+	private VorgangChangeHistoryService service;
+
+	@Test
+	void shouldReturnEmptyHistory() {
+		givenHistorieServiceReturnsCommands(List.of());
+
+		var history = service.createVorgangChangeHistory(vorgangWithEingang);
+
+		assertThat(history.getStatusChangeHistory()).isEmpty();
+		assertThat(history.getAktenzeichenChangeHistory()).isEmpty();
+		assertThat(history.getAssignedUserChangeHistory()).isEmpty();
+	}
+
+	@Test
+	void shouldReturnNonEmptyHistories() {
+		givenHistorieServiceReturnsCommands(createMixedCommands());
+		givenUserServiceReturnsUser1Profile();
+
+		var history = service.createVorgangChangeHistory(vorgangWithEingang);
+
+		assertThat(history.getStatusChangeHistory()).hasSize(1);
+		assertThat(history.getAktenzeichenChangeHistory()).hasSize(1);
+		assertThat(history.getAssignedUserChangeHistory()).hasSize(1);
+	}
+
+	@Test
+	void shouldSetCreatedByName() {
+		givenHistorieServiceReturnsCommands(List.of(new CommandFactory().statusChange(CommandOrder.VORGANG_ANNEHMEN)));
+
+		var history = service.createVorgangChangeHistory(vorgangWithEingang).getStatusChangeHistory();
+
+		assertThat(history.get(0).getAuthorFullName()).isEqualTo(USER_1_FULL_NAME);
+	}
+
+	@Test
+	void shouldSetFinishedAt() {
+		var command = new CommandFactory().statusChange(CommandOrder.VORGANG_ANNEHMEN);
+		givenHistorieServiceReturnsCommands(List.of(command));
+
+		var history = service.createVorgangChangeHistory(vorgangWithEingang).getStatusChangeHistory();
+
+		assertThat(history.get(0).getFinishedAt()).isNotNull().isEqualTo(command.getFinishedAt());
+	}
+
+	@Nested
+	class TestStatusChangeHistory {
+		@Test
+		void shouldSetBeforeAndAfterValues_untilAbgeschlossen() {
+			givenHistorieServiceReturnsCommands(createStatusChangeCommandsUntilAbgeschlossen());
+
+			var history = service.createVorgangChangeHistory(vorgangWithEingang).getStatusChangeHistory();
+
+			assertThat(history).hasSize(6);
+			checkBeforeAndAfterValues(history.get(0),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.NEU),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ANGENOMMEN));
+			checkBeforeAndAfterValues(history.get(1),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ANGENOMMEN),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.IN_BEARBEITUNG));
+			checkBeforeAndAfterValues(history.get(2),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.IN_BEARBEITUNG),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.BESCHIEDEN));
+			checkBeforeAndAfterValues(history.get(3),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.BESCHIEDEN),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ABGESCHLOSSEN));
+			checkBeforeAndAfterValues(history.get(4),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ABGESCHLOSSEN),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ZU_LOESCHEN));
+			checkBeforeAndAfterValues(history.get(5),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ZU_LOESCHEN),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ABGESCHLOSSEN));
+		}
+
+		@Test
+		void shouldSetBeforeAndAfterValues_untilVerworfen() {
+			givenHistorieServiceReturnsCommands(createStatusChangeCommandsUntilVerworfen());
+
+			var history = service.createVorgangChangeHistory(vorgangWithEingang).getStatusChangeHistory();
+
+			assertThat(history).hasSize(3);
+			checkBeforeAndAfterValues(history.get(0),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.NEU),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.VERWORFEN));
+			checkBeforeAndAfterValues(history.get(1),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.VERWORFEN),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ZU_LOESCHEN));
+			checkBeforeAndAfterValues(history.get(2),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.ZU_LOESCHEN),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.VERWORFEN));
+		}
+
+		@Test
+		void shouldSetBeforeAndAfterValues_untilZurueckholen() {
+			givenHistorieServiceReturnsCommands(createStatusChangeCommandsUntilZurueckholen());
+
+			var history = service.createVorgangChangeHistory(vorgangWithEingang).getStatusChangeHistory();
+
+			assertThat(history).hasSize(2);
+			checkBeforeAndAfterValues(history.get(0),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.NEU),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.VERWORFEN));
+			checkBeforeAndAfterValues(history.get(1),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.VERWORFEN),
+					StatusChangeHistoryBuilder.VORGANG_STATUS_TO_NAME.get(VorgangStatus.NEU));
+		}
+
+		@Test
+		void shouldSetOrder() {
+			var command = new CommandFactory().statusChange(CommandOrder.VORGANG_ANNEHMEN);
+			givenHistorieServiceReturnsCommands(List.of(command));
+
+			var history = service.createVorgangChangeHistory(vorgangWithEingang).getStatusChangeHistory();
+
+			assertThat(history.get(0).getOrder()).isNotNull().isEqualTo(CommandOrder.VORGANG_ANNEHMEN);
+		}
+	}
+
+	@Nested
+	class TestAktenzeichenChangeHistory {
+
+		@Test
+		void shouldSetBeforeAndAfterValues() {
+			givenHistorieServiceReturnsCommands(createAktenzeichenChangeCommands());
+
+			var history = service.createVorgangChangeHistory(vorgangWithEingang).getAktenzeichenChangeHistory();
+
+			assertThat(history).hasSize(3);
+			checkBeforeAndAfterValues(history.get(0), "", AKTENZEICHEN_1);
+			checkBeforeAndAfterValues(history.get(1), AKTENZEICHEN_1, AKTENZEICHEN_2);
+			checkBeforeAndAfterValues(history.get(2), AKTENZEICHEN_2, "");
+		}
+
+		@Test
+		void shouldSetOrder() {
+			var command = new CommandFactory().aktenzeichenChange(AKTENZEICHEN_1);
+			givenHistorieServiceReturnsCommands(List.of(command));
+
+			var history = service.createVorgangChangeHistory(vorgangWithEingang).getAktenzeichenChangeHistory();
+
+			assertThat(history.get(0).getOrder()).isNotNull().isEqualTo(CommandOrder.SET_AKTENZEICHEN);
+		}
+	}
+
+	@Nested
+	class TestAssignedUserChangeHistory {
+
+		@Test
+		void shouldSetBeforeAndAfterValues() {
+			givenHistorieServiceReturnsCommands(createUserChangeCommands());
+			givenUserServiceReturnsUserProfiles();
+
+			var history = service.createVorgangChangeHistory(vorgangWithEingang).getAssignedUserChangeHistory();
+
+			assertThat(history).hasSize(2);
+			checkBeforeAndAfterValues(history.get(0), "", USER_1_FULL_NAME);
+			checkBeforeAndAfterValues(history.get(1), USER_1_FULL_NAME, USER_2_FULL_NAME);
+		}
+
+		@Test
+		void shouldSetOrder() {
+			var command = new CommandFactory().userChange(USER_1_ID.toString());
+			givenHistorieServiceReturnsCommands(List.of(command));
+			givenUserServiceReturnsUser1Profile();
+
+			var history = service.createVorgangChangeHistory(vorgangWithEingang).getAssignedUserChangeHistory();
+
+			assertThat(history.get(0).getOrder()).isNotNull().isEqualTo(CommandOrder.ASSIGN_USER);
+		}
+	}
+
+	private void checkBeforeAndAfterValues(VorgangChange vorgangChange, String expectedBefore, String expectedAfter) {
+		assertThat(vorgangChange.getValueBeforeChange()).isEqualTo(expectedBefore);
+		assertThat(vorgangChange.getValueAfterChange()).isEqualTo(expectedAfter);
+	}
+
+	private void givenHistorieServiceReturnsCommands(List<Command> commands) {
+		when(commandService.findFinishedCommands(VorgangHeaderTestFactory.ID)).thenReturn(commands.stream());
+	}
+
+	private void givenUserServiceReturnsUserProfiles() {
+		givenUserServiceReturnsUser1Profile();
+		givenUserServiceReturnsUser2Profile();
+	}
+
+	private void givenUserServiceReturnsUser1Profile() {
+		when(userService.getById(USER_1_ID)).thenReturn(userProfile(USER_1_FIRST_NAME, USER_1_LAST_NAME));
+	}
+
+	private void givenUserServiceReturnsUser2Profile() {
+		when(userService.getById(USER_2_ID)).thenReturn(userProfile(USER_2_FIRST_NAME, USER_2_LAST_NAME));
+	}
+
+	private static UserProfile userProfile(String firstName, String lastName) {
+		return UserProfileTestFactory.createBuilder().firstName(firstName).lastName(lastName).build();
+	}
+
+	private List<Command> createStatusChangeCommandsUntilAbgeschlossen() {
+		var commandFactory = new CommandFactory();
+		return List.of(
+				commandFactory.statusChange(CommandOrder.VORGANG_ANNEHMEN),
+				commandFactory.statusChange(CommandOrder.VORGANG_BEARBEITEN),
+				commandFactory.statusChange(CommandOrder.VORGANG_BESCHEIDEN),
+				commandFactory.statusChange(CommandOrder.VORGANG_ABSCHLIESSEN),
+				commandFactory.statusChange(CommandOrder.VORGANG_ZUM_LOESCHEN_MARKIEREN),
+				commandFactory.statusChange(CommandOrder.VORGANG_ABSCHLIESSEN),
+				commandFactory.statusChange(CommandOrder.LOESCH_ANFORDERUNG_ZURUECKNEHMEN)
+		);
+	}
+
+	private List<Command> createStatusChangeCommandsUntilVerworfen() {
+		var commandFactory = new CommandFactory();
+		return List.of(
+				commandFactory.statusChange(CommandOrder.VORGANG_VERWERFEN),
+				commandFactory.statusChange(CommandOrder.VORGANG_ZUM_LOESCHEN_MARKIEREN),
+				commandFactory.statusChange(CommandOrder.VORGANG_VERWERFEN),
+				commandFactory.statusChange(CommandOrder.LOESCH_ANFORDERUNG_ZURUECKNEHMEN)
+		);
+	}
+
+	private List<Command> createStatusChangeCommandsUntilZurueckholen() {
+		var commandFactory = new CommandFactory();
+		return List.of(
+				commandFactory.statusChange(CommandOrder.VORGANG_VERWERFEN),
+				commandFactory.statusChange(CommandOrder.VORGANG_ZURUECKHOLEN)
+		);
+	}
+
+	private List<Command> createAktenzeichenChangeCommands() {
+		var commandFactory = new CommandFactory();
+		return List.of(
+				commandFactory.aktenzeichenChange(AKTENZEICHEN_1),
+				commandFactory.aktenzeichenChange(AKTENZEICHEN_2),
+				commandFactory.aktenzeichenChange(null)
+		);
+	}
+
+	private List<Command> createUserChangeCommands() {
+		var commandFactory = new CommandFactory();
+		return List.of(
+				commandFactory.userChange(USER_1_ID.toString()),
+				commandFactory.userChange(USER_2_ID.toString())
+		);
+	}
+
+	private List<Command> createMixedCommands() {
+		var commandFactory = new CommandFactory();
+		return List.of(
+				commandFactory.statusChange(CommandOrder.VORGANG_ANNEHMEN),
+				commandFactory.aktenzeichenChange(AKTENZEICHEN_1),
+				commandFactory.userChange(USER_1_ID.toString())
+		);
+	}
+
+	private static class CommandFactory {
+
+		private ZonedDateTime dateTime = ZonedDateTime.now(ZoneId.of("UTC"));
+
+		Command userChange(String assignedTo) {
+			return CommandTestFactory.createBuilder()
+					.order(CommandOrder.ASSIGN_USER)
+					.body(assignedTo != null ? Map.of(AssignedUserChangeHistoryBuilder.BODY_PROPERTY_ASSIGNED_USER, assignedTo) : Map.of())
+					.createdByName(USER_1_FULL_NAME)
+					.finishedAt(getAndIncrementDateTime())
+					.build();
+		}
+
+		Command aktenzeichenChange(String aktenzeichen) {
+			return CommandTestFactory.createBuilder()
+					.order(CommandOrder.SET_AKTENZEICHEN)
+					.body(aktenzeichen != null ? Map.of(AktenzeichenChangeHistoryBuilder.BODY_PROPERTY_AKTENZEICHEN, aktenzeichen) : Map.of())
+					.createdByName(USER_1_FULL_NAME)
+					.finishedAt(getAndIncrementDateTime())
+					.build();
+		}
+
+		Command statusChange(CommandOrder order) {
+			return CommandTestFactory.createBuilder()
+					.order(order)
+					.createdByName(USER_1_FULL_NAME)
+					.finishedAt(getAndIncrementDateTime())
+					.build();
+		}
+
+		private ZonedDateTime getAndIncrementDateTime() {
+			var current = dateTime;
+			dateTime = dateTime.plusMinutes(1);
+			return current;
+		}
+	}
+}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryServiceTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f582a35ec8ec508c9219aa47705eed47070709a2
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryServiceTest.java
@@ -0,0 +1,354 @@
+package de.ozgcloud.alfa.historie;
+
+import static de.ozgcloud.alfa.common.TestUtils.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Spy;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandService;
+import de.ozgcloud.alfa.common.user.UserId;
+import de.ozgcloud.alfa.common.user.UserProfile;
+import de.ozgcloud.alfa.common.user.UserProfileTestFactory;
+import de.ozgcloud.alfa.common.user.UserService;
+import de.ozgcloud.alfa.vorgang.EingangTestFactory;
+import de.ozgcloud.alfa.vorgang.VorgangWithEingang;
+import de.ozgcloud.alfa.vorgang.VorgangWithEingangTestFactory;
+import de.ozgcloud.alfa.vorgang.ZustaendigeStelleTestFactory;
+
+public class VorgangChangeHistoryServiceTest {
+
+	private static final String ORGANISATIONSEINHEITEN_ID = UUID.randomUUID().toString();
+
+	private final VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create();
+	private final Command command0105 = ChangeHistoryBuilderTest.commandFinishedAt(LocalDateTime.of(2023, 5, 1, 12, 0));
+	private final Command command0106 = ChangeHistoryBuilderTest.commandFinishedAt(LocalDateTime.of(2023, 6, 1, 12, 0));
+	private final List<Command> commands = List.of(command0105, command0106);
+
+	private final VorgangChange vorgangChange0105 = VorgangChangeTestFactory.createBuilder()
+			.valueBeforeChange(LoremIpsum.getInstance().getWords(2))
+			.valueAfterChange(LoremIpsum.getInstance().getWords(2))
+			.build();
+	private final VorgangChange vorgangChange0106 = VorgangChangeTestFactory.createBuilder()
+			.valueBeforeChange(LoremIpsum.getInstance().getWords(2))
+			.valueAfterChange(LoremIpsum.getInstance().getWords(2))
+			.build();
+	private final List<VorgangChange> vorgangChanges = List.of(vorgangChange0105, vorgangChange0106);
+
+	@Mock
+	private UserService userService;
+	@Mock
+	private HistorieService historieService;
+	@Mock
+	private CommandService commandService;
+
+	@Spy
+	@InjectMocks
+	private VorgangChangeHistoryService service;
+
+	@Nested
+	class TestCreateVorgangChangeHistory {
+
+		@BeforeEach
+		void init() {
+			mockStreamToList(commands, stream -> when(commandService.findFinishedCommands(vorgang.getId())).thenReturn(stream));
+		}
+
+		@Test
+		void shouldFindFinishedCommands() {
+			callService();
+
+			verify(commandService).findFinishedCommands(vorgang.getId());
+		}
+
+		@Test
+		void shouldSetStatusChangeHistory() {
+			doReturn(vorgangChanges.stream()).when(service).createStatusChangeHistory(vorgang, commands);
+
+			var history = callService();
+
+			assertThat(history.getStatusChangeHistory()).isEqualTo(vorgangChanges);
+		}
+
+		@Test
+		void shouldSetAktenzeichenChangeHistory() {
+			doReturn(vorgangChanges.stream()).when(service).createAktenzeichenChangeHistory(vorgang, commands);
+
+			var history = callService();
+
+			assertThat(history.getAktenzeichenChangeHistory()).isEqualTo(vorgangChanges);
+		}
+
+		@Test
+		void shouldSetAssignedUserChangeHistory() {
+			doReturn(vorgangChanges.stream()).when(service).createAssignedUserChangeHistory(vorgang, commands);
+
+			var history = callService();
+
+			assertThat(history.getAssignedUserChangeHistory()).isEqualTo(vorgangChanges);
+		}
+
+		private VorgangChangeHistory callService() {
+			return service.createVorgangChangeHistory(vorgang);
+		}
+	}
+
+	@Nested
+	class TestCreateStatusChangeHistory {
+
+		private MockedStatic<StatusChangeHistoryBuilder> staticBuilder;
+		@Mock
+		private StatusChangeHistoryBuilder builder;
+
+		@BeforeEach
+		void init() {
+			mockBuilderFactoryMethod();
+			mockBuilderWiths();
+			mockGetOrganisationseinheitenID();
+		}
+
+		private void mockBuilderFactoryMethod() {
+			staticBuilder = mockStatic(StatusChangeHistoryBuilder.class);
+			staticBuilder.when(StatusChangeHistoryBuilder::builder).thenReturn(builder);
+		}
+
+		private void mockBuilderWiths() {
+			when(builder.withCommands(commands)).thenReturn(builder);
+			when(builder.withOrganisationseinheitenID(ORGANISATIONSEINHEITEN_ID)).thenReturn(builder);
+		}
+
+		@AfterEach
+		void cleanup() {
+			staticBuilder.close();
+		}
+
+		@Test
+		void shouldSetCommands() {
+			callService();
+
+			verify(builder).withCommands(commands);
+		}
+
+		@Test
+		void shouldSetOrganisationseinheitenID() {
+			callService();
+
+			verify(builder).withOrganisationseinheitenID(ORGANISATIONSEINHEITEN_ID);
+		}
+
+		@Test
+		void shouldReturnBuiltStream() {
+			when(builder.build()).thenReturn(vorgangChanges.stream());
+
+			var changeHistory = callService();
+
+			assertThat(changeHistory).containsExactlyElementsOf(vorgangChanges);
+		}
+
+		private Stream<VorgangChange> callService() {
+			return service.createStatusChangeHistory(vorgang, commands);
+		}
+	}
+
+	@Nested
+	class TestCreateAktenzeichenChangeHistory {
+
+		private MockedStatic<AktenzeichenChangeHistoryBuilder> staticBuilder;
+		@Mock
+		private AktenzeichenChangeHistoryBuilder builder;
+
+		@BeforeEach
+		void init() {
+			mockBuilderFactoryMethod();
+			mockBuilderWiths();
+			mockGetOrganisationseinheitenID();
+		}
+
+		private void mockBuilderFactoryMethod() {
+			staticBuilder = mockStatic(AktenzeichenChangeHistoryBuilder.class);
+			staticBuilder.when(AktenzeichenChangeHistoryBuilder::builder).thenReturn(builder);
+		}
+
+		private void mockBuilderWiths() {
+			when(builder.withCommands(commands)).thenReturn(builder);
+			when(builder.withOrganisationseinheitenID(ORGANISATIONSEINHEITEN_ID)).thenReturn(builder);
+		}
+
+		@AfterEach
+		void cleanup() {
+			staticBuilder.close();
+		}
+
+		@Test
+		void shouldSetCommands() {
+			callService();
+
+			verify(builder).withCommands(commands);
+		}
+
+		@Test
+		void shouldSetOrganisationseinheitenID() {
+			callService();
+
+			verify(builder).withOrganisationseinheitenID(ORGANISATIONSEINHEITEN_ID);
+		}
+
+		@Test
+		void shouldReturnBuiltStream() {
+			when(builder.build()).thenReturn(vorgangChanges.stream());
+
+			var changeHistory = callService();
+
+			assertThat(changeHistory).containsExactlyElementsOf(vorgangChanges);
+		}
+
+		private Stream<VorgangChange> callService() {
+			return service.createAktenzeichenChangeHistory(vorgang, commands);
+		}
+	}
+
+	@Nested
+	class TestCreateAssignedUserChangeHistory {
+
+		private MockedStatic<AssignedUserChangeHistoryBuilder> staticBuilder;
+		private MockedStatic<UserProfileCache> staticUserProfileCache;
+
+		@Captor
+		private ArgumentCaptor<Function<UserId, UserProfile>> userProfileCacheArgCaptor;
+
+		@Mock
+		private AssignedUserChangeHistoryBuilder builder;
+		@Mock
+		private UserProfileCache userProfileCache;
+
+		@BeforeEach
+		void init() {
+			mockBuilderFactoryMethod();
+			mockBuilderWiths();
+			mockUserProfileCacheFactoryMethod();
+			mockGetOrganisationseinheitenID();
+		}
+
+		private void mockBuilderFactoryMethod() {
+			staticBuilder = mockStatic(AssignedUserChangeHistoryBuilder.class);
+			staticBuilder.when(AssignedUserChangeHistoryBuilder::builder).thenReturn(builder);
+		}
+
+		private void mockBuilderWiths() {
+			when(builder.withCommands(commands)).thenReturn(builder);
+			when(builder.withOrganisationseinheitenID(ORGANISATIONSEINHEITEN_ID)).thenReturn(builder);
+			when(builder.withUserProfileCache(userProfileCache)).thenReturn(builder);
+		}
+
+		private void mockUserProfileCacheFactoryMethod() {
+			staticUserProfileCache = mockStatic(UserProfileCache.class);
+			staticUserProfileCache.when(() -> UserProfileCache.create(any())).thenReturn(userProfileCache);
+		}
+
+		@AfterEach
+		void cleanup() {
+			staticBuilder.close();
+			staticUserProfileCache.close();
+		}
+
+		@Test
+		void shouldSetCommands() {
+			callService();
+
+			verify(builder).withCommands(commands);
+		}
+
+		@Test
+		void shouldSetOrganisationseinheitenID() {
+			callService();
+
+			verify(builder).withOrganisationseinheitenID(ORGANISATIONSEINHEITEN_ID);
+		}
+
+		@Test
+		void shouldCreateUserProfileCache() {
+			callService();
+
+			staticUserProfileCache.verify(() -> UserProfileCache.create(userProfileCacheArgCaptor.capture()));
+			assertArgumentToUserProfileCacheCallsUserService();
+		}
+
+		private void assertArgumentToUserProfileCacheCallsUserService() {
+			userProfileCacheArgCaptor.getValue().apply(UserProfileTestFactory.ID);
+
+			verify(userService).getById(UserProfileTestFactory.ID);
+		}
+
+		@Test
+		void shouldSetUserProfileCache() {
+			callService();
+
+			verify(builder).withUserProfileCache(userProfileCache);
+		}
+
+		@Test
+		void shouldReturnBuiltStream() {
+			when(builder.build()).thenReturn(vorgangChanges.stream());
+
+			var changeHistory = callService();
+
+			assertThat(changeHistory).containsExactlyElementsOf(vorgangChanges);
+		}
+
+		private Stream<VorgangChange> callService() {
+			return service.createAssignedUserChangeHistory(vorgang, commands);
+		}
+	}
+
+	@Nested
+	class TestGetOrganisationseinheitenID {
+
+		@Test
+		void shouldReturnNullWhenEingangIsNull() {
+			var vorgang = VorgangWithEingangTestFactory.createBuilder().eingang(null).build();
+
+			var id = service.getOrganisationseinheitenID(vorgang);
+
+			assertThat(id).isNull();
+		}
+
+		@Test
+		void shouldReturnNullIfZustaendigeStelleIsNull() {
+			var eingang = EingangTestFactory.createBuilder().zustaendigeStelle(null).build();
+			var vorgang = VorgangWithEingangTestFactory.createBuilder().eingang(eingang).build();
+
+			var id = service.getOrganisationseinheitenID(vorgang);
+
+			assertThat(id).isNull();
+		}
+
+		@Test
+		void shouldReturnOrganisationseinheitenID() {
+			var id = service.getOrganisationseinheitenID(vorgang);
+
+			assertThat(id).isEqualTo(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEITEN_ID);
+		}
+	}
+
+	private void mockGetOrganisationseinheitenID() {
+		when(service.getOrganisationseinheitenID(vorgang)).thenReturn(ORGANISATIONSEINHEITEN_ID);
+	}
+}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b4269eee0600e44d6ad3b56e71896ffd082c0da
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryTestFactory.java
@@ -0,0 +1,24 @@
+package de.ozgcloud.alfa.historie;
+
+import java.util.List;
+
+import de.ozgcloud.alfa.common.command.CommandOrder;
+
+class VorgangChangeHistoryTestFactory {
+
+	public static final List<VorgangChange> STATUS_CHANGE_HISTORY = List.of(VorgangChangeTestFactory.createBuilder()
+			.order(CommandOrder.VORGANG_ANNEHMEN).build());
+	public static final List<VorgangChange> AKTENZEICHEN_CHANGE_HISTORY = List.of(VorgangChangeTestFactory.createBuilder()
+			.order(CommandOrder.SET_AKTENZEICHEN).build());
+	public static final List<VorgangChange> ASSIGNED_USER_CHANGE_HISTORY = List.of(VorgangChangeTestFactory.createBuilder()
+			.order(CommandOrder.ASSIGN_USER).build());
+
+	public static VorgangChangeHistory create() {
+		return VorgangChangeHistory.builder()
+				.statusChangeHistory(STATUS_CHANGE_HISTORY)
+				.aktenzeichenChangeHistory(AKTENZEICHEN_CHANGE_HISTORY)
+				.assignedUserChangeHistory(ASSIGNED_USER_CHANGE_HISTORY)
+				.build();
+	}
+
+}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4c629a920970785272caa0e3901b1d1424fd336
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeTestFactory.java
@@ -0,0 +1,30 @@
+package de.ozgcloud.alfa.historie;
+
+import java.time.ZonedDateTime;
+
+import de.ozgcloud.alfa.common.command.CommandOrder;
+
+class VorgangChangeTestFactory {
+
+	public static final String VALUE_BEFORE_CHANGE = "Value before";
+	public static final String VALUE_AFTER_CHANGE = "Value after";
+	public static final String ORGANISATIONSEINHEITEN_ID = "ORGA1";
+	public static final String CREATED_BY_NAME = "User1";
+	public static final ZonedDateTime FINISHED_AT = ZonedDateTime.now();
+	public static final CommandOrder ORDER = CommandOrder.SET_AKTENZEICHEN;
+
+	public static VorgangChange create() {
+		return createBuilder().build();
+	}
+
+	public static VorgangChange.VorgangChangeBuilder createBuilder() {
+		return VorgangChange.builder()
+				.valueBeforeChange(VALUE_BEFORE_CHANGE)
+				.valueAfterChange(VALUE_AFTER_CHANGE)
+				.authorFullName(CREATED_BY_NAME)
+				.finishedAt(FINISHED_AT)
+				.order(ORDER)
+				.organisationseinheitenID(ORGANISATIONSEINHEITEN_ID);
+	}
+
+}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/CountVorgangResponseTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/CountVorgangResponseTestFactory.java
deleted file mode 100644
index 027627fd544456c5e896602dd59a5891bad4e6a3..0000000000000000000000000000000000000000
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/CountVorgangResponseTestFactory.java
+++ /dev/null
@@ -1,20 +0,0 @@
-// TODO sebo: diese Datei später löschen
-package de.ozgcloud.alfa.statistic;
-
-import java.util.Map;
-
-public class CountVorgangResponseTestFactory {
-
-	public static final String PATH_COUNT_NAME = "name:of:path";
-	public static final Integer PATH_COUNT_VALUE = 6;
-
-	public static CountVorgangResponse create() {
-		return createBuilder().build();
-	}
-
-	public static CountVorgangResponse.CountVorgangResponseBuilder createBuilder() {
-		return CountVorgangResponse.builder()
-				.byStatus(ByStatusTestFactory.create())
-				.pathCountResult(Map.of(PATH_COUNT_NAME, PATH_COUNT_VALUE));
-	}
-}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/GrpcVorgangCountTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/GrpcVorgangCountTestFactory.java
deleted file mode 100644
index fc67c39367135c10296c3ddf0f34ce99765e1bb8..0000000000000000000000000000000000000000
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/GrpcVorgangCountTestFactory.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// TODO sebo: diese Datei später löschen
-package de.ozgcloud.alfa.statistic;
-
-import de.ozgcloud.vorgang.statistic.GrpcByStatusResult;
-import de.ozgcloud.vorgang.statistic.GrpcPathCountResult;
-import de.ozgcloud.vorgang.statistic.GrpcVorgangCountResponse;
-
-public class GrpcVorgangCountTestFactory {
-
-	public static final GrpcByStatusResult BY_STATUS = GrpcByStatusResultTestFactory.create();
-	private static final GrpcPathCountResult PATH_COUNT_RESULT = GrpcPathCountResult.newBuilder()
-			.setName(CountVorgangResponseTestFactory.PATH_COUNT_NAME)
-			.setValue(CountVorgangResponseTestFactory.PATH_COUNT_VALUE)
-			.build();
-
-	public static GrpcVorgangCountResponse createResponse() {
-		return createResponseBuilder().build();
-	}
-
-	public static GrpcVorgangCountResponse.Builder createResponseBuilder() {
-		return GrpcVorgangCountResponse.newBuilder()
-				.setByStatus(BY_STATUS)
-				.addPathCountResult(PATH_COUNT_RESULT);
-	}
-}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticControllerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticControllerTest.java
index e4f3a2739326e273d9f669dab84a58292d82bc4d..a8214c3dda215800d6fa3334f77123d7e961ae46 100644
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticControllerTest.java
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticControllerTest.java
@@ -1,9 +1,7 @@
 package de.ozgcloud.alfa.statistic;
 
-import static org.assertj.core.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
@@ -20,95 +18,12 @@ class StatisticControllerTest {
 	@Nested
 	class TestGetVorgaengeStatistic {
 
-		private static final int ZU_LOESCHEN_COUNT = ByStatusTestFactory.ZU_LOESCHEN_COUNT + 1;
-
-		private final Statistic getVorgangResult = StatisticTestFactory.createBuilder()
-				.byStatus(ByStatusTestFactory.createBuilder().zuLoeschen(ZU_LOESCHEN_COUNT).build()).build();
-		private final Statistic countVorgangResult = StatisticTestFactory.create();
-
-		@BeforeEach
-		void beforeEach() {
-			when(service.countVorgang()).thenReturn(countVorgangResult);
-			when(service.getVorgaengeStatistic()).thenReturn(getVorgangResult);
-		}
-
 		@Test
-		void shouldCallGetVorgaengeStatistic() {
+		void shouldCallService() {
 			controller.getVorgaengeStatistic();
 
 			verify(service).getVorgaengeStatistic();
 		}
-
-		// TODO sebo: später löschen after only new api will be in use
-		@Test
-		void shouldCallCountVorgang() {
-			controller.getVorgaengeStatistic();
-
-			verify(service).countVorgang();
-		}
-
-		@Test
-		void shouldHaveExistsWiedervorlageOverdue() {
-			var statistic = controller.getVorgaengeStatistic();
-
-			assertThat(statistic.isExistsWiedervorlageOverdue()).isEqualTo(StatisticTestFactory.EXISTS_WIEDERVORLAGE_OVERDUE);
-		}
-
-		@Test
-		void shouldHaveWiedervorlagenCount() {
-			var statistic = controller.getVorgaengeStatistic();
-
-			assertThat(statistic.getWiedervorlagen()).isEqualTo(StatisticTestFactory.COUNT_WIEDERVORLAGEN);
-		}
-
-		@Test
-		void shouldHaveCountByVerworfenStatus() {
-			var statistic = controller.getVorgaengeStatistic();
-
-			assertThat(statistic.getByStatus().getVerworfen()).isEqualTo(ByStatusTestFactory.VERWORFEN_COUNT);
-		}
-
-		@Test
-		void shouldHaveCountByInBearbeitungStatus() {
-			var statistic = controller.getVorgaengeStatistic();
-
-			assertThat(statistic.getByStatus().getInBearbeitung()).isEqualTo(ByStatusTestFactory.IN_BEARBEITUNG_COUNT);
-		}
-
-		@Test
-		void shouldHaveCountByAngenommenStatus() {
-			var statistic = controller.getVorgaengeStatistic();
-
-			assertThat(statistic.getByStatus().getAngenommen()).isEqualTo(ByStatusTestFactory.ANGENOMMEN_COUNT);
-		}
-
-		@Test
-		void shouldHaveCountByBeschiedenStatus() {
-			var statistic = controller.getVorgaengeStatistic();
-
-			assertThat(statistic.getByStatus().getBeschieden()).isEqualTo(ByStatusTestFactory.BESCHIEDEN_COUNT);
-		}
-
-		@Test
-		void shouldHaveCountByAbgeschlossenStatus() {
-			var statistic = controller.getVorgaengeStatistic();
-
-			assertThat(statistic.getByStatus().getAbgeschlossen()).isEqualTo(ByStatusTestFactory.ABGESCHLOSSEN_COUNT);
-		}
-
-		@Test
-		void shouldHaveCountByZuLoeschenStatus() {
-			var statistic = controller.getVorgaengeStatistic();
-
-			assertThat(statistic.getByStatus().getZuLoeschen()).isEqualTo(ZU_LOESCHEN_COUNT);
-		}
-
-		@Test
-		void shouldHaveCountByNeuStatus() {
-			var statistic = controller.getVorgaengeStatistic();
-
-			assertThat(statistic.getByStatus().getNeu()).isEqualTo(ByStatusTestFactory.NEU_COUNT);
-		}
 	}
 
 }
\ No newline at end of file
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticRemoteServiceTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticRemoteServiceTest.java
index 56b85f9f3f14bf8e70f73c11d834721b7c923754..5716d65c2197741fbf8d03e498f3b283e398cc17 100644
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticRemoteServiceTest.java
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticRemoteServiceTest.java
@@ -23,7 +23,7 @@ import com.thedeanda.lorem.LoremIpsum;
 
 import de.ozgcloud.alfa.AlfaProperties;
 import de.ozgcloud.alfa.vorgang.Vorgang.VorgangStatus;
-import de.ozgcloud.vorgang.statistic.GrpcVorgangCountResponse;
+import de.ozgcloud.vorgang.statistic.GrpcVorgangStatisticQuery;
 import de.ozgcloud.vorgang.statistic.GrpcVorgangStatisticQuery.GroupMethod;
 import de.ozgcloud.vorgang.statistic.GrpcVorgangStatisticQuery.Operator;
 import de.ozgcloud.vorgang.statistic.GrpcVorgangStatisticResponse;
@@ -103,6 +103,35 @@ class StatisticRemoteServiceTest {
 		}
 	}
 
+	@Nested
+	class TestBuildCountWiedervorlageNextFristQuery {
+
+		private final String applicationName = LoremIpsum.getInstance().getFirstName();
+
+		@BeforeEach
+		void init() {
+			when(alfaProperties.getApplicationName()).thenReturn(applicationName);
+		}
+
+		@Test
+		void shouldBuildQuery() {
+			var query = service.buildCountWiedervorlageNextFristQuery();
+
+			assertThat(query)
+					.extracting(
+							GrpcVorgangStatisticQuery::getResultName,
+							GrpcVorgangStatisticQuery::getPath,
+							GrpcVorgangStatisticQuery::getOperator,
+							GrpcVorgangStatisticQuery::getGroupMethod)
+					.contains(
+							COUNT_WIEDERVORLAGE_NEXT_FRIST_RESULT_NAME,
+							String.format(StatisticRemoteService.WIEDERVORLAGE_NEXT_FRIST_PATH_TEMPLATE, applicationName),
+							Operator.UNEQUAL,
+							GroupMethod.COUNT
+					);
+		}
+	}
+
 	@Nested
 	class TestBuildExistsWiedervorlageOverdueQuery {
 
@@ -183,6 +212,13 @@ class StatisticRemoteServiceTest {
 			verify(service).buildCountByStatusQueries(STATUSES_TO_COUNT);
 		}
 
+		@Test
+		void shouldCallBuildCountByVorgangStatusQuery() {
+			service.getVorgaengeStatistics(STATUSES_TO_COUNT);
+
+			verify(service).buildCountWiedervorlageNextFristQuery();
+		}
+
 		@Test
 		void shouldCallBuildExistsWiedervorlageOverdueQuery() {
 			service.getVorgaengeStatistics(STATUSES_TO_COUNT);
@@ -234,58 +270,4 @@ class StatisticRemoteServiceTest {
 					.hasEntrySatisfying(RESULT_NAME, statisticResult -> assertThat(statisticResult.getIntValue()).isEqualTo(RESULT_INT));
 		}
 	}
-
-	// TODO sebo: später löschen
-	@DisplayName("Get statistic")
-	@Nested
-	class TestGetStatistic {
-
-		private String countByPath = "DummyCountPath";
-		private List<String> countByPathList = List.of(countByPath);
-		private final GrpcVorgangCountResponse response = GrpcVorgangCountTestFactory.createResponse();
-
-		@DisplayName("prozess flow")
-		@Nested
-		class TestProzessFlow {
-
-			@BeforeEach
-			void mockServiceStub() {
-				when(serviceStub.countVorgang(any())).thenReturn(response);
-			}
-
-			@Test
-			void shouldCallStub() {
-				service.countVorgang(countByPathList);
-
-				verify(serviceStub).countVorgang(any());
-			}
-
-			@Test
-			void shouldBuildRequest() {
-				service.countVorgang(countByPathList);
-
-				verify(service).buildVorgangCountRequest(countByPathList);
-			}
-
-			@Test
-			void shouldMapCountVorgangResponse() {
-				service.countVorgang(countByPathList);
-
-				verify(mapper).fromCountVorgangResponse(response);
-			}
-		}
-
-		@DisplayName("build vorgang count request")
-		@Nested
-		class TestBuildVorgangCountRequest {
-
-			@Test
-			void shouldHaveCountByPath() {
-				var request = service.buildVorgangCountRequest(countByPathList);
-
-				assertThat(request.getCountByPathList()).hasSize(1).containsExactly(countByPath);
-			}
-		}
-	}
-
 }
\ No newline at end of file
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticServiceTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticServiceTest.java
index 58326fa8570d3d65e80125785c0cb862579c22ee..1b030d675d4eec8e8cee9f8c2bcb0f3f83e339e1 100644
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticServiceTest.java
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/statistic/StatisticServiceTest.java
@@ -7,21 +7,15 @@ import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
 
-import com.thedeanda.lorem.LoremIpsum;
-
-import de.ozgcloud.alfa.AlfaProperties;
 import de.ozgcloud.alfa.common.user.CurrentUserService;
 import de.ozgcloud.alfa.common.user.UserRole;
 import de.ozgcloud.alfa.vorgang.Vorgang.VorgangStatus;
@@ -35,8 +29,6 @@ class StatisticServiceTest {
 	private StatisticRemoteService remoteService;
 	@Mock
 	private CurrentUserService currentUserService;
-	@Mock
-	private AlfaProperties alfaProperties;
 
 	@Nested
 	class TestBuildGetVorgaengeStatisticResult {
@@ -157,7 +149,7 @@ class StatisticServiceTest {
 
 			var result = service.getCountByVorgangStatusList();
 
-			assertThat(result).containsAll(VORGAENGE_STATUS_COUNT_VERWALTUNG_USER);
+			assertThat(result).containsAll(COUNT_BY_VORGANG_STATUS_VERWALTUNG_USER);
 		}
 
 		@Test
@@ -167,7 +159,7 @@ class StatisticServiceTest {
 
 			var result = service.getCountByVorgangStatusList();
 
-			assertThat(result).containsAll(VORGAENGE_STATUS_COUNT_POSTSTELLE_USER);
+			assertThat(result).containsAll(COUNT_BY_VORGANG_STATUS_POSTSTELLE_USER);
 		}
 
 		@Test
@@ -178,7 +170,7 @@ class StatisticServiceTest {
 
 			var result = service.getCountByVorgangStatusList();
 
-			assertThat(result).containsAll(VORGAENGE_STATUS_COUNT_EINHEITLICHER_ANSPRECHPARTNER);
+			assertThat(result).containsAll(COUNT_BY_VORGANG_EINHEITLICHER_ANSPRECHPARTNER);
 		}
 
 		@Test
@@ -196,111 +188,43 @@ class StatisticServiceTest {
 	@Nested
 	class TestGetVorgaengeStatistics {
 
-		private final List<VorgangStatus> countByVorgangStatus = List.of(VorgangStatus.ZU_LOESCHEN);
+		private final Statistic statistic = StatisticTestFactory.create();
+		private final Map<String, StatisticResult> getVorgangStatisticsResponse = Map.of(COUNT_WIEDERVORLAGE_NEXT_FRIST_RESULT_NAME,
+				StatisticResultTestFactory.create());
 
 		@BeforeEach
 		void beforeEach() {
-			when(remoteService.buildCountByStatusResultName(any())).thenCallRealMethod();
-		}
-
-		@Test
-		void shouldCallRemoteService() {
-			service.getVorgaengeStatistic();
-
-			verify(remoteService).getVorgaengeStatistics(countByVorgangStatus);
+			doReturn(COUNT_BY_VORGANG_STATUS_VERWALTUNG_USER).when(service).getCountByVorgangStatusList();
+			when(remoteService.getVorgaengeStatistics(COUNT_BY_VORGANG_STATUS_VERWALTUNG_USER)).thenReturn(getVorgangStatisticsResponse);
+			doReturn(statistic).when(service).buildGetVorgaengeStatisticResult(getVorgangStatisticsResponse);
 		}
 
 		@Test
-		void shouldBuildResult() {
-			var response = Map.of(COUNT_WIEDERVORLAGE_NEXT_FRIST_RESULT_NAME, StatisticResultTestFactory.create());
-			when(remoteService.getVorgaengeStatistics(countByVorgangStatus)).thenReturn(response);
-
-			service.getVorgaengeStatistic();
-
-			verify(service).buildGetVorgaengeStatisticResult(response);
-		}
-
-		@Test
-		@Disabled("activate if new grpc api in use")
 		void shouldCallGetCountByVorgangStatusList() {
 			service.getVorgaengeStatistic();
 
 			verify(service).getCountByVorgangStatusList();
 		}
-	}
-
-	// TODO sebo: später löschen
-	@DisplayName("Count vorgang")
-	@Nested
-	class TestCountVorgang {
-
-		private static final int WIEDERVORLAGE_COUNT = 8;
-		private CountVorgangResponse response = CountVorgangResponseTestFactory.createBuilder()
-				.pathCountResult(Map.of(StatisticService.COUNT_WIEDERVORLAGE_NEXT_FRIST_KEY, WIEDERVORLAGE_COUNT)).build();
-		private final String applicationName = LoremIpsum.getInstance().getFirstName();
 
 		@Test
 		void shouldCallRemoteService() {
-			when(alfaProperties.getApplicationName()).thenReturn(applicationName);
-			when(remoteService.countVorgang(any())).thenReturn(response);
-
-			service.countVorgang();
+			service.getVorgaengeStatistic();
 
-			verify(remoteService).countVorgang(List.of(String.format(StatisticService.COUNT_WIEDERVORLAGE_NEXT_FRIST_TEMPLATE, applicationName)));
+			verify(remoteService).getVorgaengeStatistics(COUNT_BY_VORGANG_STATUS_VERWALTUNG_USER);
 		}
 
 		@Test
-		void shouldBuildStatistic() {
-			when(alfaProperties.getApplicationName()).thenReturn(applicationName);
-			when(remoteService.countVorgang(any())).thenReturn(response);
-
-			service.countVorgang();
+		void shouldBuildResult() {
+			service.getVorgaengeStatistic();
 
-			verify(service).buildStatistic(response);
+			verify(service).buildGetVorgaengeStatisticResult(getVorgangStatisticsResponse);
 		}
 
-		@DisplayName("build statistic")
-		@Nested
-		class TestBuildStatistic {
-
-			@Test
-			void shouldSetByStatus() {
-				var statistic = service.buildStatistic(response);
-
-				assertThat(statistic.getByStatus()).usingRecursiveComparison().isEqualTo(ByStatusTestFactory.create());
-			}
-
-			@Test
-			void shouldSetWiedervorlagen() {
-				var statistic = service.buildStatistic(response);
-
-				assertThat(statistic.getWiedervorlagen()).isEqualTo(WIEDERVORLAGE_COUNT);
-			}
-
-			@DisplayName("extract wiedervorlage")
-			@Nested
-			class TestExtractWiedervorlage {
-
-				@Test
-				void shouldReturnValueIfExists() {
-					var count = 5;
-					var countVorgangResponse = CountVorgangResponseTestFactory.createBuilder()
-							.pathCountResult(Map.of(StatisticService.COUNT_WIEDERVORLAGE_NEXT_FRIST_KEY, count)).build();
-
-					var wiedervorlageCount = service.extractWiedervorlageCount(countVorgangResponse);
-
-					assertThat(wiedervorlageCount).isEqualTo(count);
-				}
-
-				@Test
-				void shouldReturnZeroIfNotExists() {
-					var wiedervorlageCount = service.extractWiedervorlageCount(
-							CountVorgangResponseTestFactory.createBuilder().pathCountResult(Collections.emptyMap()).build());
+		@Test
+		void shouldReturnStatistic() {
+			var result = service.getVorgaengeStatistic();
 
-					assertThat(wiedervorlageCount).isZero();
-				}
-			}
+			assertThat(result).isEqualTo(statistic);
 		}
 	}
-
 }
diff --git a/alfa-xdomea/pom.xml b/alfa-xdomea/pom.xml
index 4a877ce614d7f3b270517a089f8a80037ecc7f7f..11bcf3c5ca4a690e73bf5d8fc705a5e9f082a382 100644
--- a/alfa-xdomea/pom.xml
+++ b/alfa-xdomea/pom.xml
@@ -31,7 +31,7 @@
 	<parent>
 		<groupId>de.ozgcloud.alfa</groupId>
 		<artifactId>alfa</artifactId>
-		<version>2.3.0-SNAPSHOT</version>
+		<version>2.4.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>alfa-xdomea</artifactId>
diff --git a/alfa-xdomea/src/main/java/de/ozgcloud/alfa/export/ExportService.java b/alfa-xdomea/src/main/java/de/ozgcloud/alfa/export/ExportService.java
index 95015618af8cf272e6a03120e4890a5304adf5cd..215e744ec785632a082e875708dca983e90730a9 100644
--- a/alfa-xdomea/src/main/java/de/ozgcloud/alfa/export/ExportService.java
+++ b/alfa-xdomea/src/main/java/de/ozgcloud/alfa/export/ExportService.java
@@ -18,6 +18,7 @@ import org.springframework.stereotype.Service;
 import de.ozgcloud.alfa.common.ExportFilenameGenerator;
 import de.ozgcloud.alfa.common.file.OzgFile;
 import de.ozgcloud.alfa.file.ExportFileService;
+import de.ozgcloud.alfa.historie.ExportHistorieService;
 import de.ozgcloud.alfa.kommentar.ExportKommentarService;
 import de.ozgcloud.alfa.vorgang.Eingang;
 import de.ozgcloud.alfa.vorgang.EingangHeader;
@@ -39,6 +40,7 @@ class ExportService {
 
 	private final ExportFileService exportFileService;
 	private final ExportVorgangService exportVorgangService;
+	private final ExportHistorieService exportHistorieService;
 	private final ExportKommentarService exportKommentarService;
 
 	public void writeExport(String vorgangId, String filenameId, OutputStream out) {
@@ -58,6 +60,7 @@ class ExportService {
 				.withAktenzeichen(exportVorgangService.createAkteType(vorgang))
 				.withRepresentations(exportFileService.createDokumentTypes(representations, formEngineName).toList())
 				.withAttachments(exportFileService.createDokumentTypes(attachments, formEngineName).toList())
+				.withHistorie(exportHistorieService.createHistorienProtokollInformationTypes(vorgang).toList())
 				.withKommentare(exportKommentarService.createDokumentTypes(vorgang).toList())
 				.build();
 		var exportFiles = Stream.concat(representations.stream(), attachments.stream()).toList();
diff --git a/alfa-xdomea/src/main/java/de/ozgcloud/alfa/export/XdomeaNachrichtBuilder.java b/alfa-xdomea/src/main/java/de/ozgcloud/alfa/export/XdomeaNachrichtBuilder.java
index 1f0e6db45378dda2b1e8a2d07d99a071f03fb821..e7752d7a65b88c8b20350d4429b7e1f6beaeb24f 100644
--- a/alfa-xdomea/src/main/java/de/ozgcloud/alfa/export/XdomeaNachrichtBuilder.java
+++ b/alfa-xdomea/src/main/java/de/ozgcloud/alfa/export/XdomeaNachrichtBuilder.java
@@ -7,6 +7,7 @@ import de.xoev.xdomea.AbgabeAbgabe0401;
 import de.xoev.xdomea.AbgabeAbgabe0401.Schriftgutobjekt;
 import de.xoev.xdomea.AkteType;
 import de.xoev.xdomea.DokumentType;
+import de.xoev.xdomea.HistorienProtokollInformationType;
 import de.xoev.xdomea.NkAbgabeType;
 import de.xoev.xdomea.VorgangType;
 import lombok.AccessLevel;
@@ -17,10 +18,11 @@ class XdomeaNachrichtBuilder {
 
 	private VorgangType vorgang;
 	private NkAbgabeType kopf;
+	private AkteType aktenzeichen;
 	private List<DokumentType> representations = Collections.emptyList();
 	private List<DokumentType> attachments = Collections.emptyList();
 	private List<DokumentType> kommentare = Collections.emptyList();
-	private AkteType aktenzeichen;
+	private List<HistorienProtokollInformationType> historie = Collections.emptyList();
 
 	public static XdomeaNachrichtBuilder builder() {
 		return new XdomeaNachrichtBuilder();
@@ -51,18 +53,23 @@ class XdomeaNachrichtBuilder {
 		return this;
 	}
 
+	public XdomeaNachrichtBuilder withHistorie(List<HistorienProtokollInformationType> historie) {
+		this.historie = historie;
+		return this;
+	}
+
 	public XdomeaNachrichtBuilder withKommentare(List<DokumentType> kommentare) {
 		this.kommentare = kommentare;
 		return this;
 	}
 
 	public AbgabeAbgabe0401 build() {
-		var schriftgutobjekt = createSchriftgutobjekt();
 		addVorgangDokumente();
+		addVorgangChangeHistory();
 
 		var abgabeType = new AbgabeAbgabe0401();
 		abgabeType.setKopf(kopf);
-		abgabeType.getSchriftgutobjekt().add(schriftgutobjekt);
+		abgabeType.getSchriftgutobjekt().add(createSchriftgutobjekt());
 		return abgabeType;
 	}
 
@@ -79,4 +86,7 @@ class XdomeaNachrichtBuilder {
 		kommentare.forEach(vorgang.getDokument()::add);
 	}
 
+	void addVorgangChangeHistory() {
+		historie.forEach(vorgang.getHistorienProtokollInformation()::add);
+	}
 }
diff --git a/alfa-xdomea/src/main/java/de/ozgcloud/alfa/historie/ExportHistorieService.java b/alfa-xdomea/src/main/java/de/ozgcloud/alfa/historie/ExportHistorieService.java
index 547d58ab0e021d411c1c9e16af3db8096906febe..05d4f260244a2ea8569e203c9f1f2bbed19319bc 100644
--- a/alfa-xdomea/src/main/java/de/ozgcloud/alfa/historie/ExportHistorieService.java
+++ b/alfa-xdomea/src/main/java/de/ozgcloud/alfa/historie/ExportHistorieService.java
@@ -1,10 +1,61 @@
 package de.ozgcloud.alfa.historie;
 
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 
+import de.ozgcloud.alfa.common.DateConverter;
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import de.ozgcloud.alfa.vorgang.VorgangWithEingang;
+import de.xoev.xdomea.HistorienProtokollInformationType;
 import lombok.RequiredArgsConstructor;
 
 @RequiredArgsConstructor
 @Service
 public class ExportHistorieService {
+
+	private final VorgangChangeHistoryService vorgangChangeHistoryService;
+
+	public Stream<HistorienProtokollInformationType> createHistorienProtokollInformationTypes(VorgangWithEingang vorgang) {
+		var history = vorgangChangeHistoryService.createVorgangChangeHistory(vorgang);
+		return Stream.of(
+						history.getStatusChangeHistory().stream().map(this::createHistorienProtokollInformationType),
+						history.getAktenzeichenChangeHistory().stream().map(this::createHistorienProtokollInformationType),
+						history.getAssignedUserChangeHistory().stream().map(this::createHistorienProtokollInformationType))
+				.flatMap(Function.identity());
+	}
+
+	HistorienProtokollInformationType createHistorienProtokollInformationType(VorgangChange vorgangChange) {
+		var historienProtokollInformationType = new HistorienProtokollInformationType();
+		historienProtokollInformationType.setMetadatumAlterWert(createValueBeforeChange(vorgangChange));
+		historienProtokollInformationType.setMetadatumNeuerWert(createValueAfterChange(vorgangChange));
+		historienProtokollInformationType.setAkteur(createAkteur(vorgangChange));
+		historienProtokollInformationType.setDatumUhrzeit(DateConverter.toXmlGregorianCalendar(vorgangChange.getFinishedAt()));
+		historienProtokollInformationType.setAktion(vorgangChange.getOrder().name());
+		return historienProtokollInformationType;
+	}
+
+	String createAkteur(VorgangChange vorgangChange) {
+		return appendOrganisationseinheitenID(vorgangChange.getAuthorFullName(), vorgangChange.getOrganisationseinheitenID());
+	}
+
+	String createValueBeforeChange(VorgangChange vorgangChange) {
+		if (vorgangChange.getOrder() == CommandOrder.ASSIGN_USER) {
+			return appendOrganisationseinheitenID(vorgangChange.getValueBeforeChange(), vorgangChange.getOrganisationseinheitenID());
+		}
+		return vorgangChange.getValueBeforeChange();
+	}
+
+	String createValueAfterChange(VorgangChange vorgangChange) {
+		if (vorgangChange.getOrder() == CommandOrder.ASSIGN_USER) {
+			return appendOrganisationseinheitenID(vorgangChange.getValueAfterChange(), vorgangChange.getOrganisationseinheitenID());
+		}
+		return vorgangChange.getValueAfterChange();
+	}
+
+	String appendOrganisationseinheitenID(String text, String organisationseinheitenID) {
+		 return StringUtils.isNotBlank(text) ? text + "; " + organisationseinheitenID : StringUtils.EMPTY;
+	}
 }
diff --git a/alfa-xdomea/src/main/java/de/ozgcloud/alfa/kommentar/DateiformatCode.java b/alfa-xdomea/src/main/java/de/ozgcloud/alfa/kommentar/DateiformatCode.java
index e1ad860eaea329cfadb1e112eab9f5a8cbb357b1..046244d65868026dd85a0e84fcd830ea241f9e92 100644
--- a/alfa-xdomea/src/main/java/de/ozgcloud/alfa/kommentar/DateiformatCode.java
+++ b/alfa-xdomea/src/main/java/de/ozgcloud/alfa/kommentar/DateiformatCode.java
@@ -40,6 +40,7 @@ public final class DateiformatCode {
 		mimeTypesMapping.put("application/vnd.ms-works", Map.of("wps", "025"));
 		mimeTypesMapping.put("application/vnd.ms-excel", Map.of("xlc", "026", "xlm", "027", "xls", "028", "xlw", "029", "xlt", "034"));
 		mimeTypesMapping.put("application/xml", Map.of("xml", "030", "xsd", "031"));
+		mimeTypesMapping.put("text/xml", Map.of("xml", "030", "xsd", "031"));
 		mimeTypesMapping.put("application/vnd.openxmlformats-officedocument.wordprocessingml.document", Map.of("docx", "035"));
 		mimeTypesMapping.put("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", Map.of("xlsx", "036"));
 		mimeTypesMapping.put("application/vnd.openxmlformats-officedocument.presentationml.presentation", Map.of("pptx", "037"));
diff --git a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/ExportServiceITCase.java b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/ExportServiceITCase.java
index 5707c63e0fcb2168c4b5792be7ec6de3c40482ad..2f597713f7b883e44e032aeba21470f99f79dc04 100644
--- a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/ExportServiceITCase.java
+++ b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/ExportServiceITCase.java
@@ -21,6 +21,7 @@ import de.ozgcloud.alfa.common.binaryfile.FileId;
 import de.ozgcloud.alfa.common.file.OzgFile;
 import de.ozgcloud.alfa.common.file.OzgFileTestFactory;
 import de.ozgcloud.alfa.file.ExportFileService;
+import de.ozgcloud.alfa.historie.ExportHistorieService;
 import de.ozgcloud.alfa.kommentar.ExportKommentarService;
 import de.ozgcloud.alfa.vorgang.ExportVorgangService;
 import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory;
@@ -36,6 +37,8 @@ class ExportServiceITCase {
 	@SpyBean
 	private ExportVorgangService exportVorgangService;
 	@MockBean
+	private ExportHistorieService exportHistorieService;
+	@MockBean
 	private ExportKommentarService exportKommentarService;
 	@Autowired
 	private ExportService exportService;
@@ -51,6 +54,7 @@ class ExportServiceITCase {
 			doReturn(Stream.of(createOzgFile())).when(exportFileService).getRepresentations(vorgang);
 			doReturn(Stream.of(createOzgFile())).when(exportFileService).getAttachments(vorgang);
 			doNothing().when(exportFileService).writeOzgFile(any(), any());
+			when(exportHistorieService.createHistorienProtokollInformationTypes(vorgang)).thenReturn(Stream.empty());
 			when(exportKommentarService.createDokumentTypes(vorgang)).thenReturn(Stream.empty());
 		}
 
diff --git a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/ExportServiceTest.java b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/ExportServiceTest.java
index ff7fb7bdd34403759f8ca946d467d3b59ba1654e..8a2abcc4ee045bc64c47870ad5bbab1d23365ca1 100644
--- a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/ExportServiceTest.java
+++ b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/ExportServiceTest.java
@@ -31,6 +31,7 @@ import de.ozgcloud.alfa.common.TestUtils;
 import de.ozgcloud.alfa.common.file.OzgFile;
 import de.ozgcloud.alfa.common.file.OzgFileTestFactory;
 import de.ozgcloud.alfa.file.ExportFileService;
+import de.ozgcloud.alfa.historie.ExportHistorieService;
 import de.ozgcloud.alfa.kommentar.ExportKommentarService;
 import de.ozgcloud.alfa.vorgang.EingangHeaderTestFactory;
 import de.ozgcloud.alfa.vorgang.EingangTestFactory;
@@ -43,6 +44,7 @@ import de.ozgcloud.common.errorhandling.TechnicalException;
 import de.xoev.xdomea.AbgabeAbgabe0401;
 import de.xoev.xdomea.AkteType;
 import de.xoev.xdomea.DokumentType;
+import de.xoev.xdomea.HistorienProtokollInformationType;
 import de.xoev.xdomea.NkAbgabeType;
 import de.xoev.xdomea.VorgangType;
 import lombok.SneakyThrows;
@@ -61,6 +63,8 @@ class ExportServiceTest {
 	@Mock
 	private ExportVorgangService exportVorgangService;
 	@Mock
+	private ExportHistorieService exportHistorieService;
+	@Mock
 	private ExportKommentarService exportKommentarService;
 
 	@DisplayName("Write exportToXdomea")
@@ -123,6 +127,8 @@ class ExportServiceTest {
 		private final List<OzgFile> attachments = List.of(OzgFileTestFactory.create());
 		private final List<DokumentType> representationsDokumentTypes = List.of(DokumentTypeTestFactory.create());
 		private final List<DokumentType> attachmentsDokumentTypes = List.of(DokumentTypeTestFactory.create());
+		private final List<HistorienProtokollInformationType> historienProtokollInformationTypes = List.of(
+				HistorienProtokollInformationTypeTestFactory.create());
 		private final List<DokumentType> kommentareDokumentTypes = List.of(DokumentTypeTestFactory.create());
 
 		@Mock
@@ -133,6 +139,7 @@ class ExportServiceTest {
 			setUpVorgangService();
 			setUpXdomeaNachrichtBuilder();
 			setUpExportFileService();
+			setUpExportHistorieService();
 
 			doReturn(FILE_NAME).when(service).buildXmlFilename(FILENAME_ID);
 			doReturn(EingangHeaderTestFactory.FORM_ENGINE_NAME).when(service).getFormEngineName(vorgang);
@@ -153,6 +160,7 @@ class ExportServiceTest {
 			when(xdomeaNachrichtBuilder.withAktenzeichen(akteType)).thenReturn(xdomeaNachrichtBuilder);
 			when(xdomeaNachrichtBuilder.withRepresentations(representationsDokumentTypes)).thenReturn(xdomeaNachrichtBuilder);
 			when(xdomeaNachrichtBuilder.withAttachments(attachmentsDokumentTypes)).thenReturn(xdomeaNachrichtBuilder);
+			when(xdomeaNachrichtBuilder.withHistorie(historienProtokollInformationTypes)).thenReturn(xdomeaNachrichtBuilder);
 			when(xdomeaNachrichtBuilder.withKommentare(kommentareDokumentTypes)).thenReturn(xdomeaNachrichtBuilder);
 			xdomeaNachrichtBuilderMockedStatic.when(XdomeaNachrichtBuilder::builder).thenReturn(xdomeaNachrichtBuilder);
 			when(xdomeaNachrichtBuilder.build()).thenReturn(abgabe);
@@ -170,6 +178,11 @@ class ExportServiceTest {
 			mockStreamToList(kommentareDokumentTypes, stream -> when(exportKommentarService.createDokumentTypes(vorgang)).thenReturn(stream));
 		}
 
+		private void setUpExportHistorieService() {
+			mockStreamToList(historienProtokollInformationTypes,
+					stream -> when(exportHistorieService.createHistorienProtokollInformationTypes(vorgang)).thenReturn(stream));
+		}
+
 		@AfterEach
 		void tearDown() {
 			xdomeaNachrichtBuilderMockedStatic.close();
@@ -273,6 +286,13 @@ class ExportServiceTest {
 			verify(xdomeaNachrichtBuilder).withAktenzeichen(akteType);
 		}
 
+		@Test
+		void shouldSetHistorie() {
+			callService();
+
+			verify(xdomeaNachrichtBuilder).withHistorie(historienProtokollInformationTypes);
+		}
+
 		@Test
 		void shouldCreateAbgabe() {
 			callService();
diff --git a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/HistorienProtokollInformationTypeTestFactory.java b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/HistorienProtokollInformationTypeTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..ddf72dde4fcb9a47ddd3e1275794826552a6e2ac
--- /dev/null
+++ b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/HistorienProtokollInformationTypeTestFactory.java
@@ -0,0 +1,11 @@
+package de.ozgcloud.alfa.export;
+
+import de.xoev.xdomea.HistorienProtokollInformationType;
+
+public class HistorienProtokollInformationTypeTestFactory {
+
+	public static HistorienProtokollInformationType create() {
+		return new HistorienProtokollInformationType();
+	}
+
+}
diff --git a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/XdomeaNachrichtBuilderTest.java b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/XdomeaNachrichtBuilderTest.java
index 9a71c4d90605dc701a3d044fcaa954e7022abefb..0a0b7be258bfe4ff9810f2bfc9b10bc8b6158717 100644
--- a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/XdomeaNachrichtBuilderTest.java
+++ b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/XdomeaNachrichtBuilderTest.java
@@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test;
 
 import de.ozgcloud.alfa.vorgang.VorgangTypeTestFactory;
 import de.xoev.xdomea.DokumentType;
+import de.xoev.xdomea.HistorienProtokollInformationType;
 import de.xoev.xdomea.NkAbgabeType;
 import de.xoev.xdomea.VorgangType;
 
@@ -17,6 +18,8 @@ class XdomeaNachrichtBuilderTest {
 	private final VorgangType vorgangType = VorgangTypeTestFactory.create();
 	private final List<DokumentType> representations = List.of(DokumentTypeTestFactory.create(), DokumentTypeTestFactory.create());
 	private final List<DokumentType> attachments = List.of(DokumentTypeTestFactory.create(), DokumentTypeTestFactory.create());
+	private final List<HistorienProtokollInformationType> historie = List.of(HistorienProtokollInformationTypeTestFactory.create(),
+			HistorienProtokollInformationTypeTestFactory.create());
 	private final List<DokumentType> kommentare = List.of(DokumentTypeTestFactory.create(), DokumentTypeTestFactory.create());
 	private final XdomeaNachrichtBuilder builder = XdomeaNachrichtBuilder.builder().withVorgang(vorgangType);
 
@@ -55,6 +58,20 @@ class XdomeaNachrichtBuilderTest {
 		assertThat(abgabeType.getSchriftgutobjekt().get(0).getVorgang().getDokument()).isEqualTo(attachments);
 	}
 
+	@Test
+	void shouldAddHistorie() {
+		var abgabeType = builder.withHistorie(historie).build();
+
+		assertThat(abgabeType.getSchriftgutobjekt().get(0).getVorgang().getHistorienProtokollInformation()).isEqualTo(historie);
+	}
+
+	@Test
+	void shouldNotAddHistorie() {
+		var abgabeType = builder.build();
+
+		assertThat(abgabeType.getSchriftgutobjekt().get(0).getVorgang().getHistorienProtokollInformation()).isEmpty();
+	}
+
 	@Test
 	void shouldAddKommentare() {
 		var abgabeType = builder.withKommentare(kommentare).build();
diff --git a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/historie/ExportHistorieServiceTest.java b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/historie/ExportHistorieServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3482a58b25c0efd0a7f43d438d137555cbae9a76
--- /dev/null
+++ b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/historie/ExportHistorieServiceTest.java
@@ -0,0 +1,288 @@
+package de.ozgcloud.alfa.historie;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.alfa.common.DateConverter;
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import de.ozgcloud.alfa.export.HistorienProtokollInformationTypeTestFactory;
+import de.ozgcloud.alfa.vorgang.VorgangWithEingang;
+import de.ozgcloud.alfa.vorgang.VorgangWithEingangTestFactory;
+import de.xoev.xdomea.HistorienProtokollInformationType;
+
+class ExportHistorieServiceTest {
+
+	@Spy
+	@InjectMocks
+	private ExportHistorieService service;
+
+	@Mock
+	private VorgangChangeHistoryService vorgangChangeHistoryService;
+
+	@Nested
+	class TestCreateHistorienProtokollInformationTypes {
+
+		private final VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create();
+		private final VorgangChangeHistory history = VorgangChangeHistoryTestFactory.create();
+		private final HistorienProtokollInformationType statusChangeHistorienProtokollInformationType = HistorienProtokollInformationTypeTestFactory.create();
+		private final HistorienProtokollInformationType aktenzeichenChangeHistorienProtokollInformationType = HistorienProtokollInformationTypeTestFactory.create();
+		private final HistorienProtokollInformationType assignUserChangeHistorienProtokollInformationType = HistorienProtokollInformationTypeTestFactory.create();
+
+		@BeforeEach
+		void setUp() {
+			when(vorgangChangeHistoryService.createVorgangChangeHistory(vorgang)).thenReturn(history);
+			doReturn(statusChangeHistorienProtokollInformationType).when(service)
+					.createHistorienProtokollInformationType(VorgangChangeHistoryTestFactory.STATUS_CHANGE_HISTORY.get(0));
+			doReturn(aktenzeichenChangeHistorienProtokollInformationType).when(service)
+					.createHistorienProtokollInformationType(VorgangChangeHistoryTestFactory.AKTENZEICHEN_CHANGE_HISTORY.get(0));
+			doReturn(assignUserChangeHistorienProtokollInformationType).when(service)
+					.createHistorienProtokollInformationType(VorgangChangeHistoryTestFactory.ASSIGNED_USER_CHANGE_HISTORY.get(0));
+		}
+
+		@Test
+		void shouldGetVorgangChangeHistory() {
+			service.createHistorienProtokollInformationTypes(vorgang).toList();
+
+			verify(vorgangChangeHistoryService).createVorgangChangeHistory(vorgang);
+		}
+
+		@Test
+		void shouldCreateStatusChangeHistory() {
+			service.createHistorienProtokollInformationTypes(vorgang).toList();
+
+			verify(service).createHistorienProtokollInformationType(VorgangChangeHistoryTestFactory.STATUS_CHANGE_HISTORY.get(0));
+		}
+
+		@Test
+		void shouldReturnStatusChangeHistorienProtokollInformationType() {
+			var historienProtokollInformationTypes = service.createHistorienProtokollInformationTypes(vorgang);
+
+			assertThat(historienProtokollInformationTypes).contains(statusChangeHistorienProtokollInformationType);
+		}
+
+		@Test
+		void shouldCreateAktenzeichenChangeHistory() {
+			service.createHistorienProtokollInformationTypes(vorgang).toList();
+
+			verify(service).createHistorienProtokollInformationType(VorgangChangeHistoryTestFactory.AKTENZEICHEN_CHANGE_HISTORY.get(0));
+		}
+
+		@Test
+		void shouldReturnAktenzeichenHistorienProtokollInformationType() {
+			var historienProtokollInformationTypes = service.createHistorienProtokollInformationTypes(vorgang);
+
+			assertThat(historienProtokollInformationTypes).contains(aktenzeichenChangeHistorienProtokollInformationType);
+		}
+
+		@Test
+		void shouldCreateAssignUserChangeHistory() {
+			service.createHistorienProtokollInformationTypes(vorgang).toList();
+
+			verify(service).createHistorienProtokollInformationType(VorgangChangeHistoryTestFactory.ASSIGNED_USER_CHANGE_HISTORY.get(0));
+		}
+
+		@Test
+		void shouldReturnAssignUserHistorienProtokollInformationType() {
+			var historienProtokollInformationTypes = service.createHistorienProtokollInformationTypes(vorgang);
+
+			assertThat(historienProtokollInformationTypes).contains(assignUserChangeHistorienProtokollInformationType);
+		}
+	}
+
+	@Nested
+	class TestCreateHistorienProtokollInformationType {
+
+		private static final String CREATED_VALUE_BEFORE_CHANGE =
+				VorgangChangeTestFactory.VALUE_BEFORE_CHANGE + VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID;
+		private static final String CREATED_VALUE_AFTER_CHANGE =
+				VorgangChangeTestFactory.VALUE_AFTER_CHANGE + VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID;
+		private final VorgangChange vorgangChange = VorgangChangeTestFactory.create();
+
+		@BeforeEach
+		void setUp() {
+			doReturn(VorgangChangeTestFactory.CREATED_BY_NAME).when(service).createAkteur(vorgangChange);
+			doReturn(CREATED_VALUE_BEFORE_CHANGE).when(service).createValueBeforeChange(vorgangChange);
+			doReturn(CREATED_VALUE_AFTER_CHANGE).when(service).createValueAfterChange(vorgangChange);
+		}
+
+		@Test
+		void shouldCreateValueBeforeChange() {
+			service.createHistorienProtokollInformationType(vorgangChange);
+
+			verify(service).createValueBeforeChange(vorgangChange);
+		}
+
+		@Test
+		void shouldHaveAlterWert() {
+			var created = service.createHistorienProtokollInformationType(vorgangChange);
+
+			assertThat(created.getMetadatumAlterWert()).isEqualTo(CREATED_VALUE_BEFORE_CHANGE);
+		}
+
+		@Test
+		void shouldCreateValueAfterChange() {
+			service.createHistorienProtokollInformationType(vorgangChange);
+
+			verify(service).createValueAfterChange(vorgangChange);
+		}
+
+		@Test
+		void shouldHaveNeuerWert() {
+			var created = service.createHistorienProtokollInformationType(vorgangChange);
+
+			assertThat(created.getMetadatumNeuerWert()).isEqualTo(CREATED_VALUE_AFTER_CHANGE);
+		}
+
+		@Test
+		void shouldHaveAkteur() {
+			var created = service.createHistorienProtokollInformationType(vorgangChange);
+
+			assertThat(created.getAkteur()).isEqualTo(VorgangChangeTestFactory.CREATED_BY_NAME);
+		}
+
+		@Test
+		void shouldHaveDatumUhrzeit() {
+			var created = service.createHistorienProtokollInformationType(vorgangChange);
+
+			assertThat(created.getDatumUhrzeit()).isEqualTo(DateConverter.toXmlGregorianCalendar(VorgangChangeTestFactory.FINISHED_AT));
+		}
+
+		@Test
+		void shouldHaveAktion() {
+			var created = service.createHistorienProtokollInformationType(vorgangChange);
+
+			assertThat(created.getAktion()).isEqualTo(VorgangChangeTestFactory.ORDER.name());
+		}
+
+		@Test
+		void shouldCreateAkteur() {
+			service.createHistorienProtokollInformationType(vorgangChange);
+
+			verify(service).createAkteur(vorgangChange);
+		}
+	}
+
+	@Nested
+	class TestCreateAkteur {
+
+		private static final String VALUE_FOR_AKTEUR = LoremIpsum.getInstance().getWords(2);
+
+		@Test
+		void shouldGetValueForAkteur() {
+			service.createAkteur(VorgangChangeTestFactory.create());
+
+			verify(service).appendOrganisationseinheitenID(VorgangChangeTestFactory.CREATED_BY_NAME,
+					VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID);
+		}
+
+		@Test
+		void shouldReturnValueForAkteur() {
+			doReturn(VALUE_FOR_AKTEUR).when(service)
+					.appendOrganisationseinheitenID(VorgangChangeTestFactory.CREATED_BY_NAME, VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID);
+
+			var akteur = service.createAkteur(VorgangChangeTestFactory.create());
+
+			assertThat(akteur).isEqualTo(VALUE_FOR_AKTEUR);
+		}
+	}
+
+	@Nested
+	class TestCreateValueBeforeChange {
+
+		private static final String ASSIGNED_USER = LoremIpsum.getInstance().getWords(2);
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = "ASSIGN_USER")
+		void shouldReturnValueForOrder(CommandOrder order) {
+			var value = service.createValueBeforeChange(VorgangChangeTestFactory.createBuilder().order(order).build());
+
+			assertThat(value).isEqualTo(VorgangChangeTestFactory.VALUE_BEFORE_CHANGE);
+		}
+
+		@Test
+		void shouldGetValueForAssignUserOrder() {
+			service.createValueBeforeChange(VorgangChangeTestFactory.createBuilder().order(CommandOrder.ASSIGN_USER).build());
+
+			verify(service).appendOrganisationseinheitenID(VorgangChangeTestFactory.VALUE_BEFORE_CHANGE,
+					VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID);
+		}
+
+		@Test
+		void shouldReturnValueForAssignUserOrder() {
+			doReturn(ASSIGNED_USER).when(service)
+					.appendOrganisationseinheitenID(VorgangChangeTestFactory.VALUE_BEFORE_CHANGE, VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID);
+
+			var value = service.createValueBeforeChange(VorgangChangeTestFactory.createBuilder().order(CommandOrder.ASSIGN_USER).build());
+
+			assertThat(value).isEqualTo(ASSIGNED_USER);
+		}
+	}
+
+	@Nested
+	class TestCreateValueAfterChange {
+
+		private static final String ASSIGNED_USER = LoremIpsum.getInstance().getWords(2);
+
+		@ParameterizedTest
+		@EnumSource(mode = Mode.EXCLUDE, names = "ASSIGN_USER")
+		void shouldReturnValueForOrder(CommandOrder order) {
+			var value = service.createValueAfterChange(VorgangChangeTestFactory.createBuilder().order(order).build());
+
+			assertThat(value).isEqualTo(VorgangChangeTestFactory.VALUE_AFTER_CHANGE);
+		}
+
+		@Test
+		void shouldGetValueForAssignUserOrder() {
+			service.createValueAfterChange(VorgangChangeTestFactory.createBuilder().order(CommandOrder.ASSIGN_USER).build());
+
+			verify(service).appendOrganisationseinheitenID(VorgangChangeTestFactory.VALUE_AFTER_CHANGE,
+					VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID);
+		}
+
+		@Test
+		void shouldReturnValueForAssignUserOrder() {
+			doReturn(ASSIGNED_USER).when(service)
+					.appendOrganisationseinheitenID(VorgangChangeTestFactory.VALUE_AFTER_CHANGE, VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID);
+
+			var value = service.createValueAfterChange(VorgangChangeTestFactory.createBuilder().order(CommandOrder.ASSIGN_USER).build());
+
+			assertThat(value).isEqualTo(ASSIGNED_USER);
+		}
+	}
+
+	@Nested
+	class TestAppendOrganisationseinheitenID {
+
+		private static final String TEXT = LoremIpsum.getInstance().getWords(2);
+
+		@ParameterizedTest
+		@NullAndEmptySource
+		void shouldReturnEmpty(String assignedUser) {
+			var value = service.appendOrganisationseinheitenID(assignedUser, VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID);
+
+			assertThat(value).isEmpty();
+		}
+
+		@Test
+		void shouldAppendOrganisationseinheitenID() {
+			var value = service.appendOrganisationseinheitenID(TEXT, VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID);
+
+			assertThat(value).isEqualTo(
+					String.format("%s; %s", TEXT, VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID));
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/kommentar/DateiformatCodeTest.java b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/kommentar/DateiformatCodeTest.java
index 0eabf7f3eae85bd16d2ad5af6dce9e2f01e012e7..13f53f78e8b2e88120de621cbc3b00b754c237f3 100644
--- a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/kommentar/DateiformatCodeTest.java
+++ b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/kommentar/DateiformatCodeTest.java
@@ -6,6 +6,7 @@ import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
 
 class DateiformatCodeTest {
 
@@ -56,6 +57,14 @@ class DateiformatCodeTest {
 
 			assertThat(code).isEqualTo(DateiformatCode.SONSTIGES_CODE);
 		}
+
+		@ParameterizedTest
+		@ValueSource(strings = { "application/xml", "text/xml" })
+		void shouldReturnXml(String mimeType) {
+			var code = DateiformatCode.getXdomeaCode(mimeType, "xml");
+
+			assertThat(code).isEqualTo("030");
+		}
 	}
 
 }
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 2a72ba53e8e76705ce7785e948bb9fb9711156ae..4f00d84c16a1cc6f981d55ab11f3d1d05e2eb9fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,12 +30,12 @@
 	<parent>
 		<groupId>de.ozgcloud.common</groupId>
 		<artifactId>ozgcloud-common-parent</artifactId>
-		<version>3.0.0</version>
+		<version>3.0.1</version>
 	</parent>
 
 	<groupId>de.ozgcloud.alfa</groupId>
 	<artifactId>alfa</artifactId>
-	<version>2.3.0-SNAPSHOT</version>
+	<version>2.4.0-SNAPSHOT</version>
 	<name>Alfa Parent</name>
 	<packaging>pom</packaging>
 
@@ -50,9 +50,9 @@
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 
-		<vorgang-manager.version>2.2.0-SNAPSHOT</vorgang-manager.version>
-		<ozgcloud-common-pdf.version>3.0.0</ozgcloud-common-pdf.version>
-		<user-manager.version>2.0.0</user-manager.version>
+		<vorgang-manager.version>2.3.0</vorgang-manager.version>
+		<ozgcloud-common-pdf.version>3.0.1</ozgcloud-common-pdf.version>
+		<user-manager.version>2.2.0</user-manager.version>
 
 		<!-- TODO: die Version über ozgcloud-common ziehen -->
 		<jjwt.version>0.11.5</jjwt.version>
@@ -159,4 +159,4 @@
 			<url>https://nexus.ozg-sh.de/repository/ozg-snapshots/</url>
 		</snapshotRepository>
 	</distributionManagement>
-</project>
+</project>
\ No newline at end of file
diff --git a/src/main/helm/templates/_helpers.tpl b/src/main/helm/templates/_helpers.tpl
index 604a5f5280aa5f86b7ce69a9b4351b1c84194565..b47258a81c6e54873a7928527ed8f03ac6f2b556 100644
--- a/src/main/helm/templates/_helpers.tpl
+++ b/src/main/helm/templates/_helpers.tpl
@@ -1,44 +1,43 @@
 {{/* vim: set filetype=mustache: */}}
 
-{{/* Truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec) */}}
-{{/* Name */}}
-{{- define "app.name" -}}
-{{- default .Release.Name | toString | trunc 63 | trimSuffix "-" -}}
-{{- end -}}
-
+{{/* error check 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec) */}}
 {{/* Namespace */}}
 {{- define "app.namespace" -}}
-{{- default .Release.Namespace | toString | trunc 63 | trimSuffix "-" -}}
+{{- if gt (len (.Release.Namespace)) 63 -}}
+{{- fail (printf ".Release.Namespace %s ist zu lang (max. 63 Zeichen)" .Release.Namespace) -}}
 {{- end -}}
-
-{{/* Version */}}
-{{- define "app.version" -}}
-{{- default .Chart.Version | toString | trunc 63 | trimSuffix "-" -}}
+{{ printf "%s" .Release.Namespace }}
 {{- end -}}
 
 {{/* Chart: Name + Version */}}
 {{- define "app.chart" -}}
-{{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- if gt (len (printf "%s-%s" .Chart.Name .Chart.Version)) 63 -}}
+{{- fail (printf ".Chart.Name-.Chart.Version %s-%s ist zu lang (max. 63 Zeichen)" .Chart.Name .Chart.Version) -}}
+{{- end -}}
+{{ printf "%s-%s" .Chart.Name .Chart.Version }}
 {{- end -}}
 
 {{/* Managed-by -> On Helm, this value is always Helm */}}
 {{- define "app.managedBy" -}}
-{{- default .Release.Service | toString | trunc 63 | trimSuffix "-" -}}
+{{- if gt (len (.Release.Service)) 63 -}}
+{{- fail (printf ".Release.Service %s ist zu lang (max. 63 Zeichen)" .Release.Service) -}}
+{{- end -}}
+{{ printf "%s" .Release.Service }}
 {{- end -}}
 
 {{/* Default Labels: Helm recommended best-practice labels https://helm.sh/docs/chart_best_practices/labels/ */}}
 {{- define "app.defaultLabels" }}
 app.kubernetes.io/instance: alfa
 app.kubernetes.io/managed-by: {{ include "app.managedBy" . }}
-app.kubernetes.io/name: {{ include "app.name" . }}
+app.kubernetes.io/name: {{ .Release.Name }}
 app.kubernetes.io/part-of: ozgcloud
-app.kubernetes.io/version: {{ include "app.version" . }}
+app.kubernetes.io/version: {{ .Chart.Version }}
 app.kubernetes.io/namespace: {{ include "app.namespace" . }}
 helm.sh/chart: {{ include "app.chart" . }}
 {{- end -}}
 
 {{- define "app.matchLabels" }}
-app.kubernetes.io/name: {{ include "app.name" . }}
+app.kubernetes.io/name: {{ .Release.Name }}
 app.kubernetes.io/namespace: {{ include "app.namespace" . }}
 {{- end -}}
 
diff --git a/src/main/helm/templates/deployment.yaml b/src/main/helm/templates/deployment.yaml
index f3c62e8b65a11462fc2ef24f14ee5b311cf27fdd..5272422eb6c47df52e61a729940d6b7b8dadbefc 100644
--- a/src/main/helm/templates/deployment.yaml
+++ b/src/main/helm/templates/deployment.yaml
@@ -25,7 +25,7 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: {{ include "app.name" . }}
+  name: {{ .Release.Name }}
   namespace: {{ include "app.namespace" . }}
   labels:
     {{- include "app.defaultLabels" . | indent 4 }}
@@ -56,7 +56,7 @@ spec:
         whenUnsatisfiable: ScheduleAnyway
         labelSelector:
           matchLabels:
-            app.kubernetes.io/name: {{ include "app.name" . }}
+            app.kubernetes.io/name: {{ .Release.Name }}
       affinity:
         nodeAffinity:
           requiredDuringSchedulingIgnoredDuringExecution:
diff --git a/src/main/helm/templates/ingress.yaml b/src/main/helm/templates/ingress.yaml
index a4b5ccf04cc1ad289393b35dbeba455a6e9cf2df..2ed1f8d3231251c2cd051f21acce6c6c30ba5ca5 100644
--- a/src/main/helm/templates/ingress.yaml
+++ b/src/main/helm/templates/ingress.yaml
@@ -36,7 +36,7 @@ metadata:
     cert-manager.io/cluster-issuer: letsencrypt-prod
     {{- end }}
     nginx.ingress.kubernetes.io/proxy-body-size: 42m
-  name: {{ include "app.name" . }}
+  name: {{ .Release.Name }}
   namespace: {{ include "app.namespace" . }}
 spec:
   {{- if and (.Values.ingress).className (ne (.Values).cluster_env "dataport") }}
@@ -49,7 +49,7 @@ spec:
               service:
                 port:
                   number: 8080
-                name: {{ include "app.name" . }}
+                name: {{ .Release.Name }}
             path: ''
             pathType: ImplementationSpecific
       host: {{ include "app.baseDomain" . }}
@@ -59,5 +59,5 @@ spec:
       {{- if (.Values.ingress).tlsSecretName }}
       secretName: {{ (.Values.ingress).tlsSecretName }}
       {{- else if ne (.Values).cluster_env "dataport" }}
-      secretName: {{ .Values.ozgcloud.bezeichner }}-{{ include "app.name" . }}-tls
+      secretName: {{ .Values.ozgcloud.bezeichner }}-{{ .Release.Name }}-tls
       {{- end }}
\ No newline at end of file
diff --git a/src/main/helm/templates/service.yaml b/src/main/helm/templates/service.yaml
index a4e5af637451bc9bec57890ac033cdb94e2d941a..b1dbeff62cad6a001b808144834bf5ace1185d58 100644
--- a/src/main/helm/templates/service.yaml
+++ b/src/main/helm/templates/service.yaml
@@ -25,7 +25,7 @@
 apiVersion: v1
 kind: Service
 metadata:
-  name: {{ include "app.name" . }}
+  name: {{ .Release.Name }}
   namespace: {{ include "app.namespace" . }}
   labels:
     {{- include "app.defaultLabels" . | indent 4 }}
diff --git a/src/main/helm/templates/service_monitor.yaml b/src/main/helm/templates/service_monitor.yaml
index 2a5a656bbf5bb18e0aeb56b98c093ee2ab64fec2..6fb8df4dd7836be20a774ba58ba343aa9cfe632d 100644
--- a/src/main/helm/templates/service_monitor.yaml
+++ b/src/main/helm/templates/service_monitor.yaml
@@ -25,7 +25,7 @@
 apiVersion: monitoring.coreos.com/v1
 kind: ServiceMonitor
 metadata:
-  name: {{ include "app.name" . }}
+  name: {{ .Release.Name }}
   namespace: {{ include "app.namespace" . }}
   labels:
     {{- include "app.defaultLabels" . | indent 4 }}
diff --git a/src/main/helm/templates/tests/test-ingress-connection.yaml b/src/main/helm/templates/tests/test-ingress-connection.yaml
index d97aecdd4f4fee60a8e330cd98f2a077890c05a5..ded8beac5ea5c447458ab73dac698a2ef002fc6a 100644
--- a/src/main/helm/templates/tests/test-ingress-connection.yaml
+++ b/src/main/helm/templates/tests/test-ingress-connection.yaml
@@ -25,7 +25,7 @@
 apiVersion: v1
 kind: Pod
 metadata:
-  name: "{{ include "app.name" . }}-test-ingress"
+  name: "{{ .Release.Name }}-test-ingress"
   labels:
     {{- include "app.matchLabels" . | nindent 4 }}
   annotations:
diff --git a/src/main/helm/templates/tests/test-service-connection.yaml b/src/main/helm/templates/tests/test-service-connection.yaml
index aaa823ef54442491adeb2c88836f92ad53a52404..e6bcae1e9d8e11288106f39c47aa978d0cf2f225 100644
--- a/src/main/helm/templates/tests/test-service-connection.yaml
+++ b/src/main/helm/templates/tests/test-service-connection.yaml
@@ -25,7 +25,7 @@
 apiVersion: v1
 kind: Pod
 metadata:
-  name: "{{ include "app.name" . }}-test-connection"
+  name: "{{ .Release.Name }}-test-connection"
   labels:
     {{- include "app.matchLabels" . | nindent 4 }}
   annotations:
@@ -35,5 +35,5 @@ spec:
     - name: wget
       image: busybox
       command: ['wget']
-      args: ['{{ include "app.name" . }}:8080']
+      args: ['{{ .Release.Name }}:8080']
   restartPolicy: Never
diff --git a/src/test/helm/deployment_63_char_test.yaml b/src/test/helm/deployment_63_char_test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d52334f663b47d2c947bc5f8bb33c5285464b643
--- /dev/null
+++ b/src/test/helm/deployment_63_char_test.yaml
@@ -0,0 +1,62 @@
+#
+# Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
+# Ministerpräsidenten des Landes Schleswig-Holstein
+# Staatskanzlei
+# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+#
+# Lizenziert unter der EUPL, Version 1.2 oder - sobald
+# diese von der Europäischen Kommission genehmigt wurden -
+# Folgeversionen der EUPL ("Lizenz");
+# Sie dürfen dieses Werk ausschließlich gemäß
+# dieser Lizenz nutzen.
+# Eine Kopie der Lizenz finden Sie hier:
+#
+# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+#
+# Sofern nicht durch anwendbare Rechtsvorschriften
+# gefordert oder in schriftlicher Form vereinbart, wird
+# die unter der Lizenz verbreitete Software "so wie sie
+# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+# ausdrücklich oder stillschweigend - verbreitet.
+# Die sprachspezifischen Genehmigungen und Beschränkungen
+# unter der Lizenz sind dem Lizenztext zu entnehmen.
+#
+
+suite: test deyploment less than 63 chars
+release:
+  name: alfa
+  namespace: sh-helm-test
+  
+chart:
+  name: alfa
+
+templates:
+  - templates/deployment.yaml
+set:
+  ozgcloud:
+    environment: test
+    bundesland: sh
+    bezeichner: helm
+  sso:
+    serverUrl: https://sso.company.local
+  baseUrl: test.company.local
+
+tests:
+  - it: should fail on .Release.Namespace length longer than 63 characters
+    release:
+      namespace: test1234567890123123456789012345678901234567890123456789012345678901234567890123456789012345678904567890
+    asserts:
+      - failedTemplate:
+          errorMessage: .Release.Namespace test1234567890123123456789012345678901234567890123456789012345678901234567890123456789012345678904567890 ist zu lang (max. 63 Zeichen)
+  - it: should not fail on .Release.Namespace length less than 63 characters
+    asserts:
+      - notFailedTemplate: {}
+  - it: should fail on .Chart.Name-.Chart.Version length longer than 63 characters
+    chart:
+      version: 1.0-test1234567890123123456789012345678901234567890123456789012345678901234567890123456789012345678904567890
+    asserts:
+      - failedTemplate:
+          errorMessage: .Chart.Name-.Chart.Version alfa-1.0-test1234567890123123456789012345678901234567890123456789012345678901234567890123456789012345678904567890 ist zu lang (max. 63 Zeichen)
+  - it: should not fail on .Chart.Name-.Chart.Version length less than 63 characters
+    asserts:
+      - notFailedTemplate: {}
\ No newline at end of file