letter incoming
This commit is contained in:
parent
001d02c587
commit
61d6eed373
@ -44,6 +44,9 @@ func (a *App) Initialize(cfg *config.Config) error {
|
|||||||
healthHandler := handler.NewHealthHandler()
|
healthHandler := handler.NewHealthHandler()
|
||||||
fileHandler := handler.NewFileHandler(services.fileService)
|
fileHandler := handler.NewFileHandler(services.fileService)
|
||||||
rbacHandler := handler.NewRBACHandler(services.rbacService)
|
rbacHandler := handler.NewRBACHandler(services.rbacService)
|
||||||
|
masterHandler := handler.NewMasterHandler(services.masterService)
|
||||||
|
letterHandler := handler.NewLetterHandler(services.letterService)
|
||||||
|
dispositionRouteHandler := handler.NewDispositionRouteHandler(services.dispositionRouteService)
|
||||||
|
|
||||||
a.router = router.NewRouter(
|
a.router = router.NewRouter(
|
||||||
cfg,
|
cfg,
|
||||||
@ -53,6 +56,9 @@ func (a *App) Initialize(cfg *config.Config) error {
|
|||||||
handler.NewUserHandler(services.userService, validator.NewUserValidator()),
|
handler.NewUserHandler(services.userService, validator.NewUserValidator()),
|
||||||
fileHandler,
|
fileHandler,
|
||||||
rbacHandler,
|
rbacHandler,
|
||||||
|
masterHandler,
|
||||||
|
letterHandler,
|
||||||
|
dispositionRouteHandler,
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -102,6 +108,19 @@ type repositories struct {
|
|||||||
userProfileRepo *repository.UserProfileRepository
|
userProfileRepo *repository.UserProfileRepository
|
||||||
titleRepo *repository.TitleRepository
|
titleRepo *repository.TitleRepository
|
||||||
rbacRepo *repository.RBACRepository
|
rbacRepo *repository.RBACRepository
|
||||||
|
labelRepo *repository.LabelRepository
|
||||||
|
priorityRepo *repository.PriorityRepository
|
||||||
|
institutionRepo *repository.InstitutionRepository
|
||||||
|
dispRepo *repository.DispositionActionRepository
|
||||||
|
letterRepo *repository.LetterIncomingRepository
|
||||||
|
letterAttachRepo *repository.LetterIncomingAttachmentRepository
|
||||||
|
activityLogRepo *repository.LetterIncomingActivityLogRepository
|
||||||
|
dispositionRouteRepo *repository.DispositionRouteRepository
|
||||||
|
// new repos
|
||||||
|
letterDispositionRepo *repository.LetterDispositionRepository
|
||||||
|
letterDispActionSelRepo *repository.LetterDispositionActionSelectionRepository
|
||||||
|
dispositionNoteRepo *repository.DispositionNoteRepository
|
||||||
|
letterDiscussionRepo *repository.LetterDiscussionRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initRepositories() *repositories {
|
func (a *App) initRepositories() *repositories {
|
||||||
@ -110,16 +129,34 @@ func (a *App) initRepositories() *repositories {
|
|||||||
userProfileRepo: repository.NewUserProfileRepository(a.db),
|
userProfileRepo: repository.NewUserProfileRepository(a.db),
|
||||||
titleRepo: repository.NewTitleRepository(a.db),
|
titleRepo: repository.NewTitleRepository(a.db),
|
||||||
rbacRepo: repository.NewRBACRepository(a.db),
|
rbacRepo: repository.NewRBACRepository(a.db),
|
||||||
|
labelRepo: repository.NewLabelRepository(a.db),
|
||||||
|
priorityRepo: repository.NewPriorityRepository(a.db),
|
||||||
|
institutionRepo: repository.NewInstitutionRepository(a.db),
|
||||||
|
dispRepo: repository.NewDispositionActionRepository(a.db),
|
||||||
|
letterRepo: repository.NewLetterIncomingRepository(a.db),
|
||||||
|
letterAttachRepo: repository.NewLetterIncomingAttachmentRepository(a.db),
|
||||||
|
activityLogRepo: repository.NewLetterIncomingActivityLogRepository(a.db),
|
||||||
|
dispositionRouteRepo: repository.NewDispositionRouteRepository(a.db),
|
||||||
|
letterDispositionRepo: repository.NewLetterDispositionRepository(a.db),
|
||||||
|
letterDispActionSelRepo: repository.NewLetterDispositionActionSelectionRepository(a.db),
|
||||||
|
dispositionNoteRepo: repository.NewDispositionNoteRepository(a.db),
|
||||||
|
letterDiscussionRepo: repository.NewLetterDiscussionRepository(a.db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type processors struct {
|
type processors struct {
|
||||||
userProcessor *processor.UserProcessorImpl
|
userProcessor *processor.UserProcessorImpl
|
||||||
|
letterProcessor *processor.LetterProcessorImpl
|
||||||
|
activityLogger *processor.ActivityLogProcessorImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
|
func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
|
||||||
|
txMgr := repository.NewTxManager(a.db)
|
||||||
|
activity := processor.NewActivityLogProcessor(repos.activityLogRepo)
|
||||||
return &processors{
|
return &processors{
|
||||||
userProcessor: processor.NewUserProcessor(repos.userRepo, repos.userProfileRepo),
|
userProcessor: processor.NewUserProcessor(repos.userRepo, repos.userProfileRepo),
|
||||||
|
letterProcessor: processor.NewLetterProcessor(repos.letterRepo, repos.letterAttachRepo, txMgr, activity, repos.letterDispositionRepo, repos.letterDispActionSelRepo, repos.dispositionNoteRepo, repos.letterDiscussionRepo),
|
||||||
|
activityLogger: activity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +165,9 @@ type services struct {
|
|||||||
authService *service.AuthServiceImpl
|
authService *service.AuthServiceImpl
|
||||||
fileService *service.FileServiceImpl
|
fileService *service.FileServiceImpl
|
||||||
rbacService *service.RBACServiceImpl
|
rbacService *service.RBACServiceImpl
|
||||||
|
masterService *service.MasterServiceImpl
|
||||||
|
letterService *service.LetterServiceImpl
|
||||||
|
dispositionRouteService *service.DispositionRouteServiceImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
||||||
@ -137,18 +177,25 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
|||||||
|
|
||||||
userSvc := service.NewUserService(processors.userProcessor, repos.titleRepo)
|
userSvc := service.NewUserService(processors.userProcessor, repos.titleRepo)
|
||||||
|
|
||||||
// File storage client and service
|
|
||||||
fileCfg := cfg.S3Config
|
fileCfg := cfg.S3Config
|
||||||
s3Client := client.NewFileClient(fileCfg)
|
s3Client := client.NewFileClient(fileCfg)
|
||||||
fileSvc := service.NewFileService(s3Client, processors.userProcessor, "profile", "documents")
|
fileSvc := service.NewFileService(s3Client, processors.userProcessor, "profile", "documents")
|
||||||
|
|
||||||
rbacSvc := service.NewRBACService(repos.rbacRepo)
|
rbacSvc := service.NewRBACService(repos.rbacRepo)
|
||||||
|
|
||||||
|
masterSvc := service.NewMasterService(repos.labelRepo, repos.priorityRepo, repos.institutionRepo, repos.dispRepo)
|
||||||
|
|
||||||
|
letterSvc := service.NewLetterService(processors.letterProcessor)
|
||||||
|
dispRouteSvc := service.NewDispositionRouteService(repos.dispositionRouteRepo)
|
||||||
|
|
||||||
return &services{
|
return &services{
|
||||||
userService: userSvc,
|
userService: userSvc,
|
||||||
authService: authService,
|
authService: authService,
|
||||||
fileService: fileSvc,
|
fileService: fileSvc,
|
||||||
rbacService: rbacSvc,
|
rbacService: rbacSvc,
|
||||||
|
masterService: masterSvc,
|
||||||
|
letterService: letterSvc,
|
||||||
|
dispositionRouteService: dispRouteSvc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,3 +47,118 @@ type HealthResponse struct {
|
|||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LabelResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Color *string `json:"color,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateLabelRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Color *string `json:"color,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateLabelRequest struct {
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Color *string `json:"color,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListLabelsResponse struct {
|
||||||
|
Labels []LabelResponse `json:"labels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PriorityResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatePriorityRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdatePriorityRequest struct {
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Level *int `json:"level,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListPrioritiesResponse struct {
|
||||||
|
Priorities []PriorityResponse `json:"priorities"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstitutionResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Address *string `json:"address,omitempty"`
|
||||||
|
ContactPerson *string `json:"contact_person,omitempty"`
|
||||||
|
Phone *string `json:"phone,omitempty"`
|
||||||
|
Email *string `json:"email,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateInstitutionRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Address *string `json:"address,omitempty"`
|
||||||
|
ContactPerson *string `json:"contact_person,omitempty"`
|
||||||
|
Phone *string `json:"phone,omitempty"`
|
||||||
|
Email *string `json:"email,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateInstitutionRequest struct {
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Type *string `json:"type,omitempty"`
|
||||||
|
Address *string `json:"address,omitempty"`
|
||||||
|
ContactPerson *string `json:"contact_person,omitempty"`
|
||||||
|
Phone *string `json:"phone,omitempty"`
|
||||||
|
Email *string `json:"email,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListInstitutionsResponse struct {
|
||||||
|
Institutions []InstitutionResponse `json:"institutions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DispositionActionResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
RequiresNote bool `json:"requires_note"`
|
||||||
|
GroupName *string `json:"group_name,omitempty"`
|
||||||
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateDispositionActionRequest struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
RequiresNote *bool `json:"requires_note,omitempty"`
|
||||||
|
GroupName *string `json:"group_name,omitempty"`
|
||||||
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateDispositionActionRequest struct {
|
||||||
|
Code *string `json:"code,omitempty"`
|
||||||
|
Label *string `json:"label,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
RequiresNote *bool `json:"requires_note,omitempty"`
|
||||||
|
GroupName *string `json:"group_name,omitempty"`
|
||||||
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListDispositionActionsResponse struct {
|
||||||
|
Actions []DispositionActionResponse `json:"actions"`
|
||||||
|
}
|
||||||
|
|||||||
33
internal/contract/disposition_route_contract.go
Normal file
33
internal/contract/disposition_route_contract.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DispositionRouteResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
FromDepartmentID uuid.UUID `json:"from_department_id"`
|
||||||
|
ToDepartmentID uuid.UUID `json:"to_department_id"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
AllowedActions map[string]interface{} `json:"allowed_actions,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateDispositionRouteRequest struct {
|
||||||
|
FromDepartmentID uuid.UUID `json:"from_department_id"`
|
||||||
|
ToDepartmentID uuid.UUID `json:"to_department_id"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty"`
|
||||||
|
AllowedActions *map[string]interface{} `json:"allowed_actions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateDispositionRouteRequest struct {
|
||||||
|
IsActive *bool `json:"is_active,omitempty"`
|
||||||
|
AllowedActions *map[string]interface{} `json:"allowed_actions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListDispositionRoutesResponse struct {
|
||||||
|
Routes []DispositionRouteResponse `json:"routes"`
|
||||||
|
}
|
||||||
122
internal/contract/letter_contract.go
Normal file
122
internal/contract/letter_contract.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateIncomingLetterAttachment struct {
|
||||||
|
FileURL string `json:"file_url"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
FileType string `json:"file_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateIncomingLetterRequest struct {
|
||||||
|
ReferenceNumber *string `json:"reference_number,omitempty"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
||||||
|
SenderInstitutionID *uuid.UUID `json:"sender_institution_id,omitempty"`
|
||||||
|
ReceivedDate time.Time `json:"received_date"`
|
||||||
|
DueDate *time.Time `json:"due_date,omitempty"`
|
||||||
|
Attachments []CreateIncomingLetterAttachment `json:"attachments,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IncomingLetterAttachmentResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
FileURL string `json:"file_url"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
FileType string `json:"file_type"`
|
||||||
|
UploadedAt time.Time `json:"uploaded_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IncomingLetterResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
LetterNumber string `json:"letter_number"`
|
||||||
|
ReferenceNumber *string `json:"reference_number,omitempty"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
||||||
|
SenderInstitutionID *uuid.UUID `json:"sender_institution_id,omitempty"`
|
||||||
|
ReceivedDate time.Time `json:"received_date"`
|
||||||
|
DueDate *time.Time `json:"due_date,omitempty"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreatedBy uuid.UUID `json:"created_by"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Attachments []IncomingLetterAttachmentResponse `json:"attachments"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateIncomingLetterRequest struct {
|
||||||
|
ReferenceNumber *string `json:"reference_number,omitempty"`
|
||||||
|
Subject *string `json:"subject,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
||||||
|
SenderInstitutionID *uuid.UUID `json:"sender_institution_id,omitempty"`
|
||||||
|
ReceivedDate *time.Time `json:"received_date,omitempty"`
|
||||||
|
DueDate *time.Time `json:"due_date,omitempty"`
|
||||||
|
Status *string `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListIncomingLettersRequest struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
Status *string `json:"status,omitempty"`
|
||||||
|
Query *string `json:"query,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListIncomingLettersResponse struct {
|
||||||
|
Letters []IncomingLetterResponse `json:"letters"`
|
||||||
|
Pagination PaginationResponse `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateDispositionActionSelection struct {
|
||||||
|
ActionID uuid.UUID `json:"action_id"`
|
||||||
|
Note *string `json:"note,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateLetterDispositionRequest struct {
|
||||||
|
LetterID uuid.UUID `json:"letter_id"`
|
||||||
|
ToDepartmentIDs []uuid.UUID `json:"to_department_ids"`
|
||||||
|
Notes *string `json:"notes,omitempty"`
|
||||||
|
SelectedActions []CreateDispositionActionSelection `json:"selected_actions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DispositionResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
LetterID uuid.UUID `json:"letter_id"`
|
||||||
|
FromDepartmentID *uuid.UUID `json:"from_department_id,omitempty"`
|
||||||
|
ToDepartmentID *uuid.UUID `json:"to_department_id,omitempty"`
|
||||||
|
Notes *string `json:"notes,omitempty"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreatedBy uuid.UUID `json:"created_by"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListDispositionsResponse struct {
|
||||||
|
Dispositions []DispositionResponse `json:"dispositions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateLetterDiscussionRequest struct {
|
||||||
|
ParentID *uuid.UUID `json:"parent_id,omitempty"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Mentions map[string]interface{} `json:"mentions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateLetterDiscussionRequest struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Mentions map[string]interface{} `json:"mentions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LetterDiscussionResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
LetterID uuid.UUID `json:"letter_id"`
|
||||||
|
ParentID *uuid.UUID `json:"parent_id,omitempty"`
|
||||||
|
UserID uuid.UUID `json:"user_id"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Mentions map[string]interface{} `json:"mentions,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
EditedAt *time.Time `json:"edited_at,omitempty"`
|
||||||
|
}
|
||||||
22
internal/entities/disposition_action.go
Normal file
22
internal/entities/disposition_action.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DispositionAction struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
Code string `gorm:"uniqueIndex;not null" json:"code"`
|
||||||
|
Label string `gorm:"not null" json:"label"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
RequiresNote bool `gorm:"not null;default:false" json:"requires_note"`
|
||||||
|
GroupName *string `json:"group_name,omitempty"`
|
||||||
|
SortOrder *int `json:"sort_order,omitempty"`
|
||||||
|
IsActive bool `gorm:"not null;default:true" json:"is_active"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DispositionAction) TableName() string { return "disposition_actions" }
|
||||||
19
internal/entities/disposition_route.go
Normal file
19
internal/entities/disposition_route.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DispositionRoute struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
FromDepartmentID uuid.UUID `gorm:"type:uuid;not null" json:"from_department_id"`
|
||||||
|
ToDepartmentID uuid.UUID `gorm:"type:uuid;not null" json:"to_department_id"`
|
||||||
|
IsActive bool `gorm:"not null;default:true" json:"is_active"`
|
||||||
|
AllowedActions JSONB `gorm:"type:jsonb" json:"allowed_actions,omitempty"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DispositionRoute) TableName() string { return "disposition_routes" }
|
||||||
30
internal/entities/institution.go
Normal file
30
internal/entities/institution.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InstitutionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
InstGovernment InstitutionType = "government"
|
||||||
|
InstPrivate InstitutionType = "private"
|
||||||
|
InstNGO InstitutionType = "ngo"
|
||||||
|
InstIndividual InstitutionType = "individual"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Institution struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
Name string `gorm:"not null;size:255" json:"name"`
|
||||||
|
Type InstitutionType `gorm:"not null;size:32" json:"type"`
|
||||||
|
Address *string `json:"address,omitempty"`
|
||||||
|
ContactPerson *string `gorm:"size:255" json:"contact_person,omitempty"`
|
||||||
|
Phone *string `gorm:"size:50" json:"phone,omitempty"`
|
||||||
|
Email *string `gorm:"size:255" json:"email,omitempty"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Institution) TableName() string { return "institutions" }
|
||||||
17
internal/entities/label.go
Normal file
17
internal/entities/label.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
Name string `gorm:"not null;size:255" json:"name"`
|
||||||
|
Color *string `gorm:"size:16" json:"color,omitempty"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Label) TableName() string { return "labels" }
|
||||||
21
internal/entities/letter_discussion.go
Normal file
21
internal/entities/letter_discussion.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LetterDiscussion struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
LetterID uuid.UUID `gorm:"type:uuid;not null" json:"letter_id"`
|
||||||
|
ParentID *uuid.UUID `json:"parent_id,omitempty"`
|
||||||
|
UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"`
|
||||||
|
Message string `gorm:"not null" json:"message"`
|
||||||
|
Mentions JSONB `gorm:"type:jsonb" json:"mentions,omitempty"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
EditedAt *time.Time `json:"edited_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LetterDiscussion) TableName() string { return "letter_incoming_discussions" }
|
||||||
55
internal/entities/letter_disposition.go
Normal file
55
internal/entities/letter_disposition.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LetterDispositionStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DispositionPending LetterDispositionStatus = "pending"
|
||||||
|
DispositionRead LetterDispositionStatus = "read"
|
||||||
|
DispositionRejected LetterDispositionStatus = "rejected"
|
||||||
|
DispositionCompleted LetterDispositionStatus = "completed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LetterDisposition struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
LetterID uuid.UUID `gorm:"type:uuid;not null" json:"letter_id"`
|
||||||
|
FromUserID *uuid.UUID `json:"from_user_id,omitempty"`
|
||||||
|
FromDepartmentID *uuid.UUID `json:"from_department_id,omitempty"`
|
||||||
|
ToUserID *uuid.UUID `json:"to_user_id,omitempty"`
|
||||||
|
ToDepartmentID *uuid.UUID `json:"to_department_id,omitempty"`
|
||||||
|
Notes *string `json:"notes,omitempty"`
|
||||||
|
Status LetterDispositionStatus `gorm:"not null;default:'pending'" json:"status"`
|
||||||
|
CreatedBy uuid.UUID `gorm:"not null" json:"created_by"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
ReadAt *time.Time `json:"read_at,omitempty"`
|
||||||
|
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LetterDisposition) TableName() string { return "letter_dispositions" }
|
||||||
|
|
||||||
|
type DispositionNote struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
DispositionID uuid.UUID `gorm:"type:uuid;not null" json:"disposition_id"`
|
||||||
|
UserID *uuid.UUID `json:"user_id,omitempty"`
|
||||||
|
Note string `gorm:"not null" json:"note"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DispositionNote) TableName() string { return "disposition_notes" }
|
||||||
|
|
||||||
|
type LetterDispositionActionSelection struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
DispositionID uuid.UUID `gorm:"type:uuid;not null" json:"disposition_id"`
|
||||||
|
ActionID uuid.UUID `gorm:"type:uuid;not null" json:"action_id"`
|
||||||
|
Note *string `json:"note,omitempty"`
|
||||||
|
CreatedBy uuid.UUID `gorm:"not null" json:"created_by"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LetterDispositionActionSelection) TableName() string { return "letter_disposition_actions" }
|
||||||
45
internal/entities/letter_incoming.go
Normal file
45
internal/entities/letter_incoming.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LetterIncomingStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
LetterIncomingStatusNew LetterIncomingStatus = "new"
|
||||||
|
LetterIncomingStatusInProgress LetterIncomingStatus = "in_progress"
|
||||||
|
LetterIncomingStatusCompleted LetterIncomingStatus = "completed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LetterIncoming struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
LetterNumber string `gorm:"uniqueIndex;not null" json:"letter_number"`
|
||||||
|
ReferenceNumber *string `json:"reference_number,omitempty"`
|
||||||
|
Subject string `gorm:"not null" json:"subject"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
PriorityID *uuid.UUID `json:"priority_id,omitempty"`
|
||||||
|
SenderInstitutionID *uuid.UUID `json:"sender_institution_id,omitempty"`
|
||||||
|
ReceivedDate time.Time `json:"received_date"`
|
||||||
|
DueDate *time.Time `json:"due_date,omitempty"`
|
||||||
|
Status LetterIncomingStatus `gorm:"not null;default:'new'" json:"status"`
|
||||||
|
CreatedBy uuid.UUID `gorm:"not null" json:"created_by"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LetterIncoming) TableName() string { return "letters_incoming" }
|
||||||
|
|
||||||
|
type LetterIncomingAttachment struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
LetterID uuid.UUID `gorm:"type:uuid;not null" json:"letter_id"`
|
||||||
|
FileURL string `gorm:"not null" json:"file_url"`
|
||||||
|
FileName string `gorm:"not null" json:"file_name"`
|
||||||
|
FileType string `gorm:"not null" json:"file_type"`
|
||||||
|
UploadedBy *uuid.UUID `json:"uploaded_by,omitempty"`
|
||||||
|
UploadedAt time.Time `gorm:"autoCreateTime" json:"uploaded_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LetterIncomingAttachment) TableName() string { return "letter_incoming_attachments" }
|
||||||
23
internal/entities/letter_incoming_activity_log.go
Normal file
23
internal/entities/letter_incoming_activity_log.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LetterIncomingActivityLog struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
LetterID uuid.UUID `gorm:"type:uuid;not null" json:"letter_id"`
|
||||||
|
ActionType string `gorm:"not null" json:"action_type"`
|
||||||
|
ActorUserID *uuid.UUID `json:"actor_user_id,omitempty"`
|
||||||
|
ActorDepartmentID *uuid.UUID `json:"actor_department_id,omitempty"`
|
||||||
|
TargetType *string `json:"target_type,omitempty"`
|
||||||
|
TargetID *uuid.UUID `json:"target_id,omitempty"`
|
||||||
|
FromStatus *string `json:"from_status,omitempty"`
|
||||||
|
ToStatus *string `json:"to_status,omitempty"`
|
||||||
|
Context JSONB `gorm:"type:jsonb" json:"context,omitempty"`
|
||||||
|
OccurredAt time.Time `gorm:"autoCreateTime" json:"occurred_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LetterIncomingActivityLog) TableName() string { return "letter_incoming_activity_logs" }
|
||||||
17
internal/entities/priority.go
Normal file
17
internal/entities/priority.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Priority struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
Name string `gorm:"not null;size:255" json:"name"`
|
||||||
|
Level int `gorm:"not null" json:"level"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Priority) TableName() string { return "priorities" }
|
||||||
100
internal/handler/disposition_route_handler.go
Normal file
100
internal/handler/disposition_route_handler.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"eslogad-be/internal/contract"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DispositionRouteService interface {
|
||||||
|
Create(ctx context.Context, req *contract.CreateDispositionRouteRequest) (*contract.DispositionRouteResponse, error)
|
||||||
|
Update(ctx context.Context, id uuid.UUID, req *contract.UpdateDispositionRouteRequest) (*contract.DispositionRouteResponse, error)
|
||||||
|
Get(ctx context.Context, id uuid.UUID) (*contract.DispositionRouteResponse, error)
|
||||||
|
ListByFromDept(ctx context.Context, from uuid.UUID) (*contract.ListDispositionRoutesResponse, error)
|
||||||
|
SetActive(ctx context.Context, id uuid.UUID, active bool) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DispositionRouteHandler struct{ svc DispositionRouteService }
|
||||||
|
|
||||||
|
func NewDispositionRouteHandler(svc DispositionRouteService) *DispositionRouteHandler {
|
||||||
|
return &DispositionRouteHandler{svc: svc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DispositionRouteHandler) Create(c *gin.Context) {
|
||||||
|
var req contract.CreateDispositionRouteRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.Create(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(201, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DispositionRouteHandler) Update(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req contract.UpdateDispositionRouteRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.Update(c.Request.Context(), id, &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DispositionRouteHandler) Get(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.Get(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DispositionRouteHandler) ListByFromDept(c *gin.Context) {
|
||||||
|
fromID, err := uuid.Parse(c.Param("from_department_id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid from_department_id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.ListByFromDept(c.Request.Context(), fromID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DispositionRouteHandler) SetActive(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toggle := c.Query("active")
|
||||||
|
active := toggle != "false"
|
||||||
|
if err := h.svc.SetActive(c.Request.Context(), id, active); err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, &contract.SuccessResponse{Message: "updated"})
|
||||||
|
}
|
||||||
183
internal/handler/letter_handler.go
Normal file
183
internal/handler/letter_handler.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"eslogad-be/internal/contract"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LetterService interface {
|
||||||
|
CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error)
|
||||||
|
GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error)
|
||||||
|
ListIncomingLetters(ctx context.Context, req *contract.ListIncomingLettersRequest) (*contract.ListIncomingLettersResponse, error)
|
||||||
|
UpdateIncomingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateIncomingLetterRequest) (*contract.IncomingLetterResponse, error)
|
||||||
|
SoftDeleteIncomingLetter(ctx context.Context, id uuid.UUID) error
|
||||||
|
|
||||||
|
CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error)
|
||||||
|
ListDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListDispositionsResponse, error)
|
||||||
|
|
||||||
|
CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error)
|
||||||
|
UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LetterHandler struct{ svc LetterService }
|
||||||
|
|
||||||
|
func NewLetterHandler(svc LetterService) *LetterHandler { return &LetterHandler{svc: svc} }
|
||||||
|
|
||||||
|
func (h *LetterHandler) CreateIncomingLetter(c *gin.Context) {
|
||||||
|
var req contract.CreateIncomingLetterRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.CreateIncomingLetter(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LetterHandler) GetIncomingLetter(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.GetIncomingLetterByID(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LetterHandler) ListIncomingLetters(c *gin.Context) {
|
||||||
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||||
|
status := c.Query("status")
|
||||||
|
query := c.Query("q")
|
||||||
|
var statusPtr *string
|
||||||
|
var queryPtr *string
|
||||||
|
if status != "" {
|
||||||
|
statusPtr = &status
|
||||||
|
}
|
||||||
|
if query != "" {
|
||||||
|
queryPtr = &query
|
||||||
|
}
|
||||||
|
req := &contract.ListIncomingLettersRequest{Page: page, Limit: limit, Status: statusPtr, Query: queryPtr}
|
||||||
|
resp, err := h.svc.ListIncomingLetters(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LetterHandler) UpdateIncomingLetter(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req contract.UpdateIncomingLetterRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.UpdateIncomingLetter(c.Request.Context(), id, &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LetterHandler) DeleteIncomingLetter(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.svc.SoftDeleteIncomingLetter(c.Request.Context(), id); err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, &contract.SuccessResponse{Message: "deleted"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LetterHandler) CreateDispositions(c *gin.Context) {
|
||||||
|
var req contract.CreateLetterDispositionRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.CreateDispositions(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(201, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LetterHandler) ListDispositionsByLetter(c *gin.Context) {
|
||||||
|
letterID, err := uuid.Parse(c.Param("letter_id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid letter_id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.ListDispositionsByLetter(c.Request.Context(), letterID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LetterHandler) CreateDiscussion(c *gin.Context) {
|
||||||
|
letterID, err := uuid.Parse(c.Param("letter_id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid letter_id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req contract.CreateLetterDiscussionRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.CreateDiscussion(c.Request.Context(), letterID, &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(201, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LetterHandler) UpdateDiscussion(c *gin.Context) {
|
||||||
|
letterID, err := uuid.Parse(c.Param("letter_id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid letter_id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
discussionID, err := uuid.Parse(c.Param("discussion_id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid discussion_id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req contract.UpdateLetterDiscussionRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.UpdateDiscussion(c.Request.Context(), letterID, discussionID, &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
252
internal/handler/master_handler.go
Normal file
252
internal/handler/master_handler.go
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"eslogad-be/internal/contract"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MasterService interface {
|
||||||
|
CreateLabel(ctx context.Context, req *contract.CreateLabelRequest) (*contract.LabelResponse, error)
|
||||||
|
UpdateLabel(ctx context.Context, id uuid.UUID, req *contract.UpdateLabelRequest) (*contract.LabelResponse, error)
|
||||||
|
DeleteLabel(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListLabels(ctx context.Context) (*contract.ListLabelsResponse, error)
|
||||||
|
|
||||||
|
CreatePriority(ctx context.Context, req *contract.CreatePriorityRequest) (*contract.PriorityResponse, error)
|
||||||
|
UpdatePriority(ctx context.Context, id uuid.UUID, req *contract.UpdatePriorityRequest) (*contract.PriorityResponse, error)
|
||||||
|
DeletePriority(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListPriorities(ctx context.Context) (*contract.ListPrioritiesResponse, error)
|
||||||
|
|
||||||
|
CreateInstitution(ctx context.Context, req *contract.CreateInstitutionRequest) (*contract.InstitutionResponse, error)
|
||||||
|
UpdateInstitution(ctx context.Context, id uuid.UUID, req *contract.UpdateInstitutionRequest) (*contract.InstitutionResponse, error)
|
||||||
|
DeleteInstitution(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListInstitutions(ctx context.Context) (*contract.ListInstitutionsResponse, error)
|
||||||
|
|
||||||
|
CreateDispositionAction(ctx context.Context, req *contract.CreateDispositionActionRequest) (*contract.DispositionActionResponse, error)
|
||||||
|
UpdateDispositionAction(ctx context.Context, id uuid.UUID, req *contract.UpdateDispositionActionRequest) (*contract.DispositionActionResponse, error)
|
||||||
|
DeleteDispositionAction(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListDispositionActions(ctx context.Context) (*contract.ListDispositionActionsResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MasterHandler struct{ svc MasterService }
|
||||||
|
|
||||||
|
func NewMasterHandler(svc MasterService) *MasterHandler { return &MasterHandler{svc: svc} }
|
||||||
|
|
||||||
|
func (h *MasterHandler) CreateLabel(c *gin.Context) {
|
||||||
|
var req contract.CreateLabelRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.CreateLabel(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MasterHandler) UpdateLabel(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req contract.UpdateLabelRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.UpdateLabel(c.Request.Context(), id, &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
func (h *MasterHandler) DeleteLabel(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.svc.DeleteLabel(c.Request.Context(), id); err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, &contract.SuccessResponse{Message: "deleted"})
|
||||||
|
}
|
||||||
|
func (h *MasterHandler) ListLabels(c *gin.Context) {
|
||||||
|
resp, err := h.svc.ListLabels(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priorities
|
||||||
|
func (h *MasterHandler) CreatePriority(c *gin.Context) {
|
||||||
|
var req contract.CreatePriorityRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.CreatePriority(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(201, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
func (h *MasterHandler) UpdatePriority(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req contract.UpdatePriorityRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.UpdatePriority(c.Request.Context(), id, &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
func (h *MasterHandler) DeletePriority(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.svc.DeletePriority(c.Request.Context(), id); err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, &contract.SuccessResponse{Message: "deleted"})
|
||||||
|
}
|
||||||
|
func (h *MasterHandler) ListPriorities(c *gin.Context) {
|
||||||
|
resp, err := h.svc.ListPriorities(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Institutions
|
||||||
|
func (h *MasterHandler) CreateInstitution(c *gin.Context) {
|
||||||
|
var req contract.CreateInstitutionRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.CreateInstitution(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(201, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MasterHandler) UpdateInstitution(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req contract.UpdateInstitutionRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.UpdateInstitution(c.Request.Context(), id, &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MasterHandler) DeleteInstitution(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.svc.DeleteInstitution(c.Request.Context(), id); err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, &contract.SuccessResponse{Message: "deleted"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MasterHandler) ListInstitutions(c *gin.Context) {
|
||||||
|
resp, err := h.svc.ListInstitutions(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disposition Actions
|
||||||
|
func (h *MasterHandler) CreateDispositionAction(c *gin.Context) {
|
||||||
|
var req contract.CreateDispositionActionRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.CreateDispositionAction(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(201, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
func (h *MasterHandler) UpdateDispositionAction(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req contract.UpdateDispositionActionRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.svc.UpdateDispositionAction(c.Request.Context(), id, &req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
|
func (h *MasterHandler) DeleteDispositionAction(c *gin.Context) {
|
||||||
|
id, err := uuid.Parse(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, &contract.ErrorResponse{Error: "invalid id", Code: 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.svc.DeleteDispositionAction(c.Request.Context(), id); err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, &contract.SuccessResponse{Message: "deleted"})
|
||||||
|
}
|
||||||
|
func (h *MasterHandler) ListDispositionActions(c *gin.Context) {
|
||||||
|
resp, err := h.svc.ListDispositionActions(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, contract.BuildSuccessResponse(resp))
|
||||||
|
}
|
||||||
37
internal/processor/activity_log_processor.go
Normal file
37
internal/processor/activity_log_processor.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"eslogad-be/internal/entities"
|
||||||
|
"eslogad-be/internal/repository"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActivityLogProcessorImpl struct {
|
||||||
|
repo *repository.LetterIncomingActivityLogRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActivityLogProcessor(repo *repository.LetterIncomingActivityLogRepository) *ActivityLogProcessorImpl {
|
||||||
|
return &ActivityLogProcessorImpl{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ActivityLogProcessorImpl) Log(ctx context.Context, letterID uuid.UUID, actionType string, actorUserID *uuid.UUID, actorDepartmentID *uuid.UUID, targetType *string, targetID *uuid.UUID, fromStatus *string, toStatus *string, contextData map[string]interface{}) error {
|
||||||
|
ctxJSON := entities.JSONB{}
|
||||||
|
for k, v := range contextData {
|
||||||
|
ctxJSON[k] = v
|
||||||
|
}
|
||||||
|
entry := &entities.LetterIncomingActivityLog{
|
||||||
|
LetterID: letterID,
|
||||||
|
ActionType: actionType,
|
||||||
|
ActorUserID: actorUserID,
|
||||||
|
ActorDepartmentID: actorDepartmentID,
|
||||||
|
TargetType: targetType,
|
||||||
|
TargetID: targetID,
|
||||||
|
FromStatus: fromStatus,
|
||||||
|
ToStatus: toStatus,
|
||||||
|
Context: ctxJSON,
|
||||||
|
}
|
||||||
|
return p.repo.Create(ctx, entry)
|
||||||
|
}
|
||||||
319
internal/processor/letter_processor.go
Normal file
319
internal/processor/letter_processor.go
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"eslogad-be/internal/appcontext"
|
||||||
|
"eslogad-be/internal/contract"
|
||||||
|
"eslogad-be/internal/entities"
|
||||||
|
"eslogad-be/internal/repository"
|
||||||
|
"eslogad-be/internal/transformer"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LetterProcessorImpl struct {
|
||||||
|
letterRepo *repository.LetterIncomingRepository
|
||||||
|
attachRepo *repository.LetterIncomingAttachmentRepository
|
||||||
|
txManager *repository.TxManager
|
||||||
|
activity *ActivityLogProcessorImpl
|
||||||
|
// new repos for dispositions
|
||||||
|
dispositionRepo *repository.LetterDispositionRepository
|
||||||
|
dispositionActionSelRepo *repository.LetterDispositionActionSelectionRepository
|
||||||
|
dispositionNoteRepo *repository.DispositionNoteRepository
|
||||||
|
// discussion repo
|
||||||
|
discussionRepo *repository.LetterDiscussionRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLetterProcessor(letterRepo *repository.LetterIncomingRepository, attachRepo *repository.LetterIncomingAttachmentRepository, txManager *repository.TxManager, activity *ActivityLogProcessorImpl, dispRepo *repository.LetterDispositionRepository, dispSelRepo *repository.LetterDispositionActionSelectionRepository, noteRepo *repository.DispositionNoteRepository, discussionRepo *repository.LetterDiscussionRepository) *LetterProcessorImpl {
|
||||||
|
return &LetterProcessorImpl{letterRepo: letterRepo, attachRepo: attachRepo, txManager: txManager, activity: activity, dispositionRepo: dispRepo, dispositionActionSelRepo: dispSelRepo, dispositionNoteRepo: noteRepo, discussionRepo: discussionRepo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LetterProcessorImpl) CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) {
|
||||||
|
var result *contract.IncomingLetterResponse
|
||||||
|
err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||||
|
userID := appcontext.FromGinContext(txCtx).UserID
|
||||||
|
entity := &entities.LetterIncoming{
|
||||||
|
ReferenceNumber: req.ReferenceNumber,
|
||||||
|
Subject: req.Subject,
|
||||||
|
Description: req.Description,
|
||||||
|
PriorityID: req.PriorityID,
|
||||||
|
SenderInstitutionID: req.SenderInstitutionID,
|
||||||
|
ReceivedDate: req.ReceivedDate,
|
||||||
|
DueDate: req.DueDate,
|
||||||
|
Status: entities.LetterIncomingStatusNew,
|
||||||
|
CreatedBy: userID,
|
||||||
|
}
|
||||||
|
if err := p.letterRepo.Create(txCtx, entity); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.activity != nil {
|
||||||
|
action := "letter.created"
|
||||||
|
if err := p.activity.Log(txCtx, entity.ID, action, &userID, nil, nil, nil, nil, nil, map[string]interface{}{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attachments := make([]entities.LetterIncomingAttachment, 0, len(req.Attachments))
|
||||||
|
for _, a := range req.Attachments {
|
||||||
|
attachments = append(attachments, entities.LetterIncomingAttachment{
|
||||||
|
LetterID: entity.ID,
|
||||||
|
FileURL: a.FileURL,
|
||||||
|
FileName: a.FileName,
|
||||||
|
FileType: a.FileType,
|
||||||
|
UploadedBy: &userID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(attachments) > 0 {
|
||||||
|
if err := p.attachRepo.CreateBulk(txCtx, attachments); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if p.activity != nil {
|
||||||
|
action := "attachment.uploaded"
|
||||||
|
for _, a := range attachments {
|
||||||
|
ctxMap := map[string]interface{}{"file_name": a.FileName, "file_type": a.FileType}
|
||||||
|
if err := p.activity.Log(txCtx, entity.ID, action, &userID, nil, nil, nil, nil, nil, ctxMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
savedAttachments, _ := p.attachRepo.ListByLetter(txCtx, entity.ID)
|
||||||
|
result = transformer.LetterEntityToContract(entity, savedAttachments)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LetterProcessorImpl) GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error) {
|
||||||
|
entity, err := p.letterRepo.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
atts, _ := p.attachRepo.ListByLetter(ctx, id)
|
||||||
|
return transformer.LetterEntityToContract(entity, atts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LetterProcessorImpl) ListIncomingLetters(ctx context.Context, req *contract.ListIncomingLettersRequest) (*contract.ListIncomingLettersResponse, error) {
|
||||||
|
page, limit := req.Page, req.Limit
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
filter := repository.ListIncomingLettersFilter{Status: req.Status, Query: req.Query}
|
||||||
|
list, total, err := p.letterRepo.List(ctx, filter, limit, (page-1)*limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
respList := make([]contract.IncomingLetterResponse, 0, len(list))
|
||||||
|
for _, e := range list {
|
||||||
|
atts, _ := p.attachRepo.ListByLetter(ctx, e.ID)
|
||||||
|
resp := transformer.LetterEntityToContract(&e, atts)
|
||||||
|
respList = append(respList, *resp)
|
||||||
|
}
|
||||||
|
return &contract.ListIncomingLettersResponse{Letters: respList, Pagination: transformer.CreatePaginationResponse(int(total), page, limit)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LetterProcessorImpl) UpdateIncomingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) {
|
||||||
|
var out *contract.IncomingLetterResponse
|
||||||
|
err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||||
|
entity, err := p.letterRepo.Get(txCtx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fromStatus := string(entity.Status)
|
||||||
|
if req.ReferenceNumber != nil {
|
||||||
|
entity.ReferenceNumber = req.ReferenceNumber
|
||||||
|
}
|
||||||
|
if req.Subject != nil {
|
||||||
|
entity.Subject = *req.Subject
|
||||||
|
}
|
||||||
|
if req.Description != nil {
|
||||||
|
entity.Description = req.Description
|
||||||
|
}
|
||||||
|
if req.PriorityID != nil {
|
||||||
|
entity.PriorityID = req.PriorityID
|
||||||
|
}
|
||||||
|
if req.SenderInstitutionID != nil {
|
||||||
|
entity.SenderInstitutionID = req.SenderInstitutionID
|
||||||
|
}
|
||||||
|
if req.ReceivedDate != nil {
|
||||||
|
entity.ReceivedDate = *req.ReceivedDate
|
||||||
|
}
|
||||||
|
if req.DueDate != nil {
|
||||||
|
entity.DueDate = req.DueDate
|
||||||
|
}
|
||||||
|
if req.Status != nil {
|
||||||
|
entity.Status = entities.LetterIncomingStatus(*req.Status)
|
||||||
|
}
|
||||||
|
if err := p.letterRepo.Update(txCtx, entity); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
toStatus := string(entity.Status)
|
||||||
|
if p.activity != nil && fromStatus != toStatus {
|
||||||
|
userID := appcontext.FromGinContext(txCtx).UserID
|
||||||
|
action := "status.changed"
|
||||||
|
if err := p.activity.Log(txCtx, id, action, &userID, nil, nil, nil, &fromStatus, &toStatus, map[string]interface{}{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atts, _ := p.attachRepo.ListByLetter(txCtx, id)
|
||||||
|
out = transformer.LetterEntityToContract(entity, atts)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LetterProcessorImpl) SoftDeleteIncomingLetter(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||||
|
if err := p.letterRepo.SoftDelete(txCtx, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if p.activity != nil {
|
||||||
|
userID := appcontext.FromGinContext(txCtx).UserID
|
||||||
|
action := "letter.deleted"
|
||||||
|
if err := p.activity.Log(txCtx, id, action, &userID, nil, nil, nil, nil, nil, map[string]interface{}{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LetterProcessorImpl) CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error) {
|
||||||
|
var out *contract.ListDispositionsResponse
|
||||||
|
err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||||
|
userID := appcontext.FromGinContext(txCtx).UserID
|
||||||
|
created := make([]entities.LetterDisposition, 0, len(req.ToDepartmentIDs))
|
||||||
|
for _, toDept := range req.ToDepartmentIDs {
|
||||||
|
disp := entities.LetterDisposition{
|
||||||
|
LetterID: req.LetterID,
|
||||||
|
FromDepartmentID: nil,
|
||||||
|
ToDepartmentID: &toDept,
|
||||||
|
Notes: req.Notes,
|
||||||
|
Status: entities.DispositionPending,
|
||||||
|
CreatedBy: userID,
|
||||||
|
}
|
||||||
|
if err := p.dispositionRepo.Create(txCtx, &disp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
created = append(created, disp)
|
||||||
|
|
||||||
|
if len(req.SelectedActions) > 0 {
|
||||||
|
selections := make([]entities.LetterDispositionActionSelection, 0, len(req.SelectedActions))
|
||||||
|
for _, sel := range req.SelectedActions {
|
||||||
|
selections = append(selections, entities.LetterDispositionActionSelection{
|
||||||
|
DispositionID: disp.ID,
|
||||||
|
ActionID: sel.ActionID,
|
||||||
|
Note: sel.Note,
|
||||||
|
CreatedBy: userID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := p.dispositionActionSelRepo.CreateBulk(txCtx, selections); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.activity != nil {
|
||||||
|
action := "disposition.created"
|
||||||
|
for _, d := range created {
|
||||||
|
ctxMap := map[string]interface{}{"to_department_id": d.ToDepartmentID}
|
||||||
|
if err := p.activity.Log(txCtx, req.LetterID, action, &userID, nil, nil, &d.ID, nil, nil, ctxMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out = &contract.ListDispositionsResponse{Dispositions: transformer.DispositionsToContract(created)}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LetterProcessorImpl) ListDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListDispositionsResponse, error) {
|
||||||
|
list, err := p.dispositionRepo.ListByLetter(ctx, letterID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &contract.ListDispositionsResponse{Dispositions: transformer.DispositionsToContract(list)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LetterProcessorImpl) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) {
|
||||||
|
var out *contract.LetterDiscussionResponse
|
||||||
|
err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||||
|
userID := appcontext.FromGinContext(txCtx).UserID
|
||||||
|
mentions := entities.JSONB(nil)
|
||||||
|
if req.Mentions != nil {
|
||||||
|
mentions = entities.JSONB(req.Mentions)
|
||||||
|
}
|
||||||
|
disc := &entities.LetterDiscussion{LetterID: letterID, ParentID: req.ParentID, UserID: userID, Message: req.Message, Mentions: mentions}
|
||||||
|
if err := p.discussionRepo.Create(txCtx, disc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if p.activity != nil {
|
||||||
|
action := "discussion.created"
|
||||||
|
tgt := "discussion"
|
||||||
|
ctxMap := map[string]interface{}{"message": req.Message, "parent_id": req.ParentID}
|
||||||
|
if err := p.activity.Log(txCtx, letterID, action, &userID, nil, &tgt, &disc.ID, nil, nil, ctxMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = transformer.DiscussionEntityToContract(disc)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LetterProcessorImpl) UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) {
|
||||||
|
var out *contract.LetterDiscussionResponse
|
||||||
|
err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||||
|
disc, err := p.discussionRepo.Get(txCtx, discussionID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oldMessage := disc.Message
|
||||||
|
disc.Message = req.Message
|
||||||
|
if req.Mentions != nil {
|
||||||
|
disc.Mentions = entities.JSONB(req.Mentions)
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
disc.EditedAt = &now
|
||||||
|
if err := p.discussionRepo.Update(txCtx, disc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if p.activity != nil {
|
||||||
|
userID := appcontext.FromGinContext(txCtx).UserID
|
||||||
|
action := "discussion.updated"
|
||||||
|
tgt := "discussion"
|
||||||
|
ctxMap := map[string]interface{}{"old_message": oldMessage, "new_message": req.Message}
|
||||||
|
if err := p.activity.Log(txCtx, letterID, action, &userID, nil, &tgt, &disc.ID, nil, nil, ctxMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = transformer.DiscussionEntityToContract(disc)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
45
internal/repository/disposition_route_repository.go
Normal file
45
internal/repository/disposition_route_repository.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"eslogad-be/internal/entities"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DispositionRouteRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewDispositionRouteRepository(db *gorm.DB) *DispositionRouteRepository {
|
||||||
|
return &DispositionRouteRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DispositionRouteRepository) Create(ctx context.Context, e *entities.DispositionRoute) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Create(e).Error
|
||||||
|
}
|
||||||
|
func (r *DispositionRouteRepository) Update(ctx context.Context, e *entities.DispositionRoute) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Model(&entities.DispositionRoute{}).Where("id = ?", e.ID).Updates(e).Error
|
||||||
|
}
|
||||||
|
func (r *DispositionRouteRepository) Get(ctx context.Context, id uuid.UUID) (*entities.DispositionRoute, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
var e entities.DispositionRoute
|
||||||
|
if err := db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &e, nil
|
||||||
|
}
|
||||||
|
func (r *DispositionRouteRepository) ListByFromDept(ctx context.Context, fromDept uuid.UUID) ([]entities.DispositionRoute, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
var list []entities.DispositionRoute
|
||||||
|
if err := db.WithContext(ctx).Where("from_department_id = ?", fromDept).Order("to_department_id").Find(&list).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
func (r *DispositionRouteRepository) SetActive(ctx context.Context, id uuid.UUID, isActive bool) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Model(&entities.DispositionRoute{}).Where("id = ?", id).Update("is_active", isActive).Error
|
||||||
|
}
|
||||||
180
internal/repository/letter_repository.go
Normal file
180
internal/repository/letter_repository.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"eslogad-be/internal/entities"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LetterIncomingRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewLetterIncomingRepository(db *gorm.DB) *LetterIncomingRepository {
|
||||||
|
return &LetterIncomingRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LetterIncomingRepository) Create(ctx context.Context, e *entities.LetterIncoming) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Create(e).Error
|
||||||
|
}
|
||||||
|
func (r *LetterIncomingRepository) Get(ctx context.Context, id uuid.UUID) (*entities.LetterIncoming, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
var e entities.LetterIncoming
|
||||||
|
if err := db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LetterIncomingRepository) Update(ctx context.Context, e *entities.LetterIncoming) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Model(&entities.LetterIncoming{}).Where("id = ? AND deleted_at IS NULL", e.ID).Updates(e).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LetterIncomingRepository) SoftDelete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Exec("UPDATE letters_incoming SET deleted_at = CURRENT_TIMESTAMP WHERE id = ? AND deleted_at IS NULL", id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListIncomingLettersFilter struct {
|
||||||
|
Status *string
|
||||||
|
Query *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LetterIncomingRepository) List(ctx context.Context, filter ListIncomingLettersFilter, limit, offset int) ([]entities.LetterIncoming, int64, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
query := db.WithContext(ctx).Model(&entities.LetterIncoming{}).Where("deleted_at IS NULL")
|
||||||
|
if filter.Status != nil {
|
||||||
|
query = query.Where("status = ?", *filter.Status)
|
||||||
|
}
|
||||||
|
if filter.Query != nil {
|
||||||
|
q := "%" + *filter.Query + "%"
|
||||||
|
query = query.Where("subject ILIKE ? OR reference_number ILIKE ?", q, q)
|
||||||
|
}
|
||||||
|
var total int64
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
var list []entities.LetterIncoming
|
||||||
|
if err := query.Order("created_at DESC").Limit(limit).Offset(offset).Find(&list).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return list, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LetterIncomingAttachmentRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewLetterIncomingAttachmentRepository(db *gorm.DB) *LetterIncomingAttachmentRepository {
|
||||||
|
return &LetterIncomingAttachmentRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LetterIncomingAttachmentRepository) CreateBulk(ctx context.Context, list []entities.LetterIncomingAttachment) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Create(&list).Error
|
||||||
|
}
|
||||||
|
func (r *LetterIncomingAttachmentRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterIncomingAttachment, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
var list []entities.LetterIncomingAttachment
|
||||||
|
if err := db.WithContext(ctx).Where("letter_id = ?", letterID).Order("uploaded_at ASC").Find(&list).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LetterIncomingActivityLogRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewLetterIncomingActivityLogRepository(db *gorm.DB) *LetterIncomingActivityLogRepository {
|
||||||
|
return &LetterIncomingActivityLogRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LetterIncomingActivityLogRepository) Create(ctx context.Context, e *entities.LetterIncomingActivityLog) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Create(e).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LetterIncomingActivityLogRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterIncomingActivityLog, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
var list []entities.LetterIncomingActivityLog
|
||||||
|
if err := db.WithContext(ctx).Where("letter_id = ?", letterID).Order("occurred_at ASC").Find(&list).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LetterDispositionRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewLetterDispositionRepository(db *gorm.DB) *LetterDispositionRepository {
|
||||||
|
return &LetterDispositionRepository{db: db}
|
||||||
|
}
|
||||||
|
func (r *LetterDispositionRepository) Create(ctx context.Context, e *entities.LetterDisposition) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Create(e).Error
|
||||||
|
}
|
||||||
|
func (r *LetterDispositionRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterDisposition, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
var list []entities.LetterDisposition
|
||||||
|
if err := db.WithContext(ctx).Where("letter_id = ?", letterID).Order("created_at ASC").Find(&list).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DispositionNoteRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewDispositionNoteRepository(db *gorm.DB) *DispositionNoteRepository {
|
||||||
|
return &DispositionNoteRepository{db: db}
|
||||||
|
}
|
||||||
|
func (r *DispositionNoteRepository) Create(ctx context.Context, e *entities.DispositionNote) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Create(e).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
type LetterDispositionActionSelectionRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewLetterDispositionActionSelectionRepository(db *gorm.DB) *LetterDispositionActionSelectionRepository {
|
||||||
|
return &LetterDispositionActionSelectionRepository{db: db}
|
||||||
|
}
|
||||||
|
func (r *LetterDispositionActionSelectionRepository) CreateBulk(ctx context.Context, list []entities.LetterDispositionActionSelection) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Create(&list).Error
|
||||||
|
}
|
||||||
|
func (r *LetterDispositionActionSelectionRepository) ListByDisposition(ctx context.Context, dispositionID uuid.UUID) ([]entities.LetterDispositionActionSelection, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
var list []entities.LetterDispositionActionSelection
|
||||||
|
if err := db.WithContext(ctx).Where("disposition_id = ?", dispositionID).Order("created_at ASC").Find(&list).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LetterDiscussionRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewLetterDiscussionRepository(db *gorm.DB) *LetterDiscussionRepository {
|
||||||
|
return &LetterDiscussionRepository{db: db}
|
||||||
|
}
|
||||||
|
func (r *LetterDiscussionRepository) Create(ctx context.Context, e *entities.LetterDiscussion) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
return db.WithContext(ctx).Create(e).Error
|
||||||
|
}
|
||||||
|
func (r *LetterDiscussionRepository) Get(ctx context.Context, id uuid.UUID) (*entities.LetterDiscussion, error) {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
var e entities.LetterDiscussion
|
||||||
|
if err := db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &e, nil
|
||||||
|
}
|
||||||
|
func (r *LetterDiscussionRepository) Update(ctx context.Context, e *entities.LetterDiscussion) error {
|
||||||
|
db := DBFromContext(ctx, r.db)
|
||||||
|
// ensure edited_at is set when updating
|
||||||
|
if e.EditedAt == nil {
|
||||||
|
now := time.Now()
|
||||||
|
e.EditedAt = &now
|
||||||
|
}
|
||||||
|
return db.WithContext(ctx).Model(&entities.LetterDiscussion{}).
|
||||||
|
Where("id = ?", e.ID).
|
||||||
|
Updates(map[string]interface{}{"message": e.Message, "mentions": e.Mentions, "edited_at": e.EditedAt}).Error
|
||||||
|
}
|
||||||
114
internal/repository/master_repository.go
Normal file
114
internal/repository/master_repository.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"eslogad-be/internal/entities"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LabelRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewLabelRepository(db *gorm.DB) *LabelRepository { return &LabelRepository{db: db} }
|
||||||
|
func (r *LabelRepository) Create(ctx context.Context, e *entities.Label) error {
|
||||||
|
return r.db.WithContext(ctx).Create(e).Error
|
||||||
|
}
|
||||||
|
func (r *LabelRepository) Update(ctx context.Context, e *entities.Label) error {
|
||||||
|
return r.db.WithContext(ctx).Model(&entities.Label{}).Where("id = ?", e.ID).Updates(e).Error
|
||||||
|
}
|
||||||
|
func (r *LabelRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.Label{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
func (r *LabelRepository) List(ctx context.Context) ([]entities.Label, error) {
|
||||||
|
var list []entities.Label
|
||||||
|
err := r.db.WithContext(ctx).Order("name ASC").Find(&list).Error
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
func (r *LabelRepository) Get(ctx context.Context, id uuid.UUID) (*entities.Label, error) {
|
||||||
|
var e entities.Label
|
||||||
|
if err := r.db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PriorityRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewPriorityRepository(db *gorm.DB) *PriorityRepository { return &PriorityRepository{db: db} }
|
||||||
|
func (r *PriorityRepository) Create(ctx context.Context, e *entities.Priority) error {
|
||||||
|
return r.db.WithContext(ctx).Create(e).Error
|
||||||
|
}
|
||||||
|
func (r *PriorityRepository) Update(ctx context.Context, e *entities.Priority) error {
|
||||||
|
return r.db.WithContext(ctx).Model(&entities.Priority{}).Where("id = ?", e.ID).Updates(e).Error
|
||||||
|
}
|
||||||
|
func (r *PriorityRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.Priority{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
func (r *PriorityRepository) List(ctx context.Context) ([]entities.Priority, error) {
|
||||||
|
var list []entities.Priority
|
||||||
|
err := r.db.WithContext(ctx).Order("level ASC").Find(&list).Error
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
func (r *PriorityRepository) Get(ctx context.Context, id uuid.UUID) (*entities.Priority, error) {
|
||||||
|
var e entities.Priority
|
||||||
|
if err := r.db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstitutionRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewInstitutionRepository(db *gorm.DB) *InstitutionRepository {
|
||||||
|
return &InstitutionRepository{db: db}
|
||||||
|
}
|
||||||
|
func (r *InstitutionRepository) Create(ctx context.Context, e *entities.Institution) error {
|
||||||
|
return r.db.WithContext(ctx).Create(e).Error
|
||||||
|
}
|
||||||
|
func (r *InstitutionRepository) Update(ctx context.Context, e *entities.Institution) error {
|
||||||
|
return r.db.WithContext(ctx).Model(&entities.Institution{}).Where("id = ?", e.ID).Updates(e).Error
|
||||||
|
}
|
||||||
|
func (r *InstitutionRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.Institution{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
func (r *InstitutionRepository) List(ctx context.Context) ([]entities.Institution, error) {
|
||||||
|
var list []entities.Institution
|
||||||
|
err := r.db.WithContext(ctx).Order("name ASC").Find(&list).Error
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
func (r *InstitutionRepository) Get(ctx context.Context, id uuid.UUID) (*entities.Institution, error) {
|
||||||
|
var e entities.Institution
|
||||||
|
if err := r.db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DispositionActionRepository struct{ db *gorm.DB }
|
||||||
|
|
||||||
|
func NewDispositionActionRepository(db *gorm.DB) *DispositionActionRepository {
|
||||||
|
return &DispositionActionRepository{db: db}
|
||||||
|
}
|
||||||
|
func (r *DispositionActionRepository) Create(ctx context.Context, e *entities.DispositionAction) error {
|
||||||
|
return r.db.WithContext(ctx).Create(e).Error
|
||||||
|
}
|
||||||
|
func (r *DispositionActionRepository) Update(ctx context.Context, e *entities.DispositionAction) error {
|
||||||
|
return r.db.WithContext(ctx).Model(&entities.DispositionAction{}).Where("id = ?", e.ID).Updates(e).Error
|
||||||
|
}
|
||||||
|
func (r *DispositionActionRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.DispositionAction{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
func (r *DispositionActionRepository) List(ctx context.Context) ([]entities.DispositionAction, error) {
|
||||||
|
var list []entities.DispositionAction
|
||||||
|
err := r.db.WithContext(ctx).Order("sort_order NULLS LAST, label ASC").Find(&list).Error
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
func (r *DispositionActionRepository) Get(ctx context.Context, id uuid.UUID) (*entities.DispositionAction, error) {
|
||||||
|
var e entities.DispositionAction
|
||||||
|
if err := r.db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &e, nil
|
||||||
|
}
|
||||||
35
internal/repository/tx_manager.go
Normal file
35
internal/repository/tx_manager.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type txKeyType struct{}
|
||||||
|
|
||||||
|
var txKey = txKeyType{}
|
||||||
|
|
||||||
|
// DBFromContext returns the transactional *gorm.DB from context if present; otherwise returns base.
|
||||||
|
func DBFromContext(ctx context.Context, base *gorm.DB) *gorm.DB {
|
||||||
|
if v := ctx.Value(txKey); v != nil {
|
||||||
|
if tx, ok := v.(*gorm.DB); ok && tx != nil {
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
type TxManager struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTxManager(db *gorm.DB) *TxManager { return &TxManager{db: db} }
|
||||||
|
|
||||||
|
// WithTransaction runs fn inside a DB transaction, injecting the *gorm.DB tx into ctx.
|
||||||
|
func (m *TxManager) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
|
||||||
|
return m.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
ctxTx := context.WithValue(ctx, txKey, tx)
|
||||||
|
return fn(ctxTx)
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -30,3 +30,48 @@ type RBACHandler interface {
|
|||||||
DeleteRole(c *gin.Context)
|
DeleteRole(c *gin.Context)
|
||||||
ListRoles(c *gin.Context)
|
ListRoles(c *gin.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MasterHandler interface {
|
||||||
|
// labels
|
||||||
|
CreateLabel(c *gin.Context)
|
||||||
|
UpdateLabel(c *gin.Context)
|
||||||
|
DeleteLabel(c *gin.Context)
|
||||||
|
ListLabels(c *gin.Context)
|
||||||
|
// priorities
|
||||||
|
CreatePriority(c *gin.Context)
|
||||||
|
UpdatePriority(c *gin.Context)
|
||||||
|
DeletePriority(c *gin.Context)
|
||||||
|
ListPriorities(c *gin.Context)
|
||||||
|
// institutions
|
||||||
|
CreateInstitution(c *gin.Context)
|
||||||
|
UpdateInstitution(c *gin.Context)
|
||||||
|
DeleteInstitution(c *gin.Context)
|
||||||
|
ListInstitutions(c *gin.Context)
|
||||||
|
// disposition actions
|
||||||
|
CreateDispositionAction(c *gin.Context)
|
||||||
|
UpdateDispositionAction(c *gin.Context)
|
||||||
|
DeleteDispositionAction(c *gin.Context)
|
||||||
|
ListDispositionActions(c *gin.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LetterHandler interface {
|
||||||
|
CreateIncomingLetter(c *gin.Context)
|
||||||
|
GetIncomingLetter(c *gin.Context)
|
||||||
|
ListIncomingLetters(c *gin.Context)
|
||||||
|
UpdateIncomingLetter(c *gin.Context)
|
||||||
|
DeleteIncomingLetter(c *gin.Context)
|
||||||
|
|
||||||
|
CreateDispositions(c *gin.Context)
|
||||||
|
ListDispositionsByLetter(c *gin.Context)
|
||||||
|
|
||||||
|
CreateDiscussion(c *gin.Context)
|
||||||
|
UpdateDiscussion(c *gin.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DispositionRouteHandler interface {
|
||||||
|
Create(c *gin.Context)
|
||||||
|
Update(c *gin.Context)
|
||||||
|
Get(c *gin.Context)
|
||||||
|
ListByFromDept(c *gin.Context)
|
||||||
|
SetActive(c *gin.Context)
|
||||||
|
}
|
||||||
|
|||||||
@ -15,6 +15,9 @@ type Router struct {
|
|||||||
userHandler UserHandler
|
userHandler UserHandler
|
||||||
fileHandler FileHandler
|
fileHandler FileHandler
|
||||||
rbacHandler RBACHandler
|
rbacHandler RBACHandler
|
||||||
|
masterHandler MasterHandler
|
||||||
|
letterHandler LetterHandler
|
||||||
|
dispRouteHandler DispositionRouteHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(
|
func NewRouter(
|
||||||
@ -25,6 +28,9 @@ func NewRouter(
|
|||||||
userHandler UserHandler,
|
userHandler UserHandler,
|
||||||
fileHandler FileHandler,
|
fileHandler FileHandler,
|
||||||
rbacHandler RBACHandler,
|
rbacHandler RBACHandler,
|
||||||
|
masterHandler MasterHandler,
|
||||||
|
letterHandler LetterHandler,
|
||||||
|
dispRouteHandler DispositionRouteHandler,
|
||||||
) *Router {
|
) *Router {
|
||||||
return &Router{
|
return &Router{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
@ -34,6 +40,9 @@ func NewRouter(
|
|||||||
userHandler: userHandler,
|
userHandler: userHandler,
|
||||||
fileHandler: fileHandler,
|
fileHandler: fileHandler,
|
||||||
rbacHandler: rbacHandler,
|
rbacHandler: rbacHandler,
|
||||||
|
masterHandler: masterHandler,
|
||||||
|
letterHandler: letterHandler,
|
||||||
|
dispRouteHandler: dispRouteHandler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,10 +97,61 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
rbac.POST("/permissions", r.rbacHandler.CreatePermission)
|
rbac.POST("/permissions", r.rbacHandler.CreatePermission)
|
||||||
rbac.PUT("/permissions/:id", r.rbacHandler.UpdatePermission)
|
rbac.PUT("/permissions/:id", r.rbacHandler.UpdatePermission)
|
||||||
rbac.DELETE("/permissions/:id", r.rbacHandler.DeletePermission)
|
rbac.DELETE("/permissions/:id", r.rbacHandler.DeletePermission)
|
||||||
|
|
||||||
rbac.GET("/roles", r.rbacHandler.ListRoles)
|
rbac.GET("/roles", r.rbacHandler.ListRoles)
|
||||||
rbac.POST("/roles", r.rbacHandler.CreateRole)
|
rbac.POST("/roles", r.rbacHandler.CreateRole)
|
||||||
rbac.PUT("/roles/:id", r.rbacHandler.UpdateRole)
|
rbac.PUT("/roles/:id", r.rbacHandler.UpdateRole)
|
||||||
rbac.DELETE("/roles/:id", r.rbacHandler.DeleteRole)
|
rbac.DELETE("/roles/:id", r.rbacHandler.DeleteRole)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
master := v1.Group("/master")
|
||||||
|
master.Use(r.authMiddleware.RequireAuth())
|
||||||
|
{
|
||||||
|
master.GET("/labels", r.masterHandler.ListLabels)
|
||||||
|
master.POST("/labels", r.masterHandler.CreateLabel)
|
||||||
|
master.PUT("/labels/:id", r.masterHandler.UpdateLabel)
|
||||||
|
master.DELETE("/labels/:id", r.masterHandler.DeleteLabel)
|
||||||
|
|
||||||
|
master.GET("/priorities", r.masterHandler.ListPriorities)
|
||||||
|
master.POST("/priorities", r.masterHandler.CreatePriority)
|
||||||
|
master.PUT("/priorities/:id", r.masterHandler.UpdatePriority)
|
||||||
|
master.DELETE("/priorities/:id", r.masterHandler.DeletePriority)
|
||||||
|
|
||||||
|
master.GET("/institutions", r.masterHandler.ListInstitutions)
|
||||||
|
master.POST("/institutions", r.masterHandler.CreateInstitution)
|
||||||
|
master.PUT("/institutions/:id", r.masterHandler.UpdateInstitution)
|
||||||
|
master.DELETE("/institutions/:id", r.masterHandler.DeleteInstitution)
|
||||||
|
|
||||||
|
master.GET("/disposition-actions", r.masterHandler.ListDispositionActions)
|
||||||
|
master.POST("/disposition-actions", r.masterHandler.CreateDispositionAction)
|
||||||
|
master.PUT("/disposition-actions/:id", r.masterHandler.UpdateDispositionAction)
|
||||||
|
master.DELETE("/disposition-actions/:id", r.masterHandler.DeleteDispositionAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
lettersch := v1.Group("/letters")
|
||||||
|
lettersch.Use(r.authMiddleware.RequireAuth())
|
||||||
|
{
|
||||||
|
lettersch.POST("/incoming", r.letterHandler.CreateIncomingLetter)
|
||||||
|
lettersch.GET("/incoming/:id", r.letterHandler.GetIncomingLetter)
|
||||||
|
lettersch.GET("/incoming", r.letterHandler.ListIncomingLetters)
|
||||||
|
lettersch.PUT("/incoming/:id", r.letterHandler.UpdateIncomingLetter)
|
||||||
|
lettersch.DELETE("/incoming/:id", r.letterHandler.DeleteIncomingLetter)
|
||||||
|
|
||||||
|
lettersch.POST("/dispositions/:letter_id", r.letterHandler.CreateDispositions)
|
||||||
|
lettersch.GET("/dispositions/:letter_id", r.letterHandler.ListDispositionsByLetter)
|
||||||
|
|
||||||
|
lettersch.POST("/discussions/:letter_id", r.letterHandler.CreateDiscussion)
|
||||||
|
lettersch.PUT("/discussions/:letter_id/:discussion_id", r.letterHandler.UpdateDiscussion)
|
||||||
|
}
|
||||||
|
|
||||||
|
droutes := v1.Group("/disposition-routes")
|
||||||
|
droutes.Use(r.authMiddleware.RequireAuth())
|
||||||
|
{
|
||||||
|
droutes.POST("", r.dispRouteHandler.Create)
|
||||||
|
droutes.GET(":id", r.dispRouteHandler.Get)
|
||||||
|
droutes.PUT(":id", r.dispRouteHandler.Update)
|
||||||
|
droutes.GET("from/:from_department_id", r.dispRouteHandler.ListByFromDept)
|
||||||
|
droutes.PUT(":id/active", r.dispRouteHandler.SetActive)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
70
internal/service/disposition_route_service.go
Normal file
70
internal/service/disposition_route_service.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"eslogad-be/internal/contract"
|
||||||
|
"eslogad-be/internal/entities"
|
||||||
|
"eslogad-be/internal/repository"
|
||||||
|
"eslogad-be/internal/transformer"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DispositionRouteServiceImpl struct {
|
||||||
|
repo *repository.DispositionRouteRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDispositionRouteService(repo *repository.DispositionRouteRepository) *DispositionRouteServiceImpl {
|
||||||
|
return &DispositionRouteServiceImpl{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DispositionRouteServiceImpl) Create(ctx context.Context, req *contract.CreateDispositionRouteRequest) (*contract.DispositionRouteResponse, error) {
|
||||||
|
entity := &entities.DispositionRoute{FromDepartmentID: req.FromDepartmentID, ToDepartmentID: req.ToDepartmentID}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
entity.IsActive = *req.IsActive
|
||||||
|
}
|
||||||
|
if req.AllowedActions != nil {
|
||||||
|
entity.AllowedActions = entities.JSONB(*req.AllowedActions)
|
||||||
|
}
|
||||||
|
if err := s.repo.Create(ctx, entity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := transformer.DispositionRoutesToContract([]entities.DispositionRoute{*entity})[0]
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
func (s *DispositionRouteServiceImpl) Update(ctx context.Context, id uuid.UUID, req *contract.UpdateDispositionRouteRequest) (*contract.DispositionRouteResponse, error) {
|
||||||
|
entity, err := s.repo.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
entity.IsActive = *req.IsActive
|
||||||
|
}
|
||||||
|
if req.AllowedActions != nil {
|
||||||
|
entity.AllowedActions = entities.JSONB(*req.AllowedActions)
|
||||||
|
}
|
||||||
|
if err := s.repo.Update(ctx, entity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := transformer.DispositionRoutesToContract([]entities.DispositionRoute{*entity})[0]
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
func (s *DispositionRouteServiceImpl) Get(ctx context.Context, id uuid.UUID) (*contract.DispositionRouteResponse, error) {
|
||||||
|
entity, err := s.repo.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := transformer.DispositionRoutesToContract([]entities.DispositionRoute{*entity})[0]
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
func (s *DispositionRouteServiceImpl) ListByFromDept(ctx context.Context, from uuid.UUID) (*contract.ListDispositionRoutesResponse, error) {
|
||||||
|
list, err := s.repo.ListByFromDept(ctx, from)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &contract.ListDispositionRoutesResponse{Routes: transformer.DispositionRoutesToContract(list)}, nil
|
||||||
|
}
|
||||||
|
func (s *DispositionRouteServiceImpl) SetActive(ctx context.Context, id uuid.UUID, active bool) error {
|
||||||
|
return s.repo.SetActive(ctx, id, active)
|
||||||
|
}
|
||||||
63
internal/service/letter_service.go
Normal file
63
internal/service/letter_service.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"eslogad-be/internal/contract"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LetterProcessor interface {
|
||||||
|
CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error)
|
||||||
|
GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error)
|
||||||
|
ListIncomingLetters(ctx context.Context, req *contract.ListIncomingLettersRequest) (*contract.ListIncomingLettersResponse, error)
|
||||||
|
UpdateIncomingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateIncomingLetterRequest) (*contract.IncomingLetterResponse, error)
|
||||||
|
SoftDeleteIncomingLetter(ctx context.Context, id uuid.UUID) error
|
||||||
|
|
||||||
|
CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error)
|
||||||
|
ListDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListDispositionsResponse, error)
|
||||||
|
|
||||||
|
CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error)
|
||||||
|
UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LetterServiceImpl struct {
|
||||||
|
processor LetterProcessor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLetterService(processor LetterProcessor) *LetterServiceImpl {
|
||||||
|
return &LetterServiceImpl{processor: processor}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LetterServiceImpl) CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) {
|
||||||
|
return s.processor.CreateIncomingLetter(ctx, req)
|
||||||
|
}
|
||||||
|
func (s *LetterServiceImpl) GetIncomingLetterByID(ctx context.Context, id uuid.UUID) (*contract.IncomingLetterResponse, error) {
|
||||||
|
return s.processor.GetIncomingLetterByID(ctx, id)
|
||||||
|
}
|
||||||
|
func (s *LetterServiceImpl) ListIncomingLetters(ctx context.Context, req *contract.ListIncomingLettersRequest) (*contract.ListIncomingLettersResponse, error) {
|
||||||
|
return s.processor.ListIncomingLetters(ctx, req)
|
||||||
|
}
|
||||||
|
func (s *LetterServiceImpl) UpdateIncomingLetter(ctx context.Context, id uuid.UUID, req *contract.UpdateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) {
|
||||||
|
return s.processor.UpdateIncomingLetter(ctx, id, req)
|
||||||
|
}
|
||||||
|
func (s *LetterServiceImpl) SoftDeleteIncomingLetter(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return s.processor.SoftDeleteIncomingLetter(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LetterServiceImpl) CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error) {
|
||||||
|
return s.processor.CreateDispositions(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LetterServiceImpl) ListDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListDispositionsResponse, error) {
|
||||||
|
return s.processor.ListDispositionsByLetter(ctx, letterID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LetterServiceImpl) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) {
|
||||||
|
return s.processor.CreateDiscussion(ctx, letterID, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LetterServiceImpl) UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) {
|
||||||
|
return s.processor.UpdateDiscussion(ctx, letterID, discussionID, req)
|
||||||
|
}
|
||||||
214
internal/service/master_service.go
Normal file
214
internal/service/master_service.go
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"eslogad-be/internal/contract"
|
||||||
|
"eslogad-be/internal/entities"
|
||||||
|
"eslogad-be/internal/repository"
|
||||||
|
"eslogad-be/internal/transformer"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MasterServiceImpl struct {
|
||||||
|
labelRepo *repository.LabelRepository
|
||||||
|
priorityRepo *repository.PriorityRepository
|
||||||
|
institutionRepo *repository.InstitutionRepository
|
||||||
|
dispRepo *repository.DispositionActionRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMasterService(label *repository.LabelRepository, priority *repository.PriorityRepository, institution *repository.InstitutionRepository, disp *repository.DispositionActionRepository) *MasterServiceImpl {
|
||||||
|
return &MasterServiceImpl{labelRepo: label, priorityRepo: priority, institutionRepo: institution, dispRepo: disp}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
func (s *MasterServiceImpl) CreateLabel(ctx context.Context, req *contract.CreateLabelRequest) (*contract.LabelResponse, error) {
|
||||||
|
entity := &entities.Label{Name: req.Name, Color: req.Color}
|
||||||
|
if err := s.labelRepo.Create(ctx, entity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := transformer.LabelsToContract([]entities.Label{*entity})[0]
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) UpdateLabel(ctx context.Context, id uuid.UUID, req *contract.UpdateLabelRequest) (*contract.LabelResponse, error) {
|
||||||
|
entity := &entities.Label{ID: id}
|
||||||
|
if req.Name != nil {
|
||||||
|
entity.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Color != nil {
|
||||||
|
entity.Color = req.Color
|
||||||
|
}
|
||||||
|
if err := s.labelRepo.Update(ctx, entity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e, err := s.labelRepo.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := transformer.LabelsToContract([]entities.Label{*e})[0]
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) DeleteLabel(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return s.labelRepo.Delete(ctx, id)
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) ListLabels(ctx context.Context) (*contract.ListLabelsResponse, error) {
|
||||||
|
list, err := s.labelRepo.List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &contract.ListLabelsResponse{Labels: transformer.LabelsToContract(list)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priorities
|
||||||
|
func (s *MasterServiceImpl) CreatePriority(ctx context.Context, req *contract.CreatePriorityRequest) (*contract.PriorityResponse, error) {
|
||||||
|
entity := &entities.Priority{Name: req.Name, Level: req.Level}
|
||||||
|
if err := s.priorityRepo.Create(ctx, entity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := transformer.PrioritiesToContract([]entities.Priority{*entity})[0]
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) UpdatePriority(ctx context.Context, id uuid.UUID, req *contract.UpdatePriorityRequest) (*contract.PriorityResponse, error) {
|
||||||
|
entity := &entities.Priority{ID: id}
|
||||||
|
if req.Name != nil {
|
||||||
|
entity.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Level != nil {
|
||||||
|
entity.Level = *req.Level
|
||||||
|
}
|
||||||
|
if err := s.priorityRepo.Update(ctx, entity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e, err := s.priorityRepo.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := transformer.PrioritiesToContract([]entities.Priority{*e})[0]
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) DeletePriority(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return s.priorityRepo.Delete(ctx, id)
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) ListPriorities(ctx context.Context) (*contract.ListPrioritiesResponse, error) {
|
||||||
|
list, err := s.priorityRepo.List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &contract.ListPrioritiesResponse{Priorities: transformer.PrioritiesToContract(list)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Institutions
|
||||||
|
func (s *MasterServiceImpl) CreateInstitution(ctx context.Context, req *contract.CreateInstitutionRequest) (*contract.InstitutionResponse, error) {
|
||||||
|
entity := &entities.Institution{Name: req.Name, Type: entities.InstitutionType(req.Type), Address: req.Address, ContactPerson: req.ContactPerson, Phone: req.Phone, Email: req.Email}
|
||||||
|
if err := s.institutionRepo.Create(ctx, entity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := transformer.InstitutionsToContract([]entities.Institution{*entity})[0]
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) UpdateInstitution(ctx context.Context, id uuid.UUID, req *contract.UpdateInstitutionRequest) (*contract.InstitutionResponse, error) {
|
||||||
|
entity := &entities.Institution{ID: id}
|
||||||
|
if req.Name != nil {
|
||||||
|
entity.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Type != nil {
|
||||||
|
entity.Type = entities.InstitutionType(*req.Type)
|
||||||
|
}
|
||||||
|
if req.Address != nil {
|
||||||
|
entity.Address = req.Address
|
||||||
|
}
|
||||||
|
if req.ContactPerson != nil {
|
||||||
|
entity.ContactPerson = req.ContactPerson
|
||||||
|
}
|
||||||
|
if req.Phone != nil {
|
||||||
|
entity.Phone = req.Phone
|
||||||
|
}
|
||||||
|
if req.Email != nil {
|
||||||
|
entity.Email = req.Email
|
||||||
|
}
|
||||||
|
if err := s.institutionRepo.Update(ctx, entity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e, err := s.institutionRepo.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := transformer.InstitutionsToContract([]entities.Institution{*e})[0]
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) DeleteInstitution(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return s.institutionRepo.Delete(ctx, id)
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) ListInstitutions(ctx context.Context) (*contract.ListInstitutionsResponse, error) {
|
||||||
|
list, err := s.institutionRepo.List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &contract.ListInstitutionsResponse{Institutions: transformer.InstitutionsToContract(list)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disposition Actions
|
||||||
|
func (s *MasterServiceImpl) CreateDispositionAction(ctx context.Context, req *contract.CreateDispositionActionRequest) (*contract.DispositionActionResponse, error) {
|
||||||
|
entity := &entities.DispositionAction{Code: req.Code, Label: req.Label, Description: req.Description}
|
||||||
|
if req.RequiresNote != nil {
|
||||||
|
entity.RequiresNote = *req.RequiresNote
|
||||||
|
}
|
||||||
|
if req.GroupName != nil {
|
||||||
|
entity.GroupName = req.GroupName
|
||||||
|
}
|
||||||
|
if req.SortOrder != nil {
|
||||||
|
entity.SortOrder = req.SortOrder
|
||||||
|
}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
entity.IsActive = *req.IsActive
|
||||||
|
}
|
||||||
|
if err := s.dispRepo.Create(ctx, entity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := transformer.DispositionActionsToContract([]entities.DispositionAction{*entity})[0]
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) UpdateDispositionAction(ctx context.Context, id uuid.UUID, req *contract.UpdateDispositionActionRequest) (*contract.DispositionActionResponse, error) {
|
||||||
|
entity := &entities.DispositionAction{ID: id}
|
||||||
|
if req.Code != nil {
|
||||||
|
entity.Code = *req.Code
|
||||||
|
}
|
||||||
|
if req.Label != nil {
|
||||||
|
entity.Label = *req.Label
|
||||||
|
}
|
||||||
|
if req.Description != nil {
|
||||||
|
entity.Description = req.Description
|
||||||
|
}
|
||||||
|
if req.RequiresNote != nil {
|
||||||
|
entity.RequiresNote = *req.RequiresNote
|
||||||
|
}
|
||||||
|
if req.GroupName != nil {
|
||||||
|
entity.GroupName = req.GroupName
|
||||||
|
}
|
||||||
|
if req.SortOrder != nil {
|
||||||
|
entity.SortOrder = req.SortOrder
|
||||||
|
}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
entity.IsActive = *req.IsActive
|
||||||
|
}
|
||||||
|
if err := s.dispRepo.Update(ctx, entity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e, err := s.dispRepo.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := transformer.DispositionActionsToContract([]entities.DispositionAction{*e})[0]
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) DeleteDispositionAction(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return s.dispRepo.Delete(ctx, id)
|
||||||
|
}
|
||||||
|
func (s *MasterServiceImpl) ListDispositionActions(ctx context.Context) (*contract.ListDispositionActionsResponse, error) {
|
||||||
|
list, err := s.dispRepo.List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &contract.ListDispositionActionsResponse{Actions: transformer.DispositionActionsToContract(list)}, nil
|
||||||
|
}
|
||||||
@ -190,3 +190,66 @@ func RoleWithPermissionsToContract(role entities.Role, perms []entities.Permissi
|
|||||||
UpdatedAt: role.UpdatedAt,
|
UpdatedAt: role.UpdatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LabelsToContract(list []entities.Label) []contract.LabelResponse {
|
||||||
|
out := make([]contract.LabelResponse, 0, len(list))
|
||||||
|
for _, e := range list {
|
||||||
|
out = append(out, contract.LabelResponse{ID: e.ID.String(), Name: e.Name, Color: e.Color, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrioritiesToContract(list []entities.Priority) []contract.PriorityResponse {
|
||||||
|
out := make([]contract.PriorityResponse, 0, len(list))
|
||||||
|
for _, e := range list {
|
||||||
|
out = append(out, contract.PriorityResponse{ID: e.ID.String(), Name: e.Name, Level: e.Level, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstitutionsToContract(list []entities.Institution) []contract.InstitutionResponse {
|
||||||
|
out := make([]contract.InstitutionResponse, 0, len(list))
|
||||||
|
for _, e := range list {
|
||||||
|
out = append(out, contract.InstitutionResponse{ID: e.ID.String(), Name: e.Name, Type: string(e.Type), Address: e.Address, ContactPerson: e.ContactPerson, Phone: e.Phone, Email: e.Email, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func DispositionActionsToContract(list []entities.DispositionAction) []contract.DispositionActionResponse {
|
||||||
|
out := make([]contract.DispositionActionResponse, 0, len(list))
|
||||||
|
for _, e := range list {
|
||||||
|
out = append(out, contract.DispositionActionResponse{
|
||||||
|
ID: e.ID.String(),
|
||||||
|
Code: e.Code,
|
||||||
|
Label: e.Label,
|
||||||
|
Description: e.Description,
|
||||||
|
RequiresNote: e.RequiresNote,
|
||||||
|
GroupName: e.GroupName,
|
||||||
|
SortOrder: e.SortOrder,
|
||||||
|
IsActive: e.IsActive,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func DispositionRoutesToContract(list []entities.DispositionRoute) []contract.DispositionRouteResponse {
|
||||||
|
out := make([]contract.DispositionRouteResponse, 0, len(list))
|
||||||
|
for _, e := range list {
|
||||||
|
var allowed map[string]interface{}
|
||||||
|
if e.AllowedActions != nil {
|
||||||
|
allowed = map[string]interface{}(e.AllowedActions)
|
||||||
|
}
|
||||||
|
out = append(out, contract.DispositionRouteResponse{
|
||||||
|
ID: e.ID,
|
||||||
|
FromDepartmentID: e.FromDepartmentID,
|
||||||
|
ToDepartmentID: e.ToDepartmentID,
|
||||||
|
IsActive: e.IsActive,
|
||||||
|
AllowedActions: allowed,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
70
internal/transformer/letter_transformer.go
Normal file
70
internal/transformer/letter_transformer.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package transformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"eslogad-be/internal/contract"
|
||||||
|
"eslogad-be/internal/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LetterEntityToContract(e *entities.LetterIncoming, attachments []entities.LetterIncomingAttachment) *contract.IncomingLetterResponse {
|
||||||
|
resp := &contract.IncomingLetterResponse{
|
||||||
|
ID: e.ID,
|
||||||
|
LetterNumber: e.LetterNumber,
|
||||||
|
ReferenceNumber: e.ReferenceNumber,
|
||||||
|
Subject: e.Subject,
|
||||||
|
Description: e.Description,
|
||||||
|
PriorityID: e.PriorityID,
|
||||||
|
SenderInstitutionID: e.SenderInstitutionID,
|
||||||
|
ReceivedDate: e.ReceivedDate,
|
||||||
|
DueDate: e.DueDate,
|
||||||
|
Status: string(e.Status),
|
||||||
|
CreatedBy: e.CreatedBy,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
Attachments: make([]contract.IncomingLetterAttachmentResponse, 0, len(attachments)),
|
||||||
|
}
|
||||||
|
for _, a := range attachments {
|
||||||
|
resp.Attachments = append(resp.Attachments, contract.IncomingLetterAttachmentResponse{
|
||||||
|
ID: a.ID,
|
||||||
|
FileURL: a.FileURL,
|
||||||
|
FileName: a.FileName,
|
||||||
|
FileType: a.FileType,
|
||||||
|
UploadedAt: a.UploadedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func DispositionsToContract(list []entities.LetterDisposition) []contract.DispositionResponse {
|
||||||
|
out := make([]contract.DispositionResponse, 0, len(list))
|
||||||
|
for _, d := range list {
|
||||||
|
out = append(out, contract.DispositionResponse{
|
||||||
|
ID: d.ID,
|
||||||
|
LetterID: d.LetterID,
|
||||||
|
FromDepartmentID: d.FromDepartmentID,
|
||||||
|
ToDepartmentID: d.ToDepartmentID,
|
||||||
|
Notes: d.Notes,
|
||||||
|
Status: string(d.Status),
|
||||||
|
CreatedBy: d.CreatedBy,
|
||||||
|
CreatedAt: d.CreatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func DiscussionEntityToContract(e *entities.LetterDiscussion) *contract.LetterDiscussionResponse {
|
||||||
|
var mentions map[string]interface{}
|
||||||
|
if e.Mentions != nil {
|
||||||
|
mentions = map[string]interface{}(e.Mentions)
|
||||||
|
}
|
||||||
|
return &contract.LetterDiscussionResponse{
|
||||||
|
ID: e.ID,
|
||||||
|
LetterID: e.LetterID,
|
||||||
|
ParentID: e.ParentID,
|
||||||
|
UserID: e.UserID,
|
||||||
|
Message: e.Message,
|
||||||
|
Mentions: mentions,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
EditedAt: e.EditedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS institutions;
|
||||||
|
DROP TABLE IF EXISTS priorities;
|
||||||
|
DROP TABLE IF EXISTS labels;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
52
migrations/000006_labels_priorities_institutions.up.sql
Normal file
52
migrations/000006_labels_priorities_institutions.up.sql
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- LABELS
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS labels (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
color VARCHAR(16), -- HEX color code (e.g., #FF0000)
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_labels_updated_at
|
||||||
|
BEFORE UPDATE ON labels
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- PRIORITIES
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS priorities (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
level INT NOT NULL,
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_priorities_updated_at
|
||||||
|
BEFORE UPDATE ON priorities
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- INSTITUTIONS
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS institutions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
type TEXT NOT NULL CHECK (type IN ('government','private','ngo','individual')),
|
||||||
|
address TEXT,
|
||||||
|
contact_person VARCHAR(255),
|
||||||
|
phone VARCHAR(50),
|
||||||
|
email VARCHAR(255),
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_institutions_updated_at
|
||||||
|
BEFORE UPDATE ON institutions
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
5
migrations/000007_disposition_actions.down.sql
Normal file
5
migrations/000007_disposition_actions.down.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS disposition_actions;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
23
migrations/000007_disposition_actions.up.sql
Normal file
23
migrations/000007_disposition_actions.up.sql
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- DISPOSITION ACTIONS
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS disposition_actions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
code TEXT UNIQUE NOT NULL,
|
||||||
|
label TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
requires_note BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
group_name TEXT,
|
||||||
|
sort_order INT,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_disposition_actions_updated_at
|
||||||
|
BEFORE UPDATE ON disposition_actions
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
16
migrations/000008_letters_incoming_suite.down.sql
Normal file
16
migrations/000008_letters_incoming_suite.down.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS letter_incoming_activity_logs;
|
||||||
|
DROP TABLE IF EXISTS letter_incoming_discussion_attachments;
|
||||||
|
DROP TABLE IF EXISTS letter_incoming_discussions;
|
||||||
|
DROP TABLE IF EXISTS letter_disposition_actions;
|
||||||
|
DROP TABLE IF EXISTS disposition_notes;
|
||||||
|
DROP TABLE IF EXISTS letter_dispositions;
|
||||||
|
DROP TABLE IF EXISTS letter_incoming_attachments;
|
||||||
|
DROP TABLE IF EXISTS letter_incoming_labels;
|
||||||
|
DROP TABLE IF EXISTS letter_incoming_recipients;
|
||||||
|
DROP TABLE IF EXISTS letters_incoming;
|
||||||
|
|
||||||
|
DROP SEQUENCE IF EXISTS letters_incoming_seq;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
189
migrations/000008_letters_incoming_suite.up.sql
Normal file
189
migrations/000008_letters_incoming_suite.up.sql
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- SEQUENCE FOR LETTER NUMBER
|
||||||
|
-- =======================
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS letters_incoming_seq;
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- LETTERS INCOMING
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS letters_incoming (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
letter_number TEXT NOT NULL UNIQUE DEFAULT ('IN-' || lpad(nextval('letters_incoming_seq')::text, 8, '0')),
|
||||||
|
reference_number TEXT,
|
||||||
|
subject TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
priority_id UUID REFERENCES priorities(id) ON DELETE SET NULL,
|
||||||
|
sender_institution_id UUID REFERENCES institutions(id) ON DELETE SET NULL,
|
||||||
|
received_date DATE NOT NULL,
|
||||||
|
due_date DATE,
|
||||||
|
status TEXT NOT NULL DEFAULT 'new' CHECK (status IN ('new','in_progress','completed')),
|
||||||
|
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP WITHOUT TIME ZONE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_status ON letters_incoming(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letters_incoming_received_date ON letters_incoming(received_date);
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_letters_incoming_updated_at
|
||||||
|
BEFORE UPDATE ON letters_incoming
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- LETTER INCOMING RECIPIENTS
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS letter_incoming_recipients (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||||
|
recipient_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
recipient_department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'new' CHECK (status IN ('new','read','completed')),
|
||||||
|
read_at TIMESTAMP WITHOUT TIME ZONE,
|
||||||
|
completed_at TIMESTAMP WITHOUT TIME ZONE,
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_incoming_recipients_letter ON letter_incoming_recipients(letter_id);
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- LETTER INCOMING LABELS (M:N)
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS letter_incoming_labels (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||||
|
label_id UUID NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE (letter_id, label_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_incoming_labels_letter ON letter_incoming_labels(letter_id);
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- LETTER INCOMING ATTACHMENTS
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS letter_incoming_attachments (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||||
|
file_url TEXT NOT NULL,
|
||||||
|
file_name TEXT NOT NULL,
|
||||||
|
file_type TEXT NOT NULL,
|
||||||
|
uploaded_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
uploaded_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_incoming_attachments_letter ON letter_incoming_attachments(letter_id);
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- LETTER DISPOSITIONS
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS letter_dispositions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||||
|
from_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
from_department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
|
||||||
|
to_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
to_department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
|
||||||
|
notes TEXT,
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','read','rejected','completed')),
|
||||||
|
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
read_at TIMESTAMP WITHOUT TIME ZONE,
|
||||||
|
completed_at TIMESTAMP WITHOUT TIME ZONE,
|
||||||
|
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_dispositions_letter ON letter_dispositions(letter_id);
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_letter_dispositions_updated_at
|
||||||
|
BEFORE UPDATE ON letter_dispositions
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- DISPOSITION NOTES
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS disposition_notes (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
disposition_id UUID NOT NULL REFERENCES letter_dispositions(id) ON DELETE CASCADE,
|
||||||
|
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
note TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_disposition_notes_disposition ON disposition_notes(disposition_id);
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- LETTER DISPOSITION ACTIONS (Selections)
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS letter_disposition_actions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
disposition_id UUID NOT NULL REFERENCES letter_dispositions(id) ON DELETE CASCADE,
|
||||||
|
action_id UUID NOT NULL REFERENCES disposition_actions(id) ON DELETE RESTRICT,
|
||||||
|
note TEXT,
|
||||||
|
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE (disposition_id, action_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_disposition_actions_disposition ON letter_disposition_actions(disposition_id);
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- LETTER INCOMING DISCUSSIONS (Threaded)
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS letter_incoming_discussions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||||
|
parent_id UUID REFERENCES letter_incoming_discussions(id) ON DELETE CASCADE,
|
||||||
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
mentions JSONB,
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
edited_at TIMESTAMP WITHOUT TIME ZONE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_incoming_discussions_letter ON letter_incoming_discussions(letter_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_incoming_discussions_parent ON letter_incoming_discussions(parent_id);
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_letter_incoming_discussions_updated_at
|
||||||
|
BEFORE UPDATE ON letter_incoming_discussions
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- LETTER INCOMING DISCUSSION ATTACHMENTS
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS letter_incoming_discussion_attachments (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
discussion_id UUID NOT NULL REFERENCES letter_incoming_discussions(id) ON DELETE CASCADE,
|
||||||
|
file_url TEXT NOT NULL,
|
||||||
|
file_name TEXT NOT NULL,
|
||||||
|
file_type TEXT NOT NULL,
|
||||||
|
uploaded_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
uploaded_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_incoming_discussion_attachments_discussion ON letter_incoming_discussion_attachments(discussion_id);
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- LETTER INCOMING ACTIVITY LOGS (Immutable)
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS letter_incoming_activity_logs (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
letter_id UUID NOT NULL REFERENCES letters_incoming(id) ON DELETE CASCADE,
|
||||||
|
action_type TEXT NOT NULL,
|
||||||
|
actor_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
actor_department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
|
||||||
|
target_type TEXT,
|
||||||
|
target_id UUID,
|
||||||
|
from_status TEXT,
|
||||||
|
to_status TEXT,
|
||||||
|
context JSONB,
|
||||||
|
occurred_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_incoming_activity_logs_letter ON letter_incoming_activity_logs(letter_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_letter_incoming_activity_logs_action ON letter_incoming_activity_logs(action_type);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
5
migrations/000009_disposition_routes.down.sql
Normal file
5
migrations/000009_disposition_routes.down.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS disposition_routes;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
27
migrations/000009_disposition_routes.up.sql
Normal file
27
migrations/000009_disposition_routes.up.sql
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- DISPOSITION ROUTES
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE IF NOT EXISTS disposition_routes (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
from_department_id UUID NOT NULL REFERENCES departments(id) ON DELETE CASCADE,
|
||||||
|
to_department_id UUID NOT NULL REFERENCES departments(id) ON DELETE CASCADE,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
allowed_actions JSONB,
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_disposition_routes_from_dept ON disposition_routes(from_department_id);
|
||||||
|
|
||||||
|
-- Prevent duplicate active routes from -> to
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_disposition_routes_active
|
||||||
|
ON disposition_routes(from_department_id, to_department_id)
|
||||||
|
WHERE is_active = TRUE;
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_disposition_routes_updated_at
|
||||||
|
BEFORE UPDATE ON disposition_routes
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
Loading…
x
Reference in New Issue
Block a user