ProjectPositionCommandService.java

package de.mirkosertic.powerstaff.project.command;

import de.mirkosertic.powerstaff.shared.query.ProjectPositionStatusQueryService;
import de.mirkosertic.powerstaff.shared.query.ProjectPositionStatusView;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class ProjectPositionCommandService {

    private final ProjectPositionRepository repository;
    private final ProjectPositionStatusQueryService positionStatusQueryService;

    public ProjectPositionCommandService(final ProjectPositionRepository repository,
                                         final ProjectPositionStatusQueryService positionStatusQueryService) {
        this.repository = repository;
        this.positionStatusQueryService = positionStatusQueryService;
    }

    public ProjectPosition save(final ProjectPosition position) {
        try {
            return repository.save(position);
        } catch (final DataIntegrityViolationException e) {
            if (position.getProjectId() != null && position.getFreelancerId() != null) {
                throw new FreelancerAlreadyAssignedException(position.getFreelancerId(), position.getProjectId());
            }
            throw e;
        }
    }

    /**
     * Assigns a freelancer to a project. Delegates from FreelancerController (ADR-018).
     * If statusId is null, the configured default ProjectPositionStatus is used.
     */
    public void assignFreelancerToProject(final long freelancerId, final long projectId, final Long statusId, final String konditionen, final String kommentar) {
        Long resolvedStatusId = statusId;
        if (resolvedStatusId == null) {
            resolvedStatusId = positionStatusQueryService.findDefault()
                    .map(ProjectPositionStatusView::id)
                    .orElseThrow(() -> new IllegalStateException(
                            "Kein Standard-Positionsstatus konfiguriert. Bitte im Administrationsbereich einen Standard-Status festlegen."));
        }
        final var position = new ProjectPosition();
        position.setProjectId(projectId);
        position.setFreelancerId(freelancerId);
        position.setStatusId(resolvedStatusId);
        position.setKonditionen(konditionen);
        position.setKommentar(kommentar);
        try {
            repository.save(position);
        } catch (final DataIntegrityViolationException e) {
            throw new FreelancerAlreadyAssignedException(freelancerId, projectId);
        }
    }

    /**
     * Aktualisiert die editierbaren Felder einer bestehenden Projektposition (Status, Konditionen, Kommentar).
     * Lädt den bestehenden Datensatz, um Audit-Informationen und die Freiberufler-Zuordnung zu erhalten.
     */
    public ProjectPosition updateEditable(final long positionId, final Long statusId, final String konditionen, final String kommentar, final Long dbVersion) {
        final var position = repository.findById(positionId)
                .orElseThrow(() -> new IllegalArgumentException("Projektposition nicht gefunden: " + positionId));
        position.setStatusId(statusId);
        position.setKonditionen(konditionen);
        position.setKommentar(kommentar);
        position.setDbVersion(dbVersion);
        return repository.save(position);
    }

    public void delete(final Long positionId) {
        repository.deleteById(positionId);
    }
}