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..db75ab13a8f3b04a378f95dc18768c7f9df5ebed
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/StatusChangeHistoryBuilder.java
@@ -0,0 +1,77 @@
+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_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..f855cc97a4cfcadd6511c91ec289c383934830ef
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryService.java
@@ -0,0 +1,58 @@
+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.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;
+
+	public VorgangChangeHistory createVorgangChangeHistory(VorgangWithEingang vorgang) {
+		var commands = historieService.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/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..a1616f1a22a508790191315f61d77fbaa2f2b675
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/StatusChangeHistoryBuilderTest.java
@@ -0,0 +1,208 @@
+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" })
+		@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" })
+		@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..2d29753b49568b416448a85371e608dd38b7e99c
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryServiceITCase.java
@@ -0,0 +1,342 @@
+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.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 HistorieService historieService;
+	@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(historieService.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..4e1d9d4dfdc3639e69f7ea725d696aeaabf2d79d
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/historie/VorgangChangeHistoryServiceTest.java
@@ -0,0 +1,345 @@
+package de.ozgcloud.alfa.historie;
+
+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.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.VorgangHeaderTestFactory;
+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;
+
+	@Spy
+	@InjectMocks
+	private VorgangChangeHistoryService service;
+
+	@Nested
+	class TestCreateVorgangChangeHistory {
+		private final Stream<Command> commandStream = commands.stream();
+
+		@BeforeEach
+		void init() {
+			when(historieService.findFinishedCommands(VorgangHeaderTestFactory.ID)).thenReturn(commandStream);
+		}
+
+		@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-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..c2b699793e93bcdeac8a56f4e88430bd31890bca 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,57 @@
 package de.ozgcloud.alfa.historie;
 
+import java.util.function.Function;
+import java.util.stream.Stream;
+
 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 vorgangChange.getAuthorFullName() + "; " + vorgangChange.getOrganisationseinheitenID();
+	}
+
+	String createValueBeforeChange(VorgangChange vorgangChange) {
+		if (vorgangChange.getOrder() == CommandOrder.ASSIGN_USER) {
+			return vorgangChange.getValueBeforeChange() + "; " + vorgangChange.getOrganisationseinheitenID();
+		}
+		return vorgangChange.getValueBeforeChange();
+	}
+
+	String createValueAfterChange(VorgangChange vorgangChange) {
+		if (vorgangChange.getOrder() == CommandOrder.ASSIGN_USER) {
+			return vorgangChange.getValueAfterChange() + "; " + vorgangChange.getOrganisationseinheitenID();
+		}
+		return vorgangChange.getValueAfterChange();
+	}
+
 }
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..ca25dd2f4d70211b4102c874de51016340564073
--- /dev/null
+++ b/alfa-xdomea/src/test/java/de/ozgcloud/alfa/historie/ExportHistorieServiceTest.java
@@ -0,0 +1,227 @@
+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.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+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 {
+
+		@Test
+		void shouldReturnAkteur() {
+			var akteur = service.createAkteur(VorgangChangeTestFactory.create());
+
+			assertThat(akteur).isEqualTo(
+					String.format("%s; %s", VorgangChangeTestFactory.CREATED_BY_NAME, VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID));
+		}
+	}
+
+	@Nested
+	class TestCreateValueBeforeChange {
+
+		@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 shouldReturnValueForAssignUserOrder() {
+			var value = service.createValueBeforeChange(VorgangChangeTestFactory.createBuilder().order(CommandOrder.ASSIGN_USER).build());
+
+			assertThat(value).isEqualTo(
+					String.format("%s; %s", VorgangChangeTestFactory.VALUE_BEFORE_CHANGE, VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID));
+		}
+	}
+
+	@Nested
+	class TestCreateValueAfterChange {
+
+		@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 shouldReturnValueForAssignUserOrder() {
+			var value = service.createValueAfterChange(VorgangChangeTestFactory.createBuilder().order(CommandOrder.ASSIGN_USER).build());
+
+			assertThat(value).isEqualTo(
+					String.format("%s; %s", VorgangChangeTestFactory.VALUE_AFTER_CHANGE, VorgangChangeTestFactory.ORGANISATIONSEINHEITEN_ID));
+		}
+	}
+
+}
\ No newline at end of file