feat: Frachtanzeige und Stationsabschluss für Version 0.9.23
Fracht mit Maßen und Gewicht anzeigen und Stationsabschlüsse gegen Wiederholung absichern. Backend-Abschluss atomar ausführen und mit Regressionstests abdecken. Optionale Aufgaben beim Kopieren erhalten und Kunden mit Adressen anzeigen. App und Backend auf Version 0.9.23 aktualisieren.
This commit is contained in:
1 parent
d7c0173493
commit
7325c77739
11 files changed
+277
-65
No files matched your search
@@ -31,6 +31,10 @@ import de.assecutor.votianlt.model.JobStatus;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import de.assecutor.votianlt.messaging.MessagingPublisher;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
@@ -53,6 +57,7 @@ public class MessageController {
|
||||
private final AppUserService appUserService;
|
||||
|
||||
private final JobRepository jobRepository;
|
||||
private final MongoTemplate mongoTemplate;
|
||||
|
||||
private final CargoItemRepository cargoItemRepository;
|
||||
|
||||
@@ -72,11 +77,12 @@ public class MessageController {
|
||||
TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository,
|
||||
SignatureRepository signatureRepository, CommentRepository commentRepository,
|
||||
JobHistoryService jobHistoryService, JobUpdateBroadcaster jobUpdateBroadcaster, EmailService emailService,
|
||||
MessageService messageService, TaskAssignmentService taskAssignmentService) {
|
||||
MessageService messageService, TaskAssignmentService taskAssignmentService, MongoTemplate mongoTemplate) {
|
||||
this.messagingPublisher = messagingPublisher;
|
||||
this.appUserRepository = appUserRepository;
|
||||
this.appUserService = appUserService;
|
||||
this.jobRepository = jobRepository;
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
this.cargoItemRepository = cargoItemRepository;
|
||||
this.taskRepository = taskRepository;
|
||||
this.photoRepository = photoRepository;
|
||||
@@ -420,7 +426,9 @@ public class MessageController {
|
||||
boolean allCompleted = mandatoryTasks.stream().allMatch(task -> task.isCompleted());
|
||||
|
||||
if (allCompleted) {
|
||||
updateJobStatusToCompleted(jobId);
|
||||
if (!updateJobStatusToCompleted(jobId)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
emailService.sendJobCompletionNotification(jobId, completedBy);
|
||||
} catch (Exception e) {
|
||||
@@ -438,23 +446,18 @@ public class MessageController {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateJobStatusToCompleted(ObjectId jobId) {
|
||||
private boolean updateJobStatusToCompleted(ObjectId jobId) {
|
||||
try {
|
||||
Optional<Job> jobOpt = jobRepository.findById(jobId);
|
||||
if (jobOpt.isEmpty()) {
|
||||
log.warn("[JOB] Cannot mark job {} as completed: job not found", jobId);
|
||||
return;
|
||||
}
|
||||
|
||||
Job job = jobOpt.get();
|
||||
if (job.getStatus() != JobStatus.COMPLETED) {
|
||||
job.setStatus(JobStatus.COMPLETED);
|
||||
job.setUpdatedAt(LocalDateTime.now());
|
||||
jobRepository.save(job);
|
||||
Query query = Query.query(Criteria.where("_id").is(jobId).and("status").ne(JobStatus.COMPLETED));
|
||||
Update update = new Update().set("status", JobStatus.COMPLETED).set("updatedAt", LocalDateTime.now());
|
||||
boolean completed = mongoTemplate.updateFirst(query, update, Job.class).getModifiedCount() > 0;
|
||||
if (completed) {
|
||||
log.info("[JOB] Job {} marked as COMPLETED", jobId);
|
||||
}
|
||||
return completed;
|
||||
} catch (Exception e) {
|
||||
log.error("[JOB] Could not update status of job {} to COMPLETED: {}", jobId, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -1600,6 +1600,7 @@ public class DeliveryStationDialog extends Dialog {
|
||||
|
||||
if (copy != null) {
|
||||
copy.setDescription(original.getDescription());
|
||||
copy.setOptional(original.isOptional());
|
||||
copy.setTaskOrder(original.getTaskOrder() != null ? original.getTaskOrder() : 0);
|
||||
copy.setCompleted(original.isCompleted());
|
||||
copy.setCompletedAt(original.getCompletedAt());
|
||||
|
||||
+1
-1
@@ -310,7 +310,7 @@ public class PickupStationDialog extends Dialog {
|
||||
|
||||
customerLabelMap.clear();
|
||||
for (Customer c : customers) {
|
||||
CustomerAddressLabelHelper.putName(customerLabelMap, c,
|
||||
CustomerAddressLabelHelper.put(customerLabelMap, c,
|
||||
translationHelper.getTranslation("addjob.customer.unnamed"));
|
||||
}
|
||||
customerComboBox.setItems(new ArrayList<>(customerLabelMap.keySet()));
|
||||
|
||||
@@ -243,7 +243,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
List<Customer> ownerCustomers = customerService.findAllForCurrentOwner();
|
||||
customerLabelToEntity.clear();
|
||||
for (Customer c : ownerCustomers) {
|
||||
CustomerAddressLabelHelper.putName(customerLabelToEntity, c, getTranslation("addjob.customer.unnamed"));
|
||||
CustomerAddressLabelHelper.put(customerLabelToEntity, c, getTranslation("addjob.customer.unnamed"));
|
||||
}
|
||||
customerSelection.setItems(new ArrayList<>(customerLabelToEntity.keySet()));
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package de.assecutor.votianlt.controller;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import de.assecutor.votianlt.model.Job;
|
||||
import de.assecutor.votianlt.model.JobStatus;
|
||||
import de.assecutor.votianlt.model.task.ConfirmationTask;
|
||||
import de.assecutor.votianlt.repository.JobRepository;
|
||||
import de.assecutor.votianlt.service.EmailService;
|
||||
import de.assecutor.votianlt.service.JobUpdateBroadcaster;
|
||||
import de.assecutor.votianlt.service.TaskAssignmentService;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class StationCompletionTest {
|
||||
|
||||
@Mock
|
||||
private JobRepository jobRepository;
|
||||
@Mock
|
||||
private TaskAssignmentService taskAssignmentService;
|
||||
@Mock
|
||||
private EmailService emailService;
|
||||
@Mock
|
||||
private JobUpdateBroadcaster jobUpdateBroadcaster;
|
||||
@Mock
|
||||
private MongoTemplate mongoTemplate;
|
||||
@InjectMocks
|
||||
private MessageController controller;
|
||||
|
||||
private final ObjectId jobId = new ObjectId();
|
||||
private ConfirmationTask mandatoryTask;
|
||||
private Map<String, Object> payload;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Job job = new Job();
|
||||
job.setId(jobId);
|
||||
job.setJobNumber("JOB-001");
|
||||
when(jobRepository.findById(jobId)).thenReturn(Optional.of(job));
|
||||
mandatoryTask = new ConfirmationTask("Lieferung bestätigen");
|
||||
mandatoryTask.setCompleted(true);
|
||||
when(taskAssignmentService.findTasksForJob(jobId)).thenReturn(List.of(mandatoryTask));
|
||||
payload = Map.of("jobId", jobId.toHexString(), "jobNumber", "JOB-001", "stationOrder", 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void repeatedStationCompletionSendsOnlyOneEmail() {
|
||||
when(mongoTemplate.updateFirst(any(Query.class), any(Update.class), eq(Job.class)))
|
||||
.thenReturn(UpdateResult.acknowledged(1, 1L, null), UpdateResult.acknowledged(0, 0L, null));
|
||||
|
||||
controller.handleStationCompleted("driver-1", payload);
|
||||
controller.handleStationCompleted("driver-1", payload);
|
||||
|
||||
verify(emailService).sendJobCompletionNotification(jobId, "driver-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void alreadyCompletedJobDoesNotSendAnotherEmail() {
|
||||
when(mongoTemplate.updateFirst(any(Query.class), any(Update.class), eq(Job.class)))
|
||||
.thenReturn(UpdateResult.acknowledged(0, 0L, null));
|
||||
|
||||
controller.handleStationCompleted("driver-1", payload);
|
||||
|
||||
verifyNoInteractions(emailService);
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoTemplate).updateFirst(query.capture(), any(Update.class), eq(Job.class));
|
||||
assertThat(query.getValue().getQueryObject()).containsEntry("_id", jobId)
|
||||
.containsEntry("status", new Document("$ne", JobStatus.COMPLETED));
|
||||
}
|
||||
|
||||
@Test
|
||||
void incompleteMandatoryTaskPreventsJobCompletion() {
|
||||
mandatoryTask.setCompleted(false);
|
||||
|
||||
controller.handleStationCompleted("driver-1", payload);
|
||||
|
||||
verifyNoInteractions(mongoTemplate, emailService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unfinishedOptionalTaskDoesNotPreventExplicitCompletion() {
|
||||
ConfirmationTask optionalTask = new ConfirmationTask("Zusätzliche Bestätigung");
|
||||
optionalTask.setOptional(true);
|
||||
when(taskAssignmentService.findTasksForJob(jobId)).thenReturn(List.of(mandatoryTask, optionalTask));
|
||||
when(mongoTemplate.updateFirst(any(Query.class), any(Update.class), eq(Job.class)))
|
||||
.thenReturn(UpdateResult.acknowledged(1, 1L, null));
|
||||
|
||||
controller.handleStationCompleted("driver-1", payload);
|
||||
|
||||
verify(emailService).sendJobCompletionNotification(jobId, "driver-1");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user