add sender and receiver
This commit is contained in:
parent
90da195a2e
commit
24e1d265d3
BIN
eslogad-backend
BIN
eslogad-backend
Binary file not shown.
BIN
eslogad-be
BIN
eslogad-be
Binary file not shown.
@ -6,6 +6,29 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SearchIncomingLettersRequest struct {
|
||||||
|
Query string `json:"query" form:"query"`
|
||||||
|
LetterNumber string `json:"letter_number" form:"letter_number"`
|
||||||
|
Subject string `json:"subject" form:"subject"`
|
||||||
|
Status string `json:"status" form:"status"`
|
||||||
|
PriorityID *uuid.UUID `json:"priority_id" form:"priority_id"`
|
||||||
|
InstitutionID *uuid.UUID `json:"institution_id" form:"institution_id"`
|
||||||
|
CreatedBy *uuid.UUID `json:"created_by" form:"created_by"`
|
||||||
|
DateFrom *time.Time `json:"date_from" form:"date_from"`
|
||||||
|
DateTo *time.Time `json:"date_to" form:"date_to"`
|
||||||
|
Page int `json:"page" form:"page"`
|
||||||
|
Limit int `json:"limit" form:"limit"`
|
||||||
|
SortBy string `json:"sort_by" form:"sort_by"`
|
||||||
|
SortOrder string `json:"sort_order" form:"sort_order"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchIncomingLettersResponse struct {
|
||||||
|
Letters []IncomingLetterResponse `json:"letters"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateIncomingLetterAttachment struct {
|
type CreateIncomingLetterAttachment struct {
|
||||||
FileURL string `json:"file_url"`
|
FileURL string `json:"file_url"`
|
||||||
FileName string `json:"file_name"`
|
FileName string `json:"file_name"`
|
||||||
@ -19,6 +42,7 @@ type CreateIncomingLetterRequest struct {
|
|||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
||||||
SenderInstitutionID *uuid.UUID `json:"sender_institution_id,omitempty"`
|
SenderInstitutionID *uuid.UUID `json:"sender_institution_id,omitempty"`
|
||||||
|
SenderName *string `json:"sender_name,omitempty"`
|
||||||
ReceivedDate time.Time `json:"received_date"`
|
ReceivedDate time.Time `json:"received_date"`
|
||||||
DueDate *time.Time `json:"due_date,omitempty"`
|
DueDate *time.Time `json:"due_date,omitempty"`
|
||||||
Attachments []CreateIncomingLetterAttachment `json:"attachments,omitempty"`
|
Attachments []CreateIncomingLetterAttachment `json:"attachments,omitempty"`
|
||||||
@ -40,6 +64,7 @@ type IncomingLetterResponse struct {
|
|||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
Priority *PriorityResponse `json:"priority,omitempty"`
|
Priority *PriorityResponse `json:"priority,omitempty"`
|
||||||
SenderInstitution *InstitutionResponse `json:"sender_institution,omitempty"`
|
SenderInstitution *InstitutionResponse `json:"sender_institution,omitempty"`
|
||||||
|
SenderName *string `json:"sender_name,omitempty"`
|
||||||
ReceivedDate time.Time `json:"received_date"`
|
ReceivedDate time.Time `json:"received_date"`
|
||||||
DueDate *time.Time `json:"due_date,omitempty"`
|
DueDate *time.Time `json:"due_date,omitempty"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
@ -56,6 +81,7 @@ type UpdateIncomingLetterRequest struct {
|
|||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
||||||
SenderInstitutionID *uuid.UUID `json:"sender_institution_id,omitempty"`
|
SenderInstitutionID *uuid.UUID `json:"sender_institution_id,omitempty"`
|
||||||
|
SenderName *string `json:"sender_name,omitempty"`
|
||||||
ReceivedDate *time.Time `json:"received_date,omitempty"`
|
ReceivedDate *time.Time `json:"received_date,omitempty"`
|
||||||
DueDate *time.Time `json:"due_date,omitempty"`
|
DueDate *time.Time `json:"due_date,omitempty"`
|
||||||
Status *string `json:"status,omitempty"`
|
Status *string `json:"status,omitempty"`
|
||||||
|
|||||||
@ -6,6 +6,29 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SearchOutgoingLettersRequest struct {
|
||||||
|
Query string `json:"query" form:"query"`
|
||||||
|
LetterNumber string `json:"letter_number" form:"letter_number"`
|
||||||
|
Subject string `json:"subject" form:"subject"`
|
||||||
|
Status string `json:"status" form:"status"`
|
||||||
|
PriorityID *uuid.UUID `json:"priority_id" form:"priority_id"`
|
||||||
|
InstitutionID *uuid.UUID `json:"institution_id" form:"institution_id"`
|
||||||
|
CreatedBy *uuid.UUID `json:"created_by" form:"created_by"`
|
||||||
|
DateFrom *time.Time `json:"date_from" form:"date_from"`
|
||||||
|
DateTo *time.Time `json:"date_to" form:"date_to"`
|
||||||
|
Page int `json:"page" form:"page"`
|
||||||
|
Limit int `json:"limit" form:"limit"`
|
||||||
|
SortBy string `json:"sort_by" form:"sort_by"`
|
||||||
|
SortOrder string `json:"sort_order" form:"sort_order"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchOutgoingLettersResponse struct {
|
||||||
|
Letters []OutgoingLetterResponse `json:"letters"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateOutgoingLetterRecipient struct {
|
type CreateOutgoingLetterRecipient struct {
|
||||||
LetterID uuid.UUID `json:"letter_id"`
|
LetterID uuid.UUID `json:"letter_id"`
|
||||||
UserID *uuid.UUID `json:"user_id,omitempty"`
|
UserID *uuid.UUID `json:"user_id,omitempty"`
|
||||||
@ -28,6 +51,7 @@ type CreateOutgoingLetterRequest struct {
|
|||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
||||||
ReceiverInstitutionID *uuid.UUID `json:"receiver_institution_id,omitempty"`
|
ReceiverInstitutionID *uuid.UUID `json:"receiver_institution_id,omitempty"`
|
||||||
|
ReceiverName *string `json:"receiver_name,omitempty"`
|
||||||
IssueDate time.Time `json:"issue_date" validate:"required"`
|
IssueDate time.Time `json:"issue_date" validate:"required"`
|
||||||
Attachments []CreateOutgoingLetterAttachment `json:"attachments,omitempty"`
|
Attachments []CreateOutgoingLetterAttachment `json:"attachments,omitempty"`
|
||||||
UserID uuid.UUID
|
UserID uuid.UUID
|
||||||
@ -78,6 +102,7 @@ type OutgoingLetterResponse struct {
|
|||||||
Priority *PriorityResponse `json:"priority,omitempty"`
|
Priority *PriorityResponse `json:"priority,omitempty"`
|
||||||
ReceiverInstitutionID *uuid.UUID `json:"receiver_institution_id,omitempty"`
|
ReceiverInstitutionID *uuid.UUID `json:"receiver_institution_id,omitempty"`
|
||||||
ReceiverInstitution *InstitutionResponse `json:"receiver_institution,omitempty"`
|
ReceiverInstitution *InstitutionResponse `json:"receiver_institution,omitempty"`
|
||||||
|
ReceiverName *string `json:"receiver_name,omitempty"`
|
||||||
IssueDate time.Time `json:"issue_date"`
|
IssueDate time.Time `json:"issue_date"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
ApprovalFlowID *uuid.UUID `json:"approval_flow_id,omitempty"`
|
ApprovalFlowID *uuid.UUID `json:"approval_flow_id,omitempty"`
|
||||||
@ -95,6 +120,7 @@ type UpdateOutgoingLetterRequest struct {
|
|||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
||||||
ReceiverInstitutionID *uuid.UUID `json:"receiver_institution_id,omitempty"`
|
ReceiverInstitutionID *uuid.UUID `json:"receiver_institution_id,omitempty"`
|
||||||
|
ReceiverName *string `json:"receiver_name,omitempty"`
|
||||||
IssueDate *time.Time `json:"issue_date,omitempty"`
|
IssueDate *time.Time `json:"issue_date,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,7 @@ type LetterIncoming struct {
|
|||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
||||||
SenderInstitutionID *uuid.UUID `json:"sender_institution_id,omitempty"`
|
SenderInstitutionID *uuid.UUID `json:"sender_institution_id,omitempty"`
|
||||||
|
SenderName *string `json:"sender_name,omitempty"`
|
||||||
ReceivedDate time.Time `json:"received_date"`
|
ReceivedDate time.Time `json:"received_date"`
|
||||||
DueDate *time.Time `json:"due_date,omitempty"`
|
DueDate *time.Time `json:"due_date,omitempty"`
|
||||||
Status LetterIncomingStatus `gorm:"not null;default:'new'" json:"status"`
|
Status LetterIncomingStatus `gorm:"not null;default:'new'" json:"status"`
|
||||||
|
|||||||
@ -24,6 +24,7 @@ type LetterOutgoing struct {
|
|||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
||||||
ReceiverInstitutionID *uuid.UUID `json:"receiver_institution_id,omitempty"`
|
ReceiverInstitutionID *uuid.UUID `json:"receiver_institution_id,omitempty"`
|
||||||
|
ReceiverName *string `json:"receiver_name,omitempty"`
|
||||||
IssueDate time.Time `json:"issue_date"`
|
IssueDate time.Time `json:"issue_date"`
|
||||||
Status LetterOutgoingStatus `gorm:"not null;default:'draft'" json:"status"`
|
Status LetterOutgoingStatus `gorm:"not null;default:'draft'" json:"status"`
|
||||||
ApprovalFlowID *uuid.UUID `json:"approval_flow_id,omitempty"`
|
ApprovalFlowID *uuid.UUID `json:"approval_flow_id,omitempty"`
|
||||||
|
|||||||
@ -17,6 +17,7 @@ type LetterService interface {
|
|||||||
CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error)
|
CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error)
|
||||||
GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error)
|
GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error)
|
||||||
ListIncomingLetters(ctx context.Context, req *contract.ListIncomingLettersRequest) (*contract.ListIncomingLettersResponse, error)
|
ListIncomingLetters(ctx context.Context, req *contract.ListIncomingLettersRequest) (*contract.ListIncomingLettersResponse, error)
|
||||||
|
SearchIncomingLetters(ctx context.Context, req *contract.SearchIncomingLettersRequest) (*contract.SearchIncomingLettersResponse, error)
|
||||||
GetLetterUnreadCounts(ctx context.Context) (*contract.LetterUnreadCountResponse, error)
|
GetLetterUnreadCounts(ctx context.Context) (*contract.LetterUnreadCountResponse, error)
|
||||||
MarkIncomingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error)
|
MarkIncomingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error)
|
||||||
MarkOutgoingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error)
|
MarkOutgoingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error)
|
||||||
@ -358,6 +359,34 @@ func (h *LetterHandler) UpdateDiscussion(c *gin.Context) {
|
|||||||
h.respondSuccess(c, http.StatusOK, resp)
|
h.respondSuccess(c, http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *LetterHandler) SearchIncomingLetters(c *gin.Context) {
|
||||||
|
var req contract.SearchIncomingLettersRequest
|
||||||
|
if !h.bindQuery(c, &req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Page <= 0 {
|
||||||
|
req.Page = 1
|
||||||
|
}
|
||||||
|
if req.Limit <= 0 {
|
||||||
|
req.Limit = 10
|
||||||
|
}
|
||||||
|
if req.SortOrder == "" {
|
||||||
|
req.SortOrder = "desc"
|
||||||
|
}
|
||||||
|
if req.SortBy == "" {
|
||||||
|
req.SortBy = "created_at"
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.svc.SearchIncomingLetters(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
h.handleServiceError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.respondSuccess(c, http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *LetterHandler) GetDepartmentDispositionStatus(c *gin.Context) {
|
func (h *LetterHandler) GetDepartmentDispositionStatus(c *gin.Context) {
|
||||||
letterID, ok := h.parseUUID(c, "letter_id")
|
letterID, ok := h.parseUUID(c, "letter_id")
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ type LetterOutgoingService interface {
|
|||||||
CreateOutgoingLetter(ctx context.Context, req *contract.CreateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error)
|
CreateOutgoingLetter(ctx context.Context, req *contract.CreateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error)
|
||||||
GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*contract.OutgoingLetterResponse, error)
|
GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*contract.OutgoingLetterResponse, error)
|
||||||
ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error)
|
ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error)
|
||||||
|
SearchOutgoingLetters(ctx context.Context, req *contract.SearchOutgoingLettersRequest) (*contract.SearchOutgoingLettersResponse, 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
|
||||||
|
|
||||||
@ -402,6 +403,35 @@ func (h *LetterOutgoingHandler) DeleteDiscussion(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "discussion deleted"})
|
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "discussion deleted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *LetterOutgoingHandler) SearchOutgoingLetters(c *gin.Context) {
|
||||||
|
var req contract.SearchOutgoingLettersRequest
|
||||||
|
if err := c.ShouldBindQuery(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid query parameters", Code: http.StatusBadRequest})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Page <= 0 {
|
||||||
|
req.Page = 1
|
||||||
|
}
|
||||||
|
if req.Limit <= 0 {
|
||||||
|
req.Limit = 10
|
||||||
|
}
|
||||||
|
if req.SortOrder == "" {
|
||||||
|
req.SortOrder = "desc"
|
||||||
|
}
|
||||||
|
if req.SortBy == "" {
|
||||||
|
req.SortBy = "created_at"
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.svc.SearchOutgoingLetters(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
func (h *LetterOutgoingHandler) GetLetterApprovalInfo(c *gin.Context) {
|
func (h *LetterOutgoingHandler) GetLetterApprovalInfo(c *gin.Context) {
|
||||||
id, err := uuid.Parse(c.Param("id"))
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ type LetterOutgoingProcessor interface {
|
|||||||
CreateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, attachments []entities.LetterOutgoingAttachment, userID, departmentID uuid.UUID) error
|
CreateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, attachments []entities.LetterOutgoingAttachment, userID, departmentID uuid.UUID) error
|
||||||
GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*entities.LetterOutgoing, error)
|
GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*entities.LetterOutgoing, error)
|
||||||
ListOutgoingLetters(ctx context.Context, filter repository.ListOutgoingLettersFilter, limit, offset int) ([]entities.LetterOutgoing, int64, error)
|
ListOutgoingLetters(ctx context.Context, filter repository.ListOutgoingLettersFilter, limit, offset int) ([]entities.LetterOutgoing, int64, error)
|
||||||
|
SearchOutgoingLetters(ctx context.Context, filters map[string]interface{}, page, limit int, sortBy, sortOrder string) ([]entities.LetterOutgoing, int64, error)
|
||||||
UpdateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, userID uuid.UUID) error
|
UpdateOutgoingLetter(ctx context.Context, letter *entities.LetterOutgoing, userID uuid.UUID) error
|
||||||
DeleteOutgoingLetter(ctx context.Context, id uuid.UUID, userID uuid.UUID) error
|
DeleteOutgoingLetter(ctx context.Context, id uuid.UUID, userID uuid.UUID) error
|
||||||
|
|
||||||
@ -780,6 +781,11 @@ func (p *LetterOutgoingProcessorImpl) GetUsersByIDs(ctx context.Context, userIDs
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *LetterOutgoingProcessorImpl) SearchOutgoingLetters(ctx context.Context, filters map[string]interface{}, page, limit int, sortBy, sortOrder string) ([]entities.LetterOutgoing, int64, error) {
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
return p.letterRepo.Search(ctx, filters, limit, offset, sortBy, sortOrder)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *LetterOutgoingProcessorImpl) BulkArchiveOutgoingLetters(ctx context.Context, letterIDs []uuid.UUID) (int64, error) {
|
func (p *LetterOutgoingProcessorImpl) BulkArchiveOutgoingLetters(ctx context.Context, letterIDs []uuid.UUID) (int64, error) {
|
||||||
return p.letterRepo.BulkArchive(ctx, letterIDs)
|
return p.letterRepo.BulkArchive(ctx, letterIDs)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,6 +67,7 @@ func (p *LetterProcessorImpl) CreateIncomingLetter(ctx context.Context, req *con
|
|||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
PriorityID: req.PriorityID,
|
PriorityID: req.PriorityID,
|
||||||
SenderInstitutionID: req.SenderInstitutionID,
|
SenderInstitutionID: req.SenderInstitutionID,
|
||||||
|
SenderName: req.SenderName,
|
||||||
ReceivedDate: req.ReceivedDate,
|
ReceivedDate: req.ReceivedDate,
|
||||||
DueDate: req.DueDate,
|
DueDate: req.DueDate,
|
||||||
Status: entities.LetterIncomingStatusNew,
|
Status: entities.LetterIncomingStatusNew,
|
||||||
@ -252,6 +253,9 @@ func (p *LetterProcessorImpl) UpdateIncomingLetter(ctx context.Context, id uuid.
|
|||||||
if req.SenderInstitutionID != nil {
|
if req.SenderInstitutionID != nil {
|
||||||
entity.SenderInstitutionID = req.SenderInstitutionID
|
entity.SenderInstitutionID = req.SenderInstitutionID
|
||||||
}
|
}
|
||||||
|
if req.SenderName != nil {
|
||||||
|
entity.SenderName = req.SenderName
|
||||||
|
}
|
||||||
if req.ReceivedDate != nil {
|
if req.ReceivedDate != nil {
|
||||||
entity.ReceivedDate = *req.ReceivedDate
|
entity.ReceivedDate = *req.ReceivedDate
|
||||||
}
|
}
|
||||||
@ -577,6 +581,11 @@ func (p *LetterProcessorImpl) createAttachments(ctx context.Context, letterID uu
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *LetterProcessorImpl) SearchIncomingLetters(ctx context.Context, filters map[string]interface{}, page, limit int, sortBy, sortOrder string) ([]entities.LetterIncoming, int64, error) {
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
return p.letterRepo.Search(ctx, filters, limit, offset, sortBy, sortOrder)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *LetterProcessorImpl) buildLetterResponse(ctx context.Context, entity *entities.LetterIncoming) (*contract.IncomingLetterResponse, error) {
|
func (p *LetterProcessorImpl) buildLetterResponse(ctx context.Context, entity *entities.LetterIncoming) (*contract.IncomingLetterResponse, error) {
|
||||||
savedAttachments, _ := p.attachRepo.ListByLetter(ctx, entity.ID)
|
savedAttachments, _ := p.attachRepo.ListByLetter(ctx, entity.ID)
|
||||||
|
|
||||||
|
|||||||
@ -188,6 +188,109 @@ func (r *LetterOutgoingRepository) List(ctx context.Context, filter ListOutgoing
|
|||||||
return list, total, nil
|
return list, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LetterOutgoingRepository) Search(ctx context.Context, filters map[string]interface{}, limit, offset int, sortBy, sortOrder string) ([]entities.LetterOutgoing, int64, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
query := db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("deleted_at IS NULL")
|
||||||
|
|
||||||
|
// Apply search filters
|
||||||
|
if q, ok := filters["query"]; ok && q != "" {
|
||||||
|
searchTerm := "%" + q.(string) + "%"
|
||||||
|
query = query.Where("subject ILIKE ? OR reference_number ILIKE ? OR letter_number ILIKE ? OR description ILIKE ? OR receiver_name ILIKE ?", searchTerm, searchTerm, searchTerm, searchTerm, searchTerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
if letterNumber, ok := filters["letter_number"]; ok && letterNumber != "" {
|
||||||
|
query = query.Where("letter_number ILIKE ?", "%"+letterNumber.(string)+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subject, ok := filters["subject"]; ok && subject != "" {
|
||||||
|
query = query.Where("subject ILIKE ?", "%"+subject.(string)+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
if status, ok := filters["status"]; ok && status != "" {
|
||||||
|
query = query.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if priorityID, ok := filters["priority_id"]; ok {
|
||||||
|
query = query.Where("priority_id = ?", priorityID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if institutionID, ok := filters["receiver_institution_id"]; ok {
|
||||||
|
query = query.Where("receiver_institution_id = ?", institutionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if createdBy, ok := filters["created_by"]; ok {
|
||||||
|
query = query.Where("created_by = ?", createdBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dateFrom, ok := filters["date_from"]; ok {
|
||||||
|
query = query.Where("issue_date >= ?", dateFrom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dateTo, ok := filters["date_to"]; ok {
|
||||||
|
query = query.Where("issue_date <= ?", dateTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply user context filters if present
|
||||||
|
if userContext, ok := filters["user_context"]; ok {
|
||||||
|
if ctx, ok := userContext.(map[string]interface{}); ok {
|
||||||
|
if userID, ok := ctx["user_id"]; ok {
|
||||||
|
// User can see: letters created by them OR letters where they are recipients
|
||||||
|
subQuery := db.Model(&entities.LetterOutgoingRecipient{}).Select("letter_id").Where("user_id = ?", userID)
|
||||||
|
query = query.Where("created_by = ? OR id IN (?)", userID, subQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count total results
|
||||||
|
var total int64
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply sorting
|
||||||
|
if sortBy == "" {
|
||||||
|
sortBy = "created_at"
|
||||||
|
}
|
||||||
|
if sortOrder == "" {
|
||||||
|
sortOrder = "desc"
|
||||||
|
}
|
||||||
|
|
||||||
|
validSortFields := map[string]bool{
|
||||||
|
"letter_number": true,
|
||||||
|
"subject": true,
|
||||||
|
"issue_date": true,
|
||||||
|
"status": true,
|
||||||
|
"created_at": true,
|
||||||
|
"updated_at": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validSortFields[sortBy] {
|
||||||
|
sortBy = "created_at"
|
||||||
|
}
|
||||||
|
|
||||||
|
if sortOrder != "asc" && sortOrder != "desc" {
|
||||||
|
sortOrder = "desc"
|
||||||
|
}
|
||||||
|
|
||||||
|
orderBy := sortBy + " " + sortOrder
|
||||||
|
|
||||||
|
// Execute query with preloads
|
||||||
|
var letters []entities.LetterOutgoing
|
||||||
|
if err := query.
|
||||||
|
Preload("Priority").
|
||||||
|
Preload("ReceiverInstitution").
|
||||||
|
Preload("Creator").
|
||||||
|
Preload("Creator.Profile").
|
||||||
|
Order(orderBy).
|
||||||
|
Limit(limit).
|
||||||
|
Offset(offset).
|
||||||
|
Find(&letters).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return letters, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LetterOutgoingRepository) UpdateStatus(ctx context.Context, id uuid.UUID, status entities.LetterOutgoingStatus) error {
|
func (r *LetterOutgoingRepository) UpdateStatus(ctx context.Context, id uuid.UUID, status entities.LetterOutgoingStatus) error {
|
||||||
db := DBFromContext(ctx, r.db)
|
db := DBFromContext(ctx, r.db)
|
||||||
return db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("id = ? AND deleted_at IS NULL", id).Update("status", status).Error
|
return db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("id = ? AND deleted_at IS NULL", id).Update("status", status).Error
|
||||||
|
|||||||
@ -210,6 +210,139 @@ func (r *LetterIncomingRepository) List(ctx context.Context, filter ListIncoming
|
|||||||
return list, total, nil
|
return list, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LetterIncomingRepository) Search(ctx context.Context, filters map[string]interface{}, limit, offset int, sortBy, sortOrder string) ([]entities.LetterIncoming, int64, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
query := db.WithContext(ctx).Model(&entities.LetterIncoming{}).Where("deleted_at IS NULL")
|
||||||
|
|
||||||
|
joinedRecipients := false
|
||||||
|
needsGroupBy := false
|
||||||
|
|
||||||
|
// Apply search filters
|
||||||
|
if q, ok := filters["query"]; ok && q != "" {
|
||||||
|
searchTerm := "%" + q.(string) + "%"
|
||||||
|
query = query.Where("subject ILIKE ? OR reference_number ILIKE ? OR letter_number ILIKE ? OR description ILIKE ? OR sender_name ILIKE ?", searchTerm, searchTerm, searchTerm, searchTerm, searchTerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
if letterNumber, ok := filters["letter_number"]; ok && letterNumber != "" {
|
||||||
|
query = query.Where("letter_number ILIKE ?", "%"+letterNumber.(string)+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subject, ok := filters["subject"]; ok && subject != "" {
|
||||||
|
query = query.Where("subject ILIKE ?", "%"+subject.(string)+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
if status, ok := filters["status"]; ok && status != "" {
|
||||||
|
query = query.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if priorityID, ok := filters["priority_id"]; ok {
|
||||||
|
query = query.Where("priority_id = ?", priorityID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if institutionID, ok := filters["sender_institution_id"]; ok {
|
||||||
|
query = query.Where("sender_institution_id = ?", institutionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if createdBy, ok := filters["created_by"]; ok {
|
||||||
|
query = query.Where("created_by = ?", createdBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dateFrom, ok := filters["date_from"]; ok {
|
||||||
|
query = query.Where("received_date >= ?", dateFrom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dateTo, ok := filters["date_to"]; ok {
|
||||||
|
query = query.Where("received_date <= ?", dateTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply user context filters if present
|
||||||
|
if userContext, ok := filters["user_context"]; ok {
|
||||||
|
if ctx, ok := userContext.(map[string]interface{}); ok {
|
||||||
|
if userID, ok := ctx["user_id"]; ok {
|
||||||
|
// User can see letters where they are recipients
|
||||||
|
query = query.Joins("JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id").
|
||||||
|
Where("letter_incoming_recipients.recipient_user_id = ?", userID)
|
||||||
|
joinedRecipients = true
|
||||||
|
needsGroupBy = true
|
||||||
|
}
|
||||||
|
if departmentID, ok := ctx["department_id"]; ok {
|
||||||
|
// Also include letters for user's department
|
||||||
|
if !joinedRecipients {
|
||||||
|
query = query.Joins("JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id")
|
||||||
|
joinedRecipients = true
|
||||||
|
needsGroupBy = true
|
||||||
|
}
|
||||||
|
query = query.Where("letter_incoming_recipients.recipient_department_id = ?", departmentID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count total results
|
||||||
|
var total int64
|
||||||
|
if needsGroupBy {
|
||||||
|
// For grouped queries, count distinct letter IDs
|
||||||
|
if err := db.WithContext(ctx).Model(&entities.LetterIncoming{}).
|
||||||
|
Joins("JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id").
|
||||||
|
Where("letters_incoming.deleted_at IS NULL").
|
||||||
|
Distinct("letters_incoming.id").
|
||||||
|
Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply sorting
|
||||||
|
if sortBy == "" {
|
||||||
|
sortBy = "created_at"
|
||||||
|
}
|
||||||
|
if sortOrder == "" {
|
||||||
|
sortOrder = "desc"
|
||||||
|
}
|
||||||
|
|
||||||
|
validSortFields := map[string]bool{
|
||||||
|
"letter_number": true,
|
||||||
|
"subject": true,
|
||||||
|
"received_date": true,
|
||||||
|
"status": true,
|
||||||
|
"created_at": true,
|
||||||
|
"updated_at": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validSortFields[sortBy] {
|
||||||
|
sortBy = "created_at"
|
||||||
|
}
|
||||||
|
|
||||||
|
if sortOrder != "asc" && sortOrder != "desc" {
|
||||||
|
sortOrder = "desc"
|
||||||
|
}
|
||||||
|
|
||||||
|
orderBy := "letters_incoming." + sortBy + " " + sortOrder
|
||||||
|
|
||||||
|
// Apply grouping if necessary
|
||||||
|
if needsGroupBy {
|
||||||
|
query = query.Group("letters_incoming.id, letters_incoming.letter_number, letters_incoming.reference_number, " +
|
||||||
|
"letters_incoming.subject, letters_incoming.description, letters_incoming.priority_id, " +
|
||||||
|
"letters_incoming.sender_institution_id, letters_incoming.received_date, letters_incoming.due_date, " +
|
||||||
|
"letters_incoming.status, letters_incoming.created_by, letters_incoming.created_at, " +
|
||||||
|
"letters_incoming.updated_at, letters_incoming.deleted_at")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute query
|
||||||
|
var letters []entities.LetterIncoming
|
||||||
|
if err := query.
|
||||||
|
Order(orderBy).
|
||||||
|
Limit(limit).
|
||||||
|
Offset(offset).
|
||||||
|
Find(&letters).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return letters, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
type LetterIncomingAttachmentRepository struct{ db *gorm.DB }
|
type LetterIncomingAttachmentRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
func NewLetterIncomingAttachmentRepository(db *gorm.DB) *LetterIncomingAttachmentRepository {
|
func NewLetterIncomingAttachmentRepository(db *gorm.DB) *LetterIncomingAttachmentRepository {
|
||||||
|
|||||||
@ -74,6 +74,7 @@ type LetterHandler interface {
|
|||||||
CreateIncomingLetter(c *gin.Context)
|
CreateIncomingLetter(c *gin.Context)
|
||||||
GetIncomingLetter(c *gin.Context)
|
GetIncomingLetter(c *gin.Context)
|
||||||
ListIncomingLetters(c *gin.Context)
|
ListIncomingLetters(c *gin.Context)
|
||||||
|
SearchIncomingLetters(c *gin.Context)
|
||||||
GetLetterUnreadCounts(c *gin.Context)
|
GetLetterUnreadCounts(c *gin.Context)
|
||||||
MarkIncomingLetterAsRead(c *gin.Context)
|
MarkIncomingLetterAsRead(c *gin.Context)
|
||||||
MarkOutgoingLetterAsRead(c *gin.Context)
|
MarkOutgoingLetterAsRead(c *gin.Context)
|
||||||
@ -95,6 +96,7 @@ type LetterOutgoingHandler interface {
|
|||||||
CreateOutgoingLetter(c *gin.Context)
|
CreateOutgoingLetter(c *gin.Context)
|
||||||
GetOutgoingLetter(c *gin.Context)
|
GetOutgoingLetter(c *gin.Context)
|
||||||
ListOutgoingLetters(c *gin.Context)
|
ListOutgoingLetters(c *gin.Context)
|
||||||
|
SearchOutgoingLetters(c *gin.Context)
|
||||||
UpdateOutgoingLetter(c *gin.Context)
|
UpdateOutgoingLetter(c *gin.Context)
|
||||||
DeleteOutgoingLetter(c *gin.Context)
|
DeleteOutgoingLetter(c *gin.Context)
|
||||||
|
|
||||||
|
|||||||
@ -169,6 +169,7 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
lettersch.GET("/unread-counts", r.letterHandler.GetLetterUnreadCounts)
|
lettersch.GET("/unread-counts", r.letterHandler.GetLetterUnreadCounts)
|
||||||
|
|
||||||
lettersch.GET("/incoming", r.letterHandler.ListIncomingLetters)
|
lettersch.GET("/incoming", r.letterHandler.ListIncomingLetters)
|
||||||
|
lettersch.GET("/incoming/search", r.letterHandler.SearchIncomingLetters)
|
||||||
lettersch.POST("/incoming", r.letterHandler.CreateIncomingLetter)
|
lettersch.POST("/incoming", r.letterHandler.CreateIncomingLetter)
|
||||||
lettersch.GET("/incoming/cta/:letter_id", r.letterHandler.GetLetterCTA)
|
lettersch.GET("/incoming/cta/:letter_id", r.letterHandler.GetLetterCTA)
|
||||||
lettersch.GET("/incoming/:id", r.letterHandler.GetIncomingLetter)
|
lettersch.GET("/incoming/:id", r.letterHandler.GetIncomingLetter)
|
||||||
@ -178,8 +179,10 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
lettersch.POST("/incoming/archive", r.letterHandler.BulkArchiveIncomingLetters)
|
lettersch.POST("/incoming/archive", r.letterHandler.BulkArchiveIncomingLetters)
|
||||||
|
|
||||||
lettersch.POST("/outgoing", r.letterOutgoingHandler.CreateOutgoingLetter)
|
lettersch.POST("/outgoing", r.letterOutgoingHandler.CreateOutgoingLetter)
|
||||||
|
lettersch.GET("/outgoing/search", r.letterOutgoingHandler.SearchOutgoingLetters)
|
||||||
lettersch.GET("/outgoing/:id", r.letterOutgoingHandler.GetOutgoingLetter)
|
lettersch.GET("/outgoing/:id", r.letterOutgoingHandler.GetOutgoingLetter)
|
||||||
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.PUT("/outgoing/:id/read", r.letterHandler.MarkOutgoingLetterAsRead)
|
lettersch.PUT("/outgoing/:id/read", r.letterHandler.MarkOutgoingLetterAsRead)
|
||||||
lettersch.DELETE("/outgoing/:id", r.letterOutgoingHandler.DeleteOutgoingLetter)
|
lettersch.DELETE("/outgoing/:id", r.letterOutgoingHandler.DeleteOutgoingLetter)
|
||||||
|
|||||||
@ -21,6 +21,7 @@ type LetterOutgoingService interface {
|
|||||||
CreateOutgoingLetter(ctx context.Context, req *contract.CreateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error)
|
CreateOutgoingLetter(ctx context.Context, req *contract.CreateOutgoingLetterRequest) (*contract.OutgoingLetterResponse, error)
|
||||||
GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*contract.OutgoingLetterResponse, error)
|
GetOutgoingLetterByID(ctx context.Context, id uuid.UUID) (*contract.OutgoingLetterResponse, error)
|
||||||
ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error)
|
ListOutgoingLetters(ctx context.Context, req *contract.ListOutgoingLettersRequest) (*contract.ListOutgoingLettersResponse, error)
|
||||||
|
SearchOutgoingLetters(ctx context.Context, req *contract.SearchOutgoingLettersRequest) (*contract.SearchOutgoingLettersResponse, 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
|
||||||
|
|
||||||
@ -99,6 +100,7 @@ func (s *LetterOutgoingServiceImpl) CreateOutgoingLetter(ctx context.Context, re
|
|||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
PriorityID: req.PriorityID,
|
PriorityID: req.PriorityID,
|
||||||
ReceiverInstitutionID: req.ReceiverInstitutionID,
|
ReceiverInstitutionID: req.ReceiverInstitutionID,
|
||||||
|
ReceiverName: req.ReceiverName,
|
||||||
IssueDate: req.IssueDate,
|
IssueDate: req.IssueDate,
|
||||||
CreatedBy: userID,
|
CreatedBy: userID,
|
||||||
}
|
}
|
||||||
@ -352,6 +354,159 @@ func (s *LetterOutgoingServiceImpl) ListOutgoingLetters(ctx context.Context, req
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LetterOutgoingServiceImpl) SearchOutgoingLetters(ctx context.Context, req *contract.SearchOutgoingLettersRequest) (*contract.SearchOutgoingLettersResponse, error) {
|
||||||
|
userID := getUserIDFromContext(ctx)
|
||||||
|
departmentID := getDepartmentIDFromContext(ctx)
|
||||||
|
|
||||||
|
// Build search filters
|
||||||
|
filters := buildOutgoingSearchFilters(req, userID, departmentID)
|
||||||
|
|
||||||
|
// Execute search with pagination
|
||||||
|
letters, total, err := s.processor.SearchOutgoingLetters(ctx, filters, req.Page, req.Limit, req.SortBy, req.SortOrder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect IDs for batch loading
|
||||||
|
letterIDs := make([]uuid.UUID, len(letters))
|
||||||
|
priorityIDMap := make(map[uuid.UUID]bool)
|
||||||
|
institutionIDMap := make(map[uuid.UUID]bool)
|
||||||
|
|
||||||
|
for i, letter := range letters {
|
||||||
|
letterIDs[i] = letter.ID
|
||||||
|
if letter.PriorityID != nil {
|
||||||
|
priorityIDMap[*letter.PriorityID] = true
|
||||||
|
}
|
||||||
|
if letter.ReceiverInstitutionID != nil {
|
||||||
|
institutionIDMap[*letter.ReceiverInstitutionID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert maps to slices
|
||||||
|
priorityIDSlice := make([]uuid.UUID, 0, len(priorityIDMap))
|
||||||
|
for id := range priorityIDMap {
|
||||||
|
priorityIDSlice = append(priorityIDSlice, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
institutionIDSlice := make([]uuid.UUID, 0, len(institutionIDMap))
|
||||||
|
for id := range institutionIDMap {
|
||||||
|
institutionIDSlice = append(institutionIDSlice, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallel batch loading
|
||||||
|
type batchLoadResult struct {
|
||||||
|
attachments map[uuid.UUID][]entities.LetterOutgoingAttachment
|
||||||
|
recipients map[uuid.UUID][]entities.LetterOutgoingRecipient
|
||||||
|
priorities map[uuid.UUID]*entities.Priority
|
||||||
|
institutions map[uuid.UUID]*entities.Institution
|
||||||
|
}
|
||||||
|
|
||||||
|
var result batchLoadResult
|
||||||
|
errChan := make(chan error, 4)
|
||||||
|
|
||||||
|
// Load attachments
|
||||||
|
go func() {
|
||||||
|
result.attachments, err = s.processor.GetBatchAttachments(ctx, letterIDs)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Load recipients
|
||||||
|
go func() {
|
||||||
|
result.recipients, err = s.processor.GetBatchRecipients(ctx, letterIDs)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Load priorities
|
||||||
|
go func() {
|
||||||
|
result.priorities, err = s.processor.GetBatchPriorities(ctx, priorityIDSlice)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Load institutions
|
||||||
|
go func() {
|
||||||
|
result.institutions, err = s.processor.GetBatchInstitutions(ctx, institutionIDSlice)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for all goroutines and check for errors
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
if err := <-errChan; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform letters with batch loaded data
|
||||||
|
items := make([]contract.OutgoingLetterResponse, len(letters))
|
||||||
|
for i, letter := range letters {
|
||||||
|
// Attach batch loaded data to letter
|
||||||
|
if attachments, ok := result.attachments[letter.ID]; ok {
|
||||||
|
letter.Attachments = attachments
|
||||||
|
}
|
||||||
|
if recipients, ok := result.recipients[letter.ID]; ok {
|
||||||
|
letter.Recipients = recipients
|
||||||
|
}
|
||||||
|
if letter.PriorityID != nil {
|
||||||
|
if priority, ok := result.priorities[*letter.PriorityID]; ok {
|
||||||
|
letter.Priority = priority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if letter.ReceiverInstitutionID != nil {
|
||||||
|
if institution, ok := result.institutions[*letter.ReceiverInstitutionID]; ok {
|
||||||
|
letter.ReceiverInstitution = institution
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items[i] = *transformLetterToResponse(&letter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &contract.SearchOutgoingLettersResponse{
|
||||||
|
Letters: items,
|
||||||
|
TotalCount: total,
|
||||||
|
Page: req.Page,
|
||||||
|
Limit: req.Limit,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildOutgoingSearchFilters(req *contract.SearchOutgoingLettersRequest, userID, departmentID uuid.UUID) map[string]interface{} {
|
||||||
|
filters := make(map[string]interface{})
|
||||||
|
|
||||||
|
if req.Query != "" {
|
||||||
|
filters["query"] = req.Query
|
||||||
|
}
|
||||||
|
if req.LetterNumber != "" {
|
||||||
|
filters["letter_number"] = req.LetterNumber
|
||||||
|
}
|
||||||
|
if req.Subject != "" {
|
||||||
|
filters["subject"] = req.Subject
|
||||||
|
}
|
||||||
|
if req.Status != "" {
|
||||||
|
filters["status"] = req.Status
|
||||||
|
}
|
||||||
|
if req.PriorityID != nil {
|
||||||
|
filters["priority_id"] = *req.PriorityID
|
||||||
|
}
|
||||||
|
if req.InstitutionID != nil {
|
||||||
|
filters["receiver_institution_id"] = *req.InstitutionID
|
||||||
|
}
|
||||||
|
if req.CreatedBy != nil {
|
||||||
|
filters["created_by"] = *req.CreatedBy
|
||||||
|
}
|
||||||
|
if req.DateFrom != nil {
|
||||||
|
filters["date_from"] = *req.DateFrom
|
||||||
|
}
|
||||||
|
if req.DateTo != nil {
|
||||||
|
filters["date_to"] = *req.DateTo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user/department context filters
|
||||||
|
filters["user_context"] = map[string]interface{}{
|
||||||
|
"user_id": userID,
|
||||||
|
"department_id": departmentID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@ -382,6 +537,9 @@ func (s *LetterOutgoingServiceImpl) UpdateOutgoingLetter(ctx context.Context, id
|
|||||||
if req.ReferenceNumber != nil {
|
if req.ReferenceNumber != nil {
|
||||||
letter.ReferenceNumber = req.ReferenceNumber
|
letter.ReferenceNumber = req.ReferenceNumber
|
||||||
}
|
}
|
||||||
|
if req.ReceiverName != nil {
|
||||||
|
letter.ReceiverName = req.ReceiverName
|
||||||
|
}
|
||||||
|
|
||||||
err = s.processor.UpdateOutgoingLetter(ctx, letter, userID)
|
err = s.processor.UpdateOutgoingLetter(ctx, letter, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1245,6 +1403,7 @@ func transformLetterToResponse(letter *entities.LetterOutgoing) *contract.Outgoi
|
|||||||
Description: letter.Description,
|
Description: letter.Description,
|
||||||
PriorityID: letter.PriorityID,
|
PriorityID: letter.PriorityID,
|
||||||
ReceiverInstitutionID: letter.ReceiverInstitutionID,
|
ReceiverInstitutionID: letter.ReceiverInstitutionID,
|
||||||
|
ReceiverName: letter.ReceiverName,
|
||||||
IssueDate: letter.IssueDate,
|
IssueDate: letter.IssueDate,
|
||||||
Status: string(letter.Status),
|
Status: string(letter.Status),
|
||||||
ApprovalFlowID: letter.ApprovalFlowID,
|
ApprovalFlowID: letter.ApprovalFlowID,
|
||||||
|
|||||||
@ -25,6 +25,7 @@ type LetterProcessor interface {
|
|||||||
CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error)
|
CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error)
|
||||||
GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error)
|
GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error)
|
||||||
ListIncomingLetters(ctx context.Context, filter repository.ListIncomingLettersFilter, page, limit int) ([]entities.LetterIncoming, int64, error)
|
ListIncomingLetters(ctx context.Context, filter repository.ListIncomingLettersFilter, page, limit int) ([]entities.LetterIncoming, int64, error)
|
||||||
|
SearchIncomingLetters(ctx context.Context, filters map[string]interface{}, page, limit int, sortBy, sortOrder string) ([]entities.LetterIncoming, int64, error)
|
||||||
GetLetterUnreadCounts(ctx context.Context) (*contract.LetterUnreadCountResponse, error)
|
GetLetterUnreadCounts(ctx context.Context) (*contract.LetterUnreadCountResponse, error)
|
||||||
MarkIncomingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error)
|
MarkIncomingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error)
|
||||||
MarkOutgoingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error)
|
MarkOutgoingLetterAsRead(ctx context.Context, letterID uuid.UUID) (*contract.MarkLetterReadResponse, error)
|
||||||
@ -448,6 +449,208 @@ func (s *LetterServiceImpl) SoftDeleteIncomingLetter(ctx context.Context, id uui
|
|||||||
return s.processor.SoftDeleteIncomingLetter(ctx, id)
|
return s.processor.SoftDeleteIncomingLetter(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LetterServiceImpl) SearchIncomingLetters(ctx context.Context, req *contract.SearchIncomingLettersRequest) (*contract.SearchIncomingLettersResponse, error) {
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
userID := appCtx.UserID
|
||||||
|
departmentID := appCtx.DepartmentID
|
||||||
|
|
||||||
|
// Build search filters
|
||||||
|
filters := buildIncomingSearchFilters(req, userID, departmentID)
|
||||||
|
|
||||||
|
// Execute search with pagination
|
||||||
|
letters, total, err := s.processor.SearchIncomingLetters(ctx, filters, req.Page, req.Limit, req.SortBy, req.SortOrder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect IDs for batch loading
|
||||||
|
letterIDs := make([]uuid.UUID, len(letters))
|
||||||
|
priorityIDMap := make(map[uuid.UUID]bool)
|
||||||
|
institutionIDMap := make(map[uuid.UUID]bool)
|
||||||
|
|
||||||
|
for i, letter := range letters {
|
||||||
|
letterIDs[i] = letter.ID
|
||||||
|
if letter.PriorityID != nil {
|
||||||
|
priorityIDMap[*letter.PriorityID] = true
|
||||||
|
}
|
||||||
|
if letter.SenderInstitutionID != nil {
|
||||||
|
institutionIDMap[*letter.SenderInstitutionID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert maps to slices
|
||||||
|
priorityIDSlice := make([]uuid.UUID, 0, len(priorityIDMap))
|
||||||
|
for id := range priorityIDMap {
|
||||||
|
priorityIDSlice = append(priorityIDSlice, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
institutionIDSlice := make([]uuid.UUID, 0, len(institutionIDMap))
|
||||||
|
for id := range institutionIDMap {
|
||||||
|
institutionIDSlice = append(institutionIDSlice, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallel batch loading
|
||||||
|
type batchLoadResult struct {
|
||||||
|
attachments map[uuid.UUID][]entities.LetterIncomingAttachment
|
||||||
|
recipients map[uuid.UUID]*entities.LetterIncomingRecipient
|
||||||
|
priorities map[uuid.UUID]*entities.Priority
|
||||||
|
institutions map[uuid.UUID]*entities.Institution
|
||||||
|
}
|
||||||
|
|
||||||
|
var result batchLoadResult
|
||||||
|
errChan := make(chan error, 4)
|
||||||
|
|
||||||
|
// Load attachments
|
||||||
|
go func() {
|
||||||
|
result.attachments, err = s.processor.GetBatchAttachments(ctx, letterIDs)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Load recipients for user
|
||||||
|
go func() {
|
||||||
|
result.recipients, err = s.processor.GetBatchRecipientsByUser(ctx, letterIDs, userID)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Load priorities
|
||||||
|
go func() {
|
||||||
|
result.priorities, err = s.processor.GetBatchPriorities(ctx, priorityIDSlice)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Load institutions
|
||||||
|
go func() {
|
||||||
|
result.institutions, err = s.processor.GetBatchInstitutions(ctx, institutionIDSlice)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for all goroutines and check for errors
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
if err := <-errChan; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform letters with batch loaded data
|
||||||
|
items := make([]contract.IncomingLetterResponse, len(letters))
|
||||||
|
for i, letter := range letters {
|
||||||
|
// Attach batch loaded data
|
||||||
|
attachmentResponses := []contract.IncomingLetterAttachmentResponse{}
|
||||||
|
if attachments, ok := result.attachments[letter.ID]; ok {
|
||||||
|
for _, att := range attachments {
|
||||||
|
attachmentResponses = append(attachmentResponses, contract.IncomingLetterAttachmentResponse{
|
||||||
|
ID: att.ID,
|
||||||
|
FileURL: att.FileURL,
|
||||||
|
FileName: att.FileName,
|
||||||
|
FileType: att.FileType,
|
||||||
|
UploadedAt: att.UploadedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var priorityResp *contract.PriorityResponse
|
||||||
|
if letter.PriorityID != nil {
|
||||||
|
if priority, ok := result.priorities[*letter.PriorityID]; ok {
|
||||||
|
priorityResp = &contract.PriorityResponse{
|
||||||
|
ID: priority.ID.String(),
|
||||||
|
Name: priority.Name,
|
||||||
|
Level: priority.Level,
|
||||||
|
CreatedAt: priority.CreatedAt,
|
||||||
|
UpdatedAt: priority.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var institutionResp *contract.InstitutionResponse
|
||||||
|
if letter.SenderInstitutionID != nil {
|
||||||
|
if institution, ok := result.institutions[*letter.SenderInstitutionID]; ok {
|
||||||
|
institutionResp = &contract.InstitutionResponse{
|
||||||
|
ID: institution.ID.String(),
|
||||||
|
Name: institution.Name,
|
||||||
|
Type: string(institution.Type),
|
||||||
|
Address: institution.Address,
|
||||||
|
ContactPerson: institution.ContactPerson,
|
||||||
|
Phone: institution.Phone,
|
||||||
|
Email: institution.Email,
|
||||||
|
CreatedAt: institution.CreatedAt,
|
||||||
|
UpdatedAt: institution.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isRead := false
|
||||||
|
if recipient, ok := result.recipients[letter.ID]; ok && recipient.ReadAt != nil {
|
||||||
|
isRead = true
|
||||||
|
}
|
||||||
|
|
||||||
|
items[i] = contract.IncomingLetterResponse{
|
||||||
|
ID: letter.ID,
|
||||||
|
LetterNumber: letter.LetterNumber,
|
||||||
|
ReferenceNumber: letter.ReferenceNumber,
|
||||||
|
Subject: letter.Subject,
|
||||||
|
Description: letter.Description,
|
||||||
|
Priority: priorityResp,
|
||||||
|
SenderInstitution: institutionResp,
|
||||||
|
SenderName: letter.SenderName,
|
||||||
|
ReceivedDate: letter.ReceivedDate,
|
||||||
|
DueDate: letter.DueDate,
|
||||||
|
Status: string(letter.Status),
|
||||||
|
CreatedBy: letter.CreatedBy,
|
||||||
|
CreatedAt: letter.CreatedAt,
|
||||||
|
UpdatedAt: letter.UpdatedAt,
|
||||||
|
Attachments: attachmentResponses,
|
||||||
|
IsRead: isRead,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &contract.SearchIncomingLettersResponse{
|
||||||
|
Letters: items,
|
||||||
|
TotalCount: total,
|
||||||
|
Page: req.Page,
|
||||||
|
Limit: req.Limit,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildIncomingSearchFilters(req *contract.SearchIncomingLettersRequest, userID, departmentID uuid.UUID) map[string]interface{} {
|
||||||
|
filters := make(map[string]interface{})
|
||||||
|
|
||||||
|
if req.Query != "" {
|
||||||
|
filters["query"] = req.Query
|
||||||
|
}
|
||||||
|
if req.LetterNumber != "" {
|
||||||
|
filters["letter_number"] = req.LetterNumber
|
||||||
|
}
|
||||||
|
if req.Subject != "" {
|
||||||
|
filters["subject"] = req.Subject
|
||||||
|
}
|
||||||
|
if req.Status != "" {
|
||||||
|
filters["status"] = req.Status
|
||||||
|
}
|
||||||
|
if req.PriorityID != nil {
|
||||||
|
filters["priority_id"] = *req.PriorityID
|
||||||
|
}
|
||||||
|
if req.InstitutionID != nil {
|
||||||
|
filters["sender_institution_id"] = *req.InstitutionID
|
||||||
|
}
|
||||||
|
if req.CreatedBy != nil {
|
||||||
|
filters["created_by"] = *req.CreatedBy
|
||||||
|
}
|
||||||
|
if req.DateFrom != nil {
|
||||||
|
filters["date_from"] = *req.DateFrom
|
||||||
|
}
|
||||||
|
if req.DateTo != nil {
|
||||||
|
filters["date_to"] = *req.DateTo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user/department context filters
|
||||||
|
filters["user_context"] = map[string]interface{}{
|
||||||
|
"user_id": userID,
|
||||||
|
"department_id": departmentID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters
|
||||||
|
}
|
||||||
|
|
||||||
func (s *LetterServiceImpl) CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error) {
|
func (s *LetterServiceImpl) CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error) {
|
||||||
log.Printf("[DEBUG] CreateDispositions START - LetterID: %s\n", req.LetterID.String())
|
log.Printf("[DEBUG] CreateDispositions START - LetterID: %s\n", req.LetterID.String())
|
||||||
userID := appcontext.FromGinContext(ctx).UserID
|
userID := appcontext.FromGinContext(ctx).UserID
|
||||||
|
|||||||
@ -14,6 +14,7 @@ func LetterEntityToContract(e *entities.LetterIncoming, attachments []entities.L
|
|||||||
ReferenceNumber: e.ReferenceNumber,
|
ReferenceNumber: e.ReferenceNumber,
|
||||||
Subject: e.Subject,
|
Subject: e.Subject,
|
||||||
Description: e.Description,
|
Description: e.Description,
|
||||||
|
SenderName: e.SenderName,
|
||||||
ReceivedDate: e.ReceivedDate,
|
ReceivedDate: e.ReceivedDate,
|
||||||
DueDate: e.DueDate,
|
DueDate: e.DueDate,
|
||||||
Status: string(e.Status),
|
Status: string(e.Status),
|
||||||
@ -75,6 +76,7 @@ func LetterIncomingEntityToContract(e *entities.LetterIncoming) *contract.Incomi
|
|||||||
ReferenceNumber: e.ReferenceNumber,
|
ReferenceNumber: e.ReferenceNumber,
|
||||||
Subject: e.Subject,
|
Subject: e.Subject,
|
||||||
Description: e.Description,
|
Description: e.Description,
|
||||||
|
SenderName: e.SenderName,
|
||||||
ReceivedDate: e.ReceivedDate,
|
ReceivedDate: e.ReceivedDate,
|
||||||
DueDate: e.DueDate,
|
DueDate: e.DueDate,
|
||||||
Status: string(e.Status),
|
Status: string(e.Status),
|
||||||
|
|||||||
31
migrations/000036_add_search_indexes.down.sql
Normal file
31
migrations/000036_add_search_indexes.down.sql
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
-- Drop functions
|
||||||
|
DROP FUNCTION IF EXISTS search_outgoing_letters(text);
|
||||||
|
DROP FUNCTION IF EXISTS search_incoming_letters(text);
|
||||||
|
|
||||||
|
-- Drop indexes for outgoing letters
|
||||||
|
DROP INDEX IF EXISTS idx_letters_outgoing_letter_number_text;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_outgoing_subject_text;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_outgoing_description_text;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_outgoing_reference_number_text;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_outgoing_status_created;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_outgoing_priority_created;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_outgoing_institution_created;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_outgoing_created_by;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_outgoing_issue_date;
|
||||||
|
|
||||||
|
-- Drop indexes for incoming letters
|
||||||
|
DROP INDEX IF EXISTS idx_letters_incoming_letter_number_text;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_incoming_subject_text;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_incoming_description_text;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_incoming_reference_number_text;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_incoming_status_created;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_incoming_priority_created;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_incoming_institution_created;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_incoming_created_by;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_incoming_received_date;
|
||||||
|
|
||||||
|
-- Drop recipient indexes
|
||||||
|
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_user;
|
||||||
|
DROP INDEX IF EXISTS idx_letter_outgoing_recipients_dept;
|
||||||
|
DROP INDEX IF EXISTS idx_letter_incoming_recipients_user;
|
||||||
|
DROP INDEX IF EXISTS idx_letter_incoming_recipients_dept;
|
||||||
63
migrations/000036_add_search_indexes.up.sql
Normal file
63
migrations/000036_add_search_indexes.up.sql
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
-- Add indexes for optimized search on outgoing letters
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_letter_number_text ON letters_outgoing USING gin(to_tsvector('simple', letter_number));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_subject_text ON letters_outgoing USING gin(to_tsvector('simple', subject));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_description_text ON letters_outgoing USING gin(to_tsvector('simple', COALESCE(description, '')));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_reference_number_text ON letters_outgoing USING gin(to_tsvector('simple', COALESCE(reference_number, '')));
|
||||||
|
|
||||||
|
-- Composite indexes for common query patterns on outgoing letters
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_status_created ON letters_outgoing(status, created_at DESC) WHERE deleted_at IS NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_priority_created ON letters_outgoing(priority_id, created_at DESC) WHERE deleted_at IS NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_institution_created ON letters_outgoing(receiver_institution_id, created_at DESC) WHERE deleted_at IS NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_created_by ON letters_outgoing(created_by, created_at DESC) WHERE deleted_at IS NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_issue_date ON letters_outgoing(issue_date DESC) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Add indexes for optimized search on incoming letters
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_letter_number_text ON letters_incoming USING gin(to_tsvector('simple', letter_number));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_subject_text ON letters_incoming USING gin(to_tsvector('simple', subject));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_description_text ON letters_incoming USING gin(to_tsvector('simple', COALESCE(description, '')));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_reference_number_text ON letters_incoming USING gin(to_tsvector('simple', COALESCE(reference_number, '')));
|
||||||
|
|
||||||
|
-- Composite indexes for common query patterns on incoming letters
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_status_created ON letters_incoming(status, created_at DESC) WHERE deleted_at IS NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_priority_created ON letters_incoming(priority_id, created_at DESC) WHERE deleted_at IS NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_institution_created ON letters_incoming(sender_institution_id, created_at DESC) WHERE deleted_at IS NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_created_by ON letters_incoming(created_by, created_at DESC) WHERE deleted_at IS NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_received_date ON letters_incoming(received_date DESC) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Indexes for recipient lookups
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_user ON letter_outgoing_recipients(user_id, letter_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_outgoing_recipients_dept ON letter_outgoing_recipients(department_id, letter_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_user ON letter_incoming_recipients(recipient_user_id, letter_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_dept ON letter_incoming_recipients(recipient_department_id, letter_id);
|
||||||
|
|
||||||
|
-- Create a function for full-text search on outgoing letters
|
||||||
|
CREATE OR REPLACE FUNCTION search_outgoing_letters(search_query text)
|
||||||
|
RETURNS SETOF letters_outgoing AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT * FROM letters_outgoing
|
||||||
|
WHERE deleted_at IS NULL
|
||||||
|
AND (
|
||||||
|
to_tsvector('simple', letter_number) @@ plainto_tsquery('simple', search_query)
|
||||||
|
OR to_tsvector('simple', subject) @@ plainto_tsquery('simple', search_query)
|
||||||
|
OR to_tsvector('simple', COALESCE(description, '')) @@ plainto_tsquery('simple', search_query)
|
||||||
|
OR to_tsvector('simple', COALESCE(reference_number, '')) @@ plainto_tsquery('simple', search_query)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Create a function for full-text search on incoming letters
|
||||||
|
CREATE OR REPLACE FUNCTION search_incoming_letters(search_query text)
|
||||||
|
RETURNS SETOF letters_incoming AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT * FROM letters_incoming
|
||||||
|
WHERE deleted_at IS NULL
|
||||||
|
AND (
|
||||||
|
to_tsvector('simple', letter_number) @@ plainto_tsquery('simple', search_query)
|
||||||
|
OR to_tsvector('simple', subject) @@ plainto_tsquery('simple', search_query)
|
||||||
|
OR to_tsvector('simple', COALESCE(description, '')) @@ plainto_tsquery('simple', search_query)
|
||||||
|
OR to_tsvector('simple', COALESCE(reference_number, '')) @@ plainto_tsquery('simple', search_query)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
12
migrations/000037_add_sender_receiver_names.down.sql
Normal file
12
migrations/000037_add_sender_receiver_names.down.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
-- Drop indexes
|
||||||
|
DROP INDEX IF EXISTS idx_letters_incoming_sender_name_text;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_outgoing_receiver_name_text;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_incoming_sender_name;
|
||||||
|
DROP INDEX IF EXISTS idx_letters_outgoing_receiver_name;
|
||||||
|
|
||||||
|
-- Remove columns
|
||||||
|
ALTER TABLE letters_incoming
|
||||||
|
DROP COLUMN IF EXISTS sender_name;
|
||||||
|
|
||||||
|
ALTER TABLE letters_outgoing
|
||||||
|
DROP COLUMN IF EXISTS receiver_name;
|
||||||
15
migrations/000037_add_sender_receiver_names.up.sql
Normal file
15
migrations/000037_add_sender_receiver_names.up.sql
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
-- Add sender_name to incoming letters
|
||||||
|
ALTER TABLE letters_incoming
|
||||||
|
ADD COLUMN IF NOT EXISTS sender_name VARCHAR(255);
|
||||||
|
|
||||||
|
-- Add receiver_name to outgoing letters
|
||||||
|
ALTER TABLE letters_outgoing
|
||||||
|
ADD COLUMN IF NOT EXISTS receiver_name VARCHAR(255);
|
||||||
|
|
||||||
|
-- Add indexes for the new fields to support searching
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_sender_name ON letters_incoming(sender_name) WHERE deleted_at IS NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_receiver_name ON letters_outgoing(receiver_name) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Add GIN indexes for full-text search
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_sender_name_text ON letters_incoming USING gin(to_tsvector('simple', COALESCE(sender_name, '')));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_outgoing_receiver_name_text ON letters_outgoing USING gin(to_tsvector('simple', COALESCE(receiver_name, '')));
|
||||||
Loading…
x
Reference in New Issue
Block a user