package com.dalab.policyengine.mapper; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.springframework.stereotype.Component; import com.dalab.policyengine.dto.PolicyDraftInputDTO; import com.dalab.policyengine.dto.PolicyDraftOutputDTO; import com.dalab.policyengine.model.PolicyDraft; import com.dalab.policyengine.model.PolicyDraftStatus; /** * Mapper for converting between PolicyDraft entities and DTOs. * Handles workflow status calculation and available actions based on current status and user permissions. */ @Component public class PolicyDraftMapper { /** * Convert PolicyDraft entity to output DTO with workflow information. */ public PolicyDraftOutputDTO toOutputDTO(PolicyDraft draft) { if (draft == null) { return null; } PolicyDraftOutputDTO dto = new PolicyDraftOutputDTO(); // Basic information dto.setId(draft.getId()); dto.setName(draft.getName()); dto.setDescription(draft.getDescription()); dto.setStatus(draft.getStatus().name()); dto.setVersion(draft.getVersion()); dto.setBasePolicyId(draft.getBasePolicyId()); dto.setConditionLogic(draft.getConditionLogic()); dto.setRulesDefinition(draft.getRulesDefinition()); dto.setActions(draft.getActions()); dto.setChangeSummary(draft.getChangeSummary()); dto.setJustification(draft.getJustification()); dto.setExpectedImpact(draft.getExpectedImpact()); dto.setTargetImplementationDate(draft.getTargetImplementationDate()); dto.setPriority(draft.getPriority()); dto.setCategory(draft.getCategory()); dto.setTags(draft.getTags()); dto.setStakeholders(draft.getStakeholders()); dto.setApprovalMetadata(draft.getApprovalMetadata()); dto.setReviewComments(draft.getReviewComments()); // Audit trail dto.setCreatedAt(draft.getCreatedAt()); dto.setUpdatedAt(draft.getUpdatedAt()); dto.setCreatedByUserId(draft.getCreatedByUserId()); dto.setUpdatedByUserId(draft.getUpdatedByUserId()); dto.setSubmittedAt(draft.getSubmittedAt()); dto.setSubmittedByUserId(draft.getSubmittedByUserId()); dto.setApprovedAt(draft.getApprovedAt()); dto.setApprovedByUserId(draft.getApprovedByUserId()); dto.setRejectedAt(draft.getRejectedAt()); dto.setRejectedByUserId(draft.getRejectedByUserId()); dto.setPublishedAt(draft.getPublishedAt()); dto.setPublishedByUserId(draft.getPublishedByUserId()); // Enhanced workflow information dto.setWorkflowStatus(createWorkflowStatus(draft)); dto.setAvailableActions(createAvailableActions(draft)); return dto; } /** * Convert input DTO to PolicyDraft entity. */ public PolicyDraft toEntity(PolicyDraftInputDTO inputDTO) { if (inputDTO == null) { return null; } PolicyDraft draft = new PolicyDraft(); draft.setName(inputDTO.getName()); draft.setDescription(inputDTO.getDescription()); draft.setBasePolicyId(inputDTO.getBasePolicyId()); draft.setConditionLogic(inputDTO.getConditionLogic()); draft.setRulesDefinition(inputDTO.getRulesDefinition()); draft.setActions(inputDTO.getActions()); draft.setChangeSummary(inputDTO.getChangeSummary()); draft.setJustification(inputDTO.getJustification()); draft.setExpectedImpact(inputDTO.getExpectedImpact()); draft.setTargetImplementationDate(inputDTO.getTargetImplementationDate()); draft.setPriority(inputDTO.getPriority()); draft.setCategory(inputDTO.getCategory()); if (inputDTO.getTags() != null) { draft.setTags(new ArrayList<>(inputDTO.getTags())); } if (inputDTO.getStakeholders() != null) { draft.setStakeholders(new ArrayList<>(inputDTO.getStakeholders())); } return draft; } /** * Update entity from input DTO (for updates). */ public void updateEntity(PolicyDraft draft, PolicyDraftInputDTO inputDTO) { if (draft == null || inputDTO == null) { return; } draft.setName(inputDTO.getName()); draft.setDescription(inputDTO.getDescription()); draft.setBasePolicyId(inputDTO.getBasePolicyId()); draft.setConditionLogic(inputDTO.getConditionLogic()); draft.setRulesDefinition(inputDTO.getRulesDefinition()); draft.setActions(inputDTO.getActions()); draft.setChangeSummary(inputDTO.getChangeSummary()); draft.setJustification(inputDTO.getJustification()); draft.setExpectedImpact(inputDTO.getExpectedImpact()); draft.setTargetImplementationDate(inputDTO.getTargetImplementationDate()); draft.setPriority(inputDTO.getPriority()); draft.setCategory(inputDTO.getCategory()); if (inputDTO.getTags() != null) { draft.getTags().clear(); draft.getTags().addAll(inputDTO.getTags()); } if (inputDTO.getStakeholders() != null) { draft.getStakeholders().clear(); draft.getStakeholders().addAll(inputDTO.getStakeholders()); } } /** * Create workflow status information for the draft. */ private PolicyDraftOutputDTO.WorkflowStatusDTO createWorkflowStatus(PolicyDraft draft) { PolicyDraftOutputDTO.WorkflowStatusDTO status = new PolicyDraftOutputDTO.WorkflowStatusDTO(); PolicyDraftStatus currentStatus = draft.getStatus(); status.setCurrentStage(currentStatus.name()); status.setStatusDescription(getStatusDescription(currentStatus)); status.setNextPossibleStates(getNextPossibleStates(currentStatus)); status.setStageColor(getStageColor(currentStatus)); // Calculate days in current status Instant statusChangeTime = getLastStatusChangeTime(draft, currentStatus); if (statusChangeTime != null) { long days = ChronoUnit.DAYS.between(statusChangeTime, Instant.now()); status.setDaysInCurrentStatus((int) days); } // Set permission flags (simplified - would integrate with security context in real implementation) status.setCanEdit(canEdit(currentStatus)); status.setCanSubmit(canSubmit(currentStatus)); status.setCanApprove(canApprove(currentStatus)); status.setCanReject(canReject(currentStatus)); status.setCanPublish(canPublish(currentStatus)); return status; } /** * Create available workflow actions for the draft. */ private List createAvailableActions(PolicyDraft draft) { List actions = new ArrayList<>(); PolicyDraftStatus status = draft.getStatus(); switch (status) { case CREATED: case REQUIRES_CHANGES: actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("SUBMIT", "Submit for Review")); actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("ARCHIVE", "Archive Draft")); break; case SUBMITTED: case UNDER_REVIEW: actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("APPROVE", "Approve Draft")); actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("REJECT", "Reject Draft")); actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("REQUEST_CHANGES", "Request Changes")); break; case APPROVED: actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("PUBLISH", "Publish Policy")); actions.add(new PolicyDraftOutputDTO.WorkflowActionDTO("REJECT", "Reject Draft")); break; default: // No actions available for REJECTED, PUBLISHED, ARCHIVED break; } // Set action properties for (PolicyDraftOutputDTO.WorkflowActionDTO action : actions) { setActionProperties(action); } return actions; } /** * Get human-readable description for status. */ private String getStatusDescription(PolicyDraftStatus status) { switch (status) { case CREATED: return "Draft has been created and is ready for editing"; case SUBMITTED: return "Draft has been submitted and is awaiting review"; case UNDER_REVIEW: return "Draft is currently being reviewed"; case REQUIRES_CHANGES: return "Draft requires changes based on reviewer feedback"; case APPROVED: return "Draft has been approved and is ready for publication"; case REJECTED: return "Draft has been rejected and will not be published"; case PUBLISHED: return "Draft has been published as an active policy"; case ARCHIVED: return "Draft has been archived and is no longer active"; default: return "Unknown status"; } } /** * Get next possible states for workflow transitions. */ private List getNextPossibleStates(PolicyDraftStatus status) { switch (status) { case CREATED: case REQUIRES_CHANGES: return Arrays.asList("SUBMITTED", "ARCHIVED"); case SUBMITTED: return Arrays.asList("UNDER_REVIEW", "APPROVED", "REJECTED", "REQUIRES_CHANGES"); case UNDER_REVIEW: return Arrays.asList("APPROVED", "REJECTED", "REQUIRES_CHANGES"); case APPROVED: return Arrays.asList("PUBLISHED", "REJECTED"); default: return new ArrayList<>(); // Terminal states } } /** * Get color for UI status indicators. */ private String getStageColor(PolicyDraftStatus status) { switch (status) { case CREATED: return "blue"; case SUBMITTED: case UNDER_REVIEW: return "orange"; case REQUIRES_CHANGES: return "yellow"; case APPROVED: return "green"; case REJECTED: return "red"; case PUBLISHED: return "purple"; case ARCHIVED: return "gray"; default: return "gray"; } } /** * Get the timestamp of last status change. */ private Instant getLastStatusChangeTime(PolicyDraft draft, PolicyDraftStatus currentStatus) { switch (currentStatus) { case SUBMITTED: return draft.getSubmittedAt(); case APPROVED: return draft.getApprovedAt(); case REJECTED: return draft.getRejectedAt(); case PUBLISHED: return draft.getPublishedAt(); default: return draft.getUpdatedAt(); } } /** * Check if draft can be edited in current status. */ private boolean canEdit(PolicyDraftStatus status) { return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; } /** * Check if draft can be submitted in current status. */ private boolean canSubmit(PolicyDraftStatus status) { return status == PolicyDraftStatus.CREATED || status == PolicyDraftStatus.REQUIRES_CHANGES; } /** * Check if draft can be approved in current status. */ private boolean canApprove(PolicyDraftStatus status) { return status == PolicyDraftStatus.SUBMITTED || status == PolicyDraftStatus.UNDER_REVIEW; } /** * Check if draft can be rejected in current status. */ private boolean canReject(PolicyDraftStatus status) { return status == PolicyDraftStatus.SUBMITTED || status == PolicyDraftStatus.UNDER_REVIEW || status == PolicyDraftStatus.APPROVED; } /** * Check if draft can be published in current status. */ private boolean canPublish(PolicyDraftStatus status) { return status == PolicyDraftStatus.APPROVED; } /** * Set properties for workflow actions. */ private void setActionProperties(PolicyDraftOutputDTO.WorkflowActionDTO action) { switch (action.getActionType()) { case "SUBMIT": action.setActionDescription("Submit the draft for review by approvers"); action.setRequiresComment(false); action.setConfirmationMessage("Are you sure you want to submit this draft for review?"); action.setButtonStyle("primary"); break; case "APPROVE": action.setActionDescription("Approve the draft for publication"); action.setRequiresComment(false); action.setConfirmationMessage("Are you sure you want to approve this draft?"); action.setButtonStyle("success"); break; case "REJECT": action.setActionDescription("Reject the draft and prevent publication"); action.setRequiresComment(true); action.setConfirmationMessage("Are you sure you want to reject this draft? This action cannot be undone."); action.setButtonStyle("danger"); break; case "REQUEST_CHANGES": action.setActionDescription("Request changes from the draft author"); action.setRequiresComment(true); action.setConfirmationMessage("Please provide details about the required changes"); action.setButtonStyle("warning"); break; case "PUBLISH": action.setActionDescription("Publish the draft as an active policy"); action.setRequiresComment(false); action.setConfirmationMessage("Are you sure you want to publish this draft as an active policy?"); action.setButtonStyle("success"); break; case "ARCHIVE": action.setActionDescription("Archive the draft"); action.setRequiresComment(false); action.setConfirmationMessage("Are you sure you want to archive this draft?"); action.setButtonStyle("secondary"); break; } } }