package processor import ( "context" "fmt" "time" "eslogad-be/internal/appcontext" "eslogad-be/internal/contract" "eslogad-be/internal/entities" "eslogad-be/internal/transformer" "github.com/google/uuid" ) func (p *LetterProcessorImpl) GetDepartmentDispositionStatus(ctx context.Context, req *contract.GetDepartmentDispositionStatusRequest) (*contract.ListDepartmentDispositionStatusResponse, error) { dispositions, err := p.dispositionDeptRepo.GetByLetterIncomingID(ctx, req.LetterIncomingID) if err != nil { return nil, err } var response []contract.DepartmentDispositionStatusResponse for _, disp := range dispositions { letterResp := transformer.LetterIncomingEntityToContract(disp.LetterIncoming) var fromDept *contract.DepartmentResponse if disp.LetterIncomingDisposition != nil && disp.LetterIncomingDisposition.DepartmentID != nil { fromDept = transformer.DepartmentEntityToContract(&disp.LetterIncomingDisposition.Department) } response = append(response, contract.DepartmentDispositionStatusResponse{ ID: disp.ID, LetterID: disp.LetterIncomingID, Letter: letterResp, FromDepartmentID: disp.LetterIncomingDisposition.DepartmentID, FromDepartment: fromDept, ToDepartmentID: disp.DepartmentID, ToDepartment: transformer.DepartmentEntityToContract(disp.Department), Status: string(disp.Status), Notes: disp.LetterIncomingDisposition.Notes, ReadAt: disp.ReadAt, CompletedAt: disp.CompletedAt, CreatedAt: disp.CreatedAt, UpdatedAt: disp.UpdatedAt, }) } return &contract.ListDepartmentDispositionStatusResponse{ Dispositions: response, Pagination: contract.PaginationResponse{ TotalCount: len(response), Page: 1, Limit: len(response), TotalPages: 1, }, }, nil } func (p *LetterProcessorImpl) UpdateDispositionStatus(ctx context.Context, req *contract.UpdateDispositionStatusRequest) (*contract.DepartmentDispositionStatusResponse, error) { var result *contract.DepartmentDispositionStatusResponse err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error { userID := appcontext.FromGinContext(txCtx).UserID departmentID := appcontext.FromGinContext(txCtx).DepartmentID dispDept, err := p.dispositionDeptRepo.GetByDispositionAndDepartment(txCtx, req.LetterIncomingID, departmentID) if err != nil { return err } notes := "" if req.Notes != nil { notes = *req.Notes } if err := p.updateDispositionDepartmentStatus(txCtx, dispDept.ID, req.Status, notes); err != nil { return err } p.activity.LogLetterDispositionStatusUpdate(txCtx, req.LetterIncomingID, userID, req.Status) if err := p.checkAndUpdateLetterCompletionStatus(txCtx, req.LetterIncomingID); err != nil { return err } updatedDispDept, err := p.dispositionDeptRepo.GetByID(txCtx, dispDept.ID) if err != nil { return err } result = p.buildDispositionStatusResponse(updatedDispDept) return nil }) if err != nil { return nil, err } return result, nil } // updateDispositionDepartmentStatus updates the status of a disposition department func (p *LetterProcessorImpl) updateDispositionDepartmentStatus(ctx context.Context, dispDeptID uuid.UUID, status, notes string) error { now := time.Now() var dispositionStatus entities.LetterIncomingDispositionDepartmentStatus var readAt, completedAt *time.Time switch status { case "completed": dispositionStatus = entities.DispositionDepartmentStatusCompleted completedAt = &now readAt = &now // Mark as read when completing case "read": dispositionStatus = entities.DispositionDepartmentStatusRead readAt = &now default: dispositionStatus = entities.DispositionDepartmentStatusPending } return p.dispositionDeptRepo.UpdateStatus(ctx, dispDeptID, dispositionStatus, notes, readAt, completedAt) } // addDispositionNoteIfProvided adds a note to the disposition if provided func (p *LetterProcessorImpl) addDispositionNoteIfProvided(ctx context.Context, dispositionID uuid.UUID, userID uuid.UUID, notes *string) error { if notes == nil || *notes == "" { return nil } note := &entities.DispositionNote{ DispositionID: dispositionID, UserID: &userID, Note: *notes, } return p.dispositionNoteRepo.Create(ctx, note) } func (p *LetterProcessorImpl) checkAndUpdateLetterCompletionStatus(ctx context.Context, letterIncomingID uuid.UUID) error { dispositions, err := p.dispositionDeptRepo.GetByLetterIncomingID(ctx, letterIncomingID) if err != nil { return err } allCompleted := true for _, disp := range dispositions { if disp.Status == entities.DispositionDepartmentStatusPending { allCompleted = false break } } if allCompleted && len(dispositions) > 0 { letter, err := p.letterRepo.GetByID(ctx, letterIncomingID) if err != nil { return err } letter.Status = "completed" if err := p.letterRepo.Update(ctx, letter); err != nil { return err } } return nil } // buildDispositionStatusResponse builds the response for disposition status func (p *LetterProcessorImpl) buildDispositionStatusResponse(dispDept *entities.LetterIncomingDispositionDepartment) *contract.DepartmentDispositionStatusResponse { letterResp := transformer.LetterIncomingEntityToContract(dispDept.LetterIncoming) var fromDept *contract.DepartmentResponse if dispDept.LetterIncomingDisposition != nil && dispDept.LetterIncomingDisposition.DepartmentID != nil { fromDept = transformer.DepartmentEntityToContract(&dispDept.LetterIncomingDisposition.Department) } return &contract.DepartmentDispositionStatusResponse{ ID: dispDept.ID, LetterID: dispDept.LetterIncomingID, Letter: letterResp, FromDepartmentID: dispDept.LetterIncomingDisposition.DepartmentID, FromDepartment: fromDept, ToDepartmentID: dispDept.DepartmentID, ToDepartment: transformer.DepartmentEntityToContract(dispDept.Department), Status: string(dispDept.Status), Notes: dispDept.LetterIncomingDisposition.Notes, ReadAt: dispDept.ReadAt, CompletedAt: dispDept.CompletedAt, CreatedAt: dispDept.CreatedAt, UpdatedAt: dispDept.UpdatedAt, } } func (p *LetterProcessorImpl) GetLetterCTA(ctx context.Context, letterIncomingID uuid.UUID, departmentID uuid.UUID) (*contract.LetterCTAResponse, error) { letter, err := p.letterRepo.GetByID(ctx, letterIncomingID) if err != nil { return nil, err } response := &contract.LetterCTAResponse{ LetterIncomingID: letterIncomingID, Actions: []contract.LetterCTAAction{}, Message: "", } isEligibleForDispo, err := p.dispoRoutes.IsEligibleForDisposition(ctx, departmentID) if err != nil { return nil, err } if letter.Status == "completed" || letter.Status == "archived" { response.Message = "Letter is no longer accepting actions" return response, nil } dispDepts, err := p.dispositionDeptRepo.GetByLetterAndDepartment(ctx, letterIncomingID, departmentID) if err != nil { return nil, err } if len(dispDepts) == 0 { response.Message = "Your department is not a recipient of this letter" return response, nil } for _, dispDept := range dispDepts { if dispDept.Status == entities.DispositionDepartmentStatusPending { response.DispositionID = &dispDept.LetterIncomingDispositionID currentStatus := string(dispDept.Status) response.CurrentStatus = ¤tStatus if isEligibleForDispo { response.Actions = append(response.Actions, contract.LetterCTAAction{ Type: "create_disposition", Label: "Disposisi", Path: fmt.Sprintf("/api/v1/letters/%s/dispositions", letterIncomingID), Method: "POST", Description: "Create a new disposition for this letter", }) } response.Actions = append(response.Actions, contract.LetterCTAAction{ Type: "update_status", Label: "Tindak Lanjut", Path: fmt.Sprintf("/api/v1/letters/dispositions/%s/status", response.LetterIncomingID), Method: "PUT", Description: "Update the status of your disposition", }) } } return response, nil }