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

@ -30,6 +30,7 @@ type CreateOutgoingLetterRequest struct {
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 {

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{

View File

@ -2,6 +2,7 @@ package handler
import ( import (
"context" "context"
"eslogad-be/internal/appcontext"
"net/http" "net/http"
"strconv" "strconv"
@ -50,6 +51,8 @@ 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 {

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

@ -186,11 +186,10 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
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

@ -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,7 +85,7 @@ 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 {
@ -124,7 +122,7 @@ 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 {
@ -135,7 +133,7 @@ func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, re
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
@ -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 {