From f9c5dc3f6372e8c33f3859fe131c7eca4ae71bca Mon Sep 17 00:00:00 2001 From: Habib Fatkhul Rohman Date: Sat, 25 Oct 2025 21:00:03 +0700 Subject: [PATCH] feat: Implement cascading delete constraints for maintenance group entities and enhance service methods for role-user relationships --- .../entities/m_maintenance_group_entity.go | 10 ++- .../m_maintenance_group_role_entity.go | 11 ++- .../m_maintenance_group_role_user_entity.go | 8 +- database/entities/m_role_entity.go | 4 +- .../maintenance_group_controller.go | 43 ++++++++-- .../query/maintenance_group_query.go | 85 ++++++++++++++++++- .../service/maintenance_group_service.go | 54 +++++++++--- 7 files changed, 179 insertions(+), 36 deletions(-) diff --git a/database/entities/m_maintenance_group_entity.go b/database/entities/m_maintenance_group_entity.go index 9ac0135..0e7894a 100644 --- a/database/entities/m_maintenance_group_entity.go +++ b/database/entities/m_maintenance_group_entity.go @@ -7,10 +7,12 @@ type M_MaintenanceGroup struct { Code string `gorm:"type:varchar(50);uniqueIndex;not null" json:"code"` Name string `gorm:"type:varchar(100);not null" json:"name"` Description string `gorm:"type:text" json:"description"` - ClientID uuid.UUID `gorm:"type:uuid;not null;index" json:"client_id"` + // ClientID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;" json:"client_id"` + + // Client M_Client `gorm:"foreignKey:ClientID;references:ID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"` + ClientID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;" json:"client_id"` + Client M_Client `gorm:"foreignKey:ClientID;references:ID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"` + MaintenanceGroupRoles []M_MaintenanceGroupRole `gorm:"foreignKey:MaintenanceGroupID;references:ID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"` - Client M_Client `gorm:"foreignKey:ClientID;references:ID"` - MaintenanceGroupRoles []M_MaintenanceGroupRole `gorm:"foreignKey:MaintenanceGroupID;references:ID;constraint:OnDelete:CASCADE;onUpdate:CASCADE;"` - FullAuditTrail } diff --git a/database/entities/m_maintenance_group_role_entity.go b/database/entities/m_maintenance_group_role_entity.go index 4cc9f76..4bebff1 100644 --- a/database/entities/m_maintenance_group_role_entity.go +++ b/database/entities/m_maintenance_group_role_entity.go @@ -4,13 +4,12 @@ import "github.com/google/uuid" type M_MaintenanceGroupRole struct { ID uuid.UUID `gorm:"type:uuid;primary_key;default:uuid_generate_v4()" json:"id"` - MaintenanceGroupID uuid.UUID `gorm:"type:uuid;not null;index"` - RoleID uuid.UUID `gorm:"type:uuid;not null;index"` + MaintenanceGroupID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` + RoleID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"` Level *int `gorm:"type:int;not null;index"` - MaintenanceGroup M_MaintenanceGroup `gorm:"foreignKey:MaintenanceGroupID;references:ID"` - MaintenanceGroupRoleUsers []M_MaintenanceGroupRoleUser `gorm:"foreignKey:MaintenanceGroupRoleID;references:ID;constraint:OnDelete:CASCADE;onUpdate:CASCADE;"` - Role M_Role `gorm:"foreignKey:RoleID;references:ID;constraint:OnDelete:CASCADE;onUpdate:CASCADE;"` - + MaintenanceGroup M_MaintenanceGroup `gorm:"foreignKey:MaintenanceGroupID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` + MaintenanceGroupRoleUsers []M_MaintenanceGroupRoleUser `gorm:"foreignKey:MaintenanceGroupRoleID;references:ID;constraint:OnDelete:CASCADE,onUpdate:CASCADE;"` + Role M_Role `gorm:"foreignKey:RoleID;references:ID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"` FullAuditTrail } diff --git a/database/entities/m_maintenance_group_role_user_entity.go b/database/entities/m_maintenance_group_role_user_entity.go index 1424bab..d51cf30 100644 --- a/database/entities/m_maintenance_group_role_user_entity.go +++ b/database/entities/m_maintenance_group_role_user_entity.go @@ -4,11 +4,11 @@ import "github.com/google/uuid" type M_MaintenanceGroupRoleUser struct { ID uuid.UUID `gorm:"type:uuid;primary_key;default:uuid_generate_v4()" json:"id"` - MaintenanceGroupRoleID uuid.UUID `gorm:"type:uuid;not null;index" json:"maintenance_group_role_id"` - UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"` + MaintenanceGroupRoleID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnDelete:CASCADE,onUpdate:CASCADE;" json:"maintenance_group_role_id"` + UserID uuid.UUID `gorm:"type:uuid;not null;index;constraint:OnDelete:CASCADE,onUpdate:CASCADE;" json:"user_id"` - MaintenanceGroupRole M_MaintenanceGroupRole `gorm:"foreignKey:MaintenanceGroupRoleID;references:ID;constraint:OnDelete:CASCADE;onUpdate:CASCADE;"` - User M_User `gorm:"foreignKey:UserID;references:ID;constraint:OnDelete:CASCADE;onUpdate:CASCADE;"` + MaintenanceGroupRole M_MaintenanceGroupRole `gorm:"foreignKey:MaintenanceGroupRoleID;references:ID;constraint:OnDelete:CASCADE,onUpdate:CASCADE;"` + User M_User `gorm:"foreignKey:UserID;references:ID;constraint:OnDelete:CASCADE,onUpdate:CASCADE;"` FullAuditTrail } diff --git a/database/entities/m_role_entity.go b/database/entities/m_role_entity.go index 02cf6cb..77b15ca 100644 --- a/database/entities/m_role_entity.go +++ b/database/entities/m_role_entity.go @@ -17,8 +17,8 @@ type M_Role struct { UserRoles []M_User_Role `gorm:"foreignKey:RoleID;references:ID" json:"user_roles"` Users []M_User `gorm:"many2many:m_user_roles;" json:"users"` Permissions []M_Permissions `gorm:"many2many:m_role_permissions;joinForeignKey:RoleID;JoinReferences:PermissionID" json:"permissions"` - MaintenanceGroupRoles []M_MaintenanceGroupRole `gorm:"foreignKey:RoleID;references:ID" json:"maintenance_group_roles"` - + MaintenanceGroupRoles []M_MaintenanceGroupRole `gorm:"foreignKey:RoleID;references:ID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;" json:"maintenance_group_roles"` + FullAuditTrail } diff --git a/modules/maintenance_group/controller/maintenance_group_controller.go b/modules/maintenance_group/controller/maintenance_group_controller.go index 970dd50..8da6db1 100644 --- a/modules/maintenance_group/controller/maintenance_group_controller.go +++ b/modules/maintenance_group/controller/maintenance_group_controller.go @@ -4,9 +4,11 @@ import ( "net/http" "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/query" "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group/service" "github.com/Caknoooo/go-gin-clean-starter/pkg/constants" "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" + "github.com/Caknoooo/go-pagination" "github.com/gin-gonic/gin" "github.com/samber/do" "github.com/sirupsen/logrus" @@ -150,12 +152,37 @@ func (c *maintenanceGroupController) GetById(ctx *gin.Context) { // @Failure 400 {object} map[string]interface{} // @Router /maintenance-groups [get] func (c *maintenanceGroupController) GetAll(ctx *gin.Context) { - // result, err := c.maintGroupService.GetAll(ctx.Request.Context()) - // if err != nil { - // res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_LIST_MG, err.Error(), nil) - // ctx.JSON(http.StatusBadRequest, res) - // return - // } - // res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_LIST_MG, result) - // ctx.JSON(http.StatusOK, res) + clientId := ctx.MustGet("client_id").(string) + var filter = &query.MaintenanceGroupFilter{ + Name: ctx.Query("name"), + Code: ctx.Query("code"), + Description: ctx.Query("description"), + ClientID: clientId, + Includes: ctx.QueryArray("includes"), + } + filter.BindPagination(ctx) + ctx.ShouldBindQuery(filter) + logrus.Infof("pagination: page=%d, page_size=%d", filter.Pagination.Page, filter.Pagination.PerPage) + logrus.Info("1") + groups, total, err := pagination.PaginatedQueryWithIncludableAndOptions[query.M_MaintenanceGroup]( + c.db, + filter, + pagination.PaginatedQueryOptions{ + EnableSoftDelete: true, + // Dialect: pagination.MySQL, // atau PostgreSQL jika perlu + }, + ) + logrus.Info("total:", total, " len groups:", len(groups)) + logrus.Info("2") + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_LIST_MG, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + logrus.Info("3") + // groupResponses := dto.ToMaintGroupResponses(groups) + paginationResponse := pagination.CalculatePagination(filter.Pagination, total) + + response := pagination.NewPaginatedResponse(http.StatusOK, dto.MESSAGE_SUCCESS_GET_LIST_MG, groups, paginationResponse) + ctx.JSON(http.StatusOK, response) } diff --git a/modules/maintenance_group/query/maintenance_group_query.go b/modules/maintenance_group/query/maintenance_group_query.go index b3d8b8c..bb0b67a 100644 --- a/modules/maintenance_group/query/maintenance_group_query.go +++ b/modules/maintenance_group/query/maintenance_group_query.go @@ -1 +1,84 @@ -package query \ No newline at end of file +package query + +import ( + "github.com/Caknoooo/go-pagination" + "gorm.io/gorm" +) + +type M_MaintenanceGroup struct { + ID string `json:"id"` + Name string `json:"name"` + Code string `json:"code"` + Description string `json:"description"` + TotalMaintenanceGroupRole int `json:"total_maintenance_group_role"` +} + +type MaintenanceGroupFilter struct { + pagination.BaseFilter + Name string `form:"name"` // tambahkan ini + Code string `form:"code"` // tambahkan ini + Description string `form:"description"` // tambahkan ini + ClientID string `form:"client_id"` // tambahkan ini + Includes []string `form:"includes"` // tambahkan ini +} + +func (f *MaintenanceGroupFilter) ApplyFilters(query *gorm.DB) *gorm.DB { + // Apply your filters here + if f.Name != "" { + query = query.Where("name ILIKE ?", "%"+f.Name+"%") + } + if f.ClientID != "" { + query = query.Where("client_id = ?", f.ClientID) + } + + // Manual preload untuk roles dengan field terbatas + // for _, include := range f.Includes { + // if include == "Roles" { + // query = query.Preload("Roles", func(db *gorm.DB) *gorm.DB { + // return db.Select("id", "name") // Hanya ambil id dan name + // }) + // } + // } + // Hitung total maintenance group role per group + query = query.Select("m_maintenance_groups.*, " + + "(SELECT COUNT(*) FROM m_maintenance_group_roles mgr WHERE mgr.maintenance_group_id = m_maintenance_groups.id AND mgr.deleted_at IS NULL) as total_maintenance_group_role") + + return query +} + +func (f *MaintenanceGroupFilter) GetTableName() string { + return "m_maintenance_groups" +} + +func (f *MaintenanceGroupFilter) GetSearchFields() []string { + return []string{"name"} +} + +func (f *MaintenanceGroupFilter) GetDefaultSort() string { + return "id asc" +} + +func (f *MaintenanceGroupFilter) GetIncludes() []string { + return f.Includes +} + +func (f *MaintenanceGroupFilter) GetPagination() pagination.PaginationRequest { + return f.Pagination +} + +func (f *MaintenanceGroupFilter) Validate() { + var validIncludes []string + allowedIncludes := f.GetAllowedIncludes() + for _, include := range f.Includes { + if allowedIncludes[include] { + validIncludes = append(validIncludes, include) + } + } + f.Includes = validIncludes +} + +func (f *MaintenanceGroupFilter) GetAllowedIncludes() map[string]bool { + return map[string]bool{ + "Client": true, + } +} diff --git a/modules/maintenance_group/service/maintenance_group_service.go b/modules/maintenance_group/service/maintenance_group_service.go index 2c35963..c5fe880 100644 --- a/modules/maintenance_group/service/maintenance_group_service.go +++ b/modules/maintenance_group/service/maintenance_group_service.go @@ -12,7 +12,7 @@ import ( ) type MaintenanceGroupService interface { - Create(ctx context.Context, maintGroup dto.MaintGroupCreateRequest) (dto.MaintGroupCreateRequest, error) + Create(ctx context.Context, maintGroup dto.MaintGroupCreateRequest) (dto.MaintGroupResponse, error) GetById(ctx context.Context, maintGroupId string) (dto.MaintGroupResponse, error) GetByName(ctx context.Context, name string) (dto.MaintGroupResponse, error) GetByCode(ctx context.Context, code string) (dto.MaintGroupResponse, error) @@ -29,7 +29,7 @@ type maintenanceGroupService struct { } // Create implements MaintenanceGroupService. -func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroupCreateRequest) (dto.MaintGroupCreateRequest, error) { +func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroupCreateRequest) (dto.MaintGroupResponse, error) { tx := m.db.Begin() defer func() { if r := recover(); r != nil { @@ -40,7 +40,7 @@ func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroup clientUUID, err := uuid.Parse(req.ClientID) if err != nil { tx.Rollback() - return dto.MaintGroupCreateRequest{}, err + return dto.MaintGroupResponse{}, err } maintGroup := entities.M_MaintenanceGroup{ @@ -52,14 +52,14 @@ func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroup createdGroup, err := m.maintGroupRepo.Create(ctx, tx, maintGroup) if err != nil { tx.Rollback() - return dto.MaintGroupCreateRequest{}, err + return dto.MaintGroupResponse{}, err } for _, roleReq := range req.MaintenanceGroupRoles { roleUUID, err := uuid.Parse(roleReq.RoleID) if err != nil { tx.Rollback() - return dto.MaintGroupCreateRequest{}, err + return dto.MaintGroupResponse{}, err } role := entities.M_MaintenanceGroupRole{ MaintenanceGroupID: createdGroup.ID, @@ -69,13 +69,13 @@ func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroup createdRole, err := m.maintGroupRoleRepo.Create(ctx, tx, role) if err != nil { tx.Rollback() - return dto.MaintGroupCreateRequest{}, err + return dto.MaintGroupResponse{}, err } for _, userReq := range roleReq.MaintenanceGroupRoleUsers { userUUID, err := uuid.Parse(userReq.UserID) if err != nil { tx.Rollback() - return dto.MaintGroupCreateRequest{}, err + return dto.MaintGroupResponse{}, err } user := entities.M_MaintenanceGroupRoleUser{ MaintenanceGroupRoleID: createdRole.ID, @@ -84,7 +84,7 @@ func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroup _, err = m.maintGroupRoleUserRepo.Create(ctx, tx, user) if err != nil { tx.Rollback() - return dto.MaintGroupCreateRequest{}, err + return dto.MaintGroupResponse{}, err } } } @@ -92,9 +92,14 @@ func (m *maintenanceGroupService) Create(ctx context.Context, req dto.MaintGroup err = tx.Commit().Error if err != nil { tx.Rollback() - return dto.MaintGroupCreateRequest{}, err + return dto.MaintGroupResponse{}, err } - return req, nil + + result, err := m.GetById(ctx, createdGroup.ID.String()) + if err != nil { + return dto.MaintGroupResponse{}, err + } + return result, nil } // Delete implements MaintenanceGroupService. @@ -105,11 +110,38 @@ func (m *maintenanceGroupService) Delete(ctx context.Context, maintGroupId strin tx.Rollback() } }() - err := m.maintGroupRepo.Delete(ctx, tx, maintGroupId) + + // 1. Ambil semua role dari group + roles, err := m.maintGroupRoleRepo.FindByMaintGroupId(ctx, tx, maintGroupId) if err != nil { tx.Rollback() return err } + + // 2. Hapus semua user dari setiap role + for _, role := range roles { + err := m.maintGroupRoleUserRepo.DeleteByMaintGroupRoleID(ctx, tx, role.ID.String()) + if err != nil { + tx.Rollback() + return err + } + } + + // 3. Hapus semua role dari group + err = m.maintGroupRoleRepo.DeleteByMaintGroupId(ctx, tx, maintGroupId) + if err != nil { + tx.Rollback() + return err + } + + // 4. Hapus group + err = m.maintGroupRepo.Delete(ctx, tx, maintGroupId) + if err != nil { + tx.Rollback() + return err + } + + // 5. Commit err = tx.Commit().Error if err != nil { tx.Rollback()