From 04590baac4dcbdb47b3ca82ea423c74715fd14e8 Mon Sep 17 00:00:00 2001
From: OZGCloud <ozgcloud@mgm-tp.com>
Date: Tue, 29 Oct 2024 21:16:52 +0100
Subject: [PATCH] OZG-6991 implement lock vorgang

---
 .../java/de/ozgcloud/command/Command.java     |   1 +
 .../java/de/ozgcloud/command/TestCommand.java |   1 +
 .../vorgang/command/PersistedCommand.java     |   1 +
 .../de/ozgcloud/vorgang/vorgang/Vorgang.java  |   1 +
 .../ozgcloud/vorgang/vorgang/VorgangHead.java |   2 +
 .../vorgang/vorgang/VorgangService.java       |  27 +++++
 .../vorgang/command/CommandTestFactory.java   |   3 +
 .../vorgang/vorgang/VorgangServiceTest.java   | 109 ++++++++++++++++++
 8 files changed, 145 insertions(+)

diff --git a/vorgang-manager-command/src/main/java/de/ozgcloud/command/Command.java b/vorgang-manager-command/src/main/java/de/ozgcloud/command/Command.java
index 61876bd40..03beadfa1 100644
--- a/vorgang-manager-command/src/main/java/de/ozgcloud/command/Command.java
+++ b/vorgang-manager-command/src/main/java/de/ozgcloud/command/Command.java
@@ -44,6 +44,7 @@ public interface Command {
 	public String getCreatedBy();
 	public String getCreatedByName();
 	public CommandStatus getStatus();
+	String getCreatedByClient();
 
 	public Map<String, Object> getBodyObject();
 	public Map<String, String> getBody();
diff --git a/vorgang-manager-command/src/test/java/de/ozgcloud/command/TestCommand.java b/vorgang-manager-command/src/test/java/de/ozgcloud/command/TestCommand.java
index 3ebddf936..3d2c5d826 100644
--- a/vorgang-manager-command/src/test/java/de/ozgcloud/command/TestCommand.java
+++ b/vorgang-manager-command/src/test/java/de/ozgcloud/command/TestCommand.java
@@ -21,6 +21,7 @@ public class TestCommand implements Command {
 	private ZonedDateTime finishedAt;
 	private String createdBy;
 	private String createdByName;
+	private String createdByClient;
 
 	private CommandStatus status;
 
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistedCommand.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistedCommand.java
index 71964af4d..0973a3979 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistedCommand.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistedCommand.java
@@ -63,6 +63,7 @@ public class PersistedCommand implements Command {
 
 	private String createdBy;
 	private String createdByName;
+	private String createdByClient;
 
 	@Setter
 	private CommandStatus status;
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Vorgang.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Vorgang.java
index 88557e44c..979de7347 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Vorgang.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Vorgang.java
@@ -50,6 +50,7 @@ public class Vorgang {
 	static final String MONGODB_FIELDNAME_CREATED_AT = "createdAt";
 	public static final String MONGODB_FIELDNAME_ASSIGNED_TO = "assignedTo";
 	public static final String MONGODB_FIELDNAME_IN_CREATION = "inCreation";
+	public static final String MONGODB_FIELDNAME_HEADER = "header";
 
 	public static final String FIELD_EINGANGSKENNZ = "eingangs.header.requestId";
 	public static final String FIELD_AKTENZEICHEN = "aktenzeichen";
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangHead.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangHead.java
index ee5dc6b92..2f3cb7ddd 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangHead.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangHead.java
@@ -11,6 +11,8 @@ import lombok.Getter;
 @TypeAlias("VorgangHeader")
 public class VorgangHead {
 
+	public static final String FIELD_LOCK = "lock";
+
 	private ServiceKonto serviceKonto;
 
 	private String organisationsEinheitId;
diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java
index 274e1945c..801685f1f 100644
--- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java
+++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java
@@ -23,10 +23,13 @@
  */
 package de.ozgcloud.vorgang.vorgang;
 
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Stream;
 
@@ -45,6 +48,7 @@ import de.ozgcloud.command.Command;
 import de.ozgcloud.command.VorgangAssignedEvent;
 import de.ozgcloud.command.VorgangCreatedEvent;
 import de.ozgcloud.command.VorgangLockedEvent;
+import de.ozgcloud.common.errorhandling.TechnicalException;
 import de.ozgcloud.vorgang.clientattribute.ClientAttributeMap;
 import de.ozgcloud.vorgang.clientattribute.ClientAttributeReadPermitted;
 import de.ozgcloud.vorgang.clientattribute.ClientAttributesMap;
@@ -58,6 +62,7 @@ public class VorgangService {
 
 	private static final String BODY_ASSIGNED_TO_FIELD = "assignedTo";
 	public static final String BODY_OBJECT_AKTENZEICHEN = "aktenzeichen";
+	static final String KEY_HEADER_LOCK = "%s.%s".formatted(Vorgang.MONGODB_FIELDNAME_HEADER, VorgangHead.FIELD_LOCK);
 
 	@Autowired
 	private VorgangAuthorizationService vorgangAuthenticationService;
@@ -226,9 +231,31 @@ public class VorgangService {
 	}
 
 	public void lockVorgang(Command command) {
+		validateLockCommand(command);
+		repository.patch(command.getVorgangId(), command.getRelationVersion(), buildLockPatch(command));
 		publisher.publishEvent(new VorgangLockedEvent(command));
 	}
 
+	void validateLockCommand(Command command) {
+		if (Objects.isNull(command.getCreatedByClient())) {
+			throw new TechnicalException("Missing client name in lock command");
+		}
+		if (Objects.isNull(getReason(command.getBodyObject()))) {
+			throw new TechnicalException("Missing reason in lock command");
+		}
+	}
+
+	Map<String, Object> buildLockPatch(Command command) {
+		return Map.of(KEY_HEADER_LOCK, Map.of(
+				Lock.FIELD_CLIENT_NAME, command.getCreatedByClient(),
+				Lock.FIELD_LOCKED_SINCE, ZonedDateTime.now(ZoneId.of("UTC")),
+				Lock.FIELD_REASON, getReason(command.getBodyObject())));
+	}
+
+	String getReason(Map<String, Object> commandBody) {
+		return StringUtils.trimToNull(MapUtils.getString(commandBody, Lock.FIELD_REASON));
+	}
+
 	public void unlockVorgang(Command command) {
 		// TODO wird implementiert in OZG-6992
 		// publisher.publishEvent(new CommandRevokedEvent(command));
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandTestFactory.java
index ff3e080ef..47f9149c3 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandTestFactory.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandTestFactory.java
@@ -29,6 +29,7 @@ import java.util.UUID;
 
 import de.ozgcloud.command.CommandStatus;
 import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemTestFactory;
+import de.ozgcloud.vorgang.callcontext.CallContextTestFactory;
 import de.ozgcloud.vorgang.callcontext.UserTestFactory;
 import de.ozgcloud.vorgang.vorgang.VorgangTestFactory;
 
@@ -39,6 +40,7 @@ public class CommandTestFactory {
 	public static final ZonedDateTime CREATED_AT = ZonedDateTime.parse(CREATED_AT_STR);
 	public static final String CREATED_BY = UserTestFactory.ID;
 	public static final String CREATED_BY_NAME = UserTestFactory.NAME;
+	public static final String CREATED_BY_CLIENT = CallContextTestFactory.CLIENT;
 	public static final CommandStatus STATUS = CommandStatus.PENDING;
 
 	public static final String RELATION_ID = VorgangAttachedItemTestFactory.ID;
@@ -59,6 +61,7 @@ public class CommandTestFactory {
 				.createdAt(CREATED_AT)
 				.createdBy(CREATED_BY)
 				.createdByName(CREATED_BY_NAME)
+				.createdByClient(CREATED_BY_CLIENT)
 				.status(STATUS)
 				.relationId(RELATION_ID)
 				.relationVersion(RELATION_VERSION)
diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java
index c6a8daf26..db34a67ab 100644
--- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java
+++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java
@@ -28,10 +28,13 @@ import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
+import org.assertj.core.api.InstanceOfAssertFactories;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
@@ -49,6 +52,8 @@ import org.springframework.security.access.AccessDeniedException;
 import de.ozgcloud.command.Command;
 import de.ozgcloud.command.VorgangAssignedEvent;
 import de.ozgcloud.command.VorgangCreatedEvent;
+import de.ozgcloud.command.VorgangLockedEvent;
+import de.ozgcloud.common.errorhandling.TechnicalException;
 import de.ozgcloud.vorgang.clientattribute.ClientAttributeReadPermitted;
 import de.ozgcloud.vorgang.command.CommandTestFactory;
 import de.ozgcloud.vorgang.common.errorhandling.NotFoundException;
@@ -507,4 +512,108 @@ class VorgangServiceTest {
 			verify(repository).setAktenzeichen(VorgangTestFactory.ID, CommandTestFactory.RELATION_VERSION, null);
 		}
 	}
+
+	@Nested
+	class TestLockVorgang {
+
+		private static final Command LOCK_COMMAND = CommandTestFactory.create();
+		private static final Map<String, Object> LOCK_PATCH = Map.of("key", "value");
+
+		@Captor
+		private ArgumentCaptor<VorgangLockedEvent> eventCaptor;
+
+		@BeforeEach
+		void init() {
+			doNothing().when(service).validateLockCommand(any());
+			doReturn(LOCK_PATCH).when(service).buildLockPatch(any());
+		}
+
+		@Test
+		void shouldCallValidateLockCommand() {
+			service.lockVorgang(LOCK_COMMAND);
+
+			verify(service).validateLockCommand(LOCK_COMMAND);
+		}
+
+		@Test
+		void shouldCallBuildLockPatch() {
+			service.lockVorgang(LOCK_COMMAND);
+
+			verify(service).buildLockPatch(LOCK_COMMAND);
+		}
+
+		@Test
+		void shouldCallRepository() {
+			service.lockVorgang(LOCK_COMMAND);
+
+			verify(repository).patch(VorgangTestFactory.ID, CommandTestFactory.RELATION_VERSION, LOCK_PATCH);
+		}
+
+		@Test
+		void shouldCallPublishEvent() {
+			service.lockVorgang(LOCK_COMMAND);
+
+			verify(publisher).publishEvent(eventCaptor.capture());
+			assertThat(eventCaptor.getValue().getCommand()).isSameAs(LOCK_COMMAND);
+		}
+	}
+
+	@Nested
+	class TestValidateLockCommand {
+
+		@Test
+		void shouldThrowExceptionOnMissingClient() {
+			var command = CommandTestFactory.createBuilder().createdByClient(null).build();
+
+			assertThrows(TechnicalException.class, () -> service.validateLockCommand(command));
+		}
+
+		@Test
+		void shouldThrowExceptionOnMissingReason() {
+			var command = CommandTestFactory.createBuilder().bodyObject(Map.of()).build();
+
+			assertThrows(TechnicalException.class, () -> service.validateLockCommand(command));
+		}
+
+		@Test
+		void shouldAcceptValidCommand() {
+			var command = CommandTestFactory.createBuilder().bodyObject(Map.of(Lock.FIELD_REASON, LockTestFactory.REASON)).build();
+
+			assertDoesNotThrow(() -> service.validateLockCommand(command));
+		}
+	}
+
+	@Nested
+	class TestBuildLockPatch {
+
+		@Test
+		void shouldSetClientName() {
+			var command = CommandTestFactory.createBuilder().bodyObject(Map.of(Lock.FIELD_REASON, LockTestFactory.REASON)).build();
+
+			var result = service.buildLockPatch(command);
+
+			assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP)
+					.containsEntry(Lock.FIELD_CLIENT_NAME, CommandTestFactory.CREATED_BY_CLIENT);
+		}
+
+		@Test
+		void shouldSetLockedSince() {
+			var command = CommandTestFactory.createBuilder().bodyObject(Map.of(Lock.FIELD_REASON, LockTestFactory.REASON)).build();
+
+			var result = service.buildLockPatch(command);
+
+			assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP)
+					.extractingByKey(Lock.FIELD_LOCKED_SINCE, InstanceOfAssertFactories.ZONED_DATE_TIME)
+					.isCloseTo(ZonedDateTime.now(), within(1, ChronoUnit.SECONDS));
+		}
+
+		@Test
+		void shouldSetReason() {
+			var command = CommandTestFactory.createBuilder().bodyObject(Map.of(Lock.FIELD_REASON, LockTestFactory.REASON)).build();
+
+			var result = service.buildLockPatch(command);
+
+			assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP).containsEntry(Lock.FIELD_REASON, LockTestFactory.REASON);
+		}
+	}
 }
\ No newline at end of file
-- 
GitLab