diff --git a/internal/contract/letter_outgoing_contract.go b/internal/contract/letter_outgoing_contract.go index b719ffe..5feebe6 100644 --- a/internal/contract/letter_outgoing_contract.go +++ b/internal/contract/letter_outgoing_contract.go @@ -28,18 +28,19 @@ type CreateOutgoingLetterRequest struct { ReceiverInstitutionID *uuid.UUID `json:"receiver_institution_id,omitempty"` IssueDate time.Time `json:"issue_date" validate:"required"` ApprovalFlowID *uuid.UUID `json:"approval_flow_id,omitempty"` - Recipients []CreateOutgoingLetterRecipient `json:"recipients,omitempty"` + Recipients []CreateOutgoingLetterRecipient `json:"recipients,omitempty"` Attachments []CreateOutgoingLetterAttachment `json:"attachments,omitempty"` + UserID uuid.UUID } type OutgoingLetterRecipientResponse struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - Email *string `json:"email,omitempty"` - Position *string `json:"position,omitempty"` - Institution *string `json:"institution,omitempty"` - IsPrimary bool `json:"is_primary"` - CreatedAt time.Time `json:"created_at"` + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Email *string `json:"email,omitempty"` + Position *string `json:"position,omitempty"` + Institution *string `json:"institution,omitempty"` + IsPrimary bool `json:"is_primary"` + CreatedAt time.Time `json:"created_at"` } type OutgoingLetterAttachmentResponse struct { @@ -160,10 +161,10 @@ type DiscussionResponse struct { } type ApprovalFlowRequest struct { - DepartmentID uuid.UUID `json:"department_id" validate:"required"` - Name string `json:"name" validate:"required"` - Description *string `json:"description,omitempty"` - IsActive bool `json:"is_active"` + DepartmentID uuid.UUID `json:"department_id" validate:"required"` + Name string `json:"name" validate:"required"` + Description *string `json:"description,omitempty"` + IsActive bool `json:"is_active"` Steps []ApprovalFlowStepRequest `json:"steps" validate:"required,dive"` } @@ -188,16 +189,16 @@ type ApprovalFlowResponse struct { } type ApprovalFlowStepResponse struct { - ID uuid.UUID `json:"id"` - StepOrder int `json:"step_order"` - ParallelGroup int `json:"parallel_group"` - ApproverRoleID *uuid.UUID `json:"approver_role_id,omitempty"` - ApproverRole *RoleResponse `json:"approver_role,omitempty"` - ApproverUserID *uuid.UUID `json:"approver_user_id,omitempty"` - ApproverUser *UserResponse `json:"approver_user,omitempty"` - Required bool `json:"required"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uuid.UUID `json:"id"` + StepOrder int `json:"step_order"` + ParallelGroup int `json:"parallel_group"` + ApproverRoleID *uuid.UUID `json:"approver_role_id,omitempty"` + ApproverRole *RoleResponse `json:"approver_role,omitempty"` + ApproverUserID *uuid.UUID `json:"approver_user_id,omitempty"` + ApproverUser *UserResponse `json:"approver_user,omitempty"` + Required bool `json:"required"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type ListApprovalFlowsRequest struct { @@ -210,4 +211,4 @@ type ListApprovalFlowsRequest struct { type ListApprovalFlowsResponse struct { Items []*ApprovalFlowResponse `json:"items"` Total int64 `json:"total"` -} \ No newline at end of file +} diff --git a/internal/handler/admin_approval_flow_handler.go b/internal/handler/admin_approval_flow_handler.go index 18a4058..5a2bd48 100644 --- a/internal/handler/admin_approval_flow_handler.go +++ b/internal/handler/admin_approval_flow_handler.go @@ -35,13 +35,11 @@ func (h *AdminApprovalFlowHandler) CreateApprovalFlow(c *gin.Context) { return } - // Validate that at least one step is provided if len(req.Steps) == 0 { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "at least one approval step is required", Code: http.StatusBadRequest}) return } - // Validate each step has either a role or user as approver for i, step := range req.Steps { if step.ApproverRoleID == nil && step.ApproverUserID == nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ @@ -113,13 +111,11 @@ func (h *AdminApprovalFlowHandler) UpdateApprovalFlow(c *gin.Context) { return } - // Validate that at least one step is provided if len(req.Steps) == 0 { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "at least one approval step is required", Code: http.StatusBadRequest}) return } - // Validate each step has either a role or user as approver for i, step := range req.Steps { if step.ApproverRoleID == nil && step.ApproverUserID == nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ @@ -341,4 +337,4 @@ func (h *AdminApprovalFlowHandler) CloneApprovalFlow(c *gin.Context) { } c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp)) -} \ No newline at end of file +} diff --git a/internal/handler/letter_outgoing_handler.go b/internal/handler/letter_outgoing_handler.go index 5d1f4ab..3767fe7 100644 --- a/internal/handler/letter_outgoing_handler.go +++ b/internal/handler/letter_outgoing_handler.go @@ -2,6 +2,7 @@ package handler import ( "context" + "eslogad-be/internal/appcontext" "net/http" "strconv" @@ -17,20 +18,20 @@ type LetterOutgoingService interface { ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error) UpdateOutgoingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error - + SubmitForApproval(ctx context.Context, letterID uuid.UUID) error ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID) error - + AddRecipients(ctx context.Context, letterID uuid.UUID, req *contract.AddRecipientsRequest) error UpdateRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID, req *contract.UpdateRecipientRequest) error RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error - + AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID) error - + CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateDiscussionRequest) (*contract.DiscussionResponse, error) UpdateDiscussion(ctx context.Context, discussionID uuid.UUID, req *contract.UpdateDiscussionRequest) error DeleteDiscussion(ctx context.Context, discussionID uuid.UUID) error @@ -50,13 +51,15 @@ func (h *LetterOutgoingHandler) CreateOutgoingLetter(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } - + ctx := c.Request.Context() + req.UserID = appcontext.FromGinContext(ctx).UserID + resp, err := h.svc.CreateOutgoingLetter(c.Request.Context(), &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp)) } @@ -66,13 +69,13 @@ func (h *LetterOutgoingHandler) GetOutgoingLetter(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } - + resp, err := h.svc.GetOutgoingLetterByID(c.Request.Context(), id) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } @@ -80,17 +83,17 @@ func (h *LetterOutgoingHandler) ListOutgoingLetters(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) offset := (page - 1) * limit - + status := c.Query("status") query := c.Query("q") createdByStr := c.Query("created_by") receiverInstitutionStr := c.Query("receiver_institution_id") - + var statusPtr *string var queryPtr *string var createdByPtr *uuid.UUID var receiverInstitutionPtr *uuid.UUID - + if status != "" { statusPtr = &status } @@ -107,7 +110,7 @@ func (h *LetterOutgoingHandler) ListOutgoingLetters(c *gin.Context) { receiverInstitutionPtr = &receiverInstitution } } - + req := &contract.ListOutgoingLettersRequest{ Limit: limit, Offset: offset, @@ -116,13 +119,13 @@ func (h *LetterOutgoingHandler) ListOutgoingLetters(c *gin.Context) { CreatedBy: createdByPtr, ReceiverInstitutionID: receiverInstitutionPtr, } - + resp, err := h.svc.ListOutgoingLetters(c.Request.Context(), req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } @@ -132,19 +135,19 @@ func (h *LetterOutgoingHandler) UpdateOutgoingLetter(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } - + var req contract.UpdateOutgoingLetterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } - + resp, err := h.svc.UpdateOutgoingLetter(c.Request.Context(), id, &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } @@ -154,12 +157,12 @@ func (h *LetterOutgoingHandler) DeleteOutgoingLetter(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } - + if err := h.svc.DeleteOutgoingLetter(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "deleted"}) } @@ -169,12 +172,12 @@ func (h *LetterOutgoingHandler) SubmitForApproval(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } - + if err := h.svc.SubmitForApproval(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "submitted for approval"}) } @@ -184,18 +187,18 @@ func (h *LetterOutgoingHandler) ApproveOutgoingLetter(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } - + var req contract.ApproveLetterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } - + if err := h.svc.ApproveOutgoingLetter(c.Request.Context(), id, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "approved"}) } @@ -205,18 +208,18 @@ func (h *LetterOutgoingHandler) RejectOutgoingLetter(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } - + var req contract.RejectLetterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } - + if err := h.svc.RejectOutgoingLetter(c.Request.Context(), id, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "rejected"}) } @@ -226,12 +229,12 @@ func (h *LetterOutgoingHandler) SendOutgoingLetter(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } - + if err := h.svc.SendOutgoingLetter(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "sent"}) } @@ -241,12 +244,12 @@ func (h *LetterOutgoingHandler) ArchiveOutgoingLetter(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } - + if err := h.svc.ArchiveOutgoingLetter(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "archived"}) } @@ -256,18 +259,18 @@ func (h *LetterOutgoingHandler) AddRecipients(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } - + var req contract.AddRecipientsRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } - + if err := h.svc.AddRecipients(c.Request.Context(), id, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "recipients added"}) } @@ -277,24 +280,24 @@ func (h *LetterOutgoingHandler) UpdateRecipient(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest}) return } - + recipientID, err := uuid.Parse(c.Param("recipient_id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid recipient id", Code: http.StatusBadRequest}) return } - + var req contract.UpdateRecipientRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } - + if err := h.svc.UpdateRecipient(c.Request.Context(), id, recipientID, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "recipient updated"}) } @@ -304,18 +307,18 @@ func (h *LetterOutgoingHandler) RemoveRecipient(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest}) return } - + recipientID, err := uuid.Parse(c.Param("recipient_id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid recipient id", Code: http.StatusBadRequest}) return } - + if err := h.svc.RemoveRecipient(c.Request.Context(), id, recipientID); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "recipient removed"}) } @@ -325,18 +328,18 @@ func (h *LetterOutgoingHandler) AddAttachments(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } - + var req contract.AddAttachmentsRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } - + if err := h.svc.AddAttachments(c.Request.Context(), id, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "attachments added"}) } @@ -346,18 +349,18 @@ func (h *LetterOutgoingHandler) RemoveAttachment(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest}) return } - + attachmentID, err := uuid.Parse(c.Param("attachment_id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid attachment id", Code: http.StatusBadRequest}) return } - + if err := h.svc.RemoveAttachment(c.Request.Context(), id, attachmentID); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "attachment removed"}) } @@ -367,19 +370,19 @@ func (h *LetterOutgoingHandler) CreateDiscussion(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest}) return } - + var req contract.CreateDiscussionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } - + resp, err := h.svc.CreateDiscussion(c.Request.Context(), id, &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp)) } @@ -389,18 +392,18 @@ func (h *LetterOutgoingHandler) UpdateDiscussion(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid discussion id", Code: http.StatusBadRequest}) return } - + var req contract.UpdateDiscussionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } - + if err := h.svc.UpdateDiscussion(c.Request.Context(), discussionID, &req); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "discussion updated"}) } @@ -410,11 +413,11 @@ func (h *LetterOutgoingHandler) DeleteDiscussion(c *gin.Context) { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid discussion id", Code: http.StatusBadRequest}) return } - + if err := h.svc.DeleteDiscussion(c.Request.Context(), discussionID); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } - + c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "discussion deleted"}) } \ No newline at end of file diff --git a/internal/repository/letter_outgoing_repository.go b/internal/repository/letter_outgoing_repository.go index e2cc5eb..91c67a3 100644 --- a/internal/repository/letter_outgoing_repository.go +++ b/internal/repository/letter_outgoing_repository.go @@ -94,6 +94,10 @@ func (r *LetterOutgoingRepository) List(ctx context.Context, filter ListOutgoing Preload("Priority"). Preload("ReceiverInstitution"). Preload("Creator"). + Preload("Recipients"). + Preload("Attachments"). + Preload("Approvals.Step"). + Preload("Approvals.Approver"). Order("created_at DESC"). Limit(limit). Offset(offset). diff --git a/internal/router/router.go b/internal/router/router.go index 3038d9e..8fa388e 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -151,20 +151,20 @@ func (r *Router) addAppRoutes(rg *gin.Engine) { lettersch.GET("/outgoing", r.letterOutgoingHandler.ListOutgoingLetters) lettersch.PUT("/outgoing/:id", r.letterOutgoingHandler.UpdateOutgoingLetter) lettersch.DELETE("/outgoing/:id", r.letterOutgoingHandler.DeleteOutgoingLetter) - + lettersch.POST("/outgoing/:id/submit", r.letterOutgoingHandler.SubmitForApproval) lettersch.POST("/outgoing/:id/approve", r.letterOutgoingHandler.ApproveOutgoingLetter) lettersch.POST("/outgoing/:id/reject", r.letterOutgoingHandler.RejectOutgoingLetter) lettersch.POST("/outgoing/:id/send", r.letterOutgoingHandler.SendOutgoingLetter) lettersch.POST("/outgoing/:id/archive", r.letterOutgoingHandler.ArchiveOutgoingLetter) - + lettersch.POST("/outgoing/:id/recipients", r.letterOutgoingHandler.AddRecipients) lettersch.PUT("/outgoing/:id/recipients/:recipient_id", r.letterOutgoingHandler.UpdateRecipient) lettersch.DELETE("/outgoing/:id/recipients/:recipient_id", r.letterOutgoingHandler.RemoveRecipient) - + lettersch.POST("/outgoing/:id/attachments", r.letterOutgoingHandler.AddAttachments) lettersch.DELETE("/outgoing/:id/attachments/:attachment_id", r.letterOutgoingHandler.RemoveAttachment) - + lettersch.POST("/outgoing/:id/discussions", r.letterOutgoingHandler.CreateDiscussion) lettersch.PUT("/outgoing/discussions/:discussion_id", r.letterOutgoingHandler.UpdateDiscussion) lettersch.DELETE("/outgoing/discussions/:discussion_id", r.letterOutgoingHandler.DeleteDiscussion) @@ -185,12 +185,11 @@ func (r *Router) addAppRoutes(rg *gin.Engine) { droutes.GET("department", r.dispRouteHandler.ListByFromDept) droutes.PUT(":id/active", r.dispRouteHandler.SetActive) } - - admin := v1.Group("/admin") + + admin := v1.Group("/setting") admin.Use(r.authMiddleware.RequireAuth()) { approvalFlows := admin.Group("/approval-flows") - approvalFlows.Use(r.authMiddleware.RequirePermissions("admin.approval_flow")) { approvalFlows.POST("", r.adminApprovalFlowHandler.CreateApprovalFlow) approvalFlows.GET("", r.adminApprovalFlowHandler.ListApprovalFlows) diff --git a/internal/service/letter_outgoing_service.go b/internal/service/letter_outgoing_service.go index 9c26101..e002863 100644 --- a/internal/service/letter_outgoing_service.go +++ b/internal/service/letter_outgoing_service.go @@ -18,36 +18,36 @@ type LetterOutgoingService interface { ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error) UpdateOutgoingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error - + SubmitForApproval(ctx context.Context, letterID uuid.UUID) error ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID) error - + AddRecipients(ctx context.Context, letterID uuid.UUID, req *contract.AddRecipientsRequest) error UpdateRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID, req *contract.UpdateRecipientRequest) error RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error - + AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID) error - + CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateDiscussionRequest) (*contract.DiscussionResponse, error) UpdateDiscussion(ctx context.Context, discussionID uuid.UUID, req *contract.UpdateDiscussionRequest) error DeleteDiscussion(ctx context.Context, discussionID uuid.UUID) error } type LetterOutgoingServiceImpl struct { - db *gorm.DB - letterRepo *repository.LetterOutgoingRepository - attachmentRepo *repository.LetterOutgoingAttachmentRepository - recipientRepo *repository.LetterOutgoingRecipientRepository - discussionRepo *repository.LetterOutgoingDiscussionRepository - discussionAttachmentRepo *repository.LetterOutgoingDiscussionAttachmentRepository - activityLogRepo *repository.LetterOutgoingActivityLogRepository - approvalFlowRepo *repository.ApprovalFlowRepository - approvalRepo *repository.LetterOutgoingApprovalRepository - txManager *repository.TxManager + db *gorm.DB + letterRepo *repository.LetterOutgoingRepository + attachmentRepo *repository.LetterOutgoingAttachmentRepository + recipientRepo *repository.LetterOutgoingRecipientRepository + discussionRepo *repository.LetterOutgoingDiscussionRepository + discussionAttachmentRepo *repository.LetterOutgoingDiscussionAttachmentRepository + activityLogRepo *repository.LetterOutgoingActivityLogRepository + approvalFlowRepo *repository.ApprovalFlowRepository + approvalRepo *repository.LetterOutgoingApprovalRepository + txManager *repository.TxManager } func NewLetterOutgoingService( @@ -77,8 +77,6 @@ func NewLetterOutgoingService( } func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, req *contract.CreateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) { - userID := getUserIDFromContext(ctx) - letter := &entities.LetterOutgoing{ Subject: req.Subject, Description: req.Description, @@ -87,18 +85,18 @@ func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, re IssueDate: req.IssueDate, Status: entities.LetterOutgoingStatusDraft, ApprovalFlowID: req.ApprovalFlowID, - CreatedBy: userID, + CreatedBy: req.UserID, } - + if req.ReferenceNumber != nil { letter.ReferenceNumber = req.ReferenceNumber } - + err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.letterRepo.Create(txCtx, letter); err != nil { return err } - + if len(req.Recipients) > 0 { recipients := make([]entities.LetterOutgoingRecipient, len(req.Recipients)) for i, r := range req.Recipients { @@ -115,7 +113,7 @@ func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, re return err } } - + if len(req.Attachments) > 0 { attachments := make([]entities.LetterOutgoingAttachment, len(req.Attachments)) for i, a := range req.Attachments { @@ -124,35 +122,35 @@ func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, re FileURL: a.FileURL, FileName: a.FileName, FileType: a.FileType, - UploadedBy: &userID, + UploadedBy: &req.UserID, } } if err := s.attachmentRepo.CreateBulk(txCtx, attachments); err != nil { return err } } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letter.ID, ActionType: entities.LetterOutgoingActionCreated, - ActorUserID: &userID, + ActorUserID: &req.UserID, } if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) - + if err != nil { return nil, err } - + result, err := s.letterRepo.Get(ctx, letter.ID) if err != nil { return nil, err } - + return transformLetterToResponse(result), nil } @@ -161,7 +159,7 @@ func (s *LetterOutgoingServiceImpl) GetOutgoingLetterByID(ctx context.Context, i if err != nil { return nil, err } - + return transformLetterToResponse(letter), nil } @@ -174,17 +172,17 @@ func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req FromDate: req.FromDate, ToDate: req.ToDate, } - + letters, total, err := s.letterRepo.List(ctx, filter, req.Limit, req.Offset) if err != nil { return nil, err } - + items := make([]*contract.OutgoingLetterResponse, len(letters)) for i, letter := range letters { items[i] = transformLetterToResponse(&letter) } - + return &contract.ListOutgoingLettersResponse{ Items: items, Total: total, @@ -193,16 +191,16 @@ func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req func (s *LetterOutgoingServiceImpl) UpdateOutgoingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) { userID := getUserIDFromContext(ctx) - + letter, err := s.letterRepo.Get(ctx, id) if err != nil { return nil, err } - + if letter.Status != entities.LetterOutgoingStatusDraft { return nil, gorm.ErrInvalidData } - + if req.Subject != nil { letter.Subject = *req.Subject } @@ -221,12 +219,12 @@ func (s *LetterOutgoingServiceImpl) UpdateOutgoingLetter(ctx context.Context, id if req.ReferenceNumber != nil { letter.ReferenceNumber = req.ReferenceNumber } - + err = s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.letterRepo.Update(txCtx, letter); err != nil { return err } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letter.ID, ActionType: entities.LetterOutgoingActionUpdated, @@ -235,39 +233,39 @@ func (s *LetterOutgoingServiceImpl) UpdateOutgoingLetter(ctx context.Context, id if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) - + if err != nil { return nil, err } - + result, err := s.letterRepo.Get(ctx, id) if err != nil { return nil, err } - + return transformLetterToResponse(result), nil } func (s *LetterOutgoingServiceImpl) DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error { userID := getUserIDFromContext(ctx) - + letter, err := s.letterRepo.Get(ctx, id) if err != nil { return err } - + if letter.Status != entities.LetterOutgoingStatusDraft { return gorm.ErrInvalidData } - + return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.letterRepo.SoftDelete(txCtx, id); err != nil { return err } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letter.ID, ActionType: entities.LetterOutgoingActionDeleted, @@ -276,32 +274,32 @@ func (s *LetterOutgoingServiceImpl) DeleteOutgoingLetter(ctx context.Context, id if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) } func (s *LetterOutgoingServiceImpl) SubmitForApproval(ctx context.Context, letterID uuid.UUID) error { userID := getUserIDFromContext(ctx) - + letter, err := s.letterRepo.Get(ctx, letterID) if err != nil { return err } - + if letter.Status != entities.LetterOutgoingStatusDraft { return gorm.ErrInvalidData } - + if letter.ApprovalFlowID == nil { return gorm.ErrInvalidData } - + flow, err := s.approvalFlowRepo.Get(ctx, *letter.ApprovalFlowID) if err != nil { return err } - + return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { approvals := make([]entities.LetterOutgoingApproval, len(flow.Steps)) for i, step := range flow.Steps { @@ -311,15 +309,15 @@ func (s *LetterOutgoingServiceImpl) SubmitForApproval(ctx context.Context, lette Status: entities.ApprovalStatusPending, } } - + if err := s.approvalRepo.CreateBulk(txCtx, approvals); err != nil { return err } - + if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusPendingApproval); err != nil { return err } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letterID, ActionType: entities.LetterOutgoingActionSubmittedApproval, @@ -330,28 +328,28 @@ func (s *LetterOutgoingServiceImpl) SubmitForApproval(ctx context.Context, lette if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) } func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error { userID := getUserIDFromContext(ctx) - + letter, err := s.letterRepo.Get(ctx, letterID) if err != nil { return err } - + if letter.Status != entities.LetterOutgoingStatusPendingApproval { return gorm.ErrInvalidData } - + approvals, err := s.approvalRepo.ListByLetter(ctx, letterID) if err != nil { return err } - + var currentApproval *entities.LetterOutgoingApproval for i := range approvals { if approvals[i].Status == entities.ApprovalStatusPending { @@ -363,22 +361,22 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l } } } - + if currentApproval == nil { return gorm.ErrInvalidData } - + return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { now := time.Now() currentApproval.Status = entities.ApprovalStatusApproved currentApproval.ApproverID = &userID currentApproval.ActedAt = &now currentApproval.Remarks = req.Remarks - + if err := s.approvalRepo.Update(txCtx, currentApproval); err != nil { return err } - + allApproved := true for _, approval := range approvals { if approval.ID != currentApproval.ID && approval.Status == entities.ApprovalStatusPending { @@ -386,13 +384,13 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l break } } - + if allApproved { if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusApproved); err != nil { return err } } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letterID, ActionType: entities.LetterOutgoingActionApproved, @@ -402,28 +400,28 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) } func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error { userID := getUserIDFromContext(ctx) - + letter, err := s.letterRepo.Get(ctx, letterID) if err != nil { return err } - + if letter.Status != entities.LetterOutgoingStatusPendingApproval { return gorm.ErrInvalidData } - + approvals, err := s.approvalRepo.ListByLetter(ctx, letterID) if err != nil { return err } - + var currentApproval *entities.LetterOutgoingApproval for i := range approvals { if approvals[i].Status == entities.ApprovalStatusPending { @@ -435,26 +433,26 @@ func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, le } } } - + if currentApproval == nil { return gorm.ErrInvalidData } - + return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { now := time.Now() currentApproval.Status = entities.ApprovalStatusRejected currentApproval.ApproverID = &userID currentApproval.ActedAt = &now currentApproval.Remarks = &req.Reason - + if err := s.approvalRepo.Update(txCtx, currentApproval); err != nil { return err } - + if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusDraft); err != nil { return err } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letterID, ActionType: entities.LetterOutgoingActionRejected, @@ -466,28 +464,28 @@ func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, le if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) } func (s *LetterOutgoingServiceImpl) SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error { userID := getUserIDFromContext(ctx) - + letter, err := s.letterRepo.Get(ctx, letterID) if err != nil { return err } - + if letter.Status != entities.LetterOutgoingStatusApproved { return gorm.ErrInvalidData } - + return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusSent); err != nil { return err } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letterID, ActionType: entities.LetterOutgoingActionSent, @@ -498,28 +496,28 @@ func (s *LetterOutgoingServiceImpl) SendOutgoingLetter(ctx context.Context, lett if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) } func (s *LetterOutgoingServiceImpl) ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID) error { userID := getUserIDFromContext(ctx) - + letter, err := s.letterRepo.Get(ctx, letterID) if err != nil { return err } - + if letter.Status != entities.LetterOutgoingStatusSent { return gorm.ErrInvalidData } - + return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusArchived); err != nil { return err } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letterID, ActionType: entities.LetterOutgoingActionArchived, @@ -530,23 +528,23 @@ func (s *LetterOutgoingServiceImpl) ArchiveOutgoingLetter(ctx context.Context, l if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) } func (s *LetterOutgoingServiceImpl) AddRecipients(ctx context.Context, letterID uuid.UUID, req *contract.AddRecipientsRequest) error { userID := getUserIDFromContext(ctx) - + letter, err := s.letterRepo.Get(ctx, letterID) if err != nil { return err } - + if letter.Status != entities.LetterOutgoingStatusDraft { return gorm.ErrInvalidData } - + recipients := make([]entities.LetterOutgoingRecipient, len(req.Recipients)) for i, r := range req.Recipients { recipients[i] = entities.LetterOutgoingRecipient{ @@ -558,12 +556,12 @@ func (s *LetterOutgoingServiceImpl) AddRecipients(ctx context.Context, letterID IsPrimary: r.IsPrimary, } } - + return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.recipientRepo.CreateBulk(txCtx, recipients); err != nil { return err } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letterID, ActionType: entities.LetterOutgoingActionRecipientAdded, @@ -572,7 +570,7 @@ func (s *LetterOutgoingServiceImpl) AddRecipients(ctx context.Context, letterID if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) } @@ -582,11 +580,11 @@ func (s *LetterOutgoingServiceImpl) UpdateRecipient(ctx context.Context, letterI if err != nil { return err } - + if letter.Status != entities.LetterOutgoingStatusDraft { return gorm.ErrInvalidData } - + recipient := &entities.LetterOutgoingRecipient{ ID: recipientID, RecipientName: req.Name, @@ -595,27 +593,27 @@ func (s *LetterOutgoingServiceImpl) UpdateRecipient(ctx context.Context, letterI RecipientInstitution: req.Institution, IsPrimary: req.IsPrimary, } - + return s.recipientRepo.Update(ctx, recipient) } func (s *LetterOutgoingServiceImpl) RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error { userID := getUserIDFromContext(ctx) - + letter, err := s.letterRepo.Get(ctx, letterID) if err != nil { return err } - + if letter.Status != entities.LetterOutgoingStatusDraft { return gorm.ErrInvalidData } - + return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.recipientRepo.Delete(txCtx, recipientID); err != nil { return err } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letterID, ActionType: entities.LetterOutgoingActionRecipientRemoved, @@ -625,23 +623,23 @@ func (s *LetterOutgoingServiceImpl) RemoveRecipient(ctx context.Context, letterI if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) } func (s *LetterOutgoingServiceImpl) AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error { userID := getUserIDFromContext(ctx) - + letter, err := s.letterRepo.Get(ctx, letterID) if err != nil { return err } - + if letter.Status != entities.LetterOutgoingStatusDraft { return gorm.ErrInvalidData } - + attachments := make([]entities.LetterOutgoingAttachment, len(req.Attachments)) for i, a := range req.Attachments { attachments[i] = entities.LetterOutgoingAttachment{ @@ -652,12 +650,12 @@ func (s *LetterOutgoingServiceImpl) AddAttachments(ctx context.Context, letterID UploadedBy: &userID, } } - + return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.attachmentRepo.CreateBulk(txCtx, attachments); err != nil { return err } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letterID, ActionType: entities.LetterOutgoingActionAttachmentAdded, @@ -666,28 +664,28 @@ func (s *LetterOutgoingServiceImpl) AddAttachments(ctx context.Context, letterID if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) } func (s *LetterOutgoingServiceImpl) RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID) error { userID := getUserIDFromContext(ctx) - + letter, err := s.letterRepo.Get(ctx, letterID) if err != nil { return err } - + if letter.Status != entities.LetterOutgoingStatusDraft { return gorm.ErrInvalidData } - + return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.attachmentRepo.Delete(txCtx, attachmentID); err != nil { return err } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letterID, ActionType: entities.LetterOutgoingActionAttachmentRemoved, @@ -697,30 +695,30 @@ func (s *LetterOutgoingServiceImpl) RemoveAttachment(ctx context.Context, letter if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) } func (s *LetterOutgoingServiceImpl) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateDiscussionRequest) (*contract.DiscussionResponse, error) { userID := getUserIDFromContext(ctx) - + discussion := &entities.LetterOutgoingDiscussion{ LetterID: letterID, ParentID: req.ParentID, UserID: userID, Message: req.Message, } - + if req.Mentions != nil { discussion.Mentions = req.Mentions } - + err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.discussionRepo.Create(txCtx, discussion); err != nil { return err } - + if len(req.Attachments) > 0 { attachments := make([]entities.LetterOutgoingDiscussionAttachment, len(req.Attachments)) for i, a := range req.Attachments { @@ -736,7 +734,7 @@ func (s *LetterOutgoingServiceImpl) CreateDiscussion(ctx context.Context, letter return err } } - + activityLog := &entities.LetterOutgoingActivityLog{ LetterID: letterID, ActionType: entities.LetterOutgoingActionDiscussionAdded, @@ -746,19 +744,19 @@ func (s *LetterOutgoingServiceImpl) CreateDiscussion(ctx context.Context, letter if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { return err } - + return nil }) - + if err != nil { return nil, err } - + result, err := s.discussionRepo.Get(ctx, discussion.ID) if err != nil { return nil, err } - + return transformDiscussionToResponse(result), nil } @@ -767,17 +765,17 @@ func (s *LetterOutgoingServiceImpl) UpdateDiscussion(ctx context.Context, discus if err != nil { return err } - + userID := getUserIDFromContext(ctx) if discussion.UserID != userID { return gorm.ErrInvalidData } - + discussion.Message = req.Message if req.Mentions != nil { discussion.Mentions = req.Mentions } - + return s.discussionRepo.Update(ctx, discussion) } @@ -786,12 +784,12 @@ func (s *LetterOutgoingServiceImpl) DeleteDiscussion(ctx context.Context, discus if err != nil { return err } - + userID := getUserIDFromContext(ctx) if discussion.UserID != userID { return gorm.ErrInvalidData } - + return s.discussionRepo.Delete(ctx, discussionID) } @@ -808,7 +806,7 @@ func ptr(s string) *string { } func transformLetterToResponse(letter *entities.LetterOutgoing) *contract.OutgoingLetterResponse { - return &contract.OutgoingLetterResponse{ + resp := &contract.OutgoingLetterResponse{ ID: letter.ID, LetterNumber: letter.LetterNumber, ReferenceNumber: letter.ReferenceNumber, @@ -823,6 +821,82 @@ func transformLetterToResponse(letter *entities.LetterOutgoing) *contract.Outgoi CreatedAt: letter.CreatedAt, UpdatedAt: letter.UpdatedAt, } + + if letter.Priority != nil { + resp.Priority = &contract.PriorityResponse{ + ID: letter.Priority.ID.String(), + Name: letter.Priority.Name, + Level: letter.Priority.Level, + CreatedAt: letter.Priority.CreatedAt, + UpdatedAt: letter.Priority.UpdatedAt, + } + } + + if letter.ReceiverInstitution != nil { + resp.ReceiverInstitution = &contract.InstitutionResponse{ + ID: letter.ReceiverInstitution.ID.String(), + Name: letter.ReceiverInstitution.Name, + Type: string(letter.ReceiverInstitution.Type), + Address: letter.ReceiverInstitution.Address, + ContactPerson: letter.ReceiverInstitution.ContactPerson, + Phone: letter.ReceiverInstitution.Phone, + Email: letter.ReceiverInstitution.Email, + CreatedAt: letter.ReceiverInstitution.CreatedAt, + UpdatedAt: letter.ReceiverInstitution.UpdatedAt, + } + } + + if len(letter.Recipients) > 0 { + resp.Recipients = make([]contract.OutgoingLetterRecipientResponse, len(letter.Recipients)) + for i, recipient := range letter.Recipients { + resp.Recipients[i] = contract.OutgoingLetterRecipientResponse{ + ID: recipient.ID, + Name: recipient.RecipientName, + Email: recipient.RecipientEmail, + Position: recipient.RecipientPosition, + Institution: recipient.RecipientInstitution, + IsPrimary: recipient.IsPrimary, + CreatedAt: recipient.CreatedAt, + } + } + } + + if len(letter.Attachments) > 0 { + resp.Attachments = make([]contract.OutgoingLetterAttachmentResponse, len(letter.Attachments)) + for i, attachment := range letter.Attachments { + resp.Attachments[i] = contract.OutgoingLetterAttachmentResponse{ + ID: attachment.ID, + FileURL: attachment.FileURL, + FileName: attachment.FileName, + FileType: attachment.FileType, + UploadedAt: attachment.UploadedAt, + } + } + } + + // Include Approvals if loaded + if len(letter.Approvals) > 0 { + resp.Approvals = make([]contract.OutgoingLetterApprovalResponse, len(letter.Approvals)) + for i, approval := range letter.Approvals { + approvalResp := contract.OutgoingLetterApprovalResponse{ + ID: approval.ID, + ApproverID: approval.ApproverID, + Status: string(approval.Status), + Remarks: approval.Remarks, + ActedAt: approval.ActedAt, + CreatedAt: approval.CreatedAt, + } + + // Include step order if step is loaded + if approval.Step != nil { + approvalResp.StepOrder = approval.Step.StepOrder + } + + resp.Approvals[i] = approvalResp + } + } + + return resp } func transformDiscussionToResponse(discussion *entities.LetterOutgoingDiscussion) *contract.DiscussionResponse { @@ -833,4 +907,4 @@ func transformDiscussionToResponse(discussion *entities.LetterOutgoingDiscussion CreatedAt: discussion.CreatedAt, UpdatedAt: discussion.UpdatedAt, } -} \ No newline at end of file +}