diff --git a/internal/handler/letter_handler.go b/internal/handler/letter_handler.go index 7ea20e7..ceb7add 100644 --- a/internal/handler/letter_handler.go +++ b/internal/handler/letter_handler.go @@ -3,6 +3,7 @@ package handler import ( "context" "eslogad-be/internal/appcontext" + "fmt" "net/http" "strconv" "strings" @@ -23,6 +24,7 @@ type LetterService interface { MarkOutgoingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error) UpdateIncomingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) SoftDeleteIncomingLetter(ctx context.Context, id uuid.UUID) error + BulkSoftDeleteIncomingLetters(ctx context.Context, ids []uuid.UUID) error BulkArchiveIncomingLetters(ctx context.Context, letterIDs []uuid.UUID) (*contract.BulkArchiveLettersResponse, error) ArchiveIncomingLetter(ctx context.Context, letterID uuid.UUID) error @@ -282,6 +284,29 @@ func (h *LetterHandler) DeleteIncomingLetter(c *gin.Context) { }) } +func (h *LetterHandler) BulkDeleteIncomingLetters(c *gin.Context) { + var req struct { + IDs []uuid.UUID `json:"ids" binding:"required,min=1"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, contract.ErrorResponse{ + Message: "Invalid request body", + Error: err.Error(), + }) + return + } + + if err := h.svc.BulkSoftDeleteIncomingLetters(c.Request.Context(), req.IDs); err != nil { + h.handleServiceError(c, err) + return + } + + h.respondSuccess(c, http.StatusOK, &contract.SuccessResponse{ + Message: fmt.Sprintf("%d letters deleted successfully", len(req.IDs)), + }) +} + func (h *LetterHandler) CreateDispositions(c *gin.Context) { var req contract.CreateLetterDispositionRequest if !h.bindJSON(c, &req) { diff --git a/internal/processor/letter_processor.go b/internal/processor/letter_processor.go index 6892960..5b16443 100644 --- a/internal/processor/letter_processor.go +++ b/internal/processor/letter_processor.go @@ -352,6 +352,32 @@ func (p *LetterProcessorImpl) SoftDeleteIncomingLetter(ctx context.Context, id u }) } +func (p *LetterProcessorImpl) BulkSoftDeleteIncomingLetters(ctx context.Context, ids []uuid.UUID) error { + if len(ids) == 0 { + return nil + } + + return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error { + if err := p.letterRepo.BulkSoftDelete(txCtx, ids); err != nil { + return err + } + + if p.activity != nil { + userID := appcontext.FromGinContext(txCtx).UserID + action := "letter.bulk_deleted" + + // Log activity untuk setiap letter yang dihapus + for _, id := range ids { + if err := p.activity.Log(txCtx, id, action, &userID, nil, nil, nil, nil, nil, map[string]interface{}{}); err != nil { + return err + } + } + } + + return nil + }) +} + // CreateDispositions creates a new disposition with modular helper functions func (p *LetterProcessorImpl) CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error) { // Transaction should be handled at service layer diff --git a/internal/repository/letter_repository.go b/internal/repository/letter_repository.go index 64e1ca7..81420c9 100644 --- a/internal/repository/letter_repository.go +++ b/internal/repository/letter_repository.go @@ -54,6 +54,15 @@ func (r *LetterIncomingRepository) SoftDelete(ctx context.Context, id uuid.UUID) return db.WithContext(ctx).Exec("UPDATE letters_incoming SET deleted_at = CURRENT_TIMESTAMP WHERE id = ? AND deleted_at IS NULL", id).Error } +func (r *LetterIncomingRepository) BulkSoftDelete(ctx context.Context, ids []uuid.UUID) error { + if len(ids) == 0 { + return nil + } + + db := DBFromContext(ctx, r.db) + return db.WithContext(ctx).Exec("UPDATE letters_incoming SET deleted_at = CURRENT_TIMESTAMP WHERE id IN ? AND deleted_at IS NULL", ids).Error +} + func (r *LetterIncomingRepository) BulkArchive(ctx context.Context, letterIDs []uuid.UUID) (int64, error) { db := DBFromContext(ctx, r.db) now := time.Now() diff --git a/internal/router/health_handler.go b/internal/router/health_handler.go index 35be88a..8d64d03 100644 --- a/internal/router/health_handler.go +++ b/internal/router/health_handler.go @@ -82,6 +82,7 @@ type LetterHandler interface { MarkOutgoingLetterAsRead(c *gin.Context) UpdateIncomingLetter(c *gin.Context) DeleteIncomingLetter(c *gin.Context) + BulkDeleteIncomingLetters(c *gin.Context) BulkArchiveIncomingLetters(c *gin.Context) ArchiveIncomingLetter(c *gin.Context) diff --git a/internal/router/router.go b/internal/router/router.go index ff353c7..266457d 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -181,6 +181,7 @@ func (r *Router) addAppRoutes(rg *gin.Engine) { lettersch.PUT("/incoming/:id", r.letterHandler.UpdateIncomingLetter) lettersch.PUT("/incoming/:id/read", r.letterHandler.MarkIncomingLetterAsRead) lettersch.DELETE("/incoming/:id", r.letterHandler.DeleteIncomingLetter) + lettersch.DELETE("/incoming/delete", r.letterHandler.BulkDeleteIncomingLetters) lettersch.POST("/incoming/archive", r.letterHandler.BulkArchiveIncomingLetters) lettersch.PUT("/incoming/:id/archive", r.letterHandler.ArchiveIncomingLetter) diff --git a/internal/service/letter_service.go b/internal/service/letter_service.go index db36276..6292adf 100644 --- a/internal/service/letter_service.go +++ b/internal/service/letter_service.go @@ -31,6 +31,7 @@ type LetterProcessor interface { MarkOutgoingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error) UpdateIncomingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) SoftDeleteIncomingLetter(ctx context.Context, id uuid.UUID) error + BulkSoftDeleteIncomingLetters(ctx context.Context, ids []uuid.UUID) error BulkArchiveIncomingLetters(ctx context.Context, letterIDs []uuid.UUID) (int64, error) ArchiveIncomingLetter(ctx context.Context, letterID uuid.UUID) error BulkArchiveIncomingLettersForUser(ctx context.Context, letterIDs []uuid.UUID, userID uuid.UUID) (int64, error) @@ -465,6 +466,10 @@ func (s *LetterServiceImpl) SoftDeleteIncomingLetter(ctx context.Context, id uui return s.processor.SoftDeleteIncomingLetter(ctx, id) } +func (s *LetterServiceImpl) BulkSoftDeleteIncomingLetters(ctx context.Context, ids []uuid.UUID) error { + return s.processor.BulkSoftDeleteIncomingLetters(ctx, ids) +} + func (s *LetterServiceImpl) SearchIncomingLetters(ctx context.Context, req *contract.SearchIncomingLettersRequest) (*contract.SearchIncomingLettersResponse, error) { appCtx := appcontext.FromGinContext(ctx) userID := appCtx.UserID