This commit is contained in:
Aditya Siregar 2025-08-19 16:11:32 +07:00
parent f41daa63da
commit 7d5f061a1b
6 changed files with 294 additions and 217 deletions

View File

@ -28,18 +28,19 @@ type CreateOutgoingLetterRequest struct {
ReceiverInstitutionID *uuid.UUID `json:"receiver_institution_id,omitempty"` ReceiverInstitutionID *uuid.UUID `json:"receiver_institution_id,omitempty"`
IssueDate time.Time `json:"issue_date" validate:"required"` IssueDate time.Time `json:"issue_date" validate:"required"`
ApprovalFlowID *uuid.UUID `json:"approval_flow_id,omitempty"` ApprovalFlowID *uuid.UUID `json:"approval_flow_id,omitempty"`
Recipients []CreateOutgoingLetterRecipient `json:"recipients,omitempty"` Recipients []CreateOutgoingLetterRecipient `json:"recipients,omitempty"`
Attachments []CreateOutgoingLetterAttachment `json:"attachments,omitempty"` Attachments []CreateOutgoingLetterAttachment `json:"attachments,omitempty"`
UserID uuid.UUID
} }
type OutgoingLetterRecipientResponse struct { type OutgoingLetterRecipientResponse struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Email *string `json:"email,omitempty"` Email *string `json:"email,omitempty"`
Position *string `json:"position,omitempty"` Position *string `json:"position,omitempty"`
Institution *string `json:"institution,omitempty"` Institution *string `json:"institution,omitempty"`
IsPrimary bool `json:"is_primary"` IsPrimary bool `json:"is_primary"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
} }
type OutgoingLetterAttachmentResponse struct { type OutgoingLetterAttachmentResponse struct {
@ -160,10 +161,10 @@ type DiscussionResponse struct {
} }
type ApprovalFlowRequest struct { type ApprovalFlowRequest struct {
DepartmentID uuid.UUID `json:"department_id" validate:"required"` DepartmentID uuid.UUID `json:"department_id" validate:"required"`
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
Steps []ApprovalFlowStepRequest `json:"steps" validate:"required,dive"` Steps []ApprovalFlowStepRequest `json:"steps" validate:"required,dive"`
} }
@ -188,16 +189,16 @@ type ApprovalFlowResponse struct {
} }
type ApprovalFlowStepResponse struct { type ApprovalFlowStepResponse struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
StepOrder int `json:"step_order"` StepOrder int `json:"step_order"`
ParallelGroup int `json:"parallel_group"` ParallelGroup int `json:"parallel_group"`
ApproverRoleID *uuid.UUID `json:"approver_role_id,omitempty"` ApproverRoleID *uuid.UUID `json:"approver_role_id,omitempty"`
ApproverRole *RoleResponse `json:"approver_role,omitempty"` ApproverRole *RoleResponse `json:"approver_role,omitempty"`
ApproverUserID *uuid.UUID `json:"approver_user_id,omitempty"` ApproverUserID *uuid.UUID `json:"approver_user_id,omitempty"`
ApproverUser *UserResponse `json:"approver_user,omitempty"` ApproverUser *UserResponse `json:"approver_user,omitempty"`
Required bool `json:"required"` Required bool `json:"required"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
} }
type ListApprovalFlowsRequest struct { type ListApprovalFlowsRequest struct {
@ -210,4 +211,4 @@ type ListApprovalFlowsRequest struct {
type ListApprovalFlowsResponse struct { type ListApprovalFlowsResponse struct {
Items []*ApprovalFlowResponse `json:"items"` Items []*ApprovalFlowResponse `json:"items"`
Total int64 `json:"total"` Total int64 `json:"total"`
} }

View File

@ -35,13 +35,11 @@ func (h *AdminApprovalFlowHandler) CreateApprovalFlow(c *gin.Context) {
return return
} }
// Validate that at least one step is provided
if len(req.Steps) == 0 { if len(req.Steps) == 0 {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "at least one approval step is required", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "at least one approval step is required", Code: http.StatusBadRequest})
return return
} }
// Validate each step has either a role or user as approver
for i, step := range req.Steps { for i, step := range req.Steps {
if step.ApproverRoleID == nil && step.ApproverUserID == nil { if step.ApproverRoleID == nil && step.ApproverUserID == nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ c.JSON(http.StatusBadRequest, &contract.ErrorResponse{
@ -113,13 +111,11 @@ func (h *AdminApprovalFlowHandler) UpdateApprovalFlow(c *gin.Context) {
return return
} }
// Validate that at least one step is provided
if len(req.Steps) == 0 { if len(req.Steps) == 0 {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "at least one approval step is required", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "at least one approval step is required", Code: http.StatusBadRequest})
return return
} }
// Validate each step has either a role or user as approver
for i, step := range req.Steps { for i, step := range req.Steps {
if step.ApproverRoleID == nil && step.ApproverUserID == nil { if step.ApproverRoleID == nil && step.ApproverUserID == nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ c.JSON(http.StatusBadRequest, &contract.ErrorResponse{
@ -341,4 +337,4 @@ func (h *AdminApprovalFlowHandler) CloneApprovalFlow(c *gin.Context) {
} }
c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp)) c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp))
} }

View File

@ -2,6 +2,7 @@ package handler
import ( import (
"context" "context"
"eslogad-be/internal/appcontext"
"net/http" "net/http"
"strconv" "strconv"
@ -17,20 +18,20 @@ type LetterOutgoingService interface {
ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error) ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error)
UpdateOutgoingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) UpdateOutgoingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error)
DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error
SubmitForApproval(ctx context.Context, letterID uuid.UUID) error SubmitForApproval(ctx context.Context, letterID uuid.UUID) error
ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error
RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error
SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error
ArchiveOutgoingLetter(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 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 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 RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error
AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error
RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID) 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) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateDiscussionRequest) (*contract.DiscussionResponse, error)
UpdateDiscussion(ctx context.Context, discussionID uuid.UUID, req *contract.UpdateDiscussionRequest) error UpdateDiscussion(ctx context.Context, discussionID uuid.UUID, req *contract.UpdateDiscussionRequest) error
DeleteDiscussion(ctx context.Context, discussionID uuid.UUID) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest})
return return
} }
ctx := c.Request.Context()
req.UserID = appcontext.FromGinContext(ctx).UserID
resp, err := h.svc.CreateOutgoingLetter(c.Request.Context(), &req) resp, err := h.svc.CreateOutgoingLetter(c.Request.Context(), &req)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp)) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return return
} }
resp, err := h.svc.GetOutgoingLetterByID(c.Request.Context(), id) resp, err := h.svc.GetOutgoingLetterByID(c.Request.Context(), id)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) 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")) page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
offset := (page - 1) * limit offset := (page - 1) * limit
status := c.Query("status") status := c.Query("status")
query := c.Query("q") query := c.Query("q")
createdByStr := c.Query("created_by") createdByStr := c.Query("created_by")
receiverInstitutionStr := c.Query("receiver_institution_id") receiverInstitutionStr := c.Query("receiver_institution_id")
var statusPtr *string var statusPtr *string
var queryPtr *string var queryPtr *string
var createdByPtr *uuid.UUID var createdByPtr *uuid.UUID
var receiverInstitutionPtr *uuid.UUID var receiverInstitutionPtr *uuid.UUID
if status != "" { if status != "" {
statusPtr = &status statusPtr = &status
} }
@ -107,7 +110,7 @@ func (h *LetterOutgoingHandler) ListOutgoingLetters(c *gin.Context) {
receiverInstitutionPtr = &receiverInstitution receiverInstitutionPtr = &receiverInstitution
} }
} }
req := &contract.ListOutgoingLettersRequest{ req := &contract.ListOutgoingLettersRequest{
Limit: limit, Limit: limit,
Offset: offset, Offset: offset,
@ -116,13 +119,13 @@ func (h *LetterOutgoingHandler) ListOutgoingLetters(c *gin.Context) {
CreatedBy: createdByPtr, CreatedBy: createdByPtr,
ReceiverInstitutionID: receiverInstitutionPtr, ReceiverInstitutionID: receiverInstitutionPtr,
} }
resp, err := h.svc.ListOutgoingLetters(c.Request.Context(), req) resp, err := h.svc.ListOutgoingLetters(c.Request.Context(), req)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return return
} }
var req contract.UpdateOutgoingLetterRequest var req contract.UpdateOutgoingLetterRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest})
return return
} }
resp, err := h.svc.UpdateOutgoingLetter(c.Request.Context(), id, &req) resp, err := h.svc.UpdateOutgoingLetter(c.Request.Context(), id, &req)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.DeleteOutgoingLetter(c.Request.Context(), id); err != nil { if err := h.svc.DeleteOutgoingLetter(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "deleted"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.SubmitForApproval(c.Request.Context(), id); err != nil { if err := h.svc.SubmitForApproval(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "submitted for approval"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return return
} }
var req contract.ApproveLetterRequest var req contract.ApproveLetterRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.ApproveOutgoingLetter(c.Request.Context(), id, &req); err != nil { if err := h.svc.ApproveOutgoingLetter(c.Request.Context(), id, &req); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "approved"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return return
} }
var req contract.RejectLetterRequest var req contract.RejectLetterRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.RejectOutgoingLetter(c.Request.Context(), id, &req); err != nil { if err := h.svc.RejectOutgoingLetter(c.Request.Context(), id, &req); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "rejected"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.SendOutgoingLetter(c.Request.Context(), id); err != nil { if err := h.svc.SendOutgoingLetter(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "sent"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.ArchiveOutgoingLetter(c.Request.Context(), id); err != nil { if err := h.svc.ArchiveOutgoingLetter(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "archived"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return return
} }
var req contract.AddRecipientsRequest var req contract.AddRecipientsRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.AddRecipients(c.Request.Context(), id, &req); err != nil { if err := h.svc.AddRecipients(c.Request.Context(), id, &req); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "recipients added"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest})
return return
} }
recipientID, err := uuid.Parse(c.Param("recipient_id")) recipientID, err := uuid.Parse(c.Param("recipient_id"))
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid recipient id", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid recipient id", Code: http.StatusBadRequest})
return return
} }
var req contract.UpdateRecipientRequest var req contract.UpdateRecipientRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.UpdateRecipient(c.Request.Context(), id, recipientID, &req); err != nil { 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}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "recipient updated"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest})
return return
} }
recipientID, err := uuid.Parse(c.Param("recipient_id")) recipientID, err := uuid.Parse(c.Param("recipient_id"))
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid recipient id", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid recipient id", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.RemoveRecipient(c.Request.Context(), id, recipientID); err != nil { if err := h.svc.RemoveRecipient(c.Request.Context(), id, recipientID); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "recipient removed"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return return
} }
var req contract.AddAttachmentsRequest var req contract.AddAttachmentsRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.AddAttachments(c.Request.Context(), id, &req); err != nil { if err := h.svc.AddAttachments(c.Request.Context(), id, &req); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "attachments added"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest})
return return
} }
attachmentID, err := uuid.Parse(c.Param("attachment_id")) attachmentID, err := uuid.Parse(c.Param("attachment_id"))
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid attachment id", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid attachment id", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.RemoveAttachment(c.Request.Context(), id, attachmentID); err != nil { if err := h.svc.RemoveAttachment(c.Request.Context(), id, attachmentID); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "attachment removed"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid letter id", Code: http.StatusBadRequest})
return return
} }
var req contract.CreateDiscussionRequest var req contract.CreateDiscussionRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest})
return return
} }
resp, err := h.svc.CreateDiscussion(c.Request.Context(), id, &req) resp, err := h.svc.CreateDiscussion(c.Request.Context(), id, &req)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp)) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid discussion id", Code: http.StatusBadRequest})
return return
} }
var req contract.UpdateDiscussionRequest var req contract.UpdateDiscussionRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.UpdateDiscussion(c.Request.Context(), discussionID, &req); err != nil { if err := h.svc.UpdateDiscussion(c.Request.Context(), discussionID, &req); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "discussion updated"}) 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}) c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid discussion id", Code: http.StatusBadRequest})
return return
} }
if err := h.svc.DeleteDiscussion(c.Request.Context(), discussionID); err != nil { if err := h.svc.DeleteDiscussion(c.Request.Context(), discussionID); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return return
} }
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "discussion deleted"}) c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "discussion deleted"})
} }

View File

@ -94,6 +94,10 @@ func (r *LetterOutgoingRepository) List(ctx context.Context, filter ListOutgoing
Preload("Priority"). Preload("Priority").
Preload("ReceiverInstitution"). Preload("ReceiverInstitution").
Preload("Creator"). Preload("Creator").
Preload("Recipients").
Preload("Attachments").
Preload("Approvals.Step").
Preload("Approvals.Approver").
Order("created_at DESC"). Order("created_at DESC").
Limit(limit). Limit(limit).
Offset(offset). Offset(offset).

View File

@ -151,20 +151,20 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
lettersch.GET("/outgoing", r.letterOutgoingHandler.ListOutgoingLetters) lettersch.GET("/outgoing", r.letterOutgoingHandler.ListOutgoingLetters)
lettersch.PUT("/outgoing/:id", r.letterOutgoingHandler.UpdateOutgoingLetter) lettersch.PUT("/outgoing/:id", r.letterOutgoingHandler.UpdateOutgoingLetter)
lettersch.DELETE("/outgoing/:id", r.letterOutgoingHandler.DeleteOutgoingLetter) lettersch.DELETE("/outgoing/:id", r.letterOutgoingHandler.DeleteOutgoingLetter)
lettersch.POST("/outgoing/:id/submit", r.letterOutgoingHandler.SubmitForApproval) lettersch.POST("/outgoing/:id/submit", r.letterOutgoingHandler.SubmitForApproval)
lettersch.POST("/outgoing/:id/approve", r.letterOutgoingHandler.ApproveOutgoingLetter) lettersch.POST("/outgoing/:id/approve", r.letterOutgoingHandler.ApproveOutgoingLetter)
lettersch.POST("/outgoing/:id/reject", r.letterOutgoingHandler.RejectOutgoingLetter) lettersch.POST("/outgoing/:id/reject", r.letterOutgoingHandler.RejectOutgoingLetter)
lettersch.POST("/outgoing/:id/send", r.letterOutgoingHandler.SendOutgoingLetter) lettersch.POST("/outgoing/:id/send", r.letterOutgoingHandler.SendOutgoingLetter)
lettersch.POST("/outgoing/:id/archive", r.letterOutgoingHandler.ArchiveOutgoingLetter) lettersch.POST("/outgoing/:id/archive", r.letterOutgoingHandler.ArchiveOutgoingLetter)
lettersch.POST("/outgoing/:id/recipients", r.letterOutgoingHandler.AddRecipients) lettersch.POST("/outgoing/:id/recipients", r.letterOutgoingHandler.AddRecipients)
lettersch.PUT("/outgoing/:id/recipients/:recipient_id", r.letterOutgoingHandler.UpdateRecipient) lettersch.PUT("/outgoing/:id/recipients/:recipient_id", r.letterOutgoingHandler.UpdateRecipient)
lettersch.DELETE("/outgoing/:id/recipients/:recipient_id", r.letterOutgoingHandler.RemoveRecipient) lettersch.DELETE("/outgoing/:id/recipients/:recipient_id", r.letterOutgoingHandler.RemoveRecipient)
lettersch.POST("/outgoing/:id/attachments", r.letterOutgoingHandler.AddAttachments) lettersch.POST("/outgoing/:id/attachments", r.letterOutgoingHandler.AddAttachments)
lettersch.DELETE("/outgoing/:id/attachments/:attachment_id", r.letterOutgoingHandler.RemoveAttachment) lettersch.DELETE("/outgoing/:id/attachments/:attachment_id", r.letterOutgoingHandler.RemoveAttachment)
lettersch.POST("/outgoing/:id/discussions", r.letterOutgoingHandler.CreateDiscussion) lettersch.POST("/outgoing/:id/discussions", r.letterOutgoingHandler.CreateDiscussion)
lettersch.PUT("/outgoing/discussions/:discussion_id", r.letterOutgoingHandler.UpdateDiscussion) lettersch.PUT("/outgoing/discussions/:discussion_id", r.letterOutgoingHandler.UpdateDiscussion)
lettersch.DELETE("/outgoing/discussions/:discussion_id", r.letterOutgoingHandler.DeleteDiscussion) 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.GET("department", r.dispRouteHandler.ListByFromDept)
droutes.PUT(":id/active", r.dispRouteHandler.SetActive) droutes.PUT(":id/active", r.dispRouteHandler.SetActive)
} }
admin := v1.Group("/admin") admin := v1.Group("/setting")
admin.Use(r.authMiddleware.RequireAuth()) admin.Use(r.authMiddleware.RequireAuth())
{ {
approvalFlows := admin.Group("/approval-flows") approvalFlows := admin.Group("/approval-flows")
approvalFlows.Use(r.authMiddleware.RequirePermissions("admin.approval_flow"))
{ {
approvalFlows.POST("", r.adminApprovalFlowHandler.CreateApprovalFlow) approvalFlows.POST("", r.adminApprovalFlowHandler.CreateApprovalFlow)
approvalFlows.GET("", r.adminApprovalFlowHandler.ListApprovalFlows) approvalFlows.GET("", r.adminApprovalFlowHandler.ListApprovalFlows)

View File

@ -18,36 +18,36 @@ type LetterOutgoingService interface {
ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error) ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error)
UpdateOutgoingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) UpdateOutgoingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error)
DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error
SubmitForApproval(ctx context.Context, letterID uuid.UUID) error SubmitForApproval(ctx context.Context, letterID uuid.UUID) error
ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error
RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error
SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error
ArchiveOutgoingLetter(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 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 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 RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error
AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error
RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID) 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) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateDiscussionRequest) (*contract.DiscussionResponse, error)
UpdateDiscussion(ctx context.Context, discussionID uuid.UUID, req *contract.UpdateDiscussionRequest) error UpdateDiscussion(ctx context.Context, discussionID uuid.UUID, req *contract.UpdateDiscussionRequest) error
DeleteDiscussion(ctx context.Context, discussionID uuid.UUID) error DeleteDiscussion(ctx context.Context, discussionID uuid.UUID) error
} }
type LetterOutgoingServiceImpl struct { type LetterOutgoingServiceImpl struct {
db *gorm.DB db *gorm.DB
letterRepo *repository.LetterOutgoingRepository letterRepo *repository.LetterOutgoingRepository
attachmentRepo *repository.LetterOutgoingAttachmentRepository attachmentRepo *repository.LetterOutgoingAttachmentRepository
recipientRepo *repository.LetterOutgoingRecipientRepository recipientRepo *repository.LetterOutgoingRecipientRepository
discussionRepo *repository.LetterOutgoingDiscussionRepository discussionRepo *repository.LetterOutgoingDiscussionRepository
discussionAttachmentRepo *repository.LetterOutgoingDiscussionAttachmentRepository discussionAttachmentRepo *repository.LetterOutgoingDiscussionAttachmentRepository
activityLogRepo *repository.LetterOutgoingActivityLogRepository activityLogRepo *repository.LetterOutgoingActivityLogRepository
approvalFlowRepo *repository.ApprovalFlowRepository approvalFlowRepo *repository.ApprovalFlowRepository
approvalRepo *repository.LetterOutgoingApprovalRepository approvalRepo *repository.LetterOutgoingApprovalRepository
txManager *repository.TxManager txManager *repository.TxManager
} }
func NewLetterOutgoingService( func NewLetterOutgoingService(
@ -77,8 +77,6 @@ func NewLetterOutgoingService(
} }
func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, req *contract.CreateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) { func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, req *contract.CreateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) {
userID := getUserIDFromContext(ctx)
letter := &entities.LetterOutgoing{ letter := &entities.LetterOutgoing{
Subject: req.Subject, Subject: req.Subject,
Description: req.Description, Description: req.Description,
@ -87,18 +85,18 @@ func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, re
IssueDate: req.IssueDate, IssueDate: req.IssueDate,
Status: entities.LetterOutgoingStatusDraft, Status: entities.LetterOutgoingStatusDraft,
ApprovalFlowID: req.ApprovalFlowID, ApprovalFlowID: req.ApprovalFlowID,
CreatedBy: userID, CreatedBy: req.UserID,
} }
if req.ReferenceNumber != nil { if req.ReferenceNumber != nil {
letter.ReferenceNumber = req.ReferenceNumber letter.ReferenceNumber = req.ReferenceNumber
} }
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
if err := s.letterRepo.Create(txCtx, letter); err != nil { if err := s.letterRepo.Create(txCtx, letter); err != nil {
return err return err
} }
if len(req.Recipients) > 0 { if len(req.Recipients) > 0 {
recipients := make([]entities.LetterOutgoingRecipient, len(req.Recipients)) recipients := make([]entities.LetterOutgoingRecipient, len(req.Recipients))
for i, r := range req.Recipients { for i, r := range req.Recipients {
@ -115,7 +113,7 @@ func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, re
return err return err
} }
} }
if len(req.Attachments) > 0 { if len(req.Attachments) > 0 {
attachments := make([]entities.LetterOutgoingAttachment, len(req.Attachments)) attachments := make([]entities.LetterOutgoingAttachment, len(req.Attachments))
for i, a := range req.Attachments { for i, a := range req.Attachments {
@ -124,35 +122,35 @@ func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, re
FileURL: a.FileURL, FileURL: a.FileURL,
FileName: a.FileName, FileName: a.FileName,
FileType: a.FileType, FileType: a.FileType,
UploadedBy: &userID, UploadedBy: &req.UserID,
} }
} }
if err := s.attachmentRepo.CreateBulk(txCtx, attachments); err != nil { if err := s.attachmentRepo.CreateBulk(txCtx, attachments); err != nil {
return err return err
} }
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letter.ID, LetterID: letter.ID,
ActionType: entities.LetterOutgoingActionCreated, ActionType: entities.LetterOutgoingActionCreated,
ActorUserID: &userID, ActorUserID: &req.UserID,
} }
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
result, err := s.letterRepo.Get(ctx, letter.ID) result, err := s.letterRepo.Get(ctx, letter.ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return transformLetterToResponse(result), nil return transformLetterToResponse(result), nil
} }
@ -161,7 +159,7 @@ func (s *LetterOutgoingServiceImpl) GetOutgoingLetterByID(ctx context.Context, i
if err != nil { if err != nil {
return nil, err return nil, err
} }
return transformLetterToResponse(letter), nil return transformLetterToResponse(letter), nil
} }
@ -174,17 +172,17 @@ func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req
FromDate: req.FromDate, FromDate: req.FromDate,
ToDate: req.ToDate, ToDate: req.ToDate,
} }
letters, total, err := s.letterRepo.List(ctx, filter, req.Limit, req.Offset) letters, total, err := s.letterRepo.List(ctx, filter, req.Limit, req.Offset)
if err != nil { if err != nil {
return nil, err return nil, err
} }
items := make([]*contract.OutgoingLetterResponse, len(letters)) items := make([]*contract.OutgoingLetterResponse, len(letters))
for i, letter := range letters { for i, letter := range letters {
items[i] = transformLetterToResponse(&letter) items[i] = transformLetterToResponse(&letter)
} }
return &contract.ListOutgoingLettersResponse{ return &contract.ListOutgoingLettersResponse{
Items: items, Items: items,
Total: total, 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) { func (s *LetterOutgoingServiceImpl) UpdateOutgoingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error) {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
letter, err := s.letterRepo.Get(ctx, id) letter, err := s.letterRepo.Get(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if letter.Status != entities.LetterOutgoingStatusDraft { if letter.Status != entities.LetterOutgoingStatusDraft {
return nil, gorm.ErrInvalidData return nil, gorm.ErrInvalidData
} }
if req.Subject != nil { if req.Subject != nil {
letter.Subject = *req.Subject letter.Subject = *req.Subject
} }
@ -221,12 +219,12 @@ func (s *LetterOutgoingServiceImpl) UpdateOutgoingLetter(ctx context.Context, id
if req.ReferenceNumber != nil { if req.ReferenceNumber != nil {
letter.ReferenceNumber = req.ReferenceNumber letter.ReferenceNumber = req.ReferenceNumber
} }
err = s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { err = s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
if err := s.letterRepo.Update(txCtx, letter); err != nil { if err := s.letterRepo.Update(txCtx, letter); err != nil {
return err return err
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letter.ID, LetterID: letter.ID,
ActionType: entities.LetterOutgoingActionUpdated, ActionType: entities.LetterOutgoingActionUpdated,
@ -235,39 +233,39 @@ func (s *LetterOutgoingServiceImpl) UpdateOutgoingLetter(ctx context.Context, id
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
result, err := s.letterRepo.Get(ctx, id) result, err := s.letterRepo.Get(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return transformLetterToResponse(result), nil return transformLetterToResponse(result), nil
} }
func (s *LetterOutgoingServiceImpl) DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error { func (s *LetterOutgoingServiceImpl) DeleteOutgoingLetter(ctx context.Context, id uuid.UUID) error {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
letter, err := s.letterRepo.Get(ctx, id) letter, err := s.letterRepo.Get(ctx, id)
if err != nil { if err != nil {
return err return err
} }
if letter.Status != entities.LetterOutgoingStatusDraft { if letter.Status != entities.LetterOutgoingStatusDraft {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
if err := s.letterRepo.SoftDelete(txCtx, id); err != nil { if err := s.letterRepo.SoftDelete(txCtx, id); err != nil {
return err return err
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letter.ID, LetterID: letter.ID,
ActionType: entities.LetterOutgoingActionDeleted, ActionType: entities.LetterOutgoingActionDeleted,
@ -276,32 +274,32 @@ func (s *LetterOutgoingServiceImpl) DeleteOutgoingLetter(ctx context.Context, id
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
} }
func (s *LetterOutgoingServiceImpl) SubmitForApproval(ctx context.Context, letterID uuid.UUID) error { func (s *LetterOutgoingServiceImpl) SubmitForApproval(ctx context.Context, letterID uuid.UUID) error {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
letter, err := s.letterRepo.Get(ctx, letterID) letter, err := s.letterRepo.Get(ctx, letterID)
if err != nil { if err != nil {
return err return err
} }
if letter.Status != entities.LetterOutgoingStatusDraft { if letter.Status != entities.LetterOutgoingStatusDraft {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
if letter.ApprovalFlowID == nil { if letter.ApprovalFlowID == nil {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
flow, err := s.approvalFlowRepo.Get(ctx, *letter.ApprovalFlowID) flow, err := s.approvalFlowRepo.Get(ctx, *letter.ApprovalFlowID)
if err != nil { if err != nil {
return err return err
} }
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
approvals := make([]entities.LetterOutgoingApproval, len(flow.Steps)) approvals := make([]entities.LetterOutgoingApproval, len(flow.Steps))
for i, step := range flow.Steps { for i, step := range flow.Steps {
@ -311,15 +309,15 @@ func (s *LetterOutgoingServiceImpl) SubmitForApproval(ctx context.Context, lette
Status: entities.ApprovalStatusPending, Status: entities.ApprovalStatusPending,
} }
} }
if err := s.approvalRepo.CreateBulk(txCtx, approvals); err != nil { if err := s.approvalRepo.CreateBulk(txCtx, approvals); err != nil {
return err return err
} }
if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusPendingApproval); err != nil { if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusPendingApproval); err != nil {
return err return err
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letterID, LetterID: letterID,
ActionType: entities.LetterOutgoingActionSubmittedApproval, ActionType: entities.LetterOutgoingActionSubmittedApproval,
@ -330,28 +328,28 @@ func (s *LetterOutgoingServiceImpl) SubmitForApproval(ctx context.Context, lette
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
} }
func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error { func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.ApproveLetterRequest) error {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
letter, err := s.letterRepo.Get(ctx, letterID) letter, err := s.letterRepo.Get(ctx, letterID)
if err != nil { if err != nil {
return err return err
} }
if letter.Status != entities.LetterOutgoingStatusPendingApproval { if letter.Status != entities.LetterOutgoingStatusPendingApproval {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
approvals, err := s.approvalRepo.ListByLetter(ctx, letterID) approvals, err := s.approvalRepo.ListByLetter(ctx, letterID)
if err != nil { if err != nil {
return err return err
} }
var currentApproval *entities.LetterOutgoingApproval var currentApproval *entities.LetterOutgoingApproval
for i := range approvals { for i := range approvals {
if approvals[i].Status == entities.ApprovalStatusPending { if approvals[i].Status == entities.ApprovalStatusPending {
@ -363,22 +361,22 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l
} }
} }
} }
if currentApproval == nil { if currentApproval == nil {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
now := time.Now() now := time.Now()
currentApproval.Status = entities.ApprovalStatusApproved currentApproval.Status = entities.ApprovalStatusApproved
currentApproval.ApproverID = &userID currentApproval.ApproverID = &userID
currentApproval.ActedAt = &now currentApproval.ActedAt = &now
currentApproval.Remarks = req.Remarks currentApproval.Remarks = req.Remarks
if err := s.approvalRepo.Update(txCtx, currentApproval); err != nil { if err := s.approvalRepo.Update(txCtx, currentApproval); err != nil {
return err return err
} }
allApproved := true allApproved := true
for _, approval := range approvals { for _, approval := range approvals {
if approval.ID != currentApproval.ID && approval.Status == entities.ApprovalStatusPending { if approval.ID != currentApproval.ID && approval.Status == entities.ApprovalStatusPending {
@ -386,13 +384,13 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l
break break
} }
} }
if allApproved { if allApproved {
if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusApproved); err != nil { if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusApproved); err != nil {
return err return err
} }
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letterID, LetterID: letterID,
ActionType: entities.LetterOutgoingActionApproved, ActionType: entities.LetterOutgoingActionApproved,
@ -402,28 +400,28 @@ func (s *LetterOutgoingServiceImpl) ApproveOutgoingLetter(ctx context.Context, l
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
} }
func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error { func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, letterID uuid.UUID, req *contract.RejectLetterRequest) error {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
letter, err := s.letterRepo.Get(ctx, letterID) letter, err := s.letterRepo.Get(ctx, letterID)
if err != nil { if err != nil {
return err return err
} }
if letter.Status != entities.LetterOutgoingStatusPendingApproval { if letter.Status != entities.LetterOutgoingStatusPendingApproval {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
approvals, err := s.approvalRepo.ListByLetter(ctx, letterID) approvals, err := s.approvalRepo.ListByLetter(ctx, letterID)
if err != nil { if err != nil {
return err return err
} }
var currentApproval *entities.LetterOutgoingApproval var currentApproval *entities.LetterOutgoingApproval
for i := range approvals { for i := range approvals {
if approvals[i].Status == entities.ApprovalStatusPending { if approvals[i].Status == entities.ApprovalStatusPending {
@ -435,26 +433,26 @@ func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, le
} }
} }
} }
if currentApproval == nil { if currentApproval == nil {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
now := time.Now() now := time.Now()
currentApproval.Status = entities.ApprovalStatusRejected currentApproval.Status = entities.ApprovalStatusRejected
currentApproval.ApproverID = &userID currentApproval.ApproverID = &userID
currentApproval.ActedAt = &now currentApproval.ActedAt = &now
currentApproval.Remarks = &req.Reason currentApproval.Remarks = &req.Reason
if err := s.approvalRepo.Update(txCtx, currentApproval); err != nil { if err := s.approvalRepo.Update(txCtx, currentApproval); err != nil {
return err return err
} }
if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusDraft); err != nil { if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusDraft); err != nil {
return err return err
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letterID, LetterID: letterID,
ActionType: entities.LetterOutgoingActionRejected, ActionType: entities.LetterOutgoingActionRejected,
@ -466,28 +464,28 @@ func (s *LetterOutgoingServiceImpl) RejectOutgoingLetter(ctx context.Context, le
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
} }
func (s *LetterOutgoingServiceImpl) SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error { func (s *LetterOutgoingServiceImpl) SendOutgoingLetter(ctx context.Context, letterID uuid.UUID) error {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
letter, err := s.letterRepo.Get(ctx, letterID) letter, err := s.letterRepo.Get(ctx, letterID)
if err != nil { if err != nil {
return err return err
} }
if letter.Status != entities.LetterOutgoingStatusApproved { if letter.Status != entities.LetterOutgoingStatusApproved {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusSent); err != nil { if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusSent); err != nil {
return err return err
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letterID, LetterID: letterID,
ActionType: entities.LetterOutgoingActionSent, ActionType: entities.LetterOutgoingActionSent,
@ -498,28 +496,28 @@ func (s *LetterOutgoingServiceImpl) SendOutgoingLetter(ctx context.Context, lett
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
} }
func (s *LetterOutgoingServiceImpl) ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID) error { func (s *LetterOutgoingServiceImpl) ArchiveOutgoingLetter(ctx context.Context, letterID uuid.UUID) error {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
letter, err := s.letterRepo.Get(ctx, letterID) letter, err := s.letterRepo.Get(ctx, letterID)
if err != nil { if err != nil {
return err return err
} }
if letter.Status != entities.LetterOutgoingStatusSent { if letter.Status != entities.LetterOutgoingStatusSent {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusArchived); err != nil { if err := s.letterRepo.UpdateStatus(txCtx, letterID, entities.LetterOutgoingStatusArchived); err != nil {
return err return err
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letterID, LetterID: letterID,
ActionType: entities.LetterOutgoingActionArchived, ActionType: entities.LetterOutgoingActionArchived,
@ -530,23 +528,23 @@ func (s *LetterOutgoingServiceImpl) ArchiveOutgoingLetter(ctx context.Context, l
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
} }
func (s *LetterOutgoingServiceImpl) AddRecipients(ctx context.Context, letterID uuid.UUID, req *contract.AddRecipientsRequest) error { func (s *LetterOutgoingServiceImpl) AddRecipients(ctx context.Context, letterID uuid.UUID, req *contract.AddRecipientsRequest) error {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
letter, err := s.letterRepo.Get(ctx, letterID) letter, err := s.letterRepo.Get(ctx, letterID)
if err != nil { if err != nil {
return err return err
} }
if letter.Status != entities.LetterOutgoingStatusDraft { if letter.Status != entities.LetterOutgoingStatusDraft {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
recipients := make([]entities.LetterOutgoingRecipient, len(req.Recipients)) recipients := make([]entities.LetterOutgoingRecipient, len(req.Recipients))
for i, r := range req.Recipients { for i, r := range req.Recipients {
recipients[i] = entities.LetterOutgoingRecipient{ recipients[i] = entities.LetterOutgoingRecipient{
@ -558,12 +556,12 @@ func (s *LetterOutgoingServiceImpl) AddRecipients(ctx context.Context, letterID
IsPrimary: r.IsPrimary, IsPrimary: r.IsPrimary,
} }
} }
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
if err := s.recipientRepo.CreateBulk(txCtx, recipients); err != nil { if err := s.recipientRepo.CreateBulk(txCtx, recipients); err != nil {
return err return err
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letterID, LetterID: letterID,
ActionType: entities.LetterOutgoingActionRecipientAdded, ActionType: entities.LetterOutgoingActionRecipientAdded,
@ -572,7 +570,7 @@ func (s *LetterOutgoingServiceImpl) AddRecipients(ctx context.Context, letterID
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
} }
@ -582,11 +580,11 @@ func (s *LetterOutgoingServiceImpl) UpdateRecipient(ctx context.Context, letterI
if err != nil { if err != nil {
return err return err
} }
if letter.Status != entities.LetterOutgoingStatusDraft { if letter.Status != entities.LetterOutgoingStatusDraft {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
recipient := &entities.LetterOutgoingRecipient{ recipient := &entities.LetterOutgoingRecipient{
ID: recipientID, ID: recipientID,
RecipientName: req.Name, RecipientName: req.Name,
@ -595,27 +593,27 @@ func (s *LetterOutgoingServiceImpl) UpdateRecipient(ctx context.Context, letterI
RecipientInstitution: req.Institution, RecipientInstitution: req.Institution,
IsPrimary: req.IsPrimary, IsPrimary: req.IsPrimary,
} }
return s.recipientRepo.Update(ctx, recipient) return s.recipientRepo.Update(ctx, recipient)
} }
func (s *LetterOutgoingServiceImpl) RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error { func (s *LetterOutgoingServiceImpl) RemoveRecipient(ctx context.Context, letterID uuid.UUID, recipientID uuid.UUID) error {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
letter, err := s.letterRepo.Get(ctx, letterID) letter, err := s.letterRepo.Get(ctx, letterID)
if err != nil { if err != nil {
return err return err
} }
if letter.Status != entities.LetterOutgoingStatusDraft { if letter.Status != entities.LetterOutgoingStatusDraft {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
if err := s.recipientRepo.Delete(txCtx, recipientID); err != nil { if err := s.recipientRepo.Delete(txCtx, recipientID); err != nil {
return err return err
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letterID, LetterID: letterID,
ActionType: entities.LetterOutgoingActionRecipientRemoved, ActionType: entities.LetterOutgoingActionRecipientRemoved,
@ -625,23 +623,23 @@ func (s *LetterOutgoingServiceImpl) RemoveRecipient(ctx context.Context, letterI
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
} }
func (s *LetterOutgoingServiceImpl) AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error { func (s *LetterOutgoingServiceImpl) AddAttachments(ctx context.Context, letterID uuid.UUID, req *contract.AddAttachmentsRequest) error {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
letter, err := s.letterRepo.Get(ctx, letterID) letter, err := s.letterRepo.Get(ctx, letterID)
if err != nil { if err != nil {
return err return err
} }
if letter.Status != entities.LetterOutgoingStatusDraft { if letter.Status != entities.LetterOutgoingStatusDraft {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
attachments := make([]entities.LetterOutgoingAttachment, len(req.Attachments)) attachments := make([]entities.LetterOutgoingAttachment, len(req.Attachments))
for i, a := range req.Attachments { for i, a := range req.Attachments {
attachments[i] = entities.LetterOutgoingAttachment{ attachments[i] = entities.LetterOutgoingAttachment{
@ -652,12 +650,12 @@ func (s *LetterOutgoingServiceImpl) AddAttachments(ctx context.Context, letterID
UploadedBy: &userID, UploadedBy: &userID,
} }
} }
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
if err := s.attachmentRepo.CreateBulk(txCtx, attachments); err != nil { if err := s.attachmentRepo.CreateBulk(txCtx, attachments); err != nil {
return err return err
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letterID, LetterID: letterID,
ActionType: entities.LetterOutgoingActionAttachmentAdded, ActionType: entities.LetterOutgoingActionAttachmentAdded,
@ -666,28 +664,28 @@ func (s *LetterOutgoingServiceImpl) AddAttachments(ctx context.Context, letterID
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
} }
func (s *LetterOutgoingServiceImpl) RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID) error { func (s *LetterOutgoingServiceImpl) RemoveAttachment(ctx context.Context, letterID uuid.UUID, attachmentID uuid.UUID) error {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
letter, err := s.letterRepo.Get(ctx, letterID) letter, err := s.letterRepo.Get(ctx, letterID)
if err != nil { if err != nil {
return err return err
} }
if letter.Status != entities.LetterOutgoingStatusDraft { if letter.Status != entities.LetterOutgoingStatusDraft {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { return s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
if err := s.attachmentRepo.Delete(txCtx, attachmentID); err != nil { if err := s.attachmentRepo.Delete(txCtx, attachmentID); err != nil {
return err return err
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letterID, LetterID: letterID,
ActionType: entities.LetterOutgoingActionAttachmentRemoved, ActionType: entities.LetterOutgoingActionAttachmentRemoved,
@ -697,30 +695,30 @@ func (s *LetterOutgoingServiceImpl) RemoveAttachment(ctx context.Context, letter
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
} }
func (s *LetterOutgoingServiceImpl) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateDiscussionRequest) (*contract.DiscussionResponse, error) { func (s *LetterOutgoingServiceImpl) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateDiscussionRequest) (*contract.DiscussionResponse, error) {
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
discussion := &entities.LetterOutgoingDiscussion{ discussion := &entities.LetterOutgoingDiscussion{
LetterID: letterID, LetterID: letterID,
ParentID: req.ParentID, ParentID: req.ParentID,
UserID: userID, UserID: userID,
Message: req.Message, Message: req.Message,
} }
if req.Mentions != nil { if req.Mentions != nil {
discussion.Mentions = req.Mentions discussion.Mentions = req.Mentions
} }
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
if err := s.discussionRepo.Create(txCtx, discussion); err != nil { if err := s.discussionRepo.Create(txCtx, discussion); err != nil {
return err return err
} }
if len(req.Attachments) > 0 { if len(req.Attachments) > 0 {
attachments := make([]entities.LetterOutgoingDiscussionAttachment, len(req.Attachments)) attachments := make([]entities.LetterOutgoingDiscussionAttachment, len(req.Attachments))
for i, a := range req.Attachments { for i, a := range req.Attachments {
@ -736,7 +734,7 @@ func (s *LetterOutgoingServiceImpl) CreateDiscussion(ctx context.Context, letter
return err return err
} }
} }
activityLog := &entities.LetterOutgoingActivityLog{ activityLog := &entities.LetterOutgoingActivityLog{
LetterID: letterID, LetterID: letterID,
ActionType: entities.LetterOutgoingActionDiscussionAdded, ActionType: entities.LetterOutgoingActionDiscussionAdded,
@ -746,19 +744,19 @@ func (s *LetterOutgoingServiceImpl) CreateDiscussion(ctx context.Context, letter
if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil { if err := s.activityLogRepo.Create(txCtx, activityLog); err != nil {
return err return err
} }
return nil return nil
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
result, err := s.discussionRepo.Get(ctx, discussion.ID) result, err := s.discussionRepo.Get(ctx, discussion.ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return transformDiscussionToResponse(result), nil return transformDiscussionToResponse(result), nil
} }
@ -767,17 +765,17 @@ func (s *LetterOutgoingServiceImpl) UpdateDiscussion(ctx context.Context, discus
if err != nil { if err != nil {
return err return err
} }
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
if discussion.UserID != userID { if discussion.UserID != userID {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
discussion.Message = req.Message discussion.Message = req.Message
if req.Mentions != nil { if req.Mentions != nil {
discussion.Mentions = req.Mentions discussion.Mentions = req.Mentions
} }
return s.discussionRepo.Update(ctx, discussion) return s.discussionRepo.Update(ctx, discussion)
} }
@ -786,12 +784,12 @@ func (s *LetterOutgoingServiceImpl) DeleteDiscussion(ctx context.Context, discus
if err != nil { if err != nil {
return err return err
} }
userID := getUserIDFromContext(ctx) userID := getUserIDFromContext(ctx)
if discussion.UserID != userID { if discussion.UserID != userID {
return gorm.ErrInvalidData return gorm.ErrInvalidData
} }
return s.discussionRepo.Delete(ctx, discussionID) return s.discussionRepo.Delete(ctx, discussionID)
} }
@ -808,7 +806,7 @@ func ptr(s string) *string {
} }
func transformLetterToResponse(letter *entities.LetterOutgoing) *contract.OutgoingLetterResponse { func transformLetterToResponse(letter *entities.LetterOutgoing) *contract.OutgoingLetterResponse {
return &contract.OutgoingLetterResponse{ resp := &contract.OutgoingLetterResponse{
ID: letter.ID, ID: letter.ID,
LetterNumber: letter.LetterNumber, LetterNumber: letter.LetterNumber,
ReferenceNumber: letter.ReferenceNumber, ReferenceNumber: letter.ReferenceNumber,
@ -823,6 +821,82 @@ func transformLetterToResponse(letter *entities.LetterOutgoing) *contract.Outgoi
CreatedAt: letter.CreatedAt, CreatedAt: letter.CreatedAt,
UpdatedAt: letter.UpdatedAt, 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 { func transformDiscussionToResponse(discussion *entities.LetterOutgoingDiscussion) *contract.DiscussionResponse {
@ -833,4 +907,4 @@ func transformDiscussionToResponse(discussion *entities.LetterOutgoingDiscussion
CreatedAt: discussion.CreatedAt, CreatedAt: discussion.CreatedAt,
UpdatedAt: discussion.UpdatedAt, UpdatedAt: discussion.UpdatedAt,
} }
} }